From 2614c7144df7fb9dcbc68b8d76ff259352cf039b Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Wed, 28 Oct 2026 16:49:14 +1000 Subject: [PATCH 1/3] Add consumers metric to metrics command --- skills/apitally-cli/SKILL.md | 2 +- skills/apitally-cli/references/commands.md | 29 ++++++++++--------- .../apitally-cli/references/duckdb_tables.md | 3 +- src/main.rs | 3 +- src/metrics.rs | 12 +++++--- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/skills/apitally-cli/SKILL.md b/skills/apitally-cli/SKILL.md index fad7fe8..9cc405e 100644 --- a/skills/apitally-cli/SKILL.md +++ b/skills/apitally-cli/SKILL.md @@ -78,7 +78,7 @@ All commands are run via `npx @apitally/cli `. For full details, see [r ``` 4. **Fetch data** — choose based on the question. Always read the [command reference](references/commands.md) for available options. - - **Metrics** — for questions that can be answered with aggregated metrics: traffic volume, error rates, response time trends, throughput, endpoint comparisons. Use `--group-by` and `--interval` to break down by environment, endpoint, consumer, status code, or time period. + - **Metrics** — for questions that can be answered with aggregated metrics: traffic volume, error rates, response time trends, throughput, distinct consumers, endpoint comparisons. Use `--group-by` and `--interval` to break down by environment, endpoint, consumer, status code, or time period. ``` npx @apitally/cli metrics --since "" \ diff --git a/skills/apitally-cli/references/commands.md b/skills/apitally-cli/references/commands.md index 50a2836..24ccb1b 100644 --- a/skills/apitally-cli/references/commands.md +++ b/skills/apitally-cli/references/commands.md @@ -108,20 +108,21 @@ Fetch aggregated metrics for an app. Outputs NDJSON to stdout by default. ### Available metrics -| Metric | Type | Description | -| --------------------- | ------- | -------------------------------------- | -| `requests` | integer | Total request count | -| `requests_per_minute` | float | Requests per minute | -| `bytes_received` | integer | Total bytes received | -| `bytes_sent` | integer | Total bytes sent | -| `client_errors` | integer | 4xx errors (excluding expected errors) | -| `server_errors` | integer | 5xx errors (excluding expected errors) | -| `error_rate` | float | Ratio of errors to total requests | -| `response_time_p50` | integer | 50th percentile response time (ms) | -| `response_time_p75` | integer | 75th percentile response time (ms) | -| `response_time_p90` | integer | 90th percentile response time (ms) | -| `response_time_p95` | integer | 95th percentile response time (ms) | -| `response_time_p99` | integer | 99th percentile response time (ms) | +| Metric | Type | Description | +| --------------------- | ------- | ------------------------------------------------------------------------------------------ | +| `requests` | integer | Total request count | +| `requests_per_minute` | float | Requests per minute | +| `bytes_received` | integer | Total bytes received | +| `bytes_sent` | integer | Total bytes sent | +| `client_errors` | integer | 4xx errors (excluding expected errors) | +| `server_errors` | integer | 5xx errors (excluding expected errors) | +| `error_rate` | float | Ratio of errors to total requests | +| `response_time_p50` | integer | 50th percentile response time (ms) | +| `response_time_p75` | integer | 75th percentile response time (ms) | +| `response_time_p90` | integer | 90th percentile response time (ms) | +| `response_time_p95` | integer | 95th percentile response time (ms) | +| `response_time_p99` | integer | 99th percentile response time (ms) | +| `consumers` | integer | Number of distinct consumers. Can't be summed across periods or groups (consumers overlap) | ### Group-by fields diff --git a/skills/apitally-cli/references/duckdb_tables.md b/skills/apitally-cli/references/duckdb_tables.md index c847496..ce08e10 100644 --- a/skills/apitally-cli/references/duckdb_tables.md +++ b/skills/apitally-cli/references/duckdb_tables.md @@ -80,7 +80,8 @@ CREATE TABLE metrics ( response_time_p75 INTEGER, -- milliseconds response_time_p90 INTEGER, -- milliseconds response_time_p95 INTEGER, -- milliseconds - response_time_p99 INTEGER -- milliseconds + response_time_p99 INTEGER, -- milliseconds + consumers BIGINT -- distinct count, can't be summed across periods or groups ); ``` diff --git a/src/main.rs b/src/main.rs index 7e0d277..8aa1c10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,7 +141,8 @@ enum Command { /// JSON array or comma-separated list of metric names to include /// /// Available metrics: requests, requests_per_minute, bytes_received, bytes_sent, client_errors, server_errors, error_rate, - /// response_time_p50, response_time_p75, response_time_p90, response_time_p95, response_time_p99. + /// response_time_p50, response_time_p75, response_time_p90, response_time_p95, response_time_p99, consumers. + /// The consumers metric is a distinct count and can't be summed across periods or groups. #[arg(long)] metrics: String, diff --git a/src/metrics.rs b/src/metrics.rs index 465f9a8..f360118 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -30,10 +30,12 @@ pub(crate) fn ensure_metrics_table(conn: &duckdb::Connection) -> Result<()> { response_time_p75 INTEGER, response_time_p90 INTEGER, response_time_p95 INTEGER, - response_time_p99 INTEGER + response_time_p99 INTEGER, + consumers BIGINT ); ALTER TABLE metrics ADD COLUMN IF NOT EXISTS response_time_p90 INTEGER; - ALTER TABLE metrics ADD COLUMN IF NOT EXISTS response_time_p99 INTEGER;", + ALTER TABLE metrics ADD COLUMN IF NOT EXISTS response_time_p99 INTEGER; + ALTER TABLE metrics ADD COLUMN IF NOT EXISTS consumers BIGINT;", )?; Ok(()) } @@ -179,6 +181,7 @@ mod tests { Field::new("method", DataType::Utf8, true), Field::new("requests", DataType::Int64, false), Field::new("error_rate", DataType::Float64, false), + Field::new("consumers", DataType::Int64, false), ])); let batch = RecordBatch::try_new( schema.clone(), @@ -198,6 +201,7 @@ mod tests { Arc::new(StringArray::from(vec![Some("GET"); ROW_COUNT])), Arc::new(Int64Array::from(vec![100; ROW_COUNT])), Arc::new(Float64Array::from(vec![0.05; ROW_COUNT])), + Arc::new(Int64Array::from(vec![3; ROW_COUNT])), ], ) .unwrap(); @@ -262,7 +266,7 @@ mod tests { 1, "2025-01-01", None, - r#"["requests","error_rate"]"#, + r#"["requests","error_rate","consumers"]"#, Some("hour"), Some(r#"["method"]"#), None, @@ -280,7 +284,7 @@ mod tests { let (count, periods, matching): (i64, i64, i64) = conn .query_row( "SELECT count(*), count(DISTINCT period_start), count(*) FILTER ( - WHERE app_id = 1 AND method = 'GET' AND requests = 100 AND error_rate = 0.05 + WHERE app_id = 1 AND method = 'GET' AND requests = 100 AND error_rate = 0.05 AND consumers = 3 AND epoch_ms(period_end) - epoch_ms(period_start) = 3600000 ) FROM metrics", [], From 7e8b7e2f49e3468dab78345d974b628da96df681 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Wed, 28 Oct 2026 16:49:55 +1000 Subject: [PATCH 2/3] Place consumers metric after requests_per_minute --- skills/apitally-cli/references/commands.md | 2 +- skills/apitally-cli/references/duckdb_tables.md | 4 ++-- src/main.rs | 4 ++-- src/metrics.rs | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/skills/apitally-cli/references/commands.md b/skills/apitally-cli/references/commands.md index 24ccb1b..fa9abb8 100644 --- a/skills/apitally-cli/references/commands.md +++ b/skills/apitally-cli/references/commands.md @@ -112,6 +112,7 @@ Fetch aggregated metrics for an app. Outputs NDJSON to stdout by default. | --------------------- | ------- | ------------------------------------------------------------------------------------------ | | `requests` | integer | Total request count | | `requests_per_minute` | float | Requests per minute | +| `consumers` | integer | Number of distinct consumers. Can't be summed across periods or groups (consumers overlap) | | `bytes_received` | integer | Total bytes received | | `bytes_sent` | integer | Total bytes sent | | `client_errors` | integer | 4xx errors (excluding expected errors) | @@ -122,7 +123,6 @@ Fetch aggregated metrics for an app. Outputs NDJSON to stdout by default. | `response_time_p90` | integer | 90th percentile response time (ms) | | `response_time_p95` | integer | 95th percentile response time (ms) | | `response_time_p99` | integer | 99th percentile response time (ms) | -| `consumers` | integer | Number of distinct consumers. Can't be summed across periods or groups (consumers overlap) | ### Group-by fields diff --git a/skills/apitally-cli/references/duckdb_tables.md b/skills/apitally-cli/references/duckdb_tables.md index ce08e10..36d3261 100644 --- a/skills/apitally-cli/references/duckdb_tables.md +++ b/skills/apitally-cli/references/duckdb_tables.md @@ -71,6 +71,7 @@ CREATE TABLE metrics ( status_code INTEGER, requests BIGINT, requests_per_minute DOUBLE, + consumers BIGINT, -- distinct count, can't be summed across periods or groups bytes_received BIGINT, bytes_sent BIGINT, client_errors BIGINT, @@ -80,8 +81,7 @@ CREATE TABLE metrics ( response_time_p75 INTEGER, -- milliseconds response_time_p90 INTEGER, -- milliseconds response_time_p95 INTEGER, -- milliseconds - response_time_p99 INTEGER, -- milliseconds - consumers BIGINT -- distinct count, can't be summed across periods or groups + response_time_p99 INTEGER -- milliseconds ); ``` diff --git a/src/main.rs b/src/main.rs index 8aa1c10..7771055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,8 +140,8 @@ enum Command { /// JSON array or comma-separated list of metric names to include /// - /// Available metrics: requests, requests_per_minute, bytes_received, bytes_sent, client_errors, server_errors, error_rate, - /// response_time_p50, response_time_p75, response_time_p90, response_time_p95, response_time_p99, consumers. + /// Available metrics: requests, requests_per_minute, consumers, bytes_received, bytes_sent, client_errors, server_errors, + /// error_rate, response_time_p50, response_time_p75, response_time_p90, response_time_p95, response_time_p99. /// The consumers metric is a distinct count and can't be summed across periods or groups. #[arg(long)] metrics: String, diff --git a/src/metrics.rs b/src/metrics.rs index f360118..7e9205b 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -21,6 +21,7 @@ pub(crate) fn ensure_metrics_table(conn: &duckdb::Connection) -> Result<()> { status_code INTEGER, requests BIGINT, requests_per_minute DOUBLE, + consumers BIGINT, bytes_received BIGINT, bytes_sent BIGINT, client_errors BIGINT, @@ -30,8 +31,7 @@ pub(crate) fn ensure_metrics_table(conn: &duckdb::Connection) -> Result<()> { response_time_p75 INTEGER, response_time_p90 INTEGER, response_time_p95 INTEGER, - response_time_p99 INTEGER, - consumers BIGINT + response_time_p99 INTEGER ); ALTER TABLE metrics ADD COLUMN IF NOT EXISTS response_time_p90 INTEGER; ALTER TABLE metrics ADD COLUMN IF NOT EXISTS response_time_p99 INTEGER; @@ -180,8 +180,8 @@ mod tests { ), Field::new("method", DataType::Utf8, true), Field::new("requests", DataType::Int64, false), - Field::new("error_rate", DataType::Float64, false), Field::new("consumers", DataType::Int64, false), + Field::new("error_rate", DataType::Float64, false), ])); let batch = RecordBatch::try_new( schema.clone(), @@ -200,8 +200,8 @@ mod tests { ), Arc::new(StringArray::from(vec![Some("GET"); ROW_COUNT])), Arc::new(Int64Array::from(vec![100; ROW_COUNT])), - Arc::new(Float64Array::from(vec![0.05; ROW_COUNT])), Arc::new(Int64Array::from(vec![3; ROW_COUNT])), + Arc::new(Float64Array::from(vec![0.05; ROW_COUNT])), ], ) .unwrap(); @@ -266,7 +266,7 @@ mod tests { 1, "2025-01-01", None, - r#"["requests","error_rate","consumers"]"#, + r#"["requests","consumers","error_rate"]"#, Some("hour"), Some(r#"["method"]"#), None, @@ -284,7 +284,7 @@ mod tests { let (count, periods, matching): (i64, i64, i64) = conn .query_row( "SELECT count(*), count(DISTINCT period_start), count(*) FILTER ( - WHERE app_id = 1 AND method = 'GET' AND requests = 100 AND error_rate = 0.05 AND consumers = 3 + WHERE app_id = 1 AND method = 'GET' AND requests = 100 AND consumers = 3 AND error_rate = 0.05 AND epoch_ms(period_end) - epoch_ms(period_start) = 3600000 ) FROM metrics", [], From c700a4bd332acf45bb80de1b6a4b50d45e7694c1 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Wed, 28 Oct 2026 16:52:38 +1000 Subject: [PATCH 3/3] Remove consumers summing notes --- skills/apitally-cli/references/commands.md | 30 +++++++++---------- .../apitally-cli/references/duckdb_tables.md | 2 +- src/main.rs | 1 - 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/skills/apitally-cli/references/commands.md b/skills/apitally-cli/references/commands.md index fa9abb8..fab0370 100644 --- a/skills/apitally-cli/references/commands.md +++ b/skills/apitally-cli/references/commands.md @@ -108,21 +108,21 @@ Fetch aggregated metrics for an app. Outputs NDJSON to stdout by default. ### Available metrics -| Metric | Type | Description | -| --------------------- | ------- | ------------------------------------------------------------------------------------------ | -| `requests` | integer | Total request count | -| `requests_per_minute` | float | Requests per minute | -| `consumers` | integer | Number of distinct consumers. Can't be summed across periods or groups (consumers overlap) | -| `bytes_received` | integer | Total bytes received | -| `bytes_sent` | integer | Total bytes sent | -| `client_errors` | integer | 4xx errors (excluding expected errors) | -| `server_errors` | integer | 5xx errors (excluding expected errors) | -| `error_rate` | float | Ratio of errors to total requests | -| `response_time_p50` | integer | 50th percentile response time (ms) | -| `response_time_p75` | integer | 75th percentile response time (ms) | -| `response_time_p90` | integer | 90th percentile response time (ms) | -| `response_time_p95` | integer | 95th percentile response time (ms) | -| `response_time_p99` | integer | 99th percentile response time (ms) | +| Metric | Type | Description | +| --------------------- | ------- | -------------------------------------- | +| `requests` | integer | Total request count | +| `requests_per_minute` | float | Requests per minute | +| `consumers` | integer | Number of distinct consumers | +| `bytes_received` | integer | Total bytes received | +| `bytes_sent` | integer | Total bytes sent | +| `client_errors` | integer | 4xx errors (excluding expected errors) | +| `server_errors` | integer | 5xx errors (excluding expected errors) | +| `error_rate` | float | Ratio of errors to total requests | +| `response_time_p50` | integer | 50th percentile response time (ms) | +| `response_time_p75` | integer | 75th percentile response time (ms) | +| `response_time_p90` | integer | 90th percentile response time (ms) | +| `response_time_p95` | integer | 95th percentile response time (ms) | +| `response_time_p99` | integer | 99th percentile response time (ms) | ### Group-by fields diff --git a/skills/apitally-cli/references/duckdb_tables.md b/skills/apitally-cli/references/duckdb_tables.md index 36d3261..927635c 100644 --- a/skills/apitally-cli/references/duckdb_tables.md +++ b/skills/apitally-cli/references/duckdb_tables.md @@ -71,7 +71,7 @@ CREATE TABLE metrics ( status_code INTEGER, requests BIGINT, requests_per_minute DOUBLE, - consumers BIGINT, -- distinct count, can't be summed across periods or groups + consumers BIGINT, bytes_received BIGINT, bytes_sent BIGINT, client_errors BIGINT, diff --git a/src/main.rs b/src/main.rs index 7771055..a760f49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,7 +142,6 @@ enum Command { /// /// Available metrics: requests, requests_per_minute, consumers, bytes_received, bytes_sent, client_errors, server_errors, /// error_rate, response_time_p50, response_time_p75, response_time_p90, response_time_p95, response_time_p99. - /// The consumers metric is a distinct count and can't be summed across periods or groups. #[arg(long)] metrics: String,