Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ 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
os: macos-latest
- 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
Expand All @@ -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:
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
26 changes: 22 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"packageRules": [
{
"matchManagers": ["cargo"],
"matchPackageNames": ["duckdb", "arrow", "arrow-ipc"],
"matchDepNames": ["duckdb", "arrow", "arrow-ipc"],
"groupName": "duckdb and arrow"
},
{
Expand Down
10 changes: 5 additions & 5 deletions skills/apitally-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ npx @apitally/cli request-details <app-id> <request-uuid>

### 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:
Expand All @@ -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:
Expand Down Expand Up @@ -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 = <app-id> AND timestamp >= '<since>'
GROUP BY day ORDER BY day
```
Expand Down
2 changes: 1 addition & 1 deletion skills/apitally-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
Loading