diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f4db0e0..9a2cb02 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,7 +16,7 @@ jobs: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - target: aarch64-unknown-linux-gnu - os: ubuntu-latest + os: ubuntu-24.04-arm - target: x86_64-apple-darwin os: macos-latest - target: aarch64-apple-darwin @@ -24,8 +24,11 @@ jobs: - target: x86_64-pc-windows-msvc os: windows-latest - target: aarch64-pc-windows-msvc - os: windows-latest + os: windows-11-arm runs-on: ${{ matrix.os }} + env: + # Keeps DuckDB's CMake object paths below the MSVC 260 character limit + CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/target' || 'target' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable @@ -37,24 +40,15 @@ jobs: run: | VERSION="${GITHUB_REF_NAME#v}" sed -i.bak 's/^version = ".*"/version = "'"$VERSION"'"/' Cargo.toml - - name: Install aarch64 cross-compilation tools - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - name: Build run: cargo build --release --target ${{ matrix.target }} - env: - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ - name: Package (Unix) if: runner.os != 'Windows' - run: tar czf apitally-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release apitally + run: tar czf apitally-${{ matrix.target }}.tar.gz -C $CARGO_TARGET_DIR/${{ matrix.target }}/release apitally - name: Package (Windows) if: runner.os == 'Windows' shell: pwsh - run: Compress-Archive -Path target/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip + run: Compress-Archive -Path $env:CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip - name: Upload artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: diff --git a/AGENTS.md b/AGENTS.md index 419a95b..36b693c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ skills/ - **HTTP client**: ureq (blocking; response body implements `Read` for streaming) - **Serialization**: serde + serde_json - **Error handling**: anyhow -- **Embedded database**: duckdb (statically linked, bundled Arrow via `duckdb::arrow::*`) +- **Embedded database**: duckdb (statically linked with the ICU and JSON extensions, bundled Arrow via `duckdb::arrow::*`) - **Distribution**: npm package (`@apitally/cli`); postinstall downloads the native binary ## CLI Subcommands @@ -68,6 +68,8 @@ The API base URL follows the same pattern: `--api-base-url` flag > `APITALLY_API ## Common Commands +Building requires CMake, because DuckDB is compiled from source with the ICU extension. + ```bash cargo build # Build (debug) cargo build --release # Build (release, optimized) @@ -85,7 +87,7 @@ The CLI is published to npm as `@apitally/cli`. The npm package contains no Rust Triggered by publishing a GitHub release. Two jobs: -1. **build** — cross-compiles for 6 targets (linux x64/arm64, macOS x64/arm64, Windows x64/arm64), packages as `.tar.gz`/`.zip`, uploads as artifacts. +1. **build** — builds for 6 targets (linux x64/arm64, macOS x64/arm64, Windows x64/arm64) on native runners, except macOS x64, which is cross-compiled on macOS arm64. Packages as `.tar.gz`/`.zip`, uploads as artifacts. 2. **publish** — attaches artifacts to the GitHub release, then publishes to npm (version derived from the git tag; pre-releases use the `next` dist-tag). ## Architecture Patterns @@ -125,6 +127,7 @@ Inline `#[cfg(test)] mod tests` in each source file. Run with `cargo test`. - Prefer simple, concise code; avoid unnecessary abstractions - Use `anyhow::Result` for error handling in application code - Use `duckdb::arrow::*` re-exports for all Arrow type imports (ensures type compatibility with duckdb). The standalone `arrow` and `arrow-ipc` crates in `Cargo.toml` exist solely to enable features (`ipc`, `json`, `chrono-tz` on `arrow`; `lz4` on `arrow-ipc`) that duckdb doesn't activate on its bundled Arrow. Cargo unifies both into a single Arrow instance. When updating `duckdb`, check that its Arrow version still matches and bump the `arrow`/`arrow-ipc` versions in `Cargo.toml` if needed (`cargo tree -p duckdb --depth 1`). +- `duckdb` is a git dependency on a duckdb-rs release tag, because the ICU extension requires the `bundled-cmake` feature, which needs the full DuckDB sources that the crates.io package omits. To update, change the `tag` in `Cargo.toml`. - Follow existing code patterns and conventions - Verify work compiles and passes `cargo clippy` before considering a task complete - Keep user-facing output concise diff --git a/Cargo.lock b/Cargo.lock index 9acd277..7f9da06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -657,8 +666,7 @@ dependencies = [ [[package]] name = "duckdb" version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970e05eedd3f55c435194d9104f90a9b4a79a80d6e73251bc9ff43e178130c4e" +source = "git+https://github.com/duckdb/duckdb-rs?tag=v1.10505.0#88f673c5cdf09e287c010a910db53b97e523a9fa" dependencies = [ "arrow", "cast", @@ -1234,10 +1242,10 @@ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libduckdb-sys" version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb514dab5e271e849235c1cb98bd65a2ae107fbd619a6740219319c54a71d95" +source = "git+https://github.com/duckdb/duckdb-rs?tag=v1.10505.0#88f673c5cdf09e287c010a910db53b97e523a9fa" dependencies = [ "cc", + "cmake", "flate2", "pkg-config", "serde", @@ -1245,6 +1253,7 @@ dependencies = [ "tar", "ureq", "vcpkg", + "which", "zip", ] @@ -2261,6 +2270,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 5ca67dc..6dd0b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,12 @@ arrow-ipc = { version = "=58.4.0", features = ["lz4"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } clap = { version = "4", features = ["derive", "env"] } dirs = "6" -duckdb = { version = "=1.10505.0", features = ["bundled", "appender-arrow"] } +duckdb = { git = "https://github.com/duckdb/duckdb-rs", tag = "v1.10505.0", features = [ + "bundled-cmake", + "icu", + "json", + "appender-arrow", +] } iana-time-zone = "0.1" open = "5" regex = "1" diff --git a/renovate.json b/renovate.json index 5d55ead..9482f78 100644 --- a/renovate.json +++ b/renovate.json @@ -4,7 +4,7 @@ "packageRules": [ { "matchManagers": ["cargo"], - "matchPackageNames": ["duckdb", "arrow", "arrow-ipc"], + "matchDepNames": ["duckdb", "arrow", "arrow-ipc"], "groupName": "duckdb and arrow" }, { diff --git a/skills/apitally-cli/SKILL.md b/skills/apitally-cli/SKILL.md index f9340c5..fad7fe8 100644 --- a/skills/apitally-cli/SKILL.md +++ b/skills/apitally-cli/SKILL.md @@ -151,12 +151,12 @@ npx @apitally/cli request-details ### Discover slow spans and expand a trace -The examples below use app `1` and a sample trace ID; substitute IDs from your results. Discover spans taking at least 100 ms. SQL time bounds use epoch nanoseconds because the bundled DuckDB does not support `TIMESTAMPTZ - INTERVAL` (24 hours = 86400000000000 ns): +The examples below use app `1` and a sample trace ID; substitute IDs from your results. Discover spans taking at least 100 ms: ```bash npx @apitally/cli traces 1 --since 24h \ --filters '[{"field":"duration_ns","op":"gte","value":100000000}]' --db -npx @apitally/cli sql "SELECT trace_id, span_id, name, duration_ns / 1000000.0 AS duration_ms FROM spans WHERE app_id = 1 AND start_time_ns >= epoch_ns(current_timestamp) - 86400000000000 AND duration_ns >= 100000000 ORDER BY duration_ns DESC LIMIT 20" +npx @apitally/cli sql "SELECT trace_id, span_id, name, duration_ns / 1000000.0 AS duration_ms FROM spans WHERE app_id = 1 AND start_time_ns >= epoch_ns(now() - INTERVAL 1 DAY) AND duration_ns >= 100000000 ORDER BY duration_ns DESC LIMIT 20" ``` Fetch all available spans for a discovered trace ID. Keep only the trace-ID filter, remove sampling and time bounds, and select the fields needed for the investigation. `--fields` replaces defaults, so this example lists all fields: @@ -177,7 +177,7 @@ Find request trace IDs, then expand them using the trace-ID fetch above: ```bash npx @apitally/cli request-logs 1 --since 24h \ --fields 'status_code,response_time_ms' --db -npx @apitally/cli sql "SELECT request_uuid, trace_id, status_code, response_time_ms FROM request_logs WHERE app_id = 1 AND epoch_ns(timestamp) >= epoch_ns(current_timestamp) - 86400000000000 AND trace_id IS NOT NULL ORDER BY response_time_ms DESC LIMIT 20" +npx @apitally/cli sql "SELECT request_uuid, trace_id, status_code, response_time_ms FROM request_logs WHERE app_id = 1 AND timestamp >= now() - INTERVAL 1 DAY AND trace_id IS NOT NULL ORDER BY response_time_ms DESC LIMIT 20" ``` Join on **both app and trace IDs**, not request UUID: @@ -259,10 +259,10 @@ See [references/duckdb_json_functions.md](references/duckdb_json_functions.md) f ### Date truncation and grouping -Datetime columns are `TIMESTAMPTZ`. Direct casts like `timestamp::DATE` error out (no ICU extension). Use `AT TIME ZONE 'UTC'` first: +Datetime columns are `TIMESTAMPTZ`. SQL output, date functions, and casts like `timestamp::DATE` use the system time zone. For UTC boundaries, use `timestamp AT TIME ZONE 'UTC'`: ```sql -SELECT date_trunc('day', timestamp AT TIME ZONE 'UTC') AS day, count(*) AS n +SELECT date_trunc('day', timestamp) AS day, count(*) AS n FROM request_logs WHERE app_id = AND timestamp >= '' GROUP BY day ORDER BY day ``` diff --git a/skills/apitally-cli/references/commands.md b/skills/apitally-cli/references/commands.md index 597c5c1..c5716ba 100644 --- a/skills/apitally-cli/references/commands.md +++ b/skills/apitally-cli/references/commands.md @@ -396,7 +396,7 @@ Available tables: `apps`, `app_envs`, `consumers`, `endpoints`, `metrics`, `requ **Important:** The database may contain data from previous sessions. Always filter queries by `app_id` and the current investigation scope: `timestamp` for request logs, `period_start`/`period_end` for metrics, and exact `trace_id` values or integer `start_time_ns` bounds for spans. -DuckDB uses a [PostgreSQL-compatible SQL dialect](https://duckdb.org/docs/stable/sql/dialect/overview). The bundled DuckDB has no ICU extension, so `TIMESTAMPTZ` columns cannot be cast directly to `DATE`. Use `(timestamp AT TIME ZONE 'UTC')::DATE` or `date_trunc('day', timestamp AT TIME ZONE 'UTC')` for date conversion and grouping. +DuckDB uses a [PostgreSQL-compatible SQL dialect](https://duckdb.org/docs/stable/sql/dialect/overview). `TIMESTAMPTZ` values are output, truncated, and cast to `DATE` in the system time zone. Use `timestamp AT TIME ZONE 'UTC'` for UTC. Example output: