mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Exclude unknown providers from usage aggregations
This commit is contained in:
@@ -163,12 +163,19 @@ pub(super) async fn build_admin_usage_aggregation_stats_response(
|
|||||||
"api_format" => UsageAuditAggregationGroupBy::ApiFormat,
|
"api_format" => UsageAuditAggregationGroupBy::ApiFormat,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
let exclude_reserved_provider_labels = matches!(
|
||||||
|
group_by_query,
|
||||||
|
UsageAuditAggregationGroupBy::Model
|
||||||
|
| UsageAuditAggregationGroupBy::Provider
|
||||||
|
| UsageAuditAggregationGroupBy::ApiFormat
|
||||||
|
);
|
||||||
let usage = state
|
let usage = state
|
||||||
.aggregate_usage_audits(&UsageAuditAggregationQuery {
|
.aggregate_usage_audits(&UsageAuditAggregationQuery {
|
||||||
created_from_unix_secs,
|
created_from_unix_secs,
|
||||||
created_until_unix_secs,
|
created_until_unix_secs,
|
||||||
group_by: group_by_query,
|
group_by: group_by_query,
|
||||||
limit,
|
limit,
|
||||||
|
exclude_reserved_provider_labels,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -780,6 +780,7 @@ async fn dashboard_load_user_counts(
|
|||||||
created_until_unix_secs,
|
created_until_unix_secs,
|
||||||
group_by: UsageAuditAggregationGroupBy::User,
|
group_by: UsageAuditAggregationGroupBy::User,
|
||||||
limit: 10_000,
|
limit: 10_000,
|
||||||
|
exclude_reserved_provider_labels: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let count = fallback.len() as u64;
|
let count = fallback.len() as u64;
|
||||||
|
|||||||
@@ -528,8 +528,29 @@ async fn gateway_handles_admin_usage_aggregation_stats_locally_with_trusted_admi
|
|||||||
usage_3.endpoint_api_format = Some("claude:messages".to_string());
|
usage_3.endpoint_api_format = Some("claude:messages".to_string());
|
||||||
usage_3.provider_api_family = Some("claude".to_string());
|
usage_3.provider_api_family = Some("claude".to_string());
|
||||||
|
|
||||||
|
let mut unknown_usage = sample_usage_row(
|
||||||
|
"usage-unknown",
|
||||||
|
"req-unknown",
|
||||||
|
Some("user-2"),
|
||||||
|
Some("key-2"),
|
||||||
|
Some("secondary"),
|
||||||
|
"unknow",
|
||||||
|
"gpt-5",
|
||||||
|
"completed",
|
||||||
|
900,
|
||||||
|
300,
|
||||||
|
9.0,
|
||||||
|
10.8,
|
||||||
|
DAY_2_UNIX_SECS,
|
||||||
|
);
|
||||||
|
unknown_usage.provider_id = None;
|
||||||
|
unknown_usage.total_tokens = unknown_usage.input_tokens;
|
||||||
|
|
||||||
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
|
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
|
||||||
usage_1, usage_2, usage_3,
|
usage_1,
|
||||||
|
usage_2,
|
||||||
|
usage_3,
|
||||||
|
unknown_usage,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
let gateway = build_router_with_state(
|
let gateway = build_router_with_state(
|
||||||
|
|||||||
@@ -673,6 +673,7 @@ pub struct UsageAuditAggregationQuery {
|
|||||||
pub created_until_unix_secs: u64,
|
pub created_until_unix_secs: u64,
|
||||||
pub group_by: UsageAuditAggregationGroupBy,
|
pub group_by: UsageAuditAggregationGroupBy,
|
||||||
pub limit: usize,
|
pub limit: usize,
|
||||||
|
pub exclude_reserved_provider_labels: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
|||||||
@@ -1155,6 +1155,8 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
|
|||||||
if item.created_at_unix_ms < query.created_from_unix_secs
|
if item.created_at_unix_ms < query.created_from_unix_secs
|
||||||
|| item.created_at_unix_ms >= query.created_until_unix_secs
|
|| item.created_at_unix_ms >= query.created_until_unix_secs
|
||||||
|| matches!(item.status.as_str(), "pending" | "streaming")
|
|| matches!(item.status.as_str(), "pending" | "streaming")
|
||||||
|
|| (query.exclude_reserved_provider_labels
|
||||||
|
&& usage_provider_display_name(item).is_none())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -3090,6 +3092,7 @@ mod tests {
|
|||||||
created_until_unix_secs: 1_000,
|
created_until_unix_secs: 1_000,
|
||||||
group_by: UsageAuditAggregationGroupBy::Provider,
|
group_by: UsageAuditAggregationGroupBy::Provider,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
exclude_reserved_provider_labels: false,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.expect("aggregation should succeed");
|
.expect("aggregation should succeed");
|
||||||
@@ -3099,6 +3102,56 @@ mod tests {
|
|||||||
assert_eq!(rows[0].display_name.as_deref(), Some("OpenAI"));
|
assert_eq!(rows[0].display_name.as_deref(), Some("OpenAI"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn aggregation_can_skip_unknown_provider_records_for_model_and_api_format() {
|
||||||
|
let mut unknown = sample_usage("req-unknown-provider", 100);
|
||||||
|
unknown.provider_id = None;
|
||||||
|
unknown.provider_name = "unknown".to_string();
|
||||||
|
|
||||||
|
let mut typo_unknown = sample_usage("req-unknow-provider", 200);
|
||||||
|
typo_unknown.provider_id = Some("unknow".to_string());
|
||||||
|
typo_unknown.provider_name = "unknow".to_string();
|
||||||
|
|
||||||
|
let mut pending_provider = sample_usage("req-pending-provider", 300);
|
||||||
|
pending_provider.provider_id = None;
|
||||||
|
pending_provider.provider_name = "pending".to_string();
|
||||||
|
|
||||||
|
let repository = InMemoryUsageReadRepository::seed(vec![
|
||||||
|
sample_usage("req-valid-provider", 400),
|
||||||
|
unknown,
|
||||||
|
typo_unknown,
|
||||||
|
pending_provider,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let model_rows = repository
|
||||||
|
.aggregate_usage_audits(&UsageAuditAggregationQuery {
|
||||||
|
created_from_unix_secs: 0,
|
||||||
|
created_until_unix_secs: 1_000,
|
||||||
|
group_by: UsageAuditAggregationGroupBy::Model,
|
||||||
|
limit: 10,
|
||||||
|
exclude_reserved_provider_labels: true,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("model aggregation should succeed");
|
||||||
|
assert_eq!(model_rows.len(), 1);
|
||||||
|
assert_eq!(model_rows[0].group_key, "gpt-4.1");
|
||||||
|
assert_eq!(model_rows[0].request_count, 1);
|
||||||
|
|
||||||
|
let api_format_rows = repository
|
||||||
|
.aggregate_usage_audits(&UsageAuditAggregationQuery {
|
||||||
|
created_from_unix_secs: 0,
|
||||||
|
created_until_unix_secs: 1_000,
|
||||||
|
group_by: UsageAuditAggregationGroupBy::ApiFormat,
|
||||||
|
limit: 10,
|
||||||
|
exclude_reserved_provider_labels: true,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("api format aggregation should succeed");
|
||||||
|
assert_eq!(api_format_rows.len(), 1);
|
||||||
|
assert_eq!(api_format_rows[0].group_key, "openai:chat");
|
||||||
|
assert_eq!(api_format_rows[0].request_count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn stale_pending_update_does_not_regress_finalized_usage() {
|
async fn stale_pending_update_does_not_regress_finalized_usage() {
|
||||||
let repository = InMemoryUsageReadRepository::default();
|
let repository = InMemoryUsageReadRepository::default();
|
||||||
|
|||||||
@@ -1336,6 +1336,8 @@ const REBUILD_PROVIDER_API_KEY_USAGE_STATS_SQL: &str =
|
|||||||
include_str!("queries/rebuild_provider_api_key_usage_stats_sql.sql");
|
include_str!("queries/rebuild_provider_api_key_usage_stats_sql.sql");
|
||||||
|
|
||||||
const LIST_USAGE_AUDITS_PREFIX: &str = include_str!("queries/list_usage_audits_prefix.sql");
|
const LIST_USAGE_AUDITS_PREFIX: &str = include_str!("queries/list_usage_audits_prefix.sql");
|
||||||
|
const USAGE_RESERVED_PROVIDER_LABELS_FILTER_SQL: &str =
|
||||||
|
" AND BTRIM(COALESCE(\"usage\".provider_name, '')) <> '' AND lower(BTRIM(COALESCE(\"usage\".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')";
|
||||||
|
|
||||||
struct UsageAuditAggregationSqlFragments {
|
struct UsageAuditAggregationSqlFragments {
|
||||||
filtered_extra_where: &'static str,
|
filtered_extra_where: &'static str,
|
||||||
@@ -1363,7 +1365,7 @@ fn usage_audit_aggregation_sql_fragments(
|
|||||||
success_count_expr: "NULL::BIGINT",
|
success_count_expr: "NULL::BIGINT",
|
||||||
},
|
},
|
||||||
UsageAuditAggregationGroupBy::Provider => UsageAuditAggregationSqlFragments {
|
UsageAuditAggregationGroupBy::Provider => UsageAuditAggregationSqlFragments {
|
||||||
filtered_extra_where: " AND BTRIM(COALESCE(\"usage\".provider_name, '')) <> '' AND lower(BTRIM(COALESCE(\"usage\".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')",
|
filtered_extra_where: "",
|
||||||
group_key_expr: "provider_group_key",
|
group_key_expr: "provider_group_key",
|
||||||
display_name_expr: "provider_display_name",
|
display_name_expr: "provider_display_name",
|
||||||
secondary_name_expr: "NULL::varchar",
|
secondary_name_expr: "NULL::varchar",
|
||||||
@@ -6417,6 +6419,12 @@ WHERE stats_daily_api_key.date >=
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let provider_extra_where = if matches!(group_by, UsageAuditAggregationGroupBy::Provider) {
|
||||||
|
" AND BTRIM(COALESCE(provider_name, '')) <> '' AND lower(BTRIM(COALESCE(provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@@ -6441,6 +6449,7 @@ SELECT
|
|||||||
FROM {table_name}
|
FROM {table_name}
|
||||||
WHERE date >= $1
|
WHERE date >= $1
|
||||||
AND date < $2
|
AND date < $2
|
||||||
|
{provider_extra_where}
|
||||||
GROUP BY {group_column}
|
GROUP BY {group_column}
|
||||||
ORDER BY request_count DESC, group_key ASC
|
ORDER BY request_count DESC, group_key ASC
|
||||||
"#,
|
"#,
|
||||||
@@ -6449,6 +6458,7 @@ ORDER BY request_count DESC, group_key ASC
|
|||||||
avg_response_time_expr = avg_response_time_expr,
|
avg_response_time_expr = avg_response_time_expr,
|
||||||
success_count_expr = success_count_expr,
|
success_count_expr = success_count_expr,
|
||||||
table_name = table_name,
|
table_name = table_name,
|
||||||
|
provider_extra_where = provider_extra_where,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut rows = sqlx::query(&sql)
|
let mut rows = sqlx::query(&sql)
|
||||||
@@ -6467,6 +6477,13 @@ ORDER BY request_count DESC, group_key ASC
|
|||||||
query: &UsageAuditAggregationQuery,
|
query: &UsageAuditAggregationQuery,
|
||||||
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
|
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
|
||||||
let fragments = usage_audit_aggregation_sql_fragments(query.group_by);
|
let fragments = usage_audit_aggregation_sql_fragments(query.group_by);
|
||||||
|
let provider_extra_where = if matches!(query.group_by, UsageAuditAggregationGroupBy::Provider)
|
||||||
|
|| query.exclude_reserved_provider_labels
|
||||||
|
{
|
||||||
|
USAGE_RESERVED_PROVIDER_LABELS_FILTER_SQL
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
r#"
|
r#"
|
||||||
WITH filtered_usage AS (
|
WITH filtered_usage AS (
|
||||||
@@ -6518,6 +6535,7 @@ WITH filtered_usage AS (
|
|||||||
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)
|
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)
|
||||||
AND "usage".created_at < TO_TIMESTAMP($2::double precision)
|
AND "usage".created_at < TO_TIMESTAMP($2::double precision)
|
||||||
AND "usage".status NOT IN ('pending', 'streaming')
|
AND "usage".status NOT IN ('pending', 'streaming')
|
||||||
|
{provider_extra_where}
|
||||||
{filtered_extra_where}
|
{filtered_extra_where}
|
||||||
),
|
),
|
||||||
normalized_usage AS (
|
normalized_usage AS (
|
||||||
@@ -6611,6 +6629,7 @@ FROM aggregated_usage
|
|||||||
ORDER BY request_count DESC, group_key ASC
|
ORDER BY request_count DESC, group_key ASC
|
||||||
LIMIT $3
|
LIMIT $3
|
||||||
"#,
|
"#,
|
||||||
|
provider_extra_where = provider_extra_where,
|
||||||
filtered_extra_where = fragments.filtered_extra_where,
|
filtered_extra_where = fragments.filtered_extra_where,
|
||||||
group_key_expr = fragments.group_key_expr,
|
group_key_expr = fragments.group_key_expr,
|
||||||
display_name_expr = fragments.display_name_expr,
|
display_name_expr = fragments.display_name_expr,
|
||||||
@@ -6646,6 +6665,11 @@ LIMIT $3
|
|||||||
if matches!(query.group_by, UsageAuditAggregationGroupBy::User) {
|
if matches!(query.group_by, UsageAuditAggregationGroupBy::User) {
|
||||||
return self.aggregate_usage_audits_raw(query).await;
|
return self.aggregate_usage_audits_raw(query).await;
|
||||||
}
|
}
|
||||||
|
if query.exclude_reserved_provider_labels
|
||||||
|
&& !matches!(query.group_by, UsageAuditAggregationGroupBy::Provider)
|
||||||
|
{
|
||||||
|
return self.aggregate_usage_audits_raw(query).await;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
|
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
|
||||||
return self.aggregate_usage_audits_raw(query).await;
|
return self.aggregate_usage_audits_raw(query).await;
|
||||||
@@ -6666,6 +6690,7 @@ LIMIT $3
|
|||||||
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
|
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
|
||||||
group_by: query.group_by,
|
group_by: query.group_by,
|
||||||
limit: raw_merge_limit,
|
limit: raw_merge_limit,
|
||||||
|
exclude_reserved_provider_labels: query.exclude_reserved_provider_labels,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
|
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
|
||||||
@@ -6687,6 +6712,7 @@ LIMIT $3
|
|||||||
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
|
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
|
||||||
group_by: query.group_by,
|
group_by: query.group_by,
|
||||||
limit: raw_merge_limit,
|
limit: raw_merge_limit,
|
||||||
|
exclude_reserved_provider_labels: query.exclude_reserved_provider_labels,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
|
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
|
||||||
|
|||||||
@@ -420,6 +420,10 @@ fn usage_sql_provider_aggregation_excludes_unknown_provider_labels() {
|
|||||||
assert!(source.contains(
|
assert!(source.contains(
|
||||||
r#"lower(BTRIM(COALESCE(\"usage\".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')"#
|
r#"lower(BTRIM(COALESCE(\"usage\".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')"#
|
||||||
));
|
));
|
||||||
|
assert!(source.contains("exclude_reserved_provider_labels"));
|
||||||
|
assert!(source.contains(
|
||||||
|
r#"lower(BTRIM(COALESCE(provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')"#
|
||||||
|
));
|
||||||
assert!(source.contains("MAX(display_name)"));
|
assert!(source.contains("MAX(display_name)"));
|
||||||
assert!(!source.contains("COALESCE(MAX(NULLIF(display_name, 'Unknown')), 'Unknown')"));
|
assert!(!source.contains("COALESCE(MAX(NULLIF(display_name, 'Unknown')), 'Unknown')"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user