Files
Aether/crates/aether-data/src/repository/usage/postgres/mod.rs
T

11530 lines
437 KiB
Rust

use aether_data_contracts::repository::usage::{
parse_usage_body_ref, usage_body_ref, ApiKeyLastUsedDelta, ManagementTokenCounterDelta,
ProxyNodeCounterDelta, StoredUsageAuditAggregation, StoredUsageAuditSummary,
StoredUsageBreakdownSummaryRow, StoredUsageCacheAffinityHitSummary,
StoredUsageCacheAffinityIntervalRow, StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary,
StoredUsageDashboardDailyBreakdownRow, StoredUsageDashboardProviderCount,
StoredUsageDashboardSummary, StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
StoredUsagePerformancePercentilesRow, StoredUsageProviderPerformance,
StoredUsageProviderPerformanceProviderRow, StoredUsageProviderPerformanceSummary,
StoredUsageProviderPerformanceTimelineRow, StoredUsageSettledCostSummary,
StoredUsageTimeSeriesBucket, StoredUsageUserTotals, UsageAuditAggregationGroupBy,
UsageAuditAggregationQuery, UsageAuditKeywordSearchQuery, UsageAuditSummaryQuery,
UsageBodyCaptureState, UsageBodyField, UsageBreakdownGroupBy, UsageBreakdownSummaryQuery,
UsageCacheAffinityHitSummaryQuery, UsageCacheAffinityIntervalGroupBy,
UsageCacheAffinityIntervalQuery, UsageCacheHitSummaryQuery, UsageCleanupExecutionMode,
UsageCleanupSummary, UsageCleanupTargets, UsageCleanupWindow, UsageCostSavingsSummaryQuery,
UsageDashboardDailyBreakdownQuery, UsageDashboardProviderCountsQuery,
UsageDashboardSummaryQuery, UsageErrorDistributionQuery, UsageLeaderboardGroupBy,
UsageLeaderboardQuery, UsageMonitoringErrorCountQuery, UsageMonitoringErrorListQuery,
UsagePerformancePercentilesQuery, UsageProviderPerformanceQuery, UsageSettledCostSummaryQuery,
UsageTimeSeriesGranularity, UsageTimeSeriesQuery,
};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use futures_util::future::BoxFuture;
use futures_util::TryStreamExt;
use serde_json::Map;
use serde_json::Value;
use sqlx::{
postgres::{PgArguments, PgRow},
query::Query,
PgPool, Postgres, QueryBuilder, Row,
};
use std::collections::BTreeMap;
use std::io::{Read, Write};
use uuid::Uuid;
use super::{
api_key_usage_contribution, incoming_usage_can_recover_terminal_failure,
model_usage_contribution, provider_api_key_usage_contribution,
strip_deprecated_usage_display_fields, ApiKeyUsageDelta, ModelUsageDelta,
PendingUsageCleanupSummary, ProviderApiKeyUsageDelta, ProviderApiKeyWindowUsageRequest,
StoredProviderApiKeyUsageSummary, StoredProviderApiKeyWindowUsageSummary,
StoredProviderUsageSummary, StoredRequestUsageAudit, StoredUsageDailySummary,
UpsertUsageRecord, UsageAuditListQuery, UsageCounterFlushSummary, UsageCounterHealthSnapshot,
UsageDailyHeatmapQuery, UsageReadRepository, UsageWriteRepository,
};
use crate::driver::postgres::PostgresTransactionRunner;
use crate::{
error::{postgres_error, SqlxResultExt},
DataLayerError,
};
pub mod cleanup;
// Legacy inline body columns on public.usage are deprecated. Keep the threshold at zero so
// newly captured bodies always spill to usage_body_blobs and resolve through usage_http_audits.
const MAX_INLINE_USAGE_BODY_BYTES: usize = 0;
const MAX_SUPPORTED_UNIX_SECS: u64 = 253_402_300_799;
const FIND_USAGE_BODY_BLOB_BY_REF_SQL: &str =
r#"SELECT payload_gzip FROM usage_body_blobs WHERE body_ref = $1 LIMIT 1"#;
const UPSERT_USAGE_BODY_BLOB_SQL: &str = include_str!("queries/upsert_usage_body_blob_sql.sql");
const DELETE_USAGE_BODY_BLOB_SQL: &str = include_str!("queries/delete_usage_body_blob_sql.sql");
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct AggregateRangeSplit {
raw_leading: Option<(DateTime<Utc>, DateTime<Utc>)>,
aggregate: Option<(DateTime<Utc>, DateTime<Utc>)>,
raw_trailing: Option<(DateTime<Utc>, DateTime<Utc>)>,
}
fn dashboard_non_empty_utc_range(
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
(start_utc < end_utc).then_some((start_utc, end_utc))
}
fn dashboard_unix_secs_to_utc(unix_secs: u64) -> DateTime<Utc> {
DateTime::<Utc>::from_timestamp(unix_secs.min(MAX_SUPPORTED_UNIX_SECS) as i64, 0)
.expect("clamped unix timestamp should be valid")
}
fn dashboard_utc_to_unix_secs(value: DateTime<Utc>) -> u64 {
value.timestamp().max(0) as u64
}
fn dashboard_utc_midnight(value: DateTime<Utc>) -> DateTime<Utc> {
DateTime::<Utc>::from_naive_utc_and_offset(
value
.date_naive()
.and_hms_opt(0, 0, 0)
.expect("midnight should be valid"),
Utc,
)
}
fn dashboard_next_utc_midnight(value: DateTime<Utc>) -> DateTime<Utc> {
let midnight = dashboard_utc_midnight(value);
if value == midnight {
midnight
} else {
midnight + chrono::Duration::days(1)
}
}
fn dashboard_utc_hour(value: DateTime<Utc>) -> DateTime<Utc> {
let hour_unix_secs = value.timestamp().div_euclid(3600) * 3600;
DateTime::<Utc>::from_timestamp(hour_unix_secs, 0).expect("hour-aligned timestamp is valid")
}
fn dashboard_next_utc_hour(value: DateTime<Utc>) -> DateTime<Utc> {
let hour_utc = dashboard_utc_hour(value);
if value == hour_utc {
hour_utc
} else {
hour_utc + chrono::Duration::hours(1)
}
}
fn split_dashboard_daily_aggregate_range(
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
cutoff_utc: DateTime<Utc>,
) -> AggregateRangeSplit {
if start_utc >= end_utc {
return AggregateRangeSplit::default();
}
let aggregate_start = dashboard_next_utc_midnight(start_utc);
let aggregate_end = dashboard_utc_midnight(end_utc).min(cutoff_utc);
if aggregate_start < aggregate_end {
let leading_end = aggregate_start.min(end_utc);
AggregateRangeSplit {
raw_leading: dashboard_non_empty_utc_range(start_utc, leading_end),
aggregate: dashboard_non_empty_utc_range(aggregate_start, aggregate_end),
raw_trailing: dashboard_non_empty_utc_range(aggregate_end, end_utc),
}
} else {
AggregateRangeSplit {
raw_leading: dashboard_non_empty_utc_range(start_utc, end_utc),
aggregate: None,
raw_trailing: None,
}
}
}
fn split_dashboard_hourly_aggregate_range(
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
cutoff_utc: DateTime<Utc>,
) -> AggregateRangeSplit {
if start_utc >= end_utc {
return AggregateRangeSplit::default();
}
let aggregate_start = dashboard_next_utc_hour(start_utc);
let aggregate_end = dashboard_utc_hour(end_utc).min(cutoff_utc);
if aggregate_start < aggregate_end {
let leading_end = aggregate_start.min(end_utc);
AggregateRangeSplit {
raw_leading: dashboard_non_empty_utc_range(start_utc, leading_end),
aggregate: dashboard_non_empty_utc_range(aggregate_start, aggregate_end),
raw_trailing: dashboard_non_empty_utc_range(aggregate_end, end_utc),
}
} else {
AggregateRangeSplit {
raw_leading: dashboard_non_empty_utc_range(start_utc, end_utc),
aggregate: None,
raw_trailing: None,
}
}
}
fn dashboard_aggregate_schema_mismatch_message(message: &str) -> bool {
let message = message.to_ascii_lowercase();
let references_dashboard_aggregate = [
"stats_summary",
"stats_daily",
"stats_user_daily",
"stats_daily_model_provider",
"stats_user_daily_model_provider",
"cutoff_date",
"effective_input_tokens",
"total_input_context",
"response_time_sum_ms",
"response_time_samples",
]
.iter()
.any(|pattern| message.contains(pattern));
if !references_dashboard_aggregate {
return false;
}
message.contains("does not exist")
|| message.contains("unknown column")
|| message.contains("no column named")
|| message.contains("error occurred while decoding column")
|| message.contains("is not compatible with sql type")
|| message.contains("unexpected null")
}
fn dashboard_should_fallback_to_raw_on_aggregate_error(err: &DataLayerError) -> bool {
dashboard_aggregate_schema_mismatch_message(&err.to_string())
}
fn absorb_dashboard_summary(
target: &mut StoredUsageDashboardSummary,
part: &StoredUsageDashboardSummary,
) {
target.total_requests = target.total_requests.saturating_add(part.total_requests);
target.input_tokens = target.input_tokens.saturating_add(part.input_tokens);
target.effective_input_tokens = target
.effective_input_tokens
.saturating_add(part.effective_input_tokens);
target.output_tokens = target.output_tokens.saturating_add(part.output_tokens);
target.total_tokens = target.total_tokens.saturating_add(part.total_tokens);
target.cache_creation_tokens = target
.cache_creation_tokens
.saturating_add(part.cache_creation_tokens);
target.cache_read_tokens = target
.cache_read_tokens
.saturating_add(part.cache_read_tokens);
target.total_input_context = target
.total_input_context
.saturating_add(part.total_input_context);
target.cache_creation_cost_usd += part.cache_creation_cost_usd;
target.cache_read_cost_usd += part.cache_read_cost_usd;
target.total_cost_usd += part.total_cost_usd;
target.actual_total_cost_usd += part.actual_total_cost_usd;
target.error_requests = target.error_requests.saturating_add(part.error_requests);
target.response_time_sum_ms += part.response_time_sum_ms;
target.response_time_samples = target
.response_time_samples
.saturating_add(part.response_time_samples);
}
fn absorb_dashboard_provider_counts(
target: &mut BTreeMap<String, u64>,
rows: Vec<StoredUsageDashboardProviderCount>,
) {
for row in rows {
let entry = target.entry(row.provider_name).or_default();
*entry = entry.saturating_add(row.request_count);
}
}
fn finalize_dashboard_provider_counts(
grouped: BTreeMap<String, u64>,
) -> Vec<StoredUsageDashboardProviderCount> {
let mut items = grouped
.into_iter()
.map(
|(provider_name, request_count)| StoredUsageDashboardProviderCount {
provider_name,
request_count,
},
)
.collect::<Vec<_>>();
items.sort_by(|left, right| {
right
.request_count
.cmp(&left.request_count)
.then_with(|| left.provider_name.cmp(&right.provider_name))
});
items
}
fn decode_dashboard_summary_row(
row: &PgRow,
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
Ok(StoredUsageDashboardSummary {
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
effective_input_tokens: row
.try_get::<i64, _>("effective_input_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
total_input_context: row
.try_get::<i64, _>("total_input_context")
.map_postgres_err()?
.max(0) as u64,
cache_creation_cost_usd: row
.try_get::<f64, _>("cache_creation_cost_usd")
.map_postgres_err()?,
cache_read_cost_usd: row
.try_get::<f64, _>("cache_read_cost_usd")
.map_postgres_err()?,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
actual_total_cost_usd: row
.try_get::<f64, _>("actual_total_cost_usd")
.map_postgres_err()?,
error_requests: row
.try_get::<i64, _>("error_requests")
.map_postgres_err()?
.max(0) as u64,
response_time_sum_ms: row
.try_get::<f64, _>("response_time_sum_ms")
.map_postgres_err()?,
response_time_samples: row
.try_get::<i64, _>("response_time_samples")
.map_postgres_err()?
.max(0) as u64,
})
}
fn finalize_dashboard_daily_breakdown_rows(
mut items: Vec<StoredUsageDashboardDailyBreakdownRow>,
) -> Vec<StoredUsageDashboardDailyBreakdownRow> {
items.sort_by(|left, right| {
left.date
.cmp(&right.date)
.then_with(|| {
right
.total_cost_usd
.partial_cmp(&left.total_cost_usd)
.unwrap_or(std::cmp::Ordering::Equal)
})
.then_with(|| left.model.cmp(&right.model))
.then_with(|| left.provider.cmp(&right.provider))
});
items
}
fn decode_dashboard_daily_breakdown_row(
row: &PgRow,
) -> Result<StoredUsageDashboardDailyBreakdownRow, DataLayerError> {
Ok(StoredUsageDashboardDailyBreakdownRow {
date: row.try_get::<String, _>("date").map_postgres_err()?,
model: row.try_get::<String, _>("model").map_postgres_err()?,
provider: row.try_get::<String, _>("provider").map_postgres_err()?,
requests: row.try_get::<i64, _>("requests").map_postgres_err()?.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
response_time_sum_ms: row
.try_get::<f64, _>("response_time_sum_ms")
.map_postgres_err()?,
response_time_samples: row
.try_get::<i64, _>("response_time_samples")
.map_postgres_err()?
.max(0) as u64,
})
}
fn absorb_usage_breakdown_rows(
target: &mut BTreeMap<String, StoredUsageBreakdownSummaryRow>,
rows: Vec<StoredUsageBreakdownSummaryRow>,
) {
for row in rows {
let group_key = row.group_key.clone();
let entry =
target
.entry(group_key.clone())
.or_insert_with(|| StoredUsageBreakdownSummaryRow {
group_key,
..Default::default()
});
entry.request_count = entry.request_count.saturating_add(row.request_count);
entry.input_tokens = entry.input_tokens.saturating_add(row.input_tokens);
entry.total_tokens = entry.total_tokens.saturating_add(row.total_tokens);
entry.output_tokens = entry.output_tokens.saturating_add(row.output_tokens);
entry.effective_input_tokens = entry
.effective_input_tokens
.saturating_add(row.effective_input_tokens);
entry.total_input_context = entry
.total_input_context
.saturating_add(row.total_input_context);
entry.cache_creation_tokens = entry
.cache_creation_tokens
.saturating_add(row.cache_creation_tokens);
entry.cache_creation_ephemeral_5m_tokens = entry
.cache_creation_ephemeral_5m_tokens
.saturating_add(row.cache_creation_ephemeral_5m_tokens);
entry.cache_creation_ephemeral_1h_tokens = entry
.cache_creation_ephemeral_1h_tokens
.saturating_add(row.cache_creation_ephemeral_1h_tokens);
entry.cache_read_tokens = entry
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
entry.total_cost_usd += row.total_cost_usd;
entry.actual_total_cost_usd += row.actual_total_cost_usd;
entry.success_count = entry.success_count.saturating_add(row.success_count);
entry.response_time_sum_ms += row.response_time_sum_ms;
entry.response_time_samples = entry
.response_time_samples
.saturating_add(row.response_time_samples);
entry.overall_response_time_sum_ms += row.overall_response_time_sum_ms;
entry.overall_response_time_samples = entry
.overall_response_time_samples
.saturating_add(row.overall_response_time_samples);
}
}
fn finalize_usage_breakdown_rows(
grouped: BTreeMap<String, StoredUsageBreakdownSummaryRow>,
) -> Vec<StoredUsageBreakdownSummaryRow> {
let mut items = grouped.into_values().collect::<Vec<_>>();
items.sort_by(|left, right| {
right
.request_count
.cmp(&left.request_count)
.then_with(|| left.group_key.cmp(&right.group_key))
});
items
}
fn absorb_usage_audit_aggregation_rows(
target: &mut BTreeMap<String, StoredUsageAuditAggregation>,
rows: Vec<StoredUsageAuditAggregation>,
) {
for row in rows {
let group_key = row.group_key.clone();
let entry =
target
.entry(group_key.clone())
.or_insert_with(|| StoredUsageAuditAggregation {
group_key,
display_name: row.display_name.clone(),
secondary_name: row.secondary_name.clone(),
request_count: 0,
total_tokens: 0,
output_tokens: 0,
effective_input_tokens: 0,
total_input_context: 0,
cache_creation_tokens: 0,
cache_creation_ephemeral_5m_tokens: 0,
cache_creation_ephemeral_1h_tokens: 0,
cache_read_tokens: 0,
total_cost_usd: 0.0,
actual_total_cost_usd: 0.0,
avg_response_time_ms: None,
success_count: row.success_count.map(|_| 0),
});
if entry.display_name.is_none() {
entry.display_name = row.display_name;
}
if entry.secondary_name.is_none() {
entry.secondary_name = row.secondary_name;
}
let existing_request_count = entry.request_count;
let next_request_count = row.request_count;
entry.request_count = entry.request_count.saturating_add(row.request_count);
entry.total_tokens = entry.total_tokens.saturating_add(row.total_tokens);
entry.output_tokens = entry.output_tokens.saturating_add(row.output_tokens);
entry.effective_input_tokens = entry
.effective_input_tokens
.saturating_add(row.effective_input_tokens);
entry.total_input_context = entry
.total_input_context
.saturating_add(row.total_input_context);
entry.cache_creation_tokens = entry
.cache_creation_tokens
.saturating_add(row.cache_creation_tokens);
entry.cache_creation_ephemeral_5m_tokens = entry
.cache_creation_ephemeral_5m_tokens
.saturating_add(row.cache_creation_ephemeral_5m_tokens);
entry.cache_creation_ephemeral_1h_tokens = entry
.cache_creation_ephemeral_1h_tokens
.saturating_add(row.cache_creation_ephemeral_1h_tokens);
entry.cache_read_tokens = entry
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
entry.total_cost_usd += row.total_cost_usd;
entry.actual_total_cost_usd += row.actual_total_cost_usd;
entry.success_count = match (entry.success_count, row.success_count) {
(Some(left), Some(right)) => Some(left.saturating_add(right)),
(Some(left), None) => Some(left),
(None, Some(right)) => Some(right),
(None, None) => None,
};
entry.avg_response_time_ms = match (entry.avg_response_time_ms, row.avg_response_time_ms) {
(Some(left), Some(right)) if entry.request_count > 0 => Some(
((left * existing_request_count as f64) + (right * next_request_count as f64))
/ entry.request_count as f64,
),
(Some(left), _) => Some(left),
(None, Some(right)) => Some(right),
(None, None) => None,
};
}
}
fn finalize_usage_audit_aggregation_rows(
grouped: BTreeMap<String, StoredUsageAuditAggregation>,
limit: usize,
) -> Vec<StoredUsageAuditAggregation> {
let mut items = grouped.into_values().collect::<Vec<_>>();
items.sort_by(|left, right| {
right
.request_count
.cmp(&left.request_count)
.then_with(|| left.group_key.cmp(&right.group_key))
});
items.truncate(limit);
items
}
fn decode_usage_breakdown_summary_row(
row: &PgRow,
) -> Result<StoredUsageBreakdownSummaryRow, DataLayerError> {
Ok(StoredUsageBreakdownSummaryRow {
group_key: row.try_get::<String, _>("group_key").map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
effective_input_tokens: row
.try_get::<i64, _>("effective_input_tokens")
.map_postgres_err()?
.max(0) as u64,
total_input_context: row
.try_get::<i64, _>("total_input_context")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_5m_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_5m_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_1h_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_1h_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
actual_total_cost_usd: row
.try_get::<f64, _>("actual_total_cost_usd")
.map_postgres_err()?,
success_count: row
.try_get::<i64, _>("success_count")
.map_postgres_err()?
.max(0) as u64,
response_time_sum_ms: row
.try_get::<f64, _>("response_time_sum_ms")
.map_postgres_err()?,
response_time_samples: row
.try_get::<i64, _>("response_time_samples")
.map_postgres_err()?
.max(0) as u64,
overall_response_time_sum_ms: row
.try_get::<f64, _>("overall_response_time_sum_ms")
.map_postgres_err()?,
overall_response_time_samples: row
.try_get::<i64, _>("overall_response_time_samples")
.map_postgres_err()?
.max(0) as u64,
})
}
fn decode_usage_audit_aggregation_row(
row: &PgRow,
) -> Result<StoredUsageAuditAggregation, DataLayerError> {
Ok(StoredUsageAuditAggregation {
group_key: row.try_get::<String, _>("group_key").map_postgres_err()?,
display_name: row
.try_get::<Option<String>, _>("display_name")
.map_postgres_err()?,
secondary_name: row
.try_get::<Option<String>, _>("secondary_name")
.map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
effective_input_tokens: row
.try_get::<i64, _>("effective_input_tokens")
.map_postgres_err()?
.max(0) as u64,
total_input_context: row
.try_get::<i64, _>("total_input_context")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_5m_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_5m_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_1h_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_1h_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
actual_total_cost_usd: row
.try_get::<f64, _>("actual_total_cost_usd")
.map_postgres_err()?,
avg_response_time_ms: row
.try_get::<Option<f64>, _>("avg_response_time_ms")
.map_postgres_err()?,
success_count: row
.try_get::<Option<i64>, _>("success_count")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
})
}
fn absorb_usage_audit_summary(target: &mut StoredUsageAuditSummary, row: StoredUsageAuditSummary) {
target.total_requests = target.total_requests.saturating_add(row.total_requests);
target.input_tokens = target.input_tokens.saturating_add(row.input_tokens);
target.output_tokens = target.output_tokens.saturating_add(row.output_tokens);
target.recorded_total_tokens = target
.recorded_total_tokens
.saturating_add(row.recorded_total_tokens);
target.cache_creation_tokens = target
.cache_creation_tokens
.saturating_add(row.cache_creation_tokens);
target.cache_creation_ephemeral_5m_tokens = target
.cache_creation_ephemeral_5m_tokens
.saturating_add(row.cache_creation_ephemeral_5m_tokens);
target.cache_creation_ephemeral_1h_tokens = target
.cache_creation_ephemeral_1h_tokens
.saturating_add(row.cache_creation_ephemeral_1h_tokens);
target.cache_read_tokens = target
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
target.total_cost_usd += row.total_cost_usd;
target.actual_total_cost_usd += row.actual_total_cost_usd;
target.cache_creation_cost_usd += row.cache_creation_cost_usd;
target.cache_read_cost_usd += row.cache_read_cost_usd;
target.total_response_time_ms += row.total_response_time_ms;
target.error_requests = target.error_requests.saturating_add(row.error_requests);
}
fn absorb_usage_cache_hit_summary(
target: &mut StoredUsageCacheHitSummary,
row: StoredUsageCacheHitSummary,
) {
target.total_requests = target.total_requests.saturating_add(row.total_requests);
target.cache_hit_requests = target
.cache_hit_requests
.saturating_add(row.cache_hit_requests);
}
fn decode_usage_cache_hit_summary_row(
row: &PgRow,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
Ok(StoredUsageCacheHitSummary {
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
cache_hit_requests: row
.try_get::<i64, _>("cache_hit_requests")
.map_postgres_err()?
.max(0) as u64,
})
}
fn absorb_usage_cache_affinity_hit_summary(
target: &mut StoredUsageCacheAffinityHitSummary,
row: StoredUsageCacheAffinityHitSummary,
) {
target.total_requests = target.total_requests.saturating_add(row.total_requests);
target.requests_with_cache_hit = target
.requests_with_cache_hit
.saturating_add(row.requests_with_cache_hit);
target.input_tokens = target.input_tokens.saturating_add(row.input_tokens);
target.cache_read_tokens = target
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
target.cache_creation_tokens = target
.cache_creation_tokens
.saturating_add(row.cache_creation_tokens);
target.total_input_context = target
.total_input_context
.saturating_add(row.total_input_context);
target.cache_read_cost_usd += row.cache_read_cost_usd;
target.cache_creation_cost_usd += row.cache_creation_cost_usd;
}
fn decode_usage_cache_affinity_hit_summary_row(
row: &PgRow,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
Ok(StoredUsageCacheAffinityHitSummary {
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
requests_with_cache_hit: row
.try_get::<i64, _>("requests_with_cache_hit")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
total_input_context: row
.try_get::<i64, _>("total_input_context")
.map_postgres_err()?
.max(0) as u64,
cache_read_cost_usd: row
.try_get::<f64, _>("cache_read_cost_usd")
.map_postgres_err()?,
cache_creation_cost_usd: row
.try_get::<f64, _>("cache_creation_cost_usd")
.map_postgres_err()?,
})
}
fn absorb_usage_settled_cost_summary(
target: &mut StoredUsageSettledCostSummary,
row: StoredUsageSettledCostSummary,
) {
target.total_cost_usd += row.total_cost_usd;
target.total_requests = target.total_requests.saturating_add(row.total_requests);
target.input_tokens = target.input_tokens.saturating_add(row.input_tokens);
target.output_tokens = target.output_tokens.saturating_add(row.output_tokens);
target.cache_creation_tokens = target
.cache_creation_tokens
.saturating_add(row.cache_creation_tokens);
target.cache_read_tokens = target
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
target.first_finalized_at_unix_secs = match (
target.first_finalized_at_unix_secs,
row.first_finalized_at_unix_secs,
) {
(Some(existing), Some(candidate)) => Some(existing.min(candidate)),
(None, candidate) => candidate,
(existing, None) => existing,
};
target.last_finalized_at_unix_secs = match (
target.last_finalized_at_unix_secs,
row.last_finalized_at_unix_secs,
) {
(Some(existing), Some(candidate)) => Some(existing.max(candidate)),
(None, candidate) => candidate,
(existing, None) => existing,
};
}
fn decode_usage_settled_cost_row(
row: &PgRow,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
Ok(StoredUsageSettledCostSummary {
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
first_finalized_at_unix_secs: row
.try_get::<Option<i64>, _>("first_finalized_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
last_finalized_at_unix_secs: row
.try_get::<Option<i64>, _>("last_finalized_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
})
}
fn absorb_usage_cost_savings_summary(
target: &mut StoredUsageCostSavingsSummary,
row: StoredUsageCostSavingsSummary,
) {
target.cache_read_tokens = target
.cache_read_tokens
.saturating_add(row.cache_read_tokens);
target.cache_read_cost_usd += row.cache_read_cost_usd;
target.cache_creation_cost_usd += row.cache_creation_cost_usd;
target.estimated_full_cost_usd += row.estimated_full_cost_usd;
}
fn decode_usage_cost_savings_row(
row: &PgRow,
) -> Result<StoredUsageCostSavingsSummary, DataLayerError> {
Ok(StoredUsageCostSavingsSummary {
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_cost_usd: row
.try_get::<f64, _>("cache_read_cost_usd")
.map_postgres_err()?,
cache_creation_cost_usd: row
.try_get::<f64, _>("cache_creation_cost_usd")
.map_postgres_err()?,
estimated_full_cost_usd: row
.try_get::<f64, _>("estimated_full_cost_usd")
.map_postgres_err()?,
})
}
fn decode_usage_audit_summary_row(row: &PgRow) -> Result<StoredUsageAuditSummary, DataLayerError> {
Ok(StoredUsageAuditSummary {
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
recorded_total_tokens: row
.try_get::<i64, _>("recorded_total_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_5m_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_5m_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_ephemeral_1h_tokens: row
.try_get::<i64, _>("cache_creation_ephemeral_1h_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
actual_total_cost_usd: row
.try_get::<f64, _>("actual_total_cost_usd")
.map_postgres_err()?,
cache_creation_cost_usd: row
.try_get::<f64, _>("cache_creation_cost_usd")
.map_postgres_err()?,
cache_read_cost_usd: row
.try_get::<f64, _>("cache_read_cost_usd")
.map_postgres_err()?,
total_response_time_ms: row
.try_get::<f64, _>("total_response_time_ms")
.map_postgres_err()?,
error_requests: row
.try_get::<i64, _>("error_requests")
.map_postgres_err()?
.max(0) as u64,
})
}
fn decode_usage_error_distribution_row(
row: &PgRow,
) -> Result<StoredUsageErrorDistributionRow, DataLayerError> {
Ok(StoredUsageErrorDistributionRow {
date: row.try_get::<String, _>("date").map_postgres_err()?,
error_category: row
.try_get::<String, _>("error_category")
.map_postgres_err()?,
count: row.try_get::<i64, _>("count").map_postgres_err()?.max(0) as u64,
})
}
fn absorb_usage_error_distribution_rows(
target: &mut BTreeMap<(String, String), u64>,
rows: Vec<StoredUsageErrorDistributionRow>,
) {
for row in rows {
let key = (row.date, row.error_category);
let entry = target.entry(key).or_default();
*entry = entry.saturating_add(row.count);
}
}
fn finalize_usage_error_distribution_rows(
grouped: BTreeMap<(String, String), u64>,
) -> Vec<StoredUsageErrorDistributionRow> {
let mut items = grouped
.into_iter()
.map(
|((date, error_category), count)| StoredUsageErrorDistributionRow {
date,
error_category,
count,
},
)
.collect::<Vec<_>>();
items.sort_by(|left, right| {
left.date
.cmp(&right.date)
.then_with(|| right.count.cmp(&left.count))
.then_with(|| left.error_category.cmp(&right.error_category))
});
items
}
fn decode_usage_performance_percentiles_row(
row: &PgRow,
) -> Result<StoredUsagePerformancePercentilesRow, DataLayerError> {
Ok(StoredUsagePerformancePercentilesRow {
date: row.try_get::<String, _>("date").map_postgres_err()?,
p50_response_time_ms: row
.try_get::<Option<i64>, _>("p50_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p90_response_time_ms: row
.try_get::<Option<i64>, _>("p90_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_response_time_ms: row
.try_get::<Option<i64>, _>("p99_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p50_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p50_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p90_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p90_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p99_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
})
}
fn decode_usage_provider_performance_summary(
row: &PgRow,
) -> Result<StoredUsageProviderPerformanceSummary, DataLayerError> {
Ok(StoredUsageProviderPerformanceSummary {
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
success_count: row
.try_get::<i64, _>("success_count")
.map_postgres_err()?
.max(0) as u64,
avg_output_tps: row
.try_get::<Option<f64>, _>("avg_output_tps")
.map_postgres_err()?,
avg_first_byte_time_ms: row
.try_get::<Option<f64>, _>("avg_first_byte_time_ms")
.map_postgres_err()?,
avg_response_time_ms: row
.try_get::<Option<f64>, _>("avg_response_time_ms")
.map_postgres_err()?,
p90_response_time_ms: row
.try_get::<Option<i64>, _>("p90_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_response_time_ms: row
.try_get::<Option<i64>, _>("p99_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p90_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p90_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p99_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
tps_sample_count: row
.try_get::<i64, _>("tps_sample_count")
.map_postgres_err()?
.max(0) as u64,
response_time_sample_count: row
.try_get::<i64, _>("response_time_sample_count")
.map_postgres_err()?
.max(0) as u64,
first_byte_sample_count: row
.try_get::<i64, _>("first_byte_sample_count")
.map_postgres_err()?
.max(0) as u64,
slow_request_count: row
.try_get::<i64, _>("slow_request_count")
.map_postgres_err()?
.max(0) as u64,
})
}
fn decode_usage_provider_performance_provider_row(
row: &PgRow,
) -> Result<StoredUsageProviderPerformanceProviderRow, DataLayerError> {
Ok(StoredUsageProviderPerformanceProviderRow {
provider_id: row.try_get::<String, _>("provider_id").map_postgres_err()?,
provider: row.try_get::<String, _>("provider").map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
success_count: row
.try_get::<i64, _>("success_count")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
avg_output_tps: row
.try_get::<Option<f64>, _>("avg_output_tps")
.map_postgres_err()?,
avg_first_byte_time_ms: row
.try_get::<Option<f64>, _>("avg_first_byte_time_ms")
.map_postgres_err()?,
avg_response_time_ms: row
.try_get::<Option<f64>, _>("avg_response_time_ms")
.map_postgres_err()?,
p90_response_time_ms: row
.try_get::<Option<i64>, _>("p90_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_response_time_ms: row
.try_get::<Option<i64>, _>("p99_response_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p90_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p90_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
p99_first_byte_time_ms: row
.try_get::<Option<i64>, _>("p99_first_byte_time_ms")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
tps_sample_count: row
.try_get::<i64, _>("tps_sample_count")
.map_postgres_err()?
.max(0) as u64,
response_time_sample_count: row
.try_get::<i64, _>("response_time_sample_count")
.map_postgres_err()?
.max(0) as u64,
first_byte_sample_count: row
.try_get::<i64, _>("first_byte_sample_count")
.map_postgres_err()?
.max(0) as u64,
slow_request_count: row
.try_get::<i64, _>("slow_request_count")
.map_postgres_err()?
.max(0) as u64,
})
}
fn decode_usage_provider_performance_timeline_row(
row: &PgRow,
) -> Result<StoredUsageProviderPerformanceTimelineRow, DataLayerError> {
Ok(StoredUsageProviderPerformanceTimelineRow {
date: row.try_get::<String, _>("date").map_postgres_err()?,
provider_id: row.try_get::<String, _>("provider_id").map_postgres_err()?,
provider: row.try_get::<String, _>("provider").map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
success_count: row
.try_get::<i64, _>("success_count")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
avg_output_tps: row
.try_get::<Option<f64>, _>("avg_output_tps")
.map_postgres_err()?,
avg_first_byte_time_ms: row
.try_get::<Option<f64>, _>("avg_first_byte_time_ms")
.map_postgres_err()?,
avg_response_time_ms: row
.try_get::<Option<f64>, _>("avg_response_time_ms")
.map_postgres_err()?,
slow_request_count: row
.try_get::<i64, _>("slow_request_count")
.map_postgres_err()?
.max(0) as u64,
})
}
fn push_usage_provider_performance_text_filter(
builder: &mut QueryBuilder<'_, Postgres>,
column: &'static str,
value: &Option<String>,
) {
let Some(value) = value
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
else {
return;
};
builder
.push(" AND NULLIF(BTRIM(COALESCE(")
.push(column)
.push(", '')), '') = ")
.push_bind(value.to_string());
}
fn push_usage_provider_performance_filters(
builder: &mut QueryBuilder<'_, Postgres>,
query: &UsageProviderPerformanceQuery,
) {
push_usage_provider_performance_text_filter(
builder,
r#""usage".provider_id"#,
&query.provider_id,
);
push_usage_provider_performance_text_filter(builder, r#""usage".model"#, &query.model);
push_usage_provider_performance_text_filter(
builder,
r#""usage".api_format"#,
&query.api_format,
);
push_usage_provider_performance_text_filter(
builder,
r#""usage".endpoint_kind"#,
&query.endpoint_kind,
);
if let Some(is_stream) = query.is_stream {
builder
.push(r#" AND "usage".is_stream = "#)
.push_bind(is_stream);
}
if let Some(has_format_conversion) = query.has_format_conversion {
builder
.push(r#" AND "usage".has_format_conversion = "#)
.push_bind(has_format_conversion);
}
}
fn decode_usage_time_series_bucket_row(
row: &PgRow,
) -> Result<StoredUsageTimeSeriesBucket, DataLayerError> {
Ok(StoredUsageTimeSeriesBucket {
bucket_key: row.try_get::<String, _>("bucket_key").map_postgres_err()?,
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
input_tokens: row
.try_get::<i64, _>("input_tokens")
.map_postgres_err()?
.max(0) as u64,
output_tokens: row
.try_get::<i64, _>("output_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_creation_tokens: row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?
.max(0) as u64,
cache_read_tokens: row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
total_response_time_ms: row
.try_get::<f64, _>("total_response_time_ms")
.map_postgres_err()?,
})
}
fn absorb_usage_time_series_buckets(
target: &mut BTreeMap<String, StoredUsageTimeSeriesBucket>,
buckets: Vec<StoredUsageTimeSeriesBucket>,
) {
for bucket in buckets {
let entry = target.entry(bucket.bucket_key.clone()).or_insert_with(|| {
StoredUsageTimeSeriesBucket {
bucket_key: bucket.bucket_key.clone(),
..Default::default()
}
});
entry.total_requests = entry.total_requests.saturating_add(bucket.total_requests);
entry.input_tokens = entry.input_tokens.saturating_add(bucket.input_tokens);
entry.output_tokens = entry.output_tokens.saturating_add(bucket.output_tokens);
entry.cache_creation_tokens = entry
.cache_creation_tokens
.saturating_add(bucket.cache_creation_tokens);
entry.cache_read_tokens = entry
.cache_read_tokens
.saturating_add(bucket.cache_read_tokens);
entry.total_cost_usd += bucket.total_cost_usd;
entry.total_response_time_ms += bucket.total_response_time_ms;
}
}
fn finalize_usage_time_series_buckets(
grouped: BTreeMap<String, StoredUsageTimeSeriesBucket>,
) -> Vec<StoredUsageTimeSeriesBucket> {
grouped.into_values().collect()
}
fn decode_usage_leaderboard_row(
row: &PgRow,
) -> Result<StoredUsageLeaderboardSummary, DataLayerError> {
Ok(StoredUsageLeaderboardSummary {
group_key: row.try_get::<String, _>("group_key").map_postgres_err()?,
legacy_name: row
.try_get::<Option<String>, _>("legacy_name")
.map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
})
}
fn absorb_usage_leaderboard_rows(
target: &mut BTreeMap<String, StoredUsageLeaderboardSummary>,
rows: Vec<StoredUsageLeaderboardSummary>,
) {
for row in rows {
let entry =
target
.entry(row.group_key.clone())
.or_insert_with(|| StoredUsageLeaderboardSummary {
group_key: row.group_key.clone(),
..Default::default()
});
if entry.legacy_name.is_none() {
entry.legacy_name = row.legacy_name.clone();
}
entry.request_count = entry.request_count.saturating_add(row.request_count);
entry.total_tokens = entry.total_tokens.saturating_add(row.total_tokens);
entry.total_cost_usd += row.total_cost_usd;
}
}
fn finalize_usage_leaderboard_rows(
grouped: BTreeMap<String, StoredUsageLeaderboardSummary>,
) -> Vec<StoredUsageLeaderboardSummary> {
grouped.into_values().collect()
}
async fn fetch_usage_leaderboard_query<'q>(
query: Query<'q, Postgres, PgArguments>,
pool: &PgPool,
) -> Result<Vec<StoredUsageLeaderboardSummary>, DataLayerError> {
let mut rows = query.fetch(pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_leaderboard_row(&row)?);
}
Ok(items)
}
const RESET_STALE_VOID_USAGE_SQL: &str = include_str!("queries/reset_stale_void_usage_sql.sql");
const RESET_STALE_VOID_USAGE_SETTLEMENT_SNAPSHOT_SQL: &str =
include_str!("queries/reset_stale_void_usage_settlement_snapshot_sql.sql");
const LOCK_USAGE_REQUEST_ID_SQL: &str = include_str!("queries/lock_usage_request_id_sql.sql");
const UPSERT_USAGE_HTTP_AUDIT_SQL: &str = include_str!("queries/upsert_usage_http_audit_sql.sql");
const UPSERT_USAGE_ROUTING_SNAPSHOT_SQL: &str =
include_str!("queries/upsert_usage_routing_snapshot_sql.sql");
const UPSERT_USAGE_SETTLEMENT_PRICING_SNAPSHOT_SQL: &str =
include_str!("queries/upsert_usage_settlement_pricing_snapshot_sql.sql");
const FIND_BY_REQUEST_ID_SQL: &str = include_str!("queries/find_by_request_id_sql.sql");
const FIND_BY_ID_SQL: &str = include_str!("queries/find_by_id_sql.sql");
const SUMMARIZE_PROVIDER_USAGE_SINCE_SQL: &str =
include_str!("queries/summarize_provider_usage_since_sql.sql");
const SUMMARIZE_TOTAL_TOKENS_BY_API_KEY_IDS_SQL: &str =
include_str!("queries/summarize_total_tokens_by_api_key_ids_sql.sql");
const SUMMARIZE_USAGE_TOTALS_BY_USER_IDS_SQL: &str =
include_str!("queries/summarize_usage_totals_by_user_ids_sql.sql");
const SUMMARIZE_USAGE_BY_PROVIDER_API_KEY_IDS_SQL: &str =
include_str!("queries/summarize_usage_by_provider_api_key_ids_sql.sql");
const SUMMARIZE_PROVIDER_API_KEY_WINDOW_USAGE_SQL: &str =
include_str!("queries/summarize_provider_api_key_window_usage_sql.sql");
const APPLY_API_KEY_USAGE_DELTA_SQL: &str =
include_str!("queries/apply_api_key_usage_delta_sql.sql");
const APPLY_GLOBAL_MODEL_USAGE_DELTA_SQL: &str =
include_str!("queries/apply_global_model_usage_delta_sql.sql");
const RESET_API_KEY_USAGE_STATS_SQL: &str =
include_str!("queries/reset_api_key_usage_stats_sql.sql");
const REBUILD_API_KEY_USAGE_STATS_SQL: &str =
include_str!("queries/rebuild_api_key_usage_stats_sql.sql");
const APPLY_PROVIDER_API_KEY_USAGE_DELTA_SQL: &str =
include_str!("queries/apply_provider_api_key_usage_delta_sql.sql");
const INSERT_USAGE_COUNTER_DELTA_SQL: &str = r#"
INSERT INTO usage_counter_deltas (
id,
request_id,
kind,
target_id,
request_count_delta,
total_requests_delta,
success_count_delta,
error_count_delta,
dns_failures_delta,
stream_errors_delta,
total_tokens_delta,
total_cost_usd_delta,
total_response_time_ms_delta,
last_used_at_unix_secs,
last_used_ip,
candidate_last_used_at_unix_secs,
removed_last_used_at_unix_secs,
usage_created_at_unix_secs
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
)
"#;
const CLAIM_USAGE_COUNTER_DELTAS_SQL: &str = r#"
WITH claimed AS (
SELECT id
FROM usage_counter_deltas
WHERE processed_at IS NULL
ORDER BY created_at ASC, id ASC
LIMIT $1
FOR UPDATE SKIP LOCKED
)
SELECT
delta.id,
delta.kind,
delta.target_id,
delta.request_count_delta,
delta.total_requests_delta,
delta.success_count_delta,
delta.error_count_delta,
delta.dns_failures_delta,
delta.stream_errors_delta,
delta.total_tokens_delta,
delta.total_cost_usd_delta,
delta.total_response_time_ms_delta,
delta.last_used_at_unix_secs,
delta.last_used_ip,
delta.candidate_last_used_at_unix_secs,
delta.removed_last_used_at_unix_secs,
delta.usage_created_at_unix_secs
FROM usage_counter_deltas AS delta
JOIN claimed ON claimed.id = delta.id
ORDER BY delta.created_at ASC, delta.id ASC
"#;
const READ_USAGE_COUNTER_HEALTH_SQL: &str = r#"
SELECT
COUNT(*) FILTER (WHERE processed_at IS NULL)::BIGINT AS pending_rows,
COUNT(*) FILTER (WHERE processed_at IS NOT NULL)::BIGINT AS processed_rows,
CAST(EXTRACT(EPOCH FROM MIN(created_at) FILTER (WHERE processed_at IS NULL)) AS BIGINT)
AS oldest_pending_created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM MAX(processed_at)) AS BIGINT)
AS latest_processed_at_unix_secs
FROM usage_counter_deltas
"#;
const READ_PENDING_USAGE_COUNTER_DELTAS_BY_KIND_SQL: &str = r#"
SELECT kind, COUNT(*)::BIGINT AS pending_rows
FROM usage_counter_deltas
WHERE processed_at IS NULL
GROUP BY kind
ORDER BY kind ASC
"#;
const MARK_USAGE_COUNTER_DELTAS_PROCESSED_SQL: &str = r#"
UPDATE usage_counter_deltas
SET processed_at = NOW()
WHERE id = ANY($1::TEXT[])
"#;
const DELETE_PROCESSED_USAGE_COUNTER_DELTAS_SQL: &str = r#"
WITH doomed AS (
SELECT id
FROM usage_counter_deltas
WHERE processed_at IS NOT NULL
AND processed_at < TO_TIMESTAMP($1::double precision)
ORDER BY processed_at ASC, created_at ASC, id ASC
LIMIT $2
)
DELETE FROM usage_counter_deltas AS delta
USING doomed
WHERE delta.id = doomed.id
"#;
const TRY_LOCK_USAGE_COUNTER_FLUSH_SQL: &str =
"SELECT pg_try_advisory_xact_lock(hashtext('usage_counter_flush')::BIGINT) AS locked";
const USAGE_COUNTER_KIND_API_KEY: &str = "api_key";
const USAGE_COUNTER_KIND_PROVIDER_API_KEY: &str = "provider_api_key";
const USAGE_COUNTER_KIND_MODEL: &str = "model";
const USAGE_COUNTER_KIND_PROVIDER_MONTHLY: &str = "provider_monthly";
const USAGE_COUNTER_KIND_PROXY_NODE: &str = "proxy_node";
const USAGE_COUNTER_KIND_MANAGEMENT_TOKEN: &str = "management_token";
const USAGE_COUNTER_KIND_API_KEY_LAST_USED: &str = "api_key_last_used";
const APPLY_PROVIDER_MONTHLY_USAGE_DELTA_SQL: &str = r#"
UPDATE providers
SET
monthly_used_usd = COALESCE(monthly_used_usd, 0) + $2,
updated_at = NOW()
WHERE id = $1
"#;
const APPLY_PROXY_NODE_COUNTER_DELTA_SQL: &str = r#"
UPDATE proxy_nodes
SET
total_requests = total_requests + GREATEST($2::bigint, 0),
failed_requests = failed_requests + GREATEST($3::bigint, 0),
dns_failures = dns_failures + GREATEST($4::bigint, 0),
stream_errors = stream_errors + GREATEST($5::bigint, 0),
updated_at = NOW()
WHERE id = $1
"#;
const APPLY_MANAGEMENT_TOKEN_COUNTER_DELTA_SQL: &str = r#"
UPDATE management_tokens
SET
usage_count = COALESCE(usage_count, 0) + GREATEST($2::bigint, 0),
last_used_at = CASE
WHEN $3::double precision IS NULL THEN last_used_at
ELSE GREATEST(COALESCE(last_used_at, TO_TIMESTAMP(0)), TO_TIMESTAMP($3::double precision))
END,
last_used_ip = COALESCE($4, last_used_ip),
updated_at = NOW()
WHERE id = $1
"#;
const APPLY_API_KEY_LAST_USED_DELTA_SQL: &str = r#"
UPDATE api_keys
SET last_used_at = GREATEST(COALESCE(last_used_at, TO_TIMESTAMP(0)), TO_TIMESTAMP($2::double precision))
WHERE id = $1
"#;
const RESET_PROVIDER_API_KEY_USAGE_STATS_SQL: &str =
include_str!("queries/reset_provider_api_key_usage_stats_sql.sql");
const REBUILD_PROVIDER_API_KEY_USAGE_STATS_SQL: &str =
include_str!("queries/rebuild_provider_api_key_usage_stats_sql.sql");
const REBUILD_PROVIDER_API_KEY_CODEX_WINDOW_USAGE_STATS_SQL: &str =
include_str!("queries/rebuild_provider_api_key_codex_window_usage_stats_sql.sql");
const LIST_USAGE_AUDITS_PREFIX: &str = include_str!("queries/list_usage_audits_prefix.sql");
const USAGE_PROVIDER_IDENTITY_FILTER_SQL: &str = r#" AND (
(
BTRIM(COALESCE("usage".provider_id, '')) <> ''
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'unknow', 'pending')
)
OR (
BTRIM(COALESCE("usage".provider_name, '')) <> ''
AND lower(BTRIM(COALESCE("usage".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')
)
)"#;
const USAGE_RAW_PROVIDER_GROUP_KEY_SQL: &str = r#"CASE
WHEN BTRIM(COALESCE("usage".provider_id, '')) = ''
OR lower(BTRIM(COALESCE("usage".provider_id, ''))) IN ('unknown', 'unknow', 'pending')
THEN BTRIM("usage".provider_name)
ELSE BTRIM("usage".provider_id)
END"#;
const USAGE_RAW_PROVIDER_DISPLAY_NAME_SQL: &str = r#"CASE
WHEN BTRIM(COALESCE("usage".provider_name, '')) = ''
OR lower(BTRIM(COALESCE("usage".provider_name, ''))) IN ('unknown', 'unknow', 'pending')
THEN NULL
ELSE BTRIM("usage".provider_name)
END"#;
const USAGE_PROVIDER_IDENTITY_SOURCE_SQL: &str = r#"CASE
WHEN BTRIM(COALESCE("usage".provider_id, '')) <> ''
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'unknow', 'pending')
THEN 'provider_id'
WHEN BTRIM(COALESCE("usage".provider_name, '')) <> ''
AND lower(BTRIM(COALESCE("usage".provider_name, ''))) NOT IN ('unknown', 'unknow', 'pending')
THEN 'legacy_name'
ELSE NULL
END"#;
const USAGE_PROVIDER_IDENTITY_JOIN_SQL: &str = r#" LEFT JOIN providers AS provider_by_id
ON BTRIM(COALESCE("usage".provider_id, '')) <> ''
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'unknow', 'pending')
AND provider_by_id.id = BTRIM("usage".provider_id)"#;
const USAGE_RESOLVED_PROVIDER_GROUP_KEY_SQL: &str = r#"COALESCE(
provider_by_id.id,
CASE
WHEN BTRIM(COALESCE("usage".provider_id, '')) = ''
OR lower(BTRIM(COALESCE("usage".provider_id, ''))) IN ('unknown', 'unknow', 'pending')
THEN NULL
ELSE BTRIM("usage".provider_id)
END,
CASE
WHEN BTRIM(COALESCE("usage".provider_name, '')) = ''
OR lower(BTRIM(COALESCE("usage".provider_name, ''))) IN ('unknown', 'unknow', 'pending')
THEN NULL
ELSE BTRIM("usage".provider_name)
END
)"#;
const USAGE_RESOLVED_PROVIDER_DISPLAY_NAME_SQL: &str = r#"COALESCE(
provider_by_id.name,
CASE
WHEN BTRIM(COALESCE("usage".provider_name, '')) = ''
OR lower(BTRIM(COALESCE("usage".provider_name, ''))) IN ('unknown', 'unknow', 'pending')
THEN NULL
ELSE BTRIM("usage".provider_name)
END
)"#;
struct UsageAuditAggregationSqlFragments {
provider_identity_join: &'static str,
provider_group_key_expr: &'static str,
provider_display_name_expr: &'static str,
filtered_extra_where: &'static str,
group_key_expr: &'static str,
display_name_expr: &'static str,
secondary_name_expr: &'static str,
aggregate_display_name_expr: &'static str,
aggregate_secondary_name_expr: &'static str,
avg_response_time_expr: &'static str,
success_count_expr: &'static str,
}
fn usage_audit_aggregation_sql_fragments(
group_by: UsageAuditAggregationGroupBy,
) -> UsageAuditAggregationSqlFragments {
match group_by {
UsageAuditAggregationGroupBy::Model => UsageAuditAggregationSqlFragments {
provider_identity_join: "",
provider_group_key_expr: USAGE_RAW_PROVIDER_GROUP_KEY_SQL,
provider_display_name_expr: USAGE_RAW_PROVIDER_DISPLAY_NAME_SQL,
filtered_extra_where: "",
group_key_expr: "model",
display_name_expr: "NULL::varchar",
secondary_name_expr: "NULL::varchar",
aggregate_display_name_expr: "NULL::varchar",
aggregate_secondary_name_expr: "NULL::varchar",
avg_response_time_expr: "NULL::DOUBLE PRECISION",
success_count_expr: "NULL::BIGINT",
},
UsageAuditAggregationGroupBy::Provider => UsageAuditAggregationSqlFragments {
provider_identity_join: USAGE_PROVIDER_IDENTITY_JOIN_SQL,
provider_group_key_expr: USAGE_RESOLVED_PROVIDER_GROUP_KEY_SQL,
provider_display_name_expr: USAGE_RESOLVED_PROVIDER_DISPLAY_NAME_SQL,
filtered_extra_where: "",
group_key_expr: "provider_group_key",
display_name_expr: "provider_display_name",
secondary_name_expr: "provider_identity_source",
aggregate_display_name_expr: "MAX(display_name)",
aggregate_secondary_name_expr: "CASE WHEN COUNT(*) FILTER (WHERE secondary_name = 'provider_id') > 0 THEN 'provider_id' WHEN COUNT(*) FILTER (WHERE secondary_name = 'legacy_name') > 0 THEN 'legacy_name' ELSE NULL END",
avg_response_time_expr: "AVG(response_time_ms::DOUBLE PRECISION)",
success_count_expr: "COALESCE(SUM(success_flag), 0)::BIGINT",
},
UsageAuditAggregationGroupBy::ApiFormat => UsageAuditAggregationSqlFragments {
provider_identity_join: "",
provider_group_key_expr: USAGE_RAW_PROVIDER_GROUP_KEY_SQL,
provider_display_name_expr: USAGE_RAW_PROVIDER_DISPLAY_NAME_SQL,
filtered_extra_where: "",
group_key_expr: "api_format_group_key",
display_name_expr: "NULL::varchar",
secondary_name_expr: "NULL::varchar",
aggregate_display_name_expr: "NULL::varchar",
aggregate_secondary_name_expr: "NULL::varchar",
avg_response_time_expr: "AVG(response_time_ms::DOUBLE PRECISION)",
success_count_expr: "NULL::BIGINT",
},
UsageAuditAggregationGroupBy::User => UsageAuditAggregationSqlFragments {
provider_identity_join: "",
provider_group_key_expr: USAGE_RAW_PROVIDER_GROUP_KEY_SQL,
provider_display_name_expr: USAGE_RAW_PROVIDER_DISPLAY_NAME_SQL,
filtered_extra_where: " AND \"usage\".user_id IS NOT NULL",
group_key_expr: "user_id",
display_name_expr: "NULL::varchar",
secondary_name_expr: "NULL::varchar",
aggregate_display_name_expr: "NULL::varchar",
aggregate_secondary_name_expr: "NULL::varchar",
avg_response_time_expr: "NULL::DOUBLE PRECISION",
success_count_expr: "NULL::BIGINT",
},
}
}
struct UsageLeaderboardSqlFragments {
filtered_extra_where: &'static str,
group_key_expr: &'static str,
legacy_name_expr: &'static str,
}
fn usage_leaderboard_sql_fragments(
group_by: UsageLeaderboardGroupBy,
) -> UsageLeaderboardSqlFragments {
match group_by {
UsageLeaderboardGroupBy::Model => UsageLeaderboardSqlFragments {
filtered_extra_where: "",
group_key_expr: "\"usage\".model",
legacy_name_expr: "NULL::varchar",
},
UsageLeaderboardGroupBy::User => UsageLeaderboardSqlFragments {
filtered_extra_where: " AND \"usage\".user_id IS NOT NULL",
group_key_expr: "\"usage\".user_id",
legacy_name_expr: "NULLIF(BTRIM(\"usage\".username), '')",
},
UsageLeaderboardGroupBy::ApiKey => UsageLeaderboardSqlFragments {
filtered_extra_where: " AND \"usage\".api_key_id IS NOT NULL",
group_key_expr: "\"usage\".api_key_id",
legacy_name_expr: "NULLIF(BTRIM(\"usage\".api_key_name), '')",
},
}
}
const LIST_RECENT_USAGE_AUDITS_PREFIX: &str =
include_str!("queries/list_recent_usage_audits_prefix.sql");
const UPSERT_SQL: &str = include_str!("queries/upsert_sql.sql");
const SELECT_STALE_PENDING_USAGE_BATCH_SQL: &str = r#"
SELECT
usage.request_id,
usage.status,
COALESCE(usage_settlement_snapshots.billing_status, usage.billing_status) AS billing_status
FROM usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.status IN ('pending', 'streaming')
AND usage.created_at < $1
ORDER BY usage.created_at ASC, usage.request_id ASC
LIMIT $2
FOR UPDATE OF usage SKIP LOCKED
"#;
const SELECT_COMPLETED_PENDING_REQUEST_IDS_SQL: &str = r#"
SELECT DISTINCT request_id
FROM request_candidates
WHERE request_id = ANY($1)
AND (
status = 'streaming'
OR (
status = 'success'
AND COALESCE(extra_data->>'stream_completed', 'false') = 'true'
)
)
"#;
const UPDATE_RECOVERED_STALE_USAGE_SQL: &str = r#"
UPDATE usage
SET status = 'completed',
status_code = 200,
error_message = NULL
WHERE request_id = $1
"#;
const SELECT_LATEST_FAILED_CANDIDATE_FOR_STALE_REQUESTS_SQL: &str = r#"
SELECT DISTINCT ON (request_id)
request_id,
status_code,
error_message
FROM request_candidates
WHERE request_id = ANY($1)
AND status IN ('failed', 'cancelled')
ORDER BY request_id,
COALESCE(finished_at, started_at, created_at) DESC,
retry_index DESC,
candidate_index DESC,
created_at DESC
"#;
const UPDATE_FAILED_STALE_USAGE_SQL: &str = r#"
UPDATE usage
SET status = 'failed',
status_code = $3,
error_message = $2
WHERE request_id = $1
"#;
const UPDATE_FAILED_VOID_STALE_USAGE_SQL: &str = r#"
WITH updated_usage AS (
UPDATE usage
SET status = 'failed',
status_code = $4,
error_message = $2,
billing_status = 'void',
finalized_at = $3,
total_cost_usd = 0,
request_cost_usd = 0,
actual_total_cost_usd = 0,
actual_request_cost_usd = 0
WHERE request_id = $1
RETURNING request_id
)
INSERT INTO usage_settlement_snapshots (
request_id,
billing_status,
finalized_at
)
SELECT request_id, 'void', $3
FROM updated_usage
ON CONFLICT (request_id)
DO UPDATE SET
billing_status = EXCLUDED.billing_status,
finalized_at = COALESCE(
usage_settlement_snapshots.finalized_at,
EXCLUDED.finalized_at
),
updated_at = NOW()
"#;
const UPDATE_RECOVERED_STREAMING_CANDIDATES_SQL: &str = r#"
UPDATE request_candidates
SET status = 'success',
finished_at = $2
WHERE request_id = $1
AND status = 'streaming'
"#;
const UPDATE_FAILED_PENDING_CANDIDATES_SQL: &str = r#"
UPDATE request_candidates
SET status = 'failed',
finished_at = $2,
error_message = '请求超时(服务器可能已重启)'
WHERE request_id = $1
AND status IN ('pending', 'streaming')
"#;
#[derive(Debug, Clone)]
pub struct SqlxUsageReadRepository {
pool: PgPool,
tx_runner: PostgresTransactionRunner,
}
impl SqlxUsageReadRepository {
pub fn new(pool: PgPool) -> Self {
let tx_runner = PostgresTransactionRunner::new(pool.clone());
Self { pool, tx_runner }
}
pub fn pool(&self) -> &PgPool {
&self.pool
}
pub fn transaction_runner(&self) -> &PostgresTransactionRunner {
&self.tx_runner
}
async fn read_stats_daily_cutoff_date(&self) -> Result<Option<DateTime<Utc>>, DataLayerError> {
let row = sqlx::query(
r#"
SELECT cutoff_date
FROM stats_summary
ORDER BY updated_at DESC, created_at DESC
LIMIT 1
"#,
)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
if let Some(row) = row {
return row
.try_get::<DateTime<Utc>, _>("cutoff_date")
.map(Some)
.map_postgres_err();
}
let row = sqlx::query(
r#"
SELECT MAX(date) AS latest_date
FROM (
SELECT MAX(date) AS date
FROM stats_daily
WHERE total_requests > 0
OR is_complete IS TRUE
UNION ALL
SELECT MAX(date) AS date
FROM stats_user_daily
WHERE total_requests > 0
UNION ALL
SELECT MAX(date) AS date
FROM stats_daily_api_key
WHERE total_requests > 0
) AS imported_daily_aggregates
"#,
)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
let latest_date = row
.try_get::<Option<DateTime<Utc>>, _>("latest_date")
.map_postgres_err()?;
Ok(latest_date.map(|value| value + chrono::Duration::days(1)))
}
async fn read_stats_hourly_cutoff(&self) -> Result<Option<DateTime<Utc>>, DataLayerError> {
let row = sqlx::query(
r#"
SELECT MAX(hour_utc) AS latest_hour
FROM stats_hourly
WHERE is_complete IS TRUE
"#,
)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
let latest_hour = row
.try_get::<Option<DateTime<Utc>>, _>("latest_hour")
.map_postgres_err()?;
Ok(latest_hour.map(|value| value + chrono::Duration::hours(1)))
}
async fn summarize_dashboard_usage_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(StoredUsageDashboardSummary::default());
}
let row = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
COALESCE(SUM(total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(
CASE
WHEN effective_input_tokens = 0 AND total_input_context = 0 AND input_tokens > 0
THEN input_tokens
ELSE effective_input_tokens
END
), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(
CASE
WHEN effective_input_tokens = 0 AND total_input_context = 0 AND input_tokens > 0
THEN input_tokens
ELSE effective_input_tokens
END
+ output_tokens + cache_creation_tokens + cache_read_tokens
), 0)::BIGINT AS total_tokens,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
COALESCE(SUM(error_requests), 0)::BIGINT AS error_requests,
COALESCE(SUM(response_time_sum_ms), 0)::DOUBLE PRECISION AS response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS response_time_samples
FROM stats_user_daily
WHERE user_id = $1
AND date >= $2
AND date < $3
"#,
)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
SELECT
COALESCE(SUM(total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(
CASE
WHEN effective_input_tokens = 0 AND total_input_context = 0 AND input_tokens > 0
THEN input_tokens
ELSE effective_input_tokens
END
), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(
CASE
WHEN effective_input_tokens = 0 AND total_input_context = 0 AND input_tokens > 0
THEN input_tokens
ELSE effective_input_tokens
END
+ output_tokens + cache_creation_tokens + cache_read_tokens
), 0)::BIGINT AS total_tokens,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
COALESCE(SUM(error_requests), 0)::BIGINT AS error_requests,
COALESCE(SUM(response_time_sum_ms), 0)::DOUBLE PRECISION AS response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS response_time_samples
FROM stats_daily
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
};
decode_dashboard_summary_row(&row)
}
async fn list_dashboard_daily_breakdown_from_daily_totals(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let sql = if user_id.is_some() {
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
'aggregate'::TEXT AS model,
'aggregate'::TEXT AS provider,
COALESCE(SUM(total_requests), 0)::BIGINT AS requests,
COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
0::DOUBLE PRECISION AS response_time_sum_ms,
0::BIGINT AS response_time_samples
FROM stats_user_daily
WHERE user_id = $1
AND date >= $2
AND date < $3
AND total_requests > 0
GROUP BY date
ORDER BY date ASC
"#
} else {
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
'aggregate'::TEXT AS model,
'aggregate'::TEXT AS provider,
COALESCE(SUM(total_requests), 0)::BIGINT AS requests,
COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
0::DOUBLE PRECISION AS response_time_sum_ms,
0::BIGINT AS response_time_samples
FROM stats_daily
WHERE date >= $1
AND date < $2
AND total_requests > 0
GROUP BY date
ORDER BY date ASC
"#
};
let mut rows = if let Some(user_id) = user_id {
sqlx::query(sql)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
} else {
sqlx::query(sql)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
};
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_dashboard_daily_breakdown_row(&row)?);
}
Ok(items)
}
async fn summarize_dashboard_usage_raw(
&self,
created_from_unix_secs: u64,
created_until_unix_secs: u64,
user_id: Option<&str>,
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
if created_from_unix_secs >= created_until_unix_secs {
return Ok(StoredUsageDashboardSummary::default());
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COUNT(*)::BIGINT AS total_requests,
COALESCE(SUM(GREATEST(COALESCE("usage".input_tokens, 0), 0)), 0)::BIGINT AS input_tokens,
COALESCE(SUM(
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
END
), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".output_tokens, 0), 0)), 0)::BIGINT AS output_tokens,
COALESCE(SUM(
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
END
+ GREATEST(COALESCE("usage".output_tokens, 0), 0)
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
), 0)::BIGINT AS total_tokens,
COALESCE(SUM(
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
COALESCE(SUM(
CASE
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('claude', 'anthropic')
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN (
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
ELSE GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
END
) + GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
ELSE CASE
WHEN (
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
) > 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ (
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
)
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
END
END
), 0)::BIGINT AS total_input_context,
COALESCE(SUM(COALESCE(CAST("usage".cache_creation_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_creation_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".cache_read_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_read_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS actual_total_cost_usd,
COALESCE(SUM(
CASE
WHEN COALESCE("usage".status_code, 0) >= 400
OR lower(COALESCE("usage".status, '')) = 'failed'
THEN 1
ELSE 0
END
), 0)::BIGINT AS error_requests,
COALESCE(SUM(
CASE
WHEN "usage".response_time_ms IS NOT NULL
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION
ELSE 0
END
), 0) AS response_time_sum_ms,
COALESCE(SUM(
CASE
WHEN "usage".response_time_ms IS NOT NULL THEN 1
ELSE 0
END
), 0)::BIGINT AS response_time_samples
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".status NOT IN ('pending', 'streaming')");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".provider_name NOT IN ('unknown', 'pending')");
if let Some(user_id) = user_id {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_dashboard_summary_row(&row)
}
async fn summarize_dashboard_provider_counts_from_hourly_aggregates(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Result<Vec<StoredUsageDashboardProviderCount>, DataLayerError> {
if start_utc >= end_utc {
return Ok(Vec::new());
}
let mut rows = sqlx::query(
r#"
SELECT
provider_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count
FROM stats_hourly_provider
WHERE hour_utc >= $1
AND hour_utc < $2
GROUP BY provider_name
ORDER BY request_count DESC, provider_name ASC
"#,
)
.bind(start_utc)
.bind(end_utc)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(StoredUsageDashboardProviderCount {
provider_name: row
.try_get::<String, _>("provider_name")
.map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
});
}
Ok(items)
}
async fn summarize_dashboard_provider_counts_raw(
&self,
created_from_unix_secs: u64,
created_until_unix_secs: u64,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageDashboardProviderCount>, DataLayerError> {
if created_from_unix_secs >= created_until_unix_secs {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
"usage".provider_name AS provider_name,
COUNT(*)::BIGINT AS request_count
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".status NOT IN ('pending', 'streaming')");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".provider_name NOT IN ('unknown', 'pending')");
if let Some(user_id) = user_id {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
builder.push(
r#"
GROUP BY "usage".provider_name
ORDER BY request_count DESC, "usage".provider_name ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(StoredUsageDashboardProviderCount {
provider_name: row
.try_get::<String, _>("provider_name")
.map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
});
}
Ok(items)
}
pub async fn find_by_request_id(
&self,
request_id: &str,
) -> Result<Option<StoredRequestUsageAudit>, DataLayerError> {
let row = sqlx::query(FIND_BY_REQUEST_ID_SQL)
.bind(request_id)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
let usage = row
.as_ref()
.map(|row| map_usage_row(row, true))
.transpose()?;
match usage {
Some(usage) => self.hydrate_usage_body_refs(usage).await.map(Some),
None => Ok(None),
}
}
pub async fn find_by_id(
&self,
id: &str,
) -> Result<Option<StoredRequestUsageAudit>, DataLayerError> {
let row = sqlx::query(FIND_BY_ID_SQL)
.bind(id)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
row.as_ref()
.map(|row| map_usage_row(row, false))
.transpose()
}
pub async fn list_by_ids(
&self,
ids: &[String],
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
if ids.is_empty() {
return Ok(Vec::new());
}
let sql = FIND_BY_ID_SQL.replacen(
"WHERE \"usage\".id = $1\nLIMIT 1",
"WHERE \"usage\".id = ANY($1::TEXT[])\nORDER BY \"usage\".created_at DESC, \"usage\".id ASC",
1,
);
let mut rows = sqlx::query(&sql).bind(ids.to_vec()).fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(map_usage_row(&row, false)?);
}
Ok(items)
}
pub async fn resolve_body_ref(&self, body_ref: &str) -> Result<Option<Value>, DataLayerError> {
let blob_row = sqlx::query(FIND_USAGE_BODY_BLOB_BY_REF_SQL)
.bind(body_ref)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
if let Some(row) = blob_row.as_ref() {
let payload_gzip = row
.try_get::<Vec<u8>, _>("payload_gzip")
.map_postgres_err()?;
return inflate_usage_json_value(&payload_gzip).map(Some);
}
let Some((request_id, field)) = parse_usage_body_ref(body_ref) else {
return Ok(None);
};
let (inline_column, compressed_column) = usage_body_sql_columns(field);
let row = sqlx::query(&format!(
"SELECT {inline_column} AS inline_body, {compressed_column} AS compressed_body FROM \"usage\" WHERE request_id = $1 LIMIT 1"
))
.bind(request_id)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
row.as_ref()
.map(|row| usage_json_column(row, "inline_body", "compressed_body", true))
.transpose()
.map(|value| value.and_then(|column| column.value))
}
async fn hydrate_usage_body_refs(
&self,
mut usage: StoredRequestUsageAudit,
) -> Result<StoredRequestUsageAudit, DataLayerError> {
if usage.request_body.is_none() {
usage.request_body = self
.resolve_usage_body_ref(&usage, UsageBodyField::RequestBody)
.await?;
}
if usage.provider_request_body.is_none() {
usage.provider_request_body = self
.resolve_usage_body_ref(&usage, UsageBodyField::ProviderRequestBody)
.await?;
}
if usage.response_body.is_none() {
usage.response_body = self
.resolve_usage_body_ref(&usage, UsageBodyField::ResponseBody)
.await?;
}
if usage.client_response_body.is_none() {
usage.client_response_body = self
.resolve_usage_body_ref(&usage, UsageBodyField::ClientResponseBody)
.await?;
}
Ok(usage)
}
async fn resolve_usage_body_ref(
&self,
usage: &StoredRequestUsageAudit,
field: UsageBodyField,
) -> Result<Option<Value>, DataLayerError> {
let body_ref = usage.body_ref(field);
match body_ref {
Some(body_ref) => self.resolve_body_ref(body_ref).await,
None => Ok(None),
}
}
pub async fn summarize_provider_usage_since(
&self,
provider_id: &str,
since_unix_secs: u64,
) -> Result<StoredProviderUsageSummary, DataLayerError> {
let row = sqlx::query(SUMMARIZE_PROVIDER_USAGE_SINCE_SQL)
.bind(provider_id)
.bind(since_unix_secs as f64)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
Ok(StoredProviderUsageSummary {
total_requests: row
.try_get::<i64, _>("total_requests")
.map_postgres_err()?
.max(0) as u64,
successful_requests: row
.try_get::<i64, _>("successful_requests")
.map_postgres_err()?
.max(0) as u64,
failed_requests: row
.try_get::<i64, _>("failed_requests")
.map_postgres_err()?
.max(0) as u64,
avg_response_time_ms: row
.try_get::<f64, _>("avg_response_time_ms")
.map_postgres_err()?,
total_cost_usd: row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?,
})
}
pub async fn list_usage_audits(
&self,
query: &UsageAuditListQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(LIST_USAGE_AUDITS_PREFIX);
let mut has_where = false;
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
}
if let Some(created_until_unix_secs) = query.created_until_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
}
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = query.provider_name.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = query.model.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
if let Some(api_format) = query.api_format.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".api_format = ")
.push_bind(api_format.to_string());
}
if let Some(statuses) = query.statuses.as_deref() {
if !statuses.is_empty() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status IN (");
let mut separated = builder.separated(", ");
for status in statuses {
separated.push_bind(status.to_string());
}
separated.push_unseparated(")");
}
}
if let Some(is_stream) = query.is_stream {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
}
if query.error_only {
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push(
"(\"usage\".status = 'failed' \
OR COALESCE(\"usage\".status_code, 0) >= 400 \
OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''))",
);
}
if query.newest_first {
builder.push(" ORDER BY \"usage\".created_at DESC, \"usage\".id ASC");
} else {
builder.push(" ORDER BY \"usage\".created_at ASC, \"usage\".request_id ASC");
}
if let Some(limit) = query.limit {
builder.push(" LIMIT ").push_bind(limit as i64);
}
if let Some(offset) = query.offset {
builder.push(" OFFSET ").push_bind(offset as i64);
}
let query = builder.build();
let mut rows = query.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(map_usage_row(&row, false)?);
}
Ok(items)
}
pub async fn list_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(LIST_USAGE_AUDITS_PREFIX);
let mut has_where = false;
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
}
if let Some(created_until_unix_secs) = query.created_until_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
}
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = query.provider_name.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = query.model.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
if let Some(api_format) = query.api_format.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".api_format = ")
.push_bind(api_format.to_string());
}
if let Some(statuses) = query.statuses.as_deref() {
if !statuses.is_empty() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status IN (");
let mut separated = builder.separated(", ");
for status in statuses {
separated.push_bind(status.to_string());
}
separated.push_unseparated(")");
}
}
if let Some(is_stream) = query.is_stream {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
}
if query.error_only {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push(
"(\"usage\".status = 'failed' \
OR COALESCE(\"usage\".status_code, 0) >= 400 \
OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''))",
);
}
for (index, keyword) in query.keywords.iter().enumerate() {
let keyword = keyword.trim();
if keyword.is_empty() {
continue;
}
let pattern = format!("%{}%", keyword.to_ascii_lowercase());
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("(");
builder
.push("LOWER(COALESCE(\"usage\".model, '')) LIKE ")
.push_bind(pattern.clone());
builder
.push(" OR LOWER(COALESCE(\"usage\".provider_name, '')) LIKE ")
.push_bind(pattern.clone());
if query.auth_user_reader_available {
let matched_user_ids = query
.matched_user_ids_by_keyword
.get(index)
.cloned()
.unwrap_or_default();
if !matched_user_ids.is_empty() {
builder.push(" OR \"usage\".user_id IN (");
let mut separated = builder.separated(", ");
for user_id in matched_user_ids {
separated.push_bind(user_id);
}
separated.push_unseparated(")");
}
} else {
builder
.push(" OR LOWER(COALESCE(\"usage\".username, '')) LIKE ")
.push_bind(pattern.clone());
}
if query.auth_api_key_reader_available {
let matched_ids = query
.matched_api_key_ids_by_keyword
.get(index)
.cloned()
.unwrap_or_default();
if !matched_ids.is_empty() {
builder.push(" OR \"usage\".api_key_id IN (");
let mut separated = builder.separated(", ");
for api_key_id in matched_ids {
separated.push_bind(api_key_id);
}
separated.push_unseparated(")");
}
} else {
builder
.push(" OR LOWER(COALESCE(\"usage\".api_key_name, '')) LIKE ")
.push_bind(pattern);
}
builder.push(")");
}
if let Some(username_keyword) = query
.username_keyword
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
builder.push(if has_where { " AND " } else { " WHERE " });
if query.auth_user_reader_available {
if query.matched_user_ids_for_username.is_empty() {
builder.push("FALSE");
} else {
builder.push("\"usage\".user_id IN (");
let mut separated = builder.separated(", ");
for user_id in &query.matched_user_ids_for_username {
separated.push_bind(user_id.clone());
}
separated.push_unseparated(")");
}
} else {
builder
.push("LOWER(COALESCE(\"usage\".username, '')) LIKE ")
.push_bind(format!("%{}%", username_keyword.to_ascii_lowercase()));
}
}
if query.newest_first {
builder.push(" ORDER BY \"usage\".created_at DESC, \"usage\".id ASC");
} else {
builder.push(" ORDER BY \"usage\".created_at ASC, \"usage\".request_id ASC");
}
if let Some(limit) = query.limit {
builder.push(" LIMIT ").push_bind(limit as i64);
}
if let Some(offset) = query.offset {
builder.push(" OFFSET ").push_bind(offset as i64);
}
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(map_usage_row(&row, false)?);
}
Ok(items)
}
pub async fn count_usage_audits(
&self,
query: &UsageAuditListQuery,
) -> Result<u64, DataLayerError> {
let mut builder =
QueryBuilder::<Postgres>::new(r#"SELECT COUNT(*)::BIGINT AS total FROM "usage""#);
let mut has_where = false;
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
}
if let Some(created_until_unix_secs) = query.created_until_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
}
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = query.provider_name.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = query.model.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
if let Some(api_format) = query.api_format.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".api_format = ")
.push_bind(api_format.to_string());
}
if let Some(statuses) = query.statuses.as_deref() {
if !statuses.is_empty() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status IN (");
let mut separated = builder.separated(", ");
for status in statuses {
separated.push_bind(status.to_string());
}
separated.push_unseparated(")");
}
}
if let Some(is_stream) = query.is_stream {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
}
if query.error_only {
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push(
"(\"usage\".status = 'failed' \
OR COALESCE(\"usage\".status_code, 0) >= 400 \
OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''))",
);
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
Ok(row.try_get::<i64, _>("total").map_postgres_err()?.max(0) as u64)
}
pub async fn count_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<u64, DataLayerError> {
let mut builder =
QueryBuilder::<Postgres>::new(r#"SELECT COUNT(*)::BIGINT AS total FROM "usage""#);
let mut has_where = false;
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
}
if let Some(created_until_unix_secs) = query.created_until_unix_secs {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
}
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = query.provider_name.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = query.model.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
if let Some(api_format) = query.api_format.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".api_format = ")
.push_bind(api_format.to_string());
}
if let Some(statuses) = query.statuses.as_deref() {
if !statuses.is_empty() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status IN (");
let mut separated = builder.separated(", ");
for status in statuses {
separated.push_bind(status.to_string());
}
separated.push_unseparated(")");
}
}
if let Some(is_stream) = query.is_stream {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
}
if query.error_only {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push(
"(\"usage\".status = 'failed' \
OR COALESCE(\"usage\".status_code, 0) >= 400 \
OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''))",
);
}
for (index, keyword) in query.keywords.iter().enumerate() {
let keyword = keyword.trim();
if keyword.is_empty() {
continue;
}
let pattern = format!("%{}%", keyword.to_ascii_lowercase());
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("(");
builder
.push("LOWER(COALESCE(\"usage\".model, '')) LIKE ")
.push_bind(pattern.clone());
builder
.push(" OR LOWER(COALESCE(\"usage\".provider_name, '')) LIKE ")
.push_bind(pattern.clone());
if query.auth_user_reader_available {
let matched_user_ids = query
.matched_user_ids_by_keyword
.get(index)
.cloned()
.unwrap_or_default();
if !matched_user_ids.is_empty() {
builder.push(" OR \"usage\".user_id IN (");
let mut separated = builder.separated(", ");
for user_id in matched_user_ids {
separated.push_bind(user_id);
}
separated.push_unseparated(")");
}
} else {
builder
.push(" OR LOWER(COALESCE(\"usage\".username, '')) LIKE ")
.push_bind(pattern.clone());
}
if query.auth_api_key_reader_available {
let matched_ids = query
.matched_api_key_ids_by_keyword
.get(index)
.cloned()
.unwrap_or_default();
if !matched_ids.is_empty() {
builder.push(" OR \"usage\".api_key_id IN (");
let mut separated = builder.separated(", ");
for api_key_id in matched_ids {
separated.push_bind(api_key_id);
}
separated.push_unseparated(")");
}
} else {
builder
.push(" OR LOWER(COALESCE(\"usage\".api_key_name, '')) LIKE ")
.push_bind(pattern);
}
builder.push(")");
}
if let Some(username_keyword) = query
.username_keyword
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
builder.push(if has_where { " AND " } else { " WHERE " });
if query.auth_user_reader_available {
if query.matched_user_ids_for_username.is_empty() {
builder.push("FALSE");
} else {
builder.push("\"usage\".user_id IN (");
let mut separated = builder.separated(", ");
for user_id in &query.matched_user_ids_for_username {
separated.push_bind(user_id.clone());
}
separated.push_unseparated(")");
}
} else {
builder
.push("LOWER(COALESCE(\"usage\".username, '')) LIKE ")
.push_bind(format!("%{}%", username_keyword.to_ascii_lowercase()));
}
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
Ok(row.try_get::<i64, _>("total").map_postgres_err()?.max(0) as u64)
}
async fn summarize_usage_audits_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<StoredUsageAuditSummary, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(StoredUsageAuditSummary::default());
}
let row = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
COALESCE(SUM(total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(input_tokens + output_tokens), 0)::BIGINT AS recorded_total_tokens,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(response_time_sum_ms), 0)::DOUBLE PRECISION AS total_response_time_ms,
COALESCE(SUM(error_requests), 0)::BIGINT AS error_requests
FROM stats_user_daily
WHERE user_id = $1
AND date >= $2
AND date < $3
"#,
)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
SELECT
COALESCE(SUM(total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(input_tokens + output_tokens), 0)::BIGINT AS recorded_total_tokens,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(response_time_sum_ms), 0)::DOUBLE PRECISION AS total_response_time_ms,
COALESCE(SUM(error_requests), 0)::BIGINT AS error_requests
FROM stats_daily
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
};
decode_usage_audit_summary_row(&row)
}
async fn summarize_usage_audits_raw(
&self,
created_from_unix_secs: u64,
created_until_unix_secs: u64,
user_id: Option<&str>,
provider_name: Option<&str>,
model: Option<&str>,
) -> Result<StoredUsageAuditSummary, DataLayerError> {
if created_from_unix_secs >= created_until_unix_secs {
return Ok(StoredUsageAuditSummary::default());
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COUNT(*)::BIGINT AS total_requests,
COALESCE(SUM(GREATEST(COALESCE("usage".input_tokens, 0), 0)), 0)::BIGINT AS input_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".output_tokens, 0), 0)), 0)::BIGINT AS output_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".total_tokens, 0), 0)), 0)::BIGINT AS recorded_total_tokens,
COALESCE(SUM(
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_creation_input_tokens_5m, 0), 0)), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_creation_input_tokens_1h, 0), 0)), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS actual_total_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".cache_creation_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_creation_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".cache_read_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_read_cost_usd,
COALESCE(SUM(GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION), 0)
AS total_response_time_ms,
COALESCE(SUM(
CASE
WHEN COALESCE("usage".status_code, 0) >= 400 OR "usage".error_message IS NOT NULL THEN 1
ELSE 0
END
), 0)::BIGINT AS error_requests
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(created_until_unix_secs as f64)
.push("::double precision)");
if let Some(user_id) = user_id {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = provider_name {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = model {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_audit_summary_row(&row)
}
pub async fn summarize_usage_audits(
&self,
query: &UsageAuditSummaryQuery,
) -> Result<StoredUsageAuditSummary, DataLayerError> {
if query.provider_name.is_some() || query.model.is_some() {
return self
.summarize_usage_audits_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
query.provider_name.as_deref(),
query.model.as_deref(),
)
.await;
}
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_usage_audits_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
None,
None,
)
.await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_audits_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
None,
None,
)
.await;
};
let mut summary = StoredUsageAuditSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_audit_summary(
&mut summary,
self.summarize_usage_audits_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
query.user_id.as_deref(),
None,
None,
)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_audit_summary(
&mut summary,
self.summarize_usage_audits_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.user_id.as_deref(),
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_audit_summary(
&mut summary,
self.summarize_usage_audits_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
query.user_id.as_deref(),
None,
None,
)
.await?,
);
}
Ok(summary)
}
async fn summarize_usage_cache_hit_summary_raw(
&self,
query: &UsageCacheHitSummaryQuery,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COUNT(*)::BIGINT AS total_requests,
COUNT(*) FILTER (
WHERE GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) > 0
)::BIGINT AS cache_hit_requests
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_hit_summary_row(&row)
}
async fn summarize_usage_cache_hit_summary_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
let row = sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_hit_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(cache_hit_requests), 0)::BIGINT AS cache_hit_requests
FROM stats_daily
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_hit_summary_row(&row)
}
async fn summarize_usage_cache_hit_summary_from_hourly_aggregates(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
let row = sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_hit_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(cache_hit_requests), 0)::BIGINT AS cache_hit_requests
FROM stats_hourly
WHERE hour_utc >= $1
AND hour_utc < $2
"#,
)
.bind(start_utc)
.bind(end_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_hit_summary_row(&row)
}
async fn summarize_global_usage_cache_hit_summary_segment(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
if start_utc >= end_utc {
return Ok(StoredUsageCacheHitSummary::default());
}
let Some(cutoff_utc) = self.read_stats_hourly_cutoff().await? else {
return self
.summarize_usage_cache_hit_summary_raw(&UsageCacheHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: None,
})
.await;
};
let split = split_dashboard_hourly_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_cache_hit_summary_raw(&UsageCacheHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: None,
})
.await;
};
let mut summary = StoredUsageCacheHitSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_usage_cache_hit_summary_raw(&UsageCacheHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: None,
})
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_usage_cache_hit_summary_from_hourly_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_usage_cache_hit_summary_raw(&UsageCacheHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: None,
})
.await?,
);
}
Ok(summary)
}
pub async fn summarize_usage_cache_hit_summary(
&self,
query: &UsageCacheHitSummaryQuery,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
if query.user_id.is_some() {
return self.summarize_usage_cache_hit_summary_raw(query).await;
}
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_global_usage_cache_hit_summary_segment(start_utc, end_utc)
.await;
};
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_global_usage_cache_hit_summary_segment(start_utc, end_utc)
.await;
};
let mut summary = StoredUsageCacheHitSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_global_usage_cache_hit_summary_segment(raw_start, raw_end)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_usage_cache_hit_summary_from_daily_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_cache_hit_summary(
&mut summary,
self.summarize_global_usage_cache_hit_summary_segment(raw_start, raw_end)
.await?,
);
}
Ok(summary)
}
async fn summarize_usage_settled_cost_raw(
&self,
query: &UsageSettledCostSummaryQuery,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd,
COUNT(*)::BIGINT AS total_requests,
COALESCE(SUM(GREATEST(COALESCE("usage".input_tokens, 0), 0)), 0)::BIGINT AS input_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".output_tokens, 0), 0)), 0)::BIGINT AS output_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_creation_input_tokens, 0), 0)), 0)::BIGINT
AS cache_creation_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
MIN(CAST(EXTRACT(EPOCH FROM "usage".finalized_at) AS BIGINT))
AS first_finalized_at_unix_secs,
MAX(CAST(EXTRACT(EPOCH FROM "usage".finalized_at) AS BIGINT))
AS last_finalized_at_unix_secs
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".billing_status = 'settled'");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("COALESCE(CAST(\"usage\".total_cost_usd AS DOUBLE PRECISION), 0) > 0");
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_settled_cost_row(&row)
}
async fn summarize_usage_settled_cost_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
let row = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
CAST(COALESCE(SUM(settled_total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd,
COALESCE(SUM(settled_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(settled_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(settled_output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(settled_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(settled_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
MIN(settled_first_finalized_at_unix_secs) AS first_finalized_at_unix_secs,
MAX(settled_last_finalized_at_unix_secs) AS last_finalized_at_unix_secs
FROM stats_user_daily
WHERE date >= $1
AND date < $2
AND user_id = $3
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.bind(user_id)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
SELECT
CAST(COALESCE(SUM(settled_total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd,
COALESCE(SUM(settled_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(settled_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(settled_output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(settled_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(settled_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
MIN(settled_first_finalized_at_unix_secs) AS first_finalized_at_unix_secs,
MAX(settled_last_finalized_at_unix_secs) AS last_finalized_at_unix_secs
FROM stats_daily
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
};
decode_usage_settled_cost_row(&row)
}
async fn summarize_usage_settled_cost_from_hourly_aggregates(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
let row = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
CAST(COALESCE(SUM(settled_total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd,
COALESCE(SUM(settled_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(settled_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(settled_output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(settled_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(settled_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
MIN(settled_first_finalized_at_unix_secs) AS first_finalized_at_unix_secs,
MAX(settled_last_finalized_at_unix_secs) AS last_finalized_at_unix_secs
FROM stats_hourly_user
WHERE hour_utc >= $1
AND hour_utc < $2
AND user_id = $3
"#,
)
.bind(start_utc)
.bind(end_utc)
.bind(user_id)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
SELECT
CAST(COALESCE(SUM(settled_total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd,
COALESCE(SUM(settled_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(settled_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(settled_output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(settled_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(settled_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
MIN(settled_first_finalized_at_unix_secs) AS first_finalized_at_unix_secs,
MAX(settled_last_finalized_at_unix_secs) AS last_finalized_at_unix_secs
FROM stats_hourly
WHERE hour_utc >= $1
AND hour_utc < $2
"#,
)
.bind(start_utc)
.bind(end_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?
};
decode_usage_settled_cost_row(&row)
}
async fn summarize_usage_settled_cost_segment(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
if start_utc >= end_utc {
return Ok(StoredUsageSettledCostSummary::default());
}
let Some(cutoff_utc) = self.read_stats_hourly_cutoff().await? else {
return self
.summarize_usage_settled_cost_raw(&UsageSettledCostSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: user_id.map(ToOwned::to_owned),
})
.await;
};
let split = split_dashboard_hourly_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_settled_cost_raw(&UsageSettledCostSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: user_id.map(ToOwned::to_owned),
})
.await;
};
let mut summary = StoredUsageSettledCostSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_raw(&UsageSettledCostSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: user_id.map(ToOwned::to_owned),
})
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_from_hourly_aggregates(
aggregate_start,
aggregate_end,
user_id,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_raw(&UsageSettledCostSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: user_id.map(ToOwned::to_owned),
})
.await?,
);
}
Ok(summary)
}
pub async fn summarize_usage_settled_cost(
&self,
query: &UsageSettledCostSummaryQuery,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let user_id = query.user_id.as_deref();
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_usage_settled_cost_segment(start_utc, end_utc, user_id)
.await;
};
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_settled_cost_segment(start_utc, end_utc, user_id)
.await;
};
let mut summary = StoredUsageSettledCostSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_segment(raw_start, raw_end, user_id)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_from_daily_aggregates(
aggregate_start,
aggregate_end,
user_id,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_settled_cost_summary(
&mut summary,
self.summarize_usage_settled_cost_segment(raw_start, raw_end, user_id)
.await?,
);
}
Ok(summary)
}
async fn summarize_usage_cache_affinity_hit_summary_raw(
&self,
query: &UsageCacheAffinityHitSummaryQuery,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COUNT(*)::BIGINT AS total_requests,
COALESCE(SUM(
CASE
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) > 0 THEN 1
ELSE 0
END
), 0)::BIGINT AS requests_with_cache_hit,
COALESCE(SUM(GREATEST(COALESCE("usage".input_tokens, 0), 0)), 0)::BIGINT AS input_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
COALESCE(SUM(
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(
CASE
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('claude', 'anthropic')
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN (
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
ELSE GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
END
) + GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
ELSE CASE
WHEN (
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
) > 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ (
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
)
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
END
END
), 0)::BIGINT AS total_input_context,
COALESCE(SUM(COALESCE(CAST("usage".cache_read_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_read_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".cache_creation_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_creation_cost_usd
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status = 'completed'");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(api_key_id) = query.api_key_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".api_key_id = ")
.push_bind(api_key_id.to_string());
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_affinity_hit_summary_row(&row)
}
async fn summarize_usage_cache_affinity_hit_summary_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
let row = sqlx::query(
r#"
SELECT
COALESCE(SUM(completed_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(completed_cache_hit_requests), 0)::BIGINT AS requests_with_cache_hit,
COALESCE(SUM(completed_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(completed_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(completed_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(completed_total_input_context), 0)::BIGINT AS total_input_context,
CAST(COALESCE(SUM(completed_cache_read_cost), 0) AS DOUBLE PRECISION) AS cache_read_cost_usd,
CAST(COALESCE(SUM(completed_cache_creation_cost), 0) AS DOUBLE PRECISION)
AS cache_creation_cost_usd
FROM stats_daily
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_affinity_hit_summary_row(&row)
}
async fn summarize_usage_cache_affinity_hit_summary_from_hourly_aggregates(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
let row = sqlx::query(
r#"
SELECT
COALESCE(SUM(completed_total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(completed_cache_hit_requests), 0)::BIGINT AS requests_with_cache_hit,
COALESCE(SUM(completed_input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(completed_cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(completed_cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(completed_total_input_context), 0)::BIGINT AS total_input_context,
CAST(COALESCE(SUM(completed_cache_read_cost), 0) AS DOUBLE PRECISION) AS cache_read_cost_usd,
CAST(COALESCE(SUM(completed_cache_creation_cost), 0) AS DOUBLE PRECISION)
AS cache_creation_cost_usd
FROM stats_hourly
WHERE hour_utc >= $1
AND hour_utc < $2
"#,
)
.bind(start_utc)
.bind(end_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cache_affinity_hit_summary_row(&row)
}
async fn summarize_global_usage_cache_affinity_hit_summary_segment(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
if start_utc >= end_utc {
return Ok(StoredUsageCacheAffinityHitSummary::default());
}
let Some(cutoff_utc) = self.read_stats_hourly_cutoff().await? else {
return self
.summarize_usage_cache_affinity_hit_summary_raw(
&UsageCacheAffinityHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: None,
api_key_id: None,
},
)
.await;
};
let split = split_dashboard_hourly_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_cache_affinity_hit_summary_raw(
&UsageCacheAffinityHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(start_utc),
created_until_unix_secs: dashboard_utc_to_unix_secs(end_utc),
user_id: None,
api_key_id: None,
},
)
.await;
};
let mut summary = StoredUsageCacheAffinityHitSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_usage_cache_affinity_hit_summary_raw(
&UsageCacheAffinityHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: None,
api_key_id: None,
},
)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_usage_cache_affinity_hit_summary_from_hourly_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_usage_cache_affinity_hit_summary_raw(
&UsageCacheAffinityHitSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: None,
api_key_id: None,
},
)
.await?,
);
}
Ok(summary)
}
pub async fn summarize_usage_cache_affinity_hit_summary(
&self,
query: &UsageCacheAffinityHitSummaryQuery,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
if query.user_id.is_some() || query.api_key_id.is_some() {
return self
.summarize_usage_cache_affinity_hit_summary_raw(query)
.await;
}
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_global_usage_cache_affinity_hit_summary_segment(start_utc, end_utc)
.await;
};
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_global_usage_cache_affinity_hit_summary_segment(start_utc, end_utc)
.await;
};
let mut summary = StoredUsageCacheAffinityHitSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_global_usage_cache_affinity_hit_summary_segment(raw_start, raw_end)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_usage_cache_affinity_hit_summary_from_daily_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_cache_affinity_hit_summary(
&mut summary,
self.summarize_global_usage_cache_affinity_hit_summary_segment(raw_start, raw_end)
.await?,
);
}
Ok(summary)
}
pub async fn list_usage_cache_affinity_intervals(
&self,
query: &UsageCacheAffinityIntervalQuery,
) -> Result<Vec<StoredUsageCacheAffinityIntervalRow>, DataLayerError> {
let group_column = match query.group_by {
UsageCacheAffinityIntervalGroupBy::User => "\"usage\".user_id",
UsageCacheAffinityIntervalGroupBy::ApiKey => "\"usage\".api_key_id",
};
let mut builder = QueryBuilder::<Postgres>::new(
r#"
WITH filtered_usage AS (
SELECT
"#,
);
builder.push(group_column);
builder.push(
r#" AS group_id,
"usage".username AS username,
COALESCE("usage".model, '') AS model,
"usage".created_at AS created_at,
"usage".id AS usage_id
FROM "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder.push("\"usage\".status = 'completed'");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push(group_column).push(" IS NOT NULL");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(api_key_id) = query.api_key_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".api_key_id = ")
.push_bind(api_key_id.to_string());
}
builder.push(
r#"
),
intervals AS (
SELECT
group_id,
username,
model,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
usage_id,
CAST((
EXTRACT(EPOCH FROM (
created_at - LAG(created_at) OVER (
PARTITION BY group_id
ORDER BY created_at ASC, usage_id ASC
)
)) / 60.0
) AS DOUBLE PRECISION) AS interval_minutes
FROM filtered_usage
)
SELECT
group_id,
username,
model,
created_at_unix_secs,
interval_minutes
FROM intervals
WHERE interval_minutes IS NOT NULL
ORDER BY created_at_unix_secs ASC, group_id ASC, usage_id ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(StoredUsageCacheAffinityIntervalRow {
group_id: row.try_get::<String, _>("group_id").map_postgres_err()?,
username: row
.try_get::<Option<String>, _>("username")
.map_postgres_err()?,
model: row.try_get::<String, _>("model").map_postgres_err()?,
created_at_unix_secs: row
.try_get::<i64, _>("created_at_unix_secs")
.map_postgres_err()?
.max(0) as u64,
interval_minutes: row
.try_get::<f64, _>("interval_minutes")
.map_postgres_err()?,
});
}
Ok(items)
}
pub async fn summarize_dashboard_usage(
&self,
query: &UsageDashboardSummaryQuery,
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
let cutoff_utc = match self.read_stats_daily_cutoff_date().await {
Ok(value) => value,
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
return self
.summarize_dashboard_usage_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
)
.await;
}
Err(err) => return Err(err),
};
let Some(cutoff_utc) = cutoff_utc else {
return self
.summarize_dashboard_usage_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
)
.await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_dashboard_usage_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
)
.await;
};
let mut summary = StoredUsageDashboardSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
let raw = self
.summarize_dashboard_usage_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
query.user_id.as_deref(),
)
.await?;
absorb_dashboard_summary(&mut summary, &raw);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let aggregate = match self
.summarize_dashboard_usage_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.user_id.as_deref(),
)
.await
{
Ok(value) => value,
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
return self
.summarize_dashboard_usage_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
)
.await;
}
Err(err) => return Err(err),
};
absorb_dashboard_summary(&mut summary, &aggregate);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
let raw = self
.summarize_dashboard_usage_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
query.user_id.as_deref(),
)
.await?;
absorb_dashboard_summary(&mut summary, &raw);
}
Ok(summary)
}
async fn list_dashboard_daily_breakdown_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let sql = if user_id.is_some() {
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
model,
provider_name AS provider,
COALESCE(SUM(total_requests), 0)::BIGINT AS requests,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(response_time_sum_ms), 0) AS response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS response_time_samples
FROM stats_user_daily_model_provider
WHERE user_id = $1
AND date >= $2
AND date < $3
GROUP BY date, model, provider_name
ORDER BY date ASC, total_cost_usd DESC, model ASC, provider_name ASC
"#
} else {
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
model,
provider_name AS provider,
COALESCE(SUM(total_requests), 0)::BIGINT AS requests,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(response_time_sum_ms), 0) AS response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS response_time_samples
FROM stats_daily_model_provider
WHERE date >= $1
AND date < $2
GROUP BY date, model, provider_name
ORDER BY date ASC, total_cost_usd DESC, model ASC, provider_name ASC
"#
};
let mut rows = if let Some(user_id) = user_id {
sqlx::query(sql)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
} else {
sqlx::query(sql)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
};
let mut items = Vec::new();
let mut detailed_dates = std::collections::BTreeSet::<String>::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let item = decode_dashboard_daily_breakdown_row(&row)?;
detailed_dates.insert(item.date.clone());
items.push(item);
}
for item in self
.list_dashboard_daily_breakdown_from_daily_totals(start_day_utc, end_day_utc, user_id)
.await?
{
if !detailed_dates.contains(&item.date) {
items.push(item);
}
}
Ok(items)
}
async fn list_dashboard_daily_breakdown_raw(
&self,
query: &UsageDashboardDailyBreakdownQuery,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
TO_CHAR(
date_trunc('day', "usage".created_at + (
"#,
);
builder.push_bind(query.tz_offset_minutes);
builder.push(
r#"::integer * INTERVAL '1 minute'
)),
'YYYY-MM-DD'
) AS date,
"usage".model AS model,
"usage".provider_name AS provider,
COUNT(*)::BIGINT AS requests,
COALESCE(SUM(
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
END
+ GREATEST(COALESCE("usage".output_tokens, 0), 0)
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
), 0)::BIGINT AS total_tokens,
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd,
COALESCE(SUM(
CASE
WHEN "usage".response_time_ms IS NOT NULL
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION
ELSE 0
END
), 0) AS response_time_sum_ms,
COALESCE(SUM(
CASE
WHEN "usage".response_time_ms IS NOT NULL THEN 1
ELSE 0
END
), 0)::BIGINT AS response_time_samples
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".status NOT IN ('pending', 'streaming')");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".provider_name NOT IN ('unknown', 'pending')");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
builder.push(
r#"
GROUP BY date, "usage".model, "usage".provider_name
ORDER BY date ASC, total_cost_usd DESC, "usage".model ASC, "usage".provider_name ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_dashboard_daily_breakdown_row(&row)?);
}
Ok(items)
}
async fn list_dashboard_daily_breakdown_aggregate_segments(
&self,
query: &UsageDashboardDailyBreakdownQuery,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
let cutoff_utc = match self.read_stats_daily_cutoff_date().await {
Ok(value) => value,
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
return Ok(Vec::new());
}
Err(err) => return Err(err),
};
let Some(cutoff_utc) = cutoff_utc else {
return Ok(Vec::new());
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some((aggregate_start, aggregate_end)) = split.aggregate else {
return Ok(Vec::new());
};
self.list_dashboard_daily_breakdown_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.user_id.as_deref(),
)
.await
}
pub async fn list_dashboard_daily_breakdown(
&self,
query: &UsageDashboardDailyBreakdownQuery,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
if query.tz_offset_minutes != 0 {
let mut items = self
.list_dashboard_daily_breakdown_aggregate_segments(query)
.await?;
let mut aggregate_dates = items
.iter()
.map(|item| item.date.clone())
.collect::<std::collections::BTreeSet<_>>();
for item in self.list_dashboard_daily_breakdown_raw(query).await? {
if aggregate_dates.insert(item.date.clone()) {
items.push(item);
}
}
return Ok(finalize_dashboard_daily_breakdown_rows(items));
}
let cutoff_utc = match self.read_stats_daily_cutoff_date().await {
Ok(value) => value,
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
return self.list_dashboard_daily_breakdown_raw(query).await;
}
Err(err) => return Err(err),
};
let Some(cutoff_utc) = cutoff_utc else {
return self.list_dashboard_daily_breakdown_raw(query).await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self.list_dashboard_daily_breakdown_raw(query).await;
};
let mut items = Vec::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
items.extend(
self.list_dashboard_daily_breakdown_raw(&UsageDashboardDailyBreakdownQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
user_id: query.user_id.clone(),
})
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let aggregate_rows = match self
.list_dashboard_daily_breakdown_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.user_id.as_deref(),
)
.await
{
Ok(value) => value,
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
return self.list_dashboard_daily_breakdown_raw(query).await;
}
Err(err) => return Err(err),
};
items.extend(aggregate_rows);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
items.extend(
self.list_dashboard_daily_breakdown_raw(&UsageDashboardDailyBreakdownQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
user_id: query.user_id.clone(),
})
.await?,
);
}
Ok(finalize_dashboard_daily_breakdown_rows(items))
}
pub async fn summarize_dashboard_provider_counts(
&self,
query: &UsageDashboardProviderCountsQuery,
) -> Result<Vec<StoredUsageDashboardProviderCount>, DataLayerError> {
if query.user_id.is_some() {
return self
.summarize_dashboard_provider_counts_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
query.user_id.as_deref(),
)
.await;
}
let Some(cutoff_utc) = self.read_stats_hourly_cutoff().await? else {
return self
.summarize_dashboard_provider_counts_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
None,
)
.await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_hourly_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_dashboard_provider_counts_raw(
query.created_from_unix_secs,
query.created_until_unix_secs,
None,
)
.await;
};
let mut grouped = BTreeMap::<String, u64>::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
let raw = self
.summarize_dashboard_provider_counts_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
None,
)
.await?;
absorb_dashboard_provider_counts(&mut grouped, raw);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let aggregate = self
.summarize_dashboard_provider_counts_from_hourly_aggregates(
aggregate_start,
aggregate_end,
)
.await?;
absorb_dashboard_provider_counts(&mut grouped, aggregate);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
let raw = self
.summarize_dashboard_provider_counts_raw(
dashboard_utc_to_unix_secs(raw_start),
dashboard_utc_to_unix_secs(raw_end),
None,
)
.await?;
absorb_dashboard_provider_counts(&mut grouped, raw);
}
Ok(finalize_dashboard_provider_counts(grouped))
}
async fn summarize_usage_breakdown_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: &str,
group_by: UsageBreakdownGroupBy,
) -> Result<Vec<StoredUsageBreakdownSummaryRow>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let (table_name, group_column) = match group_by {
UsageBreakdownGroupBy::Model => ("stats_user_daily_model", "model"),
UsageBreakdownGroupBy::Provider => ("stats_user_daily_provider", "provider_name"),
UsageBreakdownGroupBy::ApiFormat => ("stats_user_daily_api_format", "api_format"),
};
let sql = format!(
r#"
SELECT
{group_column} AS group_key,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(effective_input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(effective_input_tokens), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
COALESCE(SUM(success_requests), 0)::BIGINT AS success_count,
COALESCE(SUM(successful_response_time_sum_ms), 0) AS response_time_sum_ms,
COALESCE(SUM(successful_response_time_samples), 0)::BIGINT AS response_time_samples,
COALESCE(SUM(response_time_sum_ms), 0) AS overall_response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS overall_response_time_samples
FROM {table_name}
WHERE user_id = $1
AND date >= $2
AND date < $3
GROUP BY {group_column}
ORDER BY request_count DESC, group_key ASC
"#,
);
let mut rows = sqlx::query(&sql)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_breakdown_summary_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_breakdown_raw(
&self,
query: &UsageBreakdownSummaryQuery,
) -> Result<Vec<StoredUsageBreakdownSummaryRow>, DataLayerError> {
let (group_key_expr, filtered_extra_where) = match query.group_by {
UsageBreakdownGroupBy::Model => ("\"usage\".model", ""),
UsageBreakdownGroupBy::Provider => ("\"usage\".provider_name", ""),
UsageBreakdownGroupBy::ApiFormat => (
"\"usage\".api_format",
" AND \"usage\".api_format IS NOT NULL",
),
};
let mut builder = QueryBuilder::<Postgres>::new(&format!(
r#"
WITH filtered_usage AS (
SELECT
{group_key_expr} AS group_key,
GREATEST(COALESCE("usage".input_tokens, 0), 0) AS input_tokens,
GREATEST(COALESCE("usage".output_tokens, 0), 0) AS output_tokens,
GREATEST(COALESCE("usage".total_tokens, 0), 0) AS total_tokens,
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END AS cache_creation_tokens,
GREATEST(COALESCE("usage".cache_creation_input_tokens_5m, 0), 0)
AS cache_creation_ephemeral_5m_tokens,
GREATEST(COALESCE("usage".cache_creation_input_tokens_1h, 0), 0)
AS cache_creation_ephemeral_1h_tokens,
GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) AS cache_read_tokens,
COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
COALESCE(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION), 0) AS actual_total_cost_usd,
CASE
WHEN "usage".status <> 'failed'
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
AND "usage".error_message IS NULL
THEN 1
ELSE 0
END AS success_flag,
CASE
WHEN "usage".response_time_ms IS NOT NULL
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION
ELSE 0
END AS response_time_ms,
CASE
WHEN "usage".response_time_ms IS NOT NULL
THEN 1
ELSE 0
END AS response_time_samples,
CASE
WHEN "usage".status <> 'failed'
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
AND "usage".error_message IS NULL
AND "usage".response_time_ms IS NOT NULL
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION
ELSE 0
END AS successful_response_time_ms,
CASE
WHEN "usage".status <> 'failed'
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
AND "usage".error_message IS NULL
AND "usage".response_time_ms IS NOT NULL
THEN 1
ELSE 0
END AS successful_response_time_samples,
COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '') AS normalized_api_format
FROM usage_billing_facts AS "usage"
"#,
));
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".status NOT IN ('pending', 'streaming')");
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push("\"usage\".provider_name NOT IN ('unknown', 'pending')");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
builder.push(filtered_extra_where);
builder.push(
r#"
),
normalized_usage AS (
SELECT
group_key,
input_tokens,
total_tokens,
output_tokens,
cache_creation_tokens,
cache_creation_ephemeral_5m_tokens,
cache_creation_ephemeral_1h_tokens,
cache_read_tokens,
total_cost_usd,
actual_total_cost_usd,
success_flag,
response_time_ms,
response_time_samples,
successful_response_time_ms,
successful_response_time_samples,
CASE
WHEN input_tokens <= 0 THEN 0
WHEN cache_read_tokens <= 0 THEN input_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(input_tokens - cache_read_tokens, 0)
ELSE input_tokens
END AS effective_input_tokens,
CASE
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('claude', 'anthropic')
THEN input_tokens + cache_creation_tokens + cache_read_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN (
CASE
WHEN input_tokens <= 0 THEN 0
WHEN cache_read_tokens <= 0 THEN input_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(input_tokens - cache_read_tokens, 0)
ELSE input_tokens
END
) + cache_read_tokens
ELSE CASE
WHEN cache_creation_tokens > 0
THEN input_tokens + cache_creation_tokens + cache_read_tokens
ELSE input_tokens + cache_read_tokens
END
END AS total_input_context
FROM filtered_usage
)
SELECT
group_key,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(effective_input_tokens), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost_usd), 0) AS total_cost_usd,
COALESCE(SUM(actual_total_cost_usd), 0) AS actual_total_cost_usd,
COALESCE(SUM(success_flag), 0)::BIGINT AS success_count,
COALESCE(SUM(successful_response_time_ms), 0) AS response_time_sum_ms,
COALESCE(SUM(successful_response_time_samples), 0)::BIGINT AS response_time_samples,
COALESCE(SUM(response_time_ms), 0) AS overall_response_time_sum_ms,
COALESCE(SUM(response_time_samples), 0)::BIGINT AS overall_response_time_samples
FROM normalized_usage
GROUP BY group_key
ORDER BY request_count DESC, group_key ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_breakdown_summary_row(&row)?);
}
Ok(items)
}
pub async fn summarize_usage_breakdown(
&self,
query: &UsageBreakdownSummaryQuery,
) -> Result<Vec<StoredUsageBreakdownSummaryRow>, DataLayerError> {
let Some(user_id) = query.user_id.as_deref() else {
return self.summarize_usage_breakdown_raw(query).await;
};
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self.summarize_usage_breakdown_raw(query).await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self.summarize_usage_breakdown_raw(query).await;
};
let mut grouped = BTreeMap::<String, StoredUsageBreakdownSummaryRow>::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
let raw = self
.summarize_usage_breakdown_raw(&UsageBreakdownSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: Some(user_id.to_string()),
group_by: query.group_by,
})
.await?;
absorb_usage_breakdown_rows(&mut grouped, raw);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let aggregate = self
.summarize_usage_breakdown_from_daily_aggregates(
aggregate_start,
aggregate_end,
user_id,
query.group_by,
)
.await?;
absorb_usage_breakdown_rows(&mut grouped, aggregate);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
let raw = self
.summarize_usage_breakdown_raw(&UsageBreakdownSummaryQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
user_id: Some(user_id.to_string()),
group_by: query.group_by,
})
.await?;
absorb_usage_breakdown_rows(&mut grouped, raw);
}
Ok(finalize_usage_breakdown_rows(grouped))
}
pub async fn count_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorCountQuery,
) -> Result<u64, DataLayerError> {
let row = sqlx::query(
r#"
SELECT COUNT(*)::BIGINT AS total
FROM "usage"
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)
AND "usage".created_at < TO_TIMESTAMP($2::double precision)
AND (
lower(BTRIM(COALESCE("usage".status, ''))) IN ('failed', 'error')
OR ("usage".error_category IS NOT NULL AND BTRIM("usage".error_category) <> '')
OR (
BTRIM(COALESCE("usage".status, '')) = ''
AND (
COALESCE("usage".status_code, 0) >= 400
OR ("usage".error_message IS NOT NULL AND BTRIM("usage".error_message) <> '')
)
)
)
"#,
)
.bind(query.created_from_unix_secs as f64)
.bind(query.created_until_unix_secs as f64)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
Ok(row.try_get::<i64, _>("total").map_postgres_err()?.max(0) as u64)
}
pub async fn list_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorListQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(LIST_USAGE_AUDITS_PREFIX);
builder.push(
r#"
WHERE "usage".created_at >= TO_TIMESTAMP(
"#,
);
builder
.push_bind(query.created_from_unix_secs as f64)
.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP(
"#,
)
.push_bind(query.created_until_unix_secs as f64)
.push(
r#"::double precision)
AND (
lower(BTRIM(COALESCE("usage".status, ''))) IN ('failed', 'error')
OR ("usage".error_category IS NOT NULL AND BTRIM("usage".error_category) <> '')
OR (
BTRIM(COALESCE("usage".status, '')) = ''
AND (
COALESCE("usage".status_code, 0) >= 400
OR ("usage".error_message IS NOT NULL AND BTRIM("usage".error_message) <> '')
)
)
)
ORDER BY "usage".created_at DESC, "usage".id ASC
"#,
);
if let Some(limit) = query.limit {
builder.push(" LIMIT ").push_bind(limit as i64);
}
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(map_usage_row(&row, false)?);
}
Ok(items)
}
async fn summarize_usage_error_distribution_raw(
&self,
query: &UsageErrorDistributionQuery,
) -> Result<Vec<StoredUsageErrorDistributionRow>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
TO_CHAR(
date_trunc('day', "usage".created_at + (
"#,
);
builder.push_bind(query.tz_offset_minutes);
builder.push(
r#"::integer * INTERVAL '1 minute'
)),
'YYYY-MM-DD'
) AS date,
"usage".error_category AS error_category,
COUNT(*)::BIGINT AS count
FROM "usage"
WHERE "usage".created_at >= TO_TIMESTAMP("#,
);
builder.push_bind(query.created_from_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP("#,
);
builder.push_bind(query.created_until_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".error_category IS NOT NULL
AND BTRIM("usage".error_category) <> ''
GROUP BY date, "usage".error_category
ORDER BY date ASC, count DESC, "usage".error_category ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_error_distribution_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_error_distribution_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
) -> Result<Vec<StoredUsageErrorDistributionRow>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let mut rows = sqlx::query(
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
error_category,
COALESCE(SUM(count), 0)::BIGINT AS count
FROM stats_daily_error
WHERE date >= $1
AND date < $2
GROUP BY date, error_category
ORDER BY date ASC, count DESC, error_category ASC
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_error_distribution_row(&row)?);
}
Ok(items)
}
pub async fn summarize_usage_error_distribution(
&self,
query: &UsageErrorDistributionQuery,
) -> Result<Vec<StoredUsageErrorDistributionRow>, DataLayerError> {
if query.tz_offset_minutes != 0 {
return self.summarize_usage_error_distribution_raw(query).await;
}
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self.summarize_usage_error_distribution_raw(query).await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self.summarize_usage_error_distribution_raw(query).await;
};
let mut grouped = BTreeMap::<(String, String), u64>::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_error_distribution_rows(
&mut grouped,
self.summarize_usage_error_distribution_raw(&UsageErrorDistributionQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
})
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_error_distribution_rows(
&mut grouped,
self.summarize_usage_error_distribution_from_daily_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_error_distribution_rows(
&mut grouped,
self.summarize_usage_error_distribution_raw(&UsageErrorDistributionQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
})
.await?,
);
}
Ok(finalize_usage_error_distribution_rows(grouped))
}
async fn summarize_usage_performance_percentiles_raw(
&self,
query: &UsagePerformancePercentilesQuery,
) -> Result<Vec<StoredUsagePerformancePercentilesRow>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
WITH filtered_usage AS (
SELECT
TO_CHAR(
date_trunc('day', "usage".created_at + (
"#,
);
builder.push_bind(query.tz_offset_minutes);
builder.push(
r#"::integer * INTERVAL '1 minute'
)),
'YYYY-MM-DD'
) AS date,
"usage".response_time_ms AS response_time_ms,
"usage".first_byte_time_ms AS first_byte_time_ms
FROM "usage"
WHERE "usage".created_at >= TO_TIMESTAMP("#,
);
builder.push_bind(query.created_from_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP("#,
);
builder.push_bind(query.created_until_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".status = 'completed'
)
SELECT
date,
CASE
WHEN COUNT(response_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY response_time_ms))::BIGINT
ELSE NULL
END AS p50_response_time_ms,
CASE
WHEN COUNT(response_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY response_time_ms))::BIGINT
ELSE NULL
END AS p90_response_time_ms,
CASE
WHEN COUNT(response_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_ms))::BIGINT
ELSE NULL
END AS p99_response_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_byte_time_ms))::BIGINT
ELSE NULL
END AS p50_first_byte_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY first_byte_time_ms))::BIGINT
ELSE NULL
END AS p90_first_byte_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY first_byte_time_ms))::BIGINT
ELSE NULL
END AS p99_first_byte_time_ms
FROM filtered_usage
GROUP BY date
ORDER BY date ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_performance_percentiles_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_performance_percentiles_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
) -> Result<Vec<StoredUsagePerformancePercentilesRow>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let mut rows = sqlx::query(
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS date,
CASE
WHEN p50_response_time_ms IS NOT NULL THEN GREATEST(p50_response_time_ms, 0)::BIGINT
ELSE NULL
END AS p50_response_time_ms,
CASE
WHEN p90_response_time_ms IS NOT NULL THEN GREATEST(p90_response_time_ms, 0)::BIGINT
ELSE NULL
END AS p90_response_time_ms,
CASE
WHEN p99_response_time_ms IS NOT NULL THEN GREATEST(p99_response_time_ms, 0)::BIGINT
ELSE NULL
END AS p99_response_time_ms,
CASE
WHEN p50_first_byte_time_ms IS NOT NULL THEN GREATEST(p50_first_byte_time_ms, 0)::BIGINT
ELSE NULL
END AS p50_first_byte_time_ms,
CASE
WHEN p90_first_byte_time_ms IS NOT NULL THEN GREATEST(p90_first_byte_time_ms, 0)::BIGINT
ELSE NULL
END AS p90_first_byte_time_ms,
CASE
WHEN p99_first_byte_time_ms IS NOT NULL THEN GREATEST(p99_first_byte_time_ms, 0)::BIGINT
ELSE NULL
END AS p99_first_byte_time_ms
FROM stats_daily
WHERE date >= $1
AND date < $2
ORDER BY date ASC
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_performance_percentiles_row(&row)?);
}
Ok(items)
}
pub async fn summarize_usage_performance_percentiles(
&self,
query: &UsagePerformancePercentilesQuery,
) -> Result<Vec<StoredUsagePerformancePercentilesRow>, DataLayerError> {
if query.tz_offset_minutes != 0 {
return self
.summarize_usage_performance_percentiles_raw(query)
.await;
}
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_usage_performance_percentiles_raw(query)
.await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_performance_percentiles_raw(query)
.await;
};
let mut items = Vec::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
items.extend(
self.summarize_usage_performance_percentiles_raw(
&UsagePerformancePercentilesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
},
)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
items.extend(
self.summarize_usage_performance_percentiles_from_daily_aggregates(
aggregate_start,
aggregate_end,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
items.extend(
self.summarize_usage_performance_percentiles_raw(
&UsagePerformancePercentilesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
tz_offset_minutes: 0,
},
)
.await?,
);
}
items.sort_by(|left, right| left.date.cmp(&right.date));
Ok(items)
}
async fn summarize_usage_provider_performance_summary(
&self,
query: &UsageProviderPerformanceQuery,
) -> Result<StoredUsageProviderPerformanceSummary, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
WITH filtered_usage AS (
SELECT
GREATEST(COALESCE("usage".output_tokens, 0), 0) AS output_tokens,
GREATEST(COALESCE("usage".response_time_ms, 0), 0) AS response_time_ms,
GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0) AS first_byte_time_ms,
"usage".response_time_ms IS NOT NULL AS has_response_time,
"usage".first_byte_time_ms IS NOT NULL AS has_first_byte_time,
CASE
WHEN COALESCE("usage".upstream_is_stream, "usage".is_stream, false)
THEN CASE
WHEN "usage".response_time_ms IS NOT NULL
AND "usage".first_byte_time_ms IS NOT NULL
AND GREATEST(COALESCE("usage".response_time_ms, 0), 0) > GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0) - GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
ELSE 0
END
ELSE GREATEST(COALESCE("usage".response_time_ms, 0), 0)
END AS output_tps_duration_ms,
CASE
WHEN lower(COALESCE("usage".status, '')) IN ('completed', 'success', 'ok', 'billed', 'settled')
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
THEN 1
ELSE 0
END AS success_flag
FROM usage_billing_facts AS "usage"
WHERE "usage".created_at >= TO_TIMESTAMP("#,
);
builder.push_bind(query.created_from_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP("#,
);
builder.push_bind(query.created_until_unix_secs as f64);
builder.push(
r#"::double precision)
AND COALESCE("usage".status, '') NOT IN ('pending', 'streaming')
AND NULLIF(BTRIM(COALESCE("usage".provider_id, '')), '') IS NOT NULL
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'pending')
AND lower(BTRIM(COALESCE("usage".provider_name, ''))) NOT IN ('unknown', 'pending')
"#,
);
push_usage_provider_performance_filters(&mut builder, query);
builder.push(
r#"
)
SELECT
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(success_flag), 0)::BIGINT AS success_count,
CASE
WHEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0) > 0
THEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN output_tokens
ELSE 0
END), 0)::DOUBLE PRECISION * 1000.0 / COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0)::DOUBLE PRECISION
ELSE NULL
END AS avg_output_tps,
AVG(first_byte_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_first_byte_time) AS avg_first_byte_time_ms,
AVG(response_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_response_time) AS avg_response_time_ms,
CASE
WHEN COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY response_time_ms)
FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
ELSE NULL
END AS p90_response_time_ms,
CASE
WHEN COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_ms)
FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
ELSE NULL
END AS p99_response_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY first_byte_time_ms)
FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
ELSE NULL
END AS p90_first_byte_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY first_byte_time_ms)
FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
ELSE NULL
END AS p99_first_byte_time_ms,
COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN 1
ELSE 0
END), 0)::BIGINT AS tps_sample_count,
(COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
AS response_time_sample_count,
(COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
AS first_byte_sample_count,
COALESCE(SUM(CASE
WHEN has_response_time AND response_time_ms >= "#,
);
builder.push_bind(query.slow_threshold_ms as i64);
builder.push(
r#"
THEN 1
ELSE 0
END), 0)::BIGINT AS slow_request_count
FROM filtered_usage
"#,
);
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_provider_performance_summary(&row)
}
async fn summarize_usage_provider_performance_providers(
&self,
query: &UsageProviderPerformanceQuery,
) -> Result<Vec<StoredUsageProviderPerformanceProviderRow>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
WITH filtered_usage AS (
SELECT
COALESCE("usage".provider_id, '') AS provider_id,
COALESCE(NULLIF(BTRIM("usage".provider_name), ''), COALESCE("usage".provider_id, '')) AS provider,
GREATEST(COALESCE("usage".output_tokens, 0), 0) AS output_tokens,
GREATEST(COALESCE("usage".response_time_ms, 0), 0) AS response_time_ms,
GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0) AS first_byte_time_ms,
"usage".response_time_ms IS NOT NULL AS has_response_time,
"usage".first_byte_time_ms IS NOT NULL AS has_first_byte_time,
CASE
WHEN COALESCE("usage".upstream_is_stream, "usage".is_stream, false)
THEN CASE
WHEN "usage".response_time_ms IS NOT NULL
AND "usage".first_byte_time_ms IS NOT NULL
AND GREATEST(COALESCE("usage".response_time_ms, 0), 0) > GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0) - GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
ELSE 0
END
ELSE GREATEST(COALESCE("usage".response_time_ms, 0), 0)
END AS output_tps_duration_ms,
CASE
WHEN lower(COALESCE("usage".status, '')) IN ('completed', 'success', 'ok', 'billed', 'settled')
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
THEN 1
ELSE 0
END AS success_flag
FROM usage_billing_facts AS "usage"
WHERE "usage".created_at >= TO_TIMESTAMP("#,
);
builder.push_bind(query.created_from_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP("#,
);
builder.push_bind(query.created_until_unix_secs as f64);
builder.push(
r#"::double precision)
AND COALESCE("usage".status, '') NOT IN ('pending', 'streaming')
AND NULLIF(BTRIM(COALESCE("usage".provider_id, '')), '') IS NOT NULL
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'pending')
AND lower(BTRIM(COALESCE("usage".provider_name, ''))) NOT IN ('unknown', 'pending')
"#,
);
push_usage_provider_performance_filters(&mut builder, query);
builder.push(
r#"
)
SELECT
provider_id,
COALESCE(MAX(NULLIF(provider, '')), provider_id) AS provider,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(success_flag), 0)::BIGINT AS success_count,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
CASE
WHEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0) > 0
THEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN output_tokens
ELSE 0
END), 0)::DOUBLE PRECISION * 1000.0 / COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0)::DOUBLE PRECISION
ELSE NULL
END AS avg_output_tps,
AVG(first_byte_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_first_byte_time) AS avg_first_byte_time_ms,
AVG(response_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_response_time) AS avg_response_time_ms,
CASE
WHEN COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY response_time_ms)
FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
ELSE NULL
END AS p90_response_time_ms,
CASE
WHEN COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_ms)
FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
ELSE NULL
END AS p99_response_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY first_byte_time_ms)
FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
ELSE NULL
END AS p90_first_byte_time_ms,
CASE
WHEN COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time) >= 10
THEN FLOOR(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY first_byte_time_ms)
FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
ELSE NULL
END AS p99_first_byte_time_ms,
COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN 1
ELSE 0
END), 0)::BIGINT AS tps_sample_count,
(COUNT(response_time_ms) FILTER (WHERE success_flag = 1 AND has_response_time))::BIGINT
AS response_time_sample_count,
(COUNT(first_byte_time_ms) FILTER (WHERE success_flag = 1 AND has_first_byte_time))::BIGINT
AS first_byte_sample_count,
COALESCE(SUM(CASE
WHEN has_response_time AND response_time_ms >= "#,
);
builder.push_bind(query.slow_threshold_ms as i64);
builder.push(
r#"
THEN 1
ELSE 0
END), 0)::BIGINT AS slow_request_count
FROM filtered_usage
GROUP BY provider_id
ORDER BY request_count DESC, provider_id ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_provider_performance_provider_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_provider_performance_timeline(
&self,
query: &UsageProviderPerformanceQuery,
provider_ids: &[String],
) -> Result<Vec<StoredUsageProviderPerformanceTimelineRow>, DataLayerError> {
if provider_ids.is_empty() {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<Postgres>::new("WITH filtered_usage AS ( SELECT ");
match query.granularity {
UsageTimeSeriesGranularity::Day => {
builder
.push("TO_CHAR(date_trunc('day', \"usage\".created_at + (")
.push_bind(query.tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD') AS date");
}
UsageTimeSeriesGranularity::Hour => {
builder
.push("TO_CHAR(date_trunc('hour', \"usage\".created_at + (")
.push_bind(query.tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD\"T\"HH24:00:00+00:00') AS date");
}
}
builder.push(
r#",
COALESCE("usage".provider_id, '') AS provider_id,
COALESCE(NULLIF(BTRIM("usage".provider_name), ''), COALESCE("usage".provider_id, '')) AS provider,
GREATEST(COALESCE("usage".output_tokens, 0), 0) AS output_tokens,
GREATEST(COALESCE("usage".response_time_ms, 0), 0) AS response_time_ms,
GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0) AS first_byte_time_ms,
"usage".response_time_ms IS NOT NULL AS has_response_time,
"usage".first_byte_time_ms IS NOT NULL AS has_first_byte_time,
CASE
WHEN COALESCE("usage".upstream_is_stream, "usage".is_stream, false)
THEN CASE
WHEN "usage".response_time_ms IS NOT NULL
AND "usage".first_byte_time_ms IS NOT NULL
AND GREATEST(COALESCE("usage".response_time_ms, 0), 0) > GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
THEN GREATEST(COALESCE("usage".response_time_ms, 0), 0) - GREATEST(COALESCE("usage".first_byte_time_ms, 0), 0)
ELSE 0
END
ELSE GREATEST(COALESCE("usage".response_time_ms, 0), 0)
END AS output_tps_duration_ms,
CASE
WHEN lower(COALESCE("usage".status, '')) IN ('completed', 'success', 'ok', 'billed', 'settled')
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
THEN 1
ELSE 0
END AS success_flag
FROM usage_billing_facts AS "usage"
WHERE "usage".created_at >= TO_TIMESTAMP("#,
);
builder.push_bind(query.created_from_unix_secs as f64);
builder.push(
r#"::double precision)
AND "usage".created_at < TO_TIMESTAMP("#,
);
builder.push_bind(query.created_until_unix_secs as f64);
builder.push(
r#"::double precision)
AND COALESCE("usage".status, '') NOT IN ('pending', 'streaming')
AND NULLIF(BTRIM(COALESCE("usage".provider_id, '')), '') IS NOT NULL
AND lower(BTRIM(COALESCE("usage".provider_id, ''))) NOT IN ('unknown', 'pending')
AND lower(BTRIM(COALESCE("usage".provider_name, ''))) NOT IN ('unknown', 'pending')
"#,
);
push_usage_provider_performance_filters(&mut builder, query);
builder.push(
r#"
AND "usage".provider_id = ANY("#,
);
builder.push_bind(provider_ids.to_vec());
builder.push(
r#")
)
SELECT
date,
provider_id,
COALESCE(MAX(NULLIF(provider, '')), provider_id) AS provider,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(success_flag), 0)::BIGINT AS success_count,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
CASE
WHEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0) > 0
THEN COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN output_tokens
ELSE 0
END), 0)::DOUBLE PRECISION * 1000.0 / COALESCE(SUM(CASE
WHEN success_flag = 1 AND response_time_ms > 0 AND output_tokens > 0
THEN response_time_ms
ELSE 0
END), 0)::DOUBLE PRECISION
ELSE NULL
END AS avg_output_tps,
AVG(first_byte_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_first_byte_time) AS avg_first_byte_time_ms,
AVG(response_time_ms::DOUBLE PRECISION)
FILTER (WHERE success_flag = 1 AND has_response_time) AS avg_response_time_ms,
COALESCE(SUM(CASE
WHEN has_response_time AND response_time_ms >= "#,
);
builder.push_bind(query.slow_threshold_ms as i64);
builder.push(
r#"
THEN 1
ELSE 0
END), 0)::BIGINT AS slow_request_count
FROM filtered_usage
GROUP BY date, provider_id
ORDER BY date ASC, provider_id ASC
"#,
);
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_provider_performance_timeline_row(&row)?);
}
Ok(items)
}
pub async fn summarize_usage_provider_performance(
&self,
query: &UsageProviderPerformanceQuery,
) -> Result<StoredUsageProviderPerformance, DataLayerError> {
if query.created_from_unix_secs >= query.created_until_unix_secs {
return Ok(StoredUsageProviderPerformance::default());
}
let summary = self
.summarize_usage_provider_performance_summary(query)
.await?;
let mut providers = self
.summarize_usage_provider_performance_providers(query)
.await?;
providers.truncate(query.limit.max(1));
let provider_ids = providers
.iter()
.map(|row| row.provider_id.clone())
.collect::<Vec<_>>();
let timeline = self
.summarize_usage_provider_performance_timeline(query, &provider_ids)
.await?;
Ok(StoredUsageProviderPerformance {
summary,
providers,
timeline,
})
}
async fn summarize_usage_cost_savings_raw_from_range(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
user_id: Option<&str>,
provider_name: Option<&str>,
model: Option<&str>,
) -> Result<StoredUsageCostSavingsSummary, DataLayerError> {
if start_utc >= end_utc {
return Ok(StoredUsageCostSavingsSummary::default());
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
COALESCE(SUM(COALESCE(CAST("usage".cache_read_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_read_cost_usd,
COALESCE(SUM(COALESCE(CAST("usage".cache_creation_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS cache_creation_cost_usd,
COALESCE(SUM(
COALESCE(
CAST("usage".input_price_per_1m AS DOUBLE PRECISION),
0
) * GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)::DOUBLE PRECISION / 1000000.0
), 0) AS estimated_full_cost_usd
FROM usage_billing_facts AS "usage"
"#,
);
builder
.push(" WHERE \"usage\".created_at >= ")
.push_bind(start_utc);
builder
.push(" AND \"usage\".created_at < ")
.push_bind(end_utc);
if let Some(user_id) = user_id {
builder.push(" AND ");
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = provider_name {
builder.push(" AND ");
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = model {
builder.push(" AND ");
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
let row = builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
decode_usage_cost_savings_row(&row)
}
async fn summarize_usage_cost_savings_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
provider_name: Option<&str>,
model: Option<&str>,
) -> Result<StoredUsageCostSavingsSummary, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(StoredUsageCostSavingsSummary::default());
}
let row = match (user_id, provider_name, model) {
(None, None, None) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_daily_cost_savings
WHERE date >= $1
AND date < $2
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(None, Some(provider_name), None) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_daily_cost_savings_provider
WHERE provider_name = $1
AND date >= $2
AND date < $3
"#,
)
.bind(provider_name)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(None, None, Some(model)) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_daily_cost_savings_model
WHERE model = $1
AND date >= $2
AND date < $3
"#,
)
.bind(model)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(None, Some(provider_name), Some(model)) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_daily_cost_savings_model_provider
WHERE provider_name = $1
AND model = $2
AND date >= $3
AND date < $4
"#,
)
.bind(provider_name)
.bind(model)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(Some(user_id), None, None) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_user_daily_cost_savings
WHERE user_id = $1
AND date >= $2
AND date < $3
"#,
)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(Some(user_id), Some(provider_name), None) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_user_daily_cost_savings_provider
WHERE user_id = $1
AND provider_name = $2
AND date >= $3
AND date < $4
"#,
)
.bind(user_id)
.bind(provider_name)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(Some(user_id), None, Some(model)) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_user_daily_cost_savings_model
WHERE user_id = $1
AND model = $2
AND date >= $3
AND date < $4
"#,
)
.bind(user_id)
.bind(model)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
(Some(user_id), Some(provider_name), Some(model)) => sqlx::query(
r#"
SELECT
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(cache_read_cost), 0)::DOUBLE PRECISION AS cache_read_cost_usd,
COALESCE(SUM(cache_creation_cost), 0)::DOUBLE PRECISION AS cache_creation_cost_usd,
COALESCE(SUM(estimated_full_cost), 0)::DOUBLE PRECISION AS estimated_full_cost_usd
FROM stats_user_daily_cost_savings_model_provider
WHERE user_id = $1
AND provider_name = $2
AND model = $3
AND date >= $4
AND date < $5
"#,
)
.bind(user_id)
.bind(provider_name)
.bind(model)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_one(&self.pool)
.await
.map_postgres_err()?,
};
decode_usage_cost_savings_row(&row)
}
pub async fn summarize_usage_cost_savings(
&self,
query: &UsageCostSavingsSummaryQuery,
) -> Result<StoredUsageCostSavingsSummary, DataLayerError> {
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let user_id = query.user_id.as_deref();
let provider_name = query.provider_name.as_deref();
let model = query.model.as_deref();
if start_utc >= end_utc {
return Ok(StoredUsageCostSavingsSummary::default());
}
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_usage_cost_savings_raw_from_range(
start_utc,
end_utc,
user_id,
provider_name,
model,
)
.await;
};
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_cost_savings_raw_from_range(
start_utc,
end_utc,
user_id,
provider_name,
model,
)
.await;
};
let mut summary = StoredUsageCostSavingsSummary::default();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_cost_savings_summary(
&mut summary,
self.summarize_usage_cost_savings_raw_from_range(
raw_start,
raw_end,
user_id,
provider_name,
model,
)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
absorb_usage_cost_savings_summary(
&mut summary,
self.summarize_usage_cost_savings_from_daily_aggregates(
aggregate_start,
aggregate_end,
user_id,
provider_name,
model,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_cost_savings_summary(
&mut summary,
self.summarize_usage_cost_savings_raw_from_range(
raw_start,
raw_end,
user_id,
provider_name,
model,
)
.await?,
);
}
Ok(summary)
}
async fn summarize_usage_time_series_raw(
&self,
query: &UsageTimeSeriesQuery,
excluded_only: bool,
) -> Result<Vec<StoredUsageTimeSeriesBucket>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new("SELECT ");
match query.granularity {
UsageTimeSeriesGranularity::Day => {
builder
.push("TO_CHAR(date_trunc('day', \"usage\".created_at + (")
.push_bind(query.tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD') AS bucket_key");
}
UsageTimeSeriesGranularity::Hour => {
builder
.push("TO_CHAR(date_trunc('hour', \"usage\".created_at + (")
.push_bind(query.tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD\"T\"HH24:00:00+00:00') AS bucket_key");
}
}
builder.push(
r#",
COUNT(*)::BIGINT AS total_requests,
COALESCE(SUM(GREATEST(COALESCE("usage".input_tokens, 0), 0)), 0)::BIGINT AS input_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".output_tokens, 0), 0)), 0)::BIGINT AS output_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_creation_input_tokens, 0), 0)), 0)::BIGINT
AS cache_creation_tokens,
COALESCE(SUM(GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)), 0)::BIGINT
AS cache_read_tokens,
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd,
COALESCE(SUM(GREATEST(COALESCE("usage".response_time_ms, 0), 0)::DOUBLE PRECISION), 0)
AS total_response_time_ms
FROM usage_billing_facts AS "usage"
"#,
);
let mut has_where = false;
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".created_at >= TO_TIMESTAMP(")
.push_bind(query.created_from_unix_secs as f64)
.push("::double precision)");
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".created_at < TO_TIMESTAMP(")
.push_bind(query.created_until_unix_secs as f64)
.push("::double precision)");
if let Some(user_id) = query.user_id.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".user_id = ")
.push_bind(user_id.to_string());
}
if let Some(provider_name) = query.provider_name.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
has_where = true;
builder
.push("\"usage\".provider_name = ")
.push_bind(provider_name.to_string());
}
if let Some(model) = query.model.as_deref() {
builder.push(if has_where { " AND " } else { " WHERE " });
builder
.push("\"usage\".model = ")
.push_bind(model.to_string());
}
if excluded_only {
builder.push(if has_where { " AND " } else { " WHERE " });
builder.push(
r#"(
"usage".status IS NULL
OR "usage".status IN ('pending', 'streaming')
OR "usage".provider_name IS NULL
OR "usage".provider_name IN ('unknown', 'pending')
)"#,
);
}
builder.push(" GROUP BY bucket_key ORDER BY bucket_key ASC");
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_time_series_bucket_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_time_series_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageTimeSeriesBucket>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let rows = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS bucket_key,
total_requests::BIGINT AS total_requests,
input_tokens::BIGINT AS input_tokens,
output_tokens::BIGINT AS output_tokens,
cache_creation_tokens::BIGINT AS cache_creation_tokens,
cache_read_tokens::BIGINT AS cache_read_tokens,
CAST(total_cost AS DOUBLE PRECISION) AS total_cost_usd,
CAST(response_time_sum_ms AS DOUBLE PRECISION) AS total_response_time_ms
FROM stats_user_daily
WHERE user_id = $1
AND date >= $2
AND date < $3
ORDER BY date ASC
"#,
)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_all(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
SELECT
TO_CHAR(date, 'YYYY-MM-DD') AS bucket_key,
total_requests::BIGINT AS total_requests,
input_tokens::BIGINT AS input_tokens,
output_tokens::BIGINT AS output_tokens,
cache_creation_tokens::BIGINT AS cache_creation_tokens,
cache_read_tokens::BIGINT AS cache_read_tokens,
CAST(total_cost AS DOUBLE PRECISION) AS total_cost_usd,
CAST(response_time_sum_ms AS DOUBLE PRECISION) AS total_response_time_ms
FROM stats_daily
WHERE date >= $1
AND date < $2
ORDER BY date ASC
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch_all(&self.pool)
.await
.map_postgres_err()?
};
let mut items = Vec::new();
for row in rows {
items.push(decode_usage_time_series_bucket_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_time_series_from_hourly_aggregates(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
granularity: UsageTimeSeriesGranularity,
tz_offset_minutes: i32,
) -> Result<Vec<StoredUsageTimeSeriesBucket>, DataLayerError> {
if start_utc >= end_utc {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<Postgres>::new("SELECT ");
match granularity {
UsageTimeSeriesGranularity::Day => {
builder
.push("TO_CHAR(date_trunc('day', hour_utc + (")
.push_bind(tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD') AS bucket_key");
}
UsageTimeSeriesGranularity::Hour => {
builder
.push("TO_CHAR(date_trunc('hour', hour_utc + (")
.push_bind(tz_offset_minutes)
.push("::integer * INTERVAL '1 minute')), 'YYYY-MM-DD\"T\"HH24:00:00+00:00') AS bucket_key");
}
}
builder.push(
r#",
COALESCE(SUM(total_requests), 0)::BIGINT AS total_requests,
COALESCE(SUM(input_tokens), 0)::BIGINT AS input_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(CAST(total_cost AS DOUBLE PRECISION)), 0) AS total_cost_usd,
COALESCE(SUM(CAST(response_time_sum_ms AS DOUBLE PRECISION)), 0) AS total_response_time_ms
FROM stats_hourly
WHERE is_complete IS TRUE
AND hour_utc >= "#,
);
builder.push_bind(start_utc).push(
r#"
AND hour_utc < "#,
);
builder
.push_bind(end_utc)
.push("\nGROUP BY bucket_key ORDER BY bucket_key ASC");
let mut rows = builder.build().fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_time_series_bucket_row(&row)?);
}
Ok(items)
}
pub async fn summarize_usage_time_series(
&self,
query: &UsageTimeSeriesQuery,
) -> Result<Vec<StoredUsageTimeSeriesBucket>, DataLayerError> {
if query.provider_name.is_some() || query.model.is_some() {
return self.summarize_usage_time_series_raw(query, false).await;
}
if query.granularity == UsageTimeSeriesGranularity::Day && query.tz_offset_minutes == 0 {
if let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? {
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let mut grouped = BTreeMap::<String, StoredUsageTimeSeriesBucket>::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
granularity: UsageTimeSeriesGranularity::Day,
tz_offset_minutes: 0,
user_id: query.user_id.clone(),
provider_name: None,
model: None,
},
false,
)
.await?,
);
}
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.user_id.as_deref(),
)
.await?,
);
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(aggregate_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(aggregate_end),
granularity: UsageTimeSeriesGranularity::Day,
tz_offset_minutes: 0,
user_id: query.user_id.clone(),
provider_name: None,
model: None,
},
true,
)
.await?,
);
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
granularity: UsageTimeSeriesGranularity::Day,
tz_offset_minutes: 0,
user_id: query.user_id.clone(),
provider_name: None,
model: None,
},
false,
)
.await?,
);
}
return Ok(finalize_usage_time_series_buckets(grouped));
}
}
}
if query.user_id.is_none() && query.tz_offset_minutes % 60 == 0 {
if let Some(cutoff_utc) = self.read_stats_hourly_cutoff().await? {
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_hourly_aggregate_range(start_utc, end_utc, cutoff_utc);
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let mut grouped = BTreeMap::<String, StoredUsageTimeSeriesBucket>::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
granularity: query.granularity,
tz_offset_minutes: query.tz_offset_minutes,
user_id: None,
provider_name: None,
model: None,
},
false,
)
.await?,
);
}
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_from_hourly_aggregates(
aggregate_start,
aggregate_end,
query.granularity,
query.tz_offset_minutes,
)
.await?,
);
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(aggregate_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(aggregate_end),
granularity: query.granularity,
tz_offset_minutes: query.tz_offset_minutes,
user_id: None,
provider_name: None,
model: None,
},
true,
)
.await?,
);
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_time_series_buckets(
&mut grouped,
self.summarize_usage_time_series_raw(
&UsageTimeSeriesQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
granularity: query.granularity,
tz_offset_minutes: query.tz_offset_minutes,
user_id: None,
provider_name: None,
model: None,
},
false,
)
.await?,
);
}
return Ok(finalize_usage_time_series_buckets(grouped));
}
}
}
self.summarize_usage_time_series_raw(query, false).await
}
async fn summarize_usage_leaderboard_raw(
&self,
query: &UsageLeaderboardQuery,
) -> Result<Vec<StoredUsageLeaderboardSummary>, DataLayerError> {
let fragments = usage_leaderboard_sql_fragments(query.group_by);
let sql = format!(
r#"
SELECT
{group_key_expr} AS group_key,
MAX({legacy_name_expr}) AS legacy_name,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(
CASE
WHEN GREATEST(COALESCE("usage".input_tokens, 0), 0) <= 0 THEN 0
WHEN GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) <= 0
THEN GREATEST(COALESCE("usage".input_tokens, 0), 0)
WHEN split_part(lower(COALESCE(COALESCE("usage".endpoint_api_format, "usage".api_format), '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(
GREATEST(COALESCE("usage".input_tokens, 0), 0)
- GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0),
0
)
ELSE GREATEST(COALESCE("usage".input_tokens, 0), 0)
END
+ GREATEST(COALESCE("usage".output_tokens, 0), 0)
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0)
), 0)::BIGINT AS total_tokens,
COALESCE(SUM(COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS total_cost_usd
FROM usage_billing_facts AS "usage"
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)
AND "usage".created_at < TO_TIMESTAMP($2::double precision)
AND "usage".status NOT IN ('pending', 'streaming')
AND "usage".provider_name NOT IN ('unknown', 'pending')
{filtered_extra_where}
AND ($3::varchar IS NULL OR "usage".user_id = $3)
AND ($4::varchar IS NULL OR "usage".provider_name = $4)
AND ($5::varchar IS NULL OR "usage".model = $5)
GROUP BY group_key
ORDER BY group_key ASC
"#,
group_key_expr = fragments.group_key_expr,
legacy_name_expr = fragments.legacy_name_expr,
filtered_extra_where = fragments.filtered_extra_where,
);
let mut rows = sqlx::query(&sql)
.bind(query.created_from_unix_secs as f64)
.bind(query.created_until_unix_secs as f64)
.bind(query.user_id.as_deref())
.bind(query.provider_name.as_deref())
.bind(query.model.as_deref())
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_leaderboard_row(&row)?);
}
Ok(items)
}
async fn summarize_usage_leaderboard_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
query: &UsageLeaderboardQuery,
) -> Result<Option<Vec<StoredUsageLeaderboardSummary>>, DataLayerError> {
let items = match query.group_by {
UsageLeaderboardGroupBy::Model => {
let mut builder = if let Some(user_id) = query.user_id.as_deref() {
if let Some(provider_name) = query.provider_name.as_deref() {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
model AS group_key,
NULL::varchar AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_user_daily_model_provider
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND user_id = ")
.push_bind(user_id.to_string())
.push(" AND provider_name = ")
.push_bind(provider_name.to_string());
if let Some(model) = query.model.as_deref() {
builder.push(" AND model = ").push_bind(model.to_string());
}
builder.push(" GROUP BY model ORDER BY model ASC");
builder
} else {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
model AS group_key,
NULL::varchar AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_user_daily_model
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND user_id = ")
.push_bind(user_id.to_string());
if let Some(model) = query.model.as_deref() {
builder.push(" AND model = ").push_bind(model.to_string());
}
builder.push(" GROUP BY model ORDER BY model ASC");
builder
}
} else if let Some(provider_name) = query.provider_name.as_deref() {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
model AS group_key,
NULL::varchar AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_daily_model_provider
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND provider_name = ")
.push_bind(provider_name.to_string());
if let Some(model) = query.model.as_deref() {
builder.push(" AND model = ").push_bind(model.to_string());
}
builder.push(" GROUP BY model ORDER BY model ASC");
builder
} else {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
model AS group_key,
NULL::varchar AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_daily_model
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc);
if let Some(model) = query.model.as_deref() {
builder.push(" AND model = ").push_bind(model.to_string());
}
builder.push(" GROUP BY model ORDER BY model ASC");
builder
};
fetch_usage_leaderboard_query(builder.build(), &self.pool).await?
}
UsageLeaderboardGroupBy::User => {
if query.provider_name.is_some() && query.model.is_some() {
return Ok(None);
}
let mut builder = if let Some(provider_name) = query.provider_name.as_deref() {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
user_id AS group_key,
MAX(NULLIF(BTRIM(username), '')) AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(effective_input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_user_daily_provider
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND provider_name = ")
.push_bind(provider_name.to_string());
if let Some(user_id) = query.user_id.as_deref() {
builder
.push(" AND user_id = ")
.push_bind(user_id.to_string());
}
builder.push(" GROUP BY user_id ORDER BY user_id ASC");
builder
} else if let Some(model) = query.model.as_deref() {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
user_id AS group_key,
MAX(NULLIF(BTRIM(username), '')) AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(effective_input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_user_daily_model
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND model = ")
.push_bind(model.to_string());
if let Some(user_id) = query.user_id.as_deref() {
builder
.push(" AND user_id = ")
.push_bind(user_id.to_string());
}
builder.push(" GROUP BY user_id ORDER BY user_id ASC");
builder
} else {
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
user_id AS group_key,
MAX(NULLIF(BTRIM(username), '')) AS legacy_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(effective_input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_user_daily
WHERE date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND date < ")
.push_bind(end_day_utc)
.push(" AND user_id IS NOT NULL");
if let Some(user_id) = query.user_id.as_deref() {
builder
.push(" AND user_id = ")
.push_bind(user_id.to_string());
}
builder.push(" GROUP BY user_id ORDER BY user_id ASC");
builder
};
fetch_usage_leaderboard_query(builder.build(), &self.pool).await?
}
UsageLeaderboardGroupBy::ApiKey => {
if query.provider_name.is_some() || query.model.is_some() {
return Ok(None);
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
stats_daily_api_key.api_key_id AS group_key,
COALESCE(
MAX(NULLIF(BTRIM(stats_daily_api_key.api_key_name), '')),
MAX(NULLIF(BTRIM(api_keys.name), ''))
) AS legacy_name,
COALESCE(SUM(stats_daily_api_key.total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(
stats_daily_api_key.input_tokens
+ stats_daily_api_key.output_tokens
+ stats_daily_api_key.cache_creation_tokens
+ stats_daily_api_key.cache_read_tokens
),
0
)::BIGINT AS total_tokens,
CAST(COALESCE(SUM(stats_daily_api_key.total_cost), 0) AS DOUBLE PRECISION) AS total_cost_usd
FROM stats_daily_api_key
LEFT JOIN api_keys ON api_keys.id = stats_daily_api_key.api_key_id
WHERE stats_daily_api_key.date >=
"#,
);
builder
.push_bind(start_day_utc)
.push(" AND stats_daily_api_key.date < ")
.push_bind(end_day_utc)
.push(" AND stats_daily_api_key.api_key_id IS NOT NULL");
if let Some(user_id) = query.user_id.as_deref() {
builder
.push(" AND api_keys.user_id = ")
.push_bind(user_id.to_string());
}
builder.push(
" GROUP BY stats_daily_api_key.api_key_id ORDER BY stats_daily_api_key.api_key_id ASC",
);
fetch_usage_leaderboard_query(builder.build(), &self.pool).await?
}
};
Ok(Some(items))
}
pub async fn summarize_usage_leaderboard(
&self,
query: &UsageLeaderboardQuery,
) -> Result<Vec<StoredUsageLeaderboardSummary>, DataLayerError> {
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self.summarize_usage_leaderboard_raw(query).await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let mut grouped = BTreeMap::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
absorb_usage_leaderboard_rows(
&mut grouped,
self.summarize_usage_leaderboard_raw(&UsageLeaderboardQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
..query.clone()
})
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
if let Some(rows) = self
.summarize_usage_leaderboard_from_daily_aggregates(
aggregate_start,
aggregate_end,
query,
)
.await?
{
absorb_usage_leaderboard_rows(&mut grouped, rows);
} else {
absorb_usage_leaderboard_rows(
&mut grouped,
self.summarize_usage_leaderboard_raw(&UsageLeaderboardQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(aggregate_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(aggregate_end),
..query.clone()
})
.await?,
);
}
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
absorb_usage_leaderboard_rows(
&mut grouped,
self.summarize_usage_leaderboard_raw(&UsageLeaderboardQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
..query.clone()
})
.await?,
);
}
Ok(finalize_usage_leaderboard_rows(grouped))
}
async fn aggregate_usage_audits_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
group_by: UsageAuditAggregationGroupBy,
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let (
table_name,
group_column,
display_name_expr,
avg_response_time_expr,
success_count_expr,
join_clause,
) = match group_by {
UsageAuditAggregationGroupBy::Model => (
"stats_user_daily_model",
"model",
"NULL::varchar",
"NULL::DOUBLE PRECISION",
"NULL::BIGINT",
"",
),
UsageAuditAggregationGroupBy::Provider => (
"stats_user_daily_provider",
"provider_name",
"MAX(provider_name)",
"CASE WHEN COALESCE(SUM(response_time_samples), 0) > 0 THEN COALESCE(SUM(response_time_sum_ms), 0) / COALESCE(SUM(response_time_samples), 0) ELSE NULL END",
"COALESCE(SUM(success_requests), 0)::BIGINT",
"",
),
UsageAuditAggregationGroupBy::ApiFormat => (
"stats_user_daily_api_format",
"api_format",
"NULL::varchar",
"CASE WHEN COALESCE(SUM(response_time_samples), 0) > 0 THEN COALESCE(SUM(response_time_sum_ms), 0) / COALESCE(SUM(response_time_samples), 0) ELSE NULL END",
"NULL::BIGINT",
"",
),
UsageAuditAggregationGroupBy::User => {
return Ok(Vec::new());
}
};
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!(
r#"
SELECT
{group_column} AS group_key,
{display_name_expr} AS display_name,
NULL::varchar AS secondary_name,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(effective_input_tokens), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost), 0)::DOUBLE PRECISION AS total_cost_usd,
COALESCE(SUM(actual_total_cost), 0)::DOUBLE PRECISION AS actual_total_cost_usd,
{avg_response_time_expr} AS avg_response_time_ms,
{success_count_expr} AS success_count
FROM {table_name}
{join_clause}
WHERE date >= $1
AND date < $2
{provider_extra_where}
GROUP BY {group_column}
ORDER BY request_count DESC, group_key ASC
"#,
group_column = group_column,
display_name_expr = display_name_expr,
avg_response_time_expr = avg_response_time_expr,
success_count_expr = success_count_expr,
table_name = table_name,
join_clause = join_clause,
provider_extra_where = provider_extra_where,
);
let mut rows = sqlx::query(&sql)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_audit_aggregation_row(&row)?);
}
Ok(items)
}
async fn aggregate_usage_audits_raw(
&self,
query: &UsageAuditAggregationQuery,
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
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_PROVIDER_IDENTITY_FILTER_SQL
} else {
""
};
let sql = format!(
r#"
WITH filtered_usage AS (
SELECT
"usage".model AS model,
"usage".user_id AS user_id,
{provider_group_key_expr} AS provider_group_key,
{provider_display_name_expr} AS provider_display_name,
{provider_identity_source_expr} AS provider_identity_source,
COALESCE("usage".api_format, 'unknown') AS api_format_group_key,
GREATEST(COALESCE("usage".input_tokens, 0), 0) AS input_tokens,
GREATEST(COALESCE("usage".output_tokens, 0), 0) AS output_tokens,
GREATEST(COALESCE("usage".total_tokens, 0), 0) AS total_tokens,
CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (
COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0)
+ COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END AS cache_creation_tokens,
GREATEST(COALESCE("usage".cache_creation_input_tokens_5m, 0), 0)
AS cache_creation_ephemeral_5m_tokens,
GREATEST(COALESCE("usage".cache_creation_input_tokens_1h, 0), 0)
AS cache_creation_ephemeral_1h_tokens,
GREATEST(COALESCE("usage".cache_read_input_tokens, 0), 0) AS cache_read_tokens,
COALESCE("usage".endpoint_api_format, "usage".api_format) AS normalized_api_format,
COALESCE(CAST("usage".total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
COALESCE(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION), 0) AS actual_total_cost_usd,
GREATEST(COALESCE("usage".response_time_ms, 0), 0) AS response_time_ms,
CASE
WHEN "usage".status IN ('completed', 'success', 'ok', 'billed', 'settled')
AND ("usage".status_code IS NULL OR "usage".status_code < 400)
THEN 1
ELSE 0
END AS success_flag
FROM usage_billing_facts AS "usage"
{provider_identity_join}
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)
AND "usage".created_at < TO_TIMESTAMP($2::double precision)
AND "usage".status NOT IN ('pending', 'streaming')
{provider_extra_where}
{filtered_extra_where}
),
normalized_usage AS (
SELECT
{group_key_expr} AS group_key,
{display_name_expr} AS display_name,
{secondary_name_expr} AS secondary_name,
total_tokens,
output_tokens,
cache_creation_tokens,
cache_creation_ephemeral_5m_tokens,
cache_creation_ephemeral_1h_tokens,
cache_read_tokens,
total_cost_usd,
actual_total_cost_usd,
response_time_ms,
success_flag,
CASE
WHEN input_tokens <= 0 THEN 0
WHEN cache_read_tokens <= 0 THEN input_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(input_tokens - cache_read_tokens, 0)
ELSE input_tokens
END AS effective_input_tokens,
CASE
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('claude', 'anthropic')
THEN input_tokens + cache_creation_tokens + cache_read_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN (
CASE
WHEN input_tokens <= 0 THEN 0
WHEN cache_read_tokens <= 0 THEN input_tokens
WHEN split_part(lower(COALESCE(normalized_api_format, '')), ':', 1)
IN ('openai', 'gemini', 'google')
THEN GREATEST(input_tokens - cache_read_tokens, 0)
ELSE input_tokens
END
) + cache_read_tokens
ELSE CASE
WHEN cache_creation_tokens > 0
THEN input_tokens + cache_creation_tokens + cache_read_tokens
ELSE input_tokens + cache_read_tokens
END
END AS total_input_context
FROM filtered_usage
),
aggregated_usage AS (
SELECT
group_key,
{aggregate_display_name_expr} AS display_name,
{aggregate_secondary_name_expr} AS secondary_name,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(total_tokens), 0)::BIGINT AS total_tokens,
COALESCE(SUM(output_tokens), 0)::BIGINT AS output_tokens,
COALESCE(SUM(effective_input_tokens), 0)::BIGINT AS effective_input_tokens,
COALESCE(SUM(total_input_context), 0)::BIGINT AS total_input_context,
COALESCE(SUM(cache_creation_tokens), 0)::BIGINT AS cache_creation_tokens,
COALESCE(SUM(cache_creation_ephemeral_5m_tokens), 0)::BIGINT
AS cache_creation_ephemeral_5m_tokens,
COALESCE(SUM(cache_creation_ephemeral_1h_tokens), 0)::BIGINT
AS cache_creation_ephemeral_1h_tokens,
COALESCE(SUM(cache_read_tokens), 0)::BIGINT AS cache_read_tokens,
COALESCE(SUM(total_cost_usd), 0) AS total_cost_usd,
COALESCE(SUM(actual_total_cost_usd), 0) AS actual_total_cost_usd,
{avg_response_time_expr} AS avg_response_time_ms,
{success_count_expr} AS success_count
FROM normalized_usage
GROUP BY group_key
)
SELECT
group_key,
display_name,
secondary_name,
request_count,
total_tokens,
output_tokens,
effective_input_tokens,
total_input_context,
cache_creation_tokens,
cache_creation_ephemeral_5m_tokens,
cache_creation_ephemeral_1h_tokens,
cache_read_tokens,
total_cost_usd,
actual_total_cost_usd,
avg_response_time_ms,
success_count
FROM aggregated_usage
ORDER BY request_count DESC, group_key ASC
LIMIT $3
"#,
provider_extra_where = provider_extra_where,
filtered_extra_where = fragments.filtered_extra_where,
provider_identity_join = fragments.provider_identity_join,
provider_group_key_expr = fragments.provider_group_key_expr,
provider_display_name_expr = fragments.provider_display_name_expr,
provider_identity_source_expr = USAGE_PROVIDER_IDENTITY_SOURCE_SQL,
group_key_expr = fragments.group_key_expr,
display_name_expr = fragments.display_name_expr,
secondary_name_expr = fragments.secondary_name_expr,
aggregate_display_name_expr = fragments.aggregate_display_name_expr,
aggregate_secondary_name_expr = fragments.aggregate_secondary_name_expr,
avg_response_time_expr = fragments.avg_response_time_expr,
success_count_expr = fragments.success_count_expr,
);
let mut rows = sqlx::query(&sql)
.bind(query.created_from_unix_secs as f64)
.bind(query.created_until_unix_secs as f64)
.bind(i64::try_from(query.limit).map_err(|_| {
DataLayerError::InvalidInput(format!(
"invalid usage aggregation limit: {}",
query.limit
))
})?)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(decode_usage_audit_aggregation_row(&row)?);
}
Ok(items)
}
pub async fn aggregate_usage_audits(
&self,
query: &UsageAuditAggregationQuery,
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
if matches!(query.group_by, UsageAuditAggregationGroupBy::User) {
return self.aggregate_usage_audits_raw(query).await;
}
if matches!(query.group_by, UsageAuditAggregationGroupBy::Provider) {
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 {
return self.aggregate_usage_audits_raw(query).await;
};
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = dashboard_unix_secs_to_utc(query.created_until_unix_secs);
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self.aggregate_usage_audits_raw(query).await;
};
let mut grouped = BTreeMap::<String, StoredUsageAuditAggregation>::new();
let raw_merge_limit = query.limit.max(10_000);
if let Some((raw_start, raw_end)) = split.raw_leading {
let raw = self
.aggregate_usage_audits_raw(&UsageAuditAggregationQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
group_by: query.group_by,
limit: raw_merge_limit,
exclude_reserved_provider_labels: query.exclude_reserved_provider_labels,
})
.await?;
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
let aggregate = self
.aggregate_usage_audits_from_daily_aggregates(
aggregate_start,
aggregate_end,
query.group_by,
)
.await?;
absorb_usage_audit_aggregation_rows(&mut grouped, aggregate);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
let raw = self
.aggregate_usage_audits_raw(&UsageAuditAggregationQuery {
created_from_unix_secs: dashboard_utc_to_unix_secs(raw_start),
created_until_unix_secs: dashboard_utc_to_unix_secs(raw_end),
group_by: query.group_by,
limit: raw_merge_limit,
exclude_reserved_provider_labels: query.exclude_reserved_provider_labels,
})
.await?;
absorb_usage_audit_aggregation_rows(&mut grouped, raw);
}
Ok(finalize_usage_audit_aggregation_rows(grouped, query.limit))
}
async fn summarize_usage_daily_heatmap_raw_from_range(
&self,
start_utc: DateTime<Utc>,
end_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
if start_utc >= end_utc {
return Ok(Vec::new());
}
let mut sql = String::from(
r#"SELECT
DATE("usage".created_at) AS day,
COUNT(*)::BIGINT AS requests,
COALESCE(SUM("usage".input_tokens + "usage".output_tokens
+ CASE
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
AND (COALESCE("usage".cache_creation_input_tokens_5m, 0) + COALESCE("usage".cache_creation_input_tokens_1h, 0)) > 0
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0) + COALESCE("usage".cache_creation_input_tokens_1h, 0)
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
END
+ COALESCE("usage".cache_read_input_tokens, 0)), 0)::BIGINT AS total_tokens,
COALESCE(SUM(CAST("usage".total_cost_usd AS DOUBLE PRECISION)), 0) AS total_cost_usd,
COALESCE(SUM(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION)), 0) AS actual_total_cost_usd
FROM usage_billing_facts AS "usage"
WHERE "usage".created_at >= $1
AND "usage".created_at < $2
AND "usage".status NOT IN ('pending', 'streaming')
AND "usage".provider_name NOT IN ('unknown', 'pending')"#,
);
let bind_index = 3;
if user_id.is_some() {
sql.push_str(&format!(" AND \"usage\".user_id = ${bind_index}"));
}
sql.push_str(" GROUP BY day ORDER BY day ASC");
let mut q = sqlx::query(&sql).bind(start_utc).bind(end_utc);
if let Some(user_id) = user_id {
q = q.bind(user_id.to_string());
}
let mut rows = q.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let day: chrono::NaiveDate = row.try_get("day").map_postgres_err()?;
let requests: i64 = row.try_get("requests").map_postgres_err()?;
let total_tokens: i64 = row.try_get("total_tokens").map_postgres_err()?;
let total_cost_usd: f64 = row.try_get("total_cost_usd").map_postgres_err()?;
let actual_total_cost_usd: f64 =
row.try_get("actual_total_cost_usd").map_postgres_err()?;
items.push(StoredUsageDailySummary {
date: day.to_string(),
requests: requests.max(0) as u64,
total_tokens: total_tokens.max(0) as u64,
total_cost_usd,
actual_total_cost_usd,
});
}
Ok(items)
}
async fn summarize_usage_daily_heatmap_from_daily_aggregates(
&self,
start_day_utc: DateTime<Utc>,
end_day_utc: DateTime<Utc>,
user_id: Option<&str>,
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
if start_day_utc >= end_day_utc {
return Ok(Vec::new());
}
let mut rows = if let Some(user_id) = user_id {
sqlx::query(
r#"
SELECT
date,
total_requests::BIGINT AS total_requests,
input_tokens::BIGINT AS input_tokens,
output_tokens::BIGINT AS output_tokens,
cache_creation_tokens::BIGINT AS cache_creation_tokens,
cache_read_tokens::BIGINT AS cache_read_tokens,
total_cost::DOUBLE PRECISION AS total_cost,
COALESCE(actual_total_cost, 0)::DOUBLE PRECISION AS actual_total_cost
FROM stats_user_daily
WHERE user_id = $1
AND date >= $2
AND date < $3
ORDER BY date ASC
"#,
)
.bind(user_id)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
} else {
sqlx::query(
r#"
SELECT
date,
total_requests::BIGINT AS total_requests,
input_tokens::BIGINT AS input_tokens,
output_tokens::BIGINT AS output_tokens,
cache_creation_tokens::BIGINT AS cache_creation_tokens,
cache_read_tokens::BIGINT AS cache_read_tokens,
total_cost::DOUBLE PRECISION AS total_cost,
actual_total_cost::DOUBLE PRECISION AS actual_total_cost
FROM stats_daily
WHERE date >= $1
AND date < $2
ORDER BY date ASC
"#,
)
.bind(start_day_utc)
.bind(end_day_utc)
.fetch(&self.pool)
};
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let date = row.try_get::<DateTime<Utc>, _>("date").map_postgres_err()?;
let requests = row.try_get::<i64, _>("total_requests").map_postgres_err()?;
let input_tokens = row.try_get::<i64, _>("input_tokens").map_postgres_err()?;
let output_tokens = row.try_get::<i64, _>("output_tokens").map_postgres_err()?;
let cache_creation_tokens = row
.try_get::<i64, _>("cache_creation_tokens")
.map_postgres_err()?;
let cache_read_tokens = row
.try_get::<i64, _>("cache_read_tokens")
.map_postgres_err()?;
let total_cost_usd = row.try_get::<f64, _>("total_cost").map_postgres_err()?;
let actual_total_cost_usd = row
.try_get::<f64, _>("actual_total_cost")
.map_postgres_err()?;
items.push(StoredUsageDailySummary {
date: date.date_naive().to_string(),
requests: requests.max(0) as u64,
total_tokens: input_tokens
.saturating_add(output_tokens)
.saturating_add(cache_creation_tokens)
.saturating_add(cache_read_tokens)
.max(0) as u64,
total_cost_usd,
actual_total_cost_usd,
});
}
Ok(items)
}
pub async fn summarize_usage_daily_heatmap(
&self,
query: &UsageDailyHeatmapQuery,
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
let end_utc = Utc::now() + chrono::Duration::seconds(1);
let user_id = query.user_id.as_deref();
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
return self
.summarize_usage_daily_heatmap_raw_from_range(start_utc, end_utc, user_id)
.await;
};
let split = split_dashboard_daily_aggregate_range(start_utc, end_utc, cutoff_utc);
let Some(_) = split.aggregate else {
return self
.summarize_usage_daily_heatmap_raw_from_range(start_utc, end_utc, user_id)
.await;
};
let mut items = Vec::new();
if let Some((raw_start, raw_end)) = split.raw_leading {
items.extend(
self.summarize_usage_daily_heatmap_raw_from_range(raw_start, raw_end, user_id)
.await?,
);
}
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
items.extend(
self.summarize_usage_daily_heatmap_from_daily_aggregates(
aggregate_start,
aggregate_end,
user_id,
)
.await?,
);
}
if let Some((raw_start, raw_end)) = split.raw_trailing {
items.extend(
self.summarize_usage_daily_heatmap_raw_from_range(raw_start, raw_end, user_id)
.await?,
);
}
items.sort_by(|left, right| left.date.cmp(&right.date));
Ok(items)
}
pub async fn list_recent_usage_audits(
&self,
user_id: Option<&str>,
limit: usize,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
let mut builder = QueryBuilder::<Postgres>::new(LIST_RECENT_USAGE_AUDITS_PREFIX);
if let Some(user_id) = user_id {
builder
.push(" WHERE \"usage\".user_id = ")
.push_bind(user_id.to_string());
}
builder
.push(" ORDER BY \"usage\".created_at DESC, \"usage\".id ASC LIMIT ")
.push_bind(i64::try_from(limit).map_err(|_| {
DataLayerError::InvalidInput(format!("invalid recent usage limit: {limit}"))
})?);
let query = builder.build();
let mut rows = query.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(map_usage_row(&row, false)?);
}
Ok(items)
}
pub async fn summarize_total_tokens_by_api_key_ids(
&self,
api_key_ids: &[String],
) -> Result<std::collections::BTreeMap<String, u64>, DataLayerError> {
if api_key_ids.is_empty() {
return Ok(std::collections::BTreeMap::new());
}
let mut totals = std::collections::BTreeMap::new();
if let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? {
let mut aggregate_rows = sqlx::query(
r#"
SELECT
api_key_id,
COALESCE(
SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens
FROM stats_daily_api_key
WHERE api_key_id = ANY($1::TEXT[])
AND date < $2
GROUP BY api_key_id
ORDER BY api_key_id ASC
"#,
)
.bind(api_key_ids)
.bind(cutoff_utc)
.fetch(&self.pool);
while let Some(row) = aggregate_rows.try_next().await.map_postgres_err()? {
let api_key_id: String = row.try_get("api_key_id").map_postgres_err()?;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
totals.insert(api_key_id, total_tokens);
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
api_key_id,
COALESCE(
SUM(
COALESCE(
total_tokens,
COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)
)
),
0
) AS total_tokens
FROM usage_billing_facts AS "usage"
WHERE api_key_id = ANY(
"#,
);
builder
.push_bind(api_key_ids)
.push("::TEXT[]) AND created_at >= ")
.push_bind(cutoff_utc)
.push(
r#"
GROUP BY api_key_id
ORDER BY api_key_id ASC
"#,
);
let mut raw_rows = builder.build().fetch(&self.pool);
while let Some(row) = raw_rows.try_next().await.map_postgres_err()? {
let api_key_id: String = row.try_get("api_key_id").map_postgres_err()?;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
let entry = totals.entry(api_key_id).or_default();
*entry = entry.saturating_add(total_tokens);
}
return Ok(totals);
}
let mut rows = sqlx::query(SUMMARIZE_TOTAL_TOKENS_BY_API_KEY_IDS_SQL)
.bind(api_key_ids)
.fetch(&self.pool);
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let api_key_id: String = row.try_get("api_key_id").map_postgres_err()?;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
totals.insert(api_key_id, total_tokens);
}
Ok(totals)
}
pub async fn summarize_usage_totals_by_user_ids(
&self,
user_ids: &[String],
) -> Result<Vec<StoredUsageUserTotals>, DataLayerError> {
if user_ids.is_empty() {
return Ok(Vec::new());
}
let mut totals = std::collections::BTreeMap::<String, StoredUsageUserTotals>::new();
if let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? {
let mut summary_user_ids = std::collections::BTreeSet::<String>::new();
let mut aggregate_rows = sqlx::query(
r#"
SELECT
user_id,
COALESCE(all_time_requests, 0)::BIGINT AS request_count,
COALESCE(
all_time_input_tokens
+ all_time_output_tokens
+ all_time_cache_creation_tokens
+ all_time_cache_read_tokens,
0
)::BIGINT AS total_tokens
FROM stats_user_summary
WHERE user_id = ANY($1::TEXT[])
ORDER BY user_id ASC
"#,
)
.bind(user_ids)
.fetch(&self.pool);
while let Some(row) = aggregate_rows.try_next().await.map_postgres_err()? {
let user_id = row.try_get::<String, _>("user_id").map_postgres_err()?;
let request_count = row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
summary_user_ids.insert(user_id.clone());
totals.insert(
user_id.clone(),
StoredUsageUserTotals {
user_id,
request_count,
total_tokens,
},
);
}
let mut daily_rows = sqlx::query(
r#"
SELECT
user_id,
COALESCE(SUM(total_requests), 0)::BIGINT AS request_count,
COALESCE(
SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens),
0
)::BIGINT AS total_tokens
FROM stats_user_daily
WHERE user_id = ANY($1::TEXT[])
AND date < $2
GROUP BY user_id
ORDER BY user_id ASC
"#,
)
.bind(user_ids)
.bind(cutoff_utc)
.fetch(&self.pool);
while let Some(row) = daily_rows.try_next().await.map_postgres_err()? {
let user_id = row.try_get::<String, _>("user_id").map_postgres_err()?;
if summary_user_ids.contains(&user_id) {
continue;
}
let request_count = row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
totals.insert(
user_id.clone(),
StoredUsageUserTotals {
user_id,
request_count,
total_tokens,
},
);
}
let mut builder = QueryBuilder::<Postgres>::new(
r#"
SELECT
"usage".user_id,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(GREATEST(COALESCE("usage".total_tokens, 0), 0)), 0)::BIGINT AS total_tokens
FROM usage_billing_facts AS "usage"
WHERE "usage".user_id = ANY(
"#,
);
builder
.push_bind(user_ids)
.push("::TEXT[])")
.push(" AND \"usage\".created_at >= ")
.push_bind(cutoff_utc)
.push(
r#"
AND "usage".status NOT IN ('pending', 'streaming')
AND "usage".provider_name NOT IN ('unknown', 'pending')
GROUP BY "usage".user_id
ORDER BY "usage".user_id ASC
"#,
);
let mut raw_rows = builder.build().fetch(&self.pool);
while let Some(row) = raw_rows.try_next().await.map_postgres_err()? {
let user_id = row.try_get::<String, _>("user_id").map_postgres_err()?;
let request_count = row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64;
let entry =
totals
.entry(user_id.clone())
.or_insert_with(|| StoredUsageUserTotals {
user_id,
request_count: 0,
total_tokens: 0,
});
entry.request_count = entry.request_count.saturating_add(request_count);
entry.total_tokens = entry.total_tokens.saturating_add(total_tokens);
}
return Ok(totals.into_values().collect());
}
let mut rows = sqlx::query(SUMMARIZE_USAGE_TOTALS_BY_USER_IDS_SQL)
.bind(user_ids)
.fetch(&self.pool);
let mut items = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
items.push(StoredUsageUserTotals {
user_id: row.try_get::<String, _>("user_id").map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.max(0) as u64,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.max(0) as u64,
});
}
Ok(items)
}
pub async fn summarize_usage_by_provider_api_key_ids(
&self,
provider_api_key_ids: &[String],
) -> Result<std::collections::BTreeMap<String, StoredProviderApiKeyUsageSummary>, DataLayerError>
{
if provider_api_key_ids.is_empty() {
return Ok(std::collections::BTreeMap::new());
}
let mut rows = sqlx::query(SUMMARIZE_USAGE_BY_PROVIDER_API_KEY_IDS_SQL)
.bind(provider_api_key_ids)
.fetch(&self.pool);
let mut summaries = std::collections::BTreeMap::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let provider_api_key_id: String =
row.try_get("provider_api_key_id").map_postgres_err()?;
let request_count = row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.try_into()
.map_err(|_| {
DataLayerError::UnexpectedValue(
"usage.request_count aggregate is negative".to_string(),
)
})?;
let total_tokens = row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.try_into()
.map_err(|_| {
DataLayerError::UnexpectedValue(
"usage.total_tokens aggregate is negative".to_string(),
)
})?;
let total_cost_usd: f64 = row.try_get("total_cost_usd").map_postgres_err()?;
if !total_cost_usd.is_finite() {
return Err(DataLayerError::UnexpectedValue(
"usage.total_cost_usd aggregate is not finite".to_string(),
));
}
let last_used_at_unix_secs = row
.try_get::<Option<i64>, _>("last_used_at_unix_secs")
.map_postgres_err()?
.map(|value| {
value.try_into().map_err(|_| {
DataLayerError::UnexpectedValue(
"usage.last_used_at_unix_secs aggregate is negative".to_string(),
)
})
})
.transpose()?;
summaries.insert(
provider_api_key_id.clone(),
StoredProviderApiKeyUsageSummary {
provider_api_key_id,
request_count,
total_tokens,
total_cost_usd,
last_used_at_unix_secs,
},
);
}
Ok(summaries)
}
pub async fn summarize_usage_by_provider_api_key_windows(
&self,
requests: &[ProviderApiKeyWindowUsageRequest],
) -> Result<Vec<StoredProviderApiKeyWindowUsageSummary>, DataLayerError> {
if requests.is_empty() {
return Ok(Vec::new());
}
let mut provider_api_key_ids = Vec::with_capacity(requests.len());
let mut window_codes = Vec::with_capacity(requests.len());
let mut start_unix_secs = Vec::with_capacity(requests.len());
let mut end_unix_secs = Vec::with_capacity(requests.len());
for request in requests {
let provider_api_key_id = request.provider_api_key_id.trim();
if provider_api_key_id.is_empty() {
return Err(DataLayerError::InvalidInput(
"provider api key window usage provider_api_key_id cannot be empty".to_string(),
));
}
let window_code = request.window_code.trim();
if window_code.is_empty() {
return Err(DataLayerError::InvalidInput(
"provider api key window usage window_code cannot be empty".to_string(),
));
}
if request.start_unix_secs >= request.end_unix_secs {
return Err(DataLayerError::InvalidInput(
"provider api key window usage range must be non-empty".to_string(),
));
}
provider_api_key_ids.push(provider_api_key_id.to_string());
window_codes.push(window_code.to_string());
start_unix_secs.push(i64::try_from(request.start_unix_secs).map_err(|_| {
DataLayerError::InvalidInput(
"provider api key window usage start_unix_secs is out of range".to_string(),
)
})?);
end_unix_secs.push(i64::try_from(request.end_unix_secs).map_err(|_| {
DataLayerError::InvalidInput(
"provider api key window usage end_unix_secs is out of range".to_string(),
)
})?);
}
let mut rows = sqlx::query(SUMMARIZE_PROVIDER_API_KEY_WINDOW_USAGE_SQL)
.bind(&provider_api_key_ids)
.bind(&window_codes)
.bind(&start_unix_secs)
.bind(&end_unix_secs)
.fetch(&self.pool);
let mut summaries = Vec::new();
while let Some(row) = rows.try_next().await.map_postgres_err()? {
let total_cost_usd = row.try_get::<f64, _>("total_cost_usd").map_postgres_err()?;
if !total_cost_usd.is_finite() {
return Err(DataLayerError::UnexpectedValue(
"usage.total_cost_usd window aggregate is not finite".to_string(),
));
}
summaries.push(StoredProviderApiKeyWindowUsageSummary {
provider_api_key_id: row
.try_get::<String, _>("provider_api_key_id")
.map_postgres_err()?,
window_code: row.try_get::<String, _>("window_code").map_postgres_err()?,
request_count: row
.try_get::<i64, _>("request_count")
.map_postgres_err()?
.try_into()
.map_err(|_| {
DataLayerError::UnexpectedValue(
"usage.request_count window aggregate is negative".to_string(),
)
})?,
total_tokens: row
.try_get::<i64, _>("total_tokens")
.map_postgres_err()?
.try_into()
.map_err(|_| {
DataLayerError::UnexpectedValue(
"usage.total_tokens window aggregate is negative".to_string(),
)
})?,
total_cost_usd,
});
}
Ok(summaries)
}
pub async fn upsert(
&self,
usage: UpsertUsageRecord,
) -> Result<StoredRequestUsageAudit, DataLayerError> {
usage.validate()?;
let usage = strip_deprecated_usage_display_fields(usage);
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
lock_usage_request_id_in_tx(tx, &usage.request_id).await?;
if incoming_usage_can_recover_terminal_failure(
usage.status.as_str(),
usage.billing_status.as_str(),
) {
sqlx::query(RESET_STALE_VOID_USAGE_SQL)
.bind(&usage.request_id)
.execute(&mut **tx)
.await
.map_postgres_err()?;
sqlx::query(RESET_STALE_VOID_USAGE_SETTLEMENT_SNAPSHOT_SQL)
.bind(&usage.request_id)
.execute(&mut **tx)
.await
.map_postgres_err()?;
}
let previous_usage =
find_usage_by_request_id_in_tx(tx, &usage.request_id).await?;
let request_headers_json = json_bind_text(usage.request_headers.as_ref())?;
let request_body_storage =
prepare_usage_body_storage(usage.request_body.as_ref())?;
let provider_request_headers_json =
json_bind_text(usage.provider_request_headers.as_ref())?;
let provider_request_body_storage =
prepare_usage_body_storage(usage.provider_request_body.as_ref())?;
let response_headers_json = json_bind_text(usage.response_headers.as_ref())?;
let response_body_storage =
prepare_usage_body_storage(usage.response_body.as_ref())?;
let client_response_headers_json =
json_bind_text(usage.client_response_headers.as_ref())?;
let client_response_body_storage =
prepare_usage_body_storage(usage.client_response_body.as_ref())?;
let http_audit_refs = UsageHttpAuditRefs {
request_body_ref: resolved_write_usage_body_ref(
usage.request_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::RequestBody,
request_body_storage.has_detached_blob(),
None,
),
provider_request_body_ref: resolved_write_usage_body_ref(
usage.provider_request_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ProviderRequestBody,
provider_request_body_storage.has_detached_blob(),
None,
),
response_body_ref: resolved_write_usage_body_ref(
usage.response_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ResponseBody,
response_body_storage.has_detached_blob(),
None,
),
client_response_body_ref: resolved_write_usage_body_ref(
usage.client_response_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ClientResponseBody,
client_response_body_storage.has_detached_blob(),
None,
),
};
let http_audit_states = UsageHttpAuditStates {
request_body_state: usage.request_body_state,
provider_request_body_state: usage.provider_request_body_state,
response_body_state: usage.response_body_state,
client_response_body_state: usage.client_response_body_state,
};
let request_metadata_value = prepare_request_metadata_for_body_storage(
usage.request_metadata.clone(),
[
(
UsageBodyField::RequestBody,
&request_body_storage,
usage.request_body.as_ref(),
usage.request_body_ref.as_deref(),
),
(
UsageBodyField::ProviderRequestBody,
&provider_request_body_storage,
usage.provider_request_body.as_ref(),
usage.provider_request_body_ref.as_deref(),
),
(
UsageBodyField::ResponseBody,
&response_body_storage,
usage.response_body.as_ref(),
usage.response_body_ref.as_deref(),
),
(
UsageBodyField::ClientResponseBody,
&client_response_body_storage,
usage.client_response_body.as_ref(),
usage.client_response_body_ref.as_deref(),
),
],
);
let http_audit_capture_mode = usage_http_audit_capture_mode(
&http_audit_refs,
[
usage.request_body.as_ref(),
usage.provider_request_body.as_ref(),
usage.response_body.as_ref(),
usage.client_response_body.as_ref(),
],
);
let routing_snapshot =
usage_routing_snapshot_from_usage(&usage, request_metadata_value.as_ref());
let settlement_pricing_snapshot = usage_settlement_pricing_snapshot_from_usage(
&usage,
request_metadata_value.as_ref(),
)?;
let request_metadata_json = json_bind_text(request_metadata_value.as_ref())?;
let _row = sqlx::query(UPSERT_SQL)
.bind(Uuid::new_v4().to_string())
.bind(&usage.request_id)
.bind(&usage.user_id)
.bind(&usage.api_key_id)
.bind(&usage.username)
.bind(&usage.api_key_name)
.bind(&usage.provider_name)
.bind(&usage.model)
.bind(&usage.target_model)
.bind(&usage.provider_id)
.bind(&usage.provider_endpoint_id)
.bind(&usage.provider_api_key_id)
.bind(&usage.request_type)
.bind(&usage.api_format)
.bind(&usage.api_family)
.bind(&usage.endpoint_kind)
.bind(&usage.endpoint_api_format)
.bind(&usage.provider_api_family)
.bind(&usage.provider_endpoint_kind)
.bind(usage.has_format_conversion)
.bind(usage.is_stream)
.bind(usage.input_tokens.map(to_i32).transpose()?)
.bind(usage.output_tokens.map(to_i32).transpose()?)
.bind(usage.total_tokens.map(to_i32).transpose()?)
.bind(usage.cache_creation_input_tokens.map(to_i32).transpose()?)
.bind(
usage
.cache_creation_ephemeral_5m_input_tokens
.map(to_i32)
.transpose()?,
)
.bind(
usage
.cache_creation_ephemeral_1h_input_tokens
.map(to_i32)
.transpose()?,
)
.bind(usage.cache_read_input_tokens.map(to_i32).transpose()?)
.bind(usage.cache_creation_cost_usd)
.bind(usage.cache_read_cost_usd)
.bind(None::<f64>)
.bind(usage.total_cost_usd)
.bind(usage.actual_total_cost_usd)
.bind(usage.status_code.map(i32::from))
.bind(&usage.error_message)
.bind(&usage.error_category)
.bind(usage.response_time_ms.map(to_i32).transpose()?)
.bind(usage.first_byte_time_ms.map(to_i32).transpose()?)
.bind(&usage.status)
.bind(&usage.billing_status)
.bind(None::<String>)
.bind(&request_body_storage.inline_json)
.bind(None::<Vec<u8>>)
.bind(None::<String>)
.bind(&provider_request_body_storage.inline_json)
.bind(None::<Vec<u8>>)
.bind(None::<String>)
.bind(&response_body_storage.inline_json)
.bind(None::<Vec<u8>>)
.bind(None::<String>)
.bind(&client_response_body_storage.inline_json)
.bind(None::<Vec<u8>>)
.bind(&request_metadata_json)
.bind(usage.finalized_at_unix_secs.map(|value| value as f64))
.bind(usage.created_at_unix_ms.map(|value| value as f64))
.bind(request_body_storage.has_detached_blob())
.bind(provider_request_body_storage.has_detached_blob())
.bind(response_body_storage.has_detached_blob())
.bind(client_response_body_storage.has_detached_blob())
.fetch_one(&mut **tx)
.await
.map_postgres_err()?;
sync_usage_body_blob_storage(
&mut **tx,
&usage.request_id,
UsageBodyField::RequestBody,
usage.request_body.as_ref(),
&request_body_storage,
)
.await?;
sync_usage_body_blob_storage(
&mut **tx,
&usage.request_id,
UsageBodyField::ProviderRequestBody,
usage.provider_request_body.as_ref(),
&provider_request_body_storage,
)
.await?;
sync_usage_body_blob_storage(
&mut **tx,
&usage.request_id,
UsageBodyField::ResponseBody,
usage.response_body.as_ref(),
&response_body_storage,
)
.await?;
sync_usage_body_blob_storage(
&mut **tx,
&usage.request_id,
UsageBodyField::ClientResponseBody,
usage.client_response_body.as_ref(),
&client_response_body_storage,
)
.await?;
let http_audit_headers = UsageHttpAuditHeaders {
request_headers_json: request_headers_json.as_deref(),
provider_request_headers_json: provider_request_headers_json.as_deref(),
response_headers_json: response_headers_json.as_deref(),
client_response_headers_json: client_response_headers_json.as_deref(),
};
sync_usage_http_audit_storage(
&mut **tx,
&usage.request_id,
&http_audit_headers,
&http_audit_refs,
&http_audit_states,
http_audit_capture_mode,
)
.await?;
sync_usage_routing_snapshot_storage(
&mut **tx,
&usage.request_id,
&routing_snapshot,
)
.await?;
sync_usage_settlement_pricing_snapshot_storage(
&mut **tx,
&usage.request_id,
&settlement_pricing_snapshot,
)
.await?;
let mut stored = find_usage_by_request_id_in_tx(tx, &usage.request_id)
.await?
.ok_or_else(|| {
DataLayerError::UnexpectedValue(format!(
"usage row missing after upsert: {}",
usage.request_id
))
})?;
if request_body_storage.has_detached_blob() {
stored.request_body = usage.request_body.clone();
}
stored.request_headers = usage.request_headers.clone();
stored.provider_request_headers = usage.provider_request_headers.clone();
if provider_request_body_storage.has_detached_blob() {
stored.provider_request_body = usage.provider_request_body.clone();
}
stored.response_headers = usage.response_headers.clone();
if response_body_storage.has_detached_blob() {
stored.response_body = usage.response_body.clone();
}
stored.client_response_headers = usage.client_response_headers.clone();
if client_response_body_storage.has_detached_blob() {
stored.client_response_body = usage.client_response_body.clone();
}
stored.request_body_ref = resolved_write_usage_body_ref(
usage.request_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::RequestBody,
request_body_storage.has_detached_blob(),
http_audit_refs.request_body_ref.as_deref(),
);
stored.provider_request_body_ref = resolved_write_usage_body_ref(
usage.provider_request_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ProviderRequestBody,
provider_request_body_storage.has_detached_blob(),
http_audit_refs.provider_request_body_ref.as_deref(),
);
stored.response_body_ref = resolved_write_usage_body_ref(
usage.response_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ResponseBody,
response_body_storage.has_detached_blob(),
http_audit_refs.response_body_ref.as_deref(),
);
stored.client_response_body_ref = resolved_write_usage_body_ref(
usage.client_response_body_ref.as_deref(),
&usage.request_id,
UsageBodyField::ClientResponseBody,
client_response_body_storage.has_detached_blob(),
http_audit_refs.client_response_body_ref.as_deref(),
);
stored.request_body_state =
usage.request_body_state.or(stored.request_body_state);
stored.provider_request_body_state = usage
.provider_request_body_state
.or(stored.provider_request_body_state);
stored.response_body_state =
usage.response_body_state.or(stored.response_body_state);
stored.client_response_body_state = usage
.client_response_body_state
.or(stored.client_response_body_state);
stored.candidate_id = routing_snapshot.candidate_id.clone();
stored.candidate_index = routing_snapshot.candidate_index;
stored.key_name = routing_snapshot.key_name.clone();
stored.planner_kind = routing_snapshot.planner_kind.clone();
stored.route_family = routing_snapshot.route_family.clone();
stored.route_kind = routing_snapshot.route_kind.clone();
stored.execution_path = routing_snapshot.execution_path.clone();
stored.local_execution_runtime_miss_reason =
routing_snapshot.local_execution_runtime_miss_reason.clone();
stored.output_price_per_1m = settlement_pricing_snapshot.output_price_per_1m;
stored.request_metadata = request_metadata_value;
let before_api_key_contribution =
previous_usage.as_ref().and_then(api_key_usage_contribution);
let after_api_key_contribution = api_key_usage_contribution(&stored);
match (
before_api_key_contribution.as_ref(),
after_api_key_contribution.as_ref(),
) {
(Some(before), Some(after)) if before.api_key_id == after.api_key_id => {
let delta = ApiKeyUsageDelta::between(before, after);
enqueue_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
before.api_key_id.as_str(),
&delta,
)
.await?;
}
_ => {
if let Some(before) = before_api_key_contribution.as_ref() {
let delta = ApiKeyUsageDelta::removal(before);
enqueue_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
before.api_key_id.as_str(),
&delta,
)
.await?;
}
if let Some(after) = after_api_key_contribution.as_ref() {
let delta = ApiKeyUsageDelta::addition(after);
enqueue_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
after.api_key_id.as_str(),
&delta,
)
.await?;
}
}
}
let before_model_contribution =
previous_usage.as_ref().and_then(model_usage_contribution);
let after_model_contribution = model_usage_contribution(&stored);
match (
before_model_contribution.as_ref(),
after_model_contribution.as_ref(),
) {
(Some(before), Some(after)) if before.model == after.model => {
let delta = ModelUsageDelta::between(before, after);
enqueue_model_usage_delta_in_tx(
tx,
&usage.request_id,
before.model.as_str(),
&delta,
)
.await?;
}
_ => {
if let Some(before) = before_model_contribution.as_ref() {
let delta = ModelUsageDelta::removal(before);
enqueue_model_usage_delta_in_tx(
tx,
&usage.request_id,
before.model.as_str(),
&delta,
)
.await?;
}
if let Some(after) = after_model_contribution.as_ref() {
let delta = ModelUsageDelta::addition(after);
enqueue_model_usage_delta_in_tx(
tx,
&usage.request_id,
after.model.as_str(),
&delta,
)
.await?;
}
}
}
let before_provider_contribution = previous_usage
.as_ref()
.and_then(provider_api_key_usage_contribution);
let after_provider_contribution = provider_api_key_usage_contribution(&stored);
match (
before_provider_contribution.as_ref(),
after_provider_contribution.as_ref(),
) {
(Some(before), Some(after)) if before.key_id == after.key_id => {
let delta = ProviderApiKeyUsageDelta::between(before, after);
enqueue_provider_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
before.key_id.as_str(),
&delta,
)
.await?;
}
_ => {
if let Some(before) = before_provider_contribution.as_ref() {
let delta = ProviderApiKeyUsageDelta::removal(before);
enqueue_provider_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
before.key_id.as_str(),
&delta,
)
.await?;
}
if let Some(after) = after_provider_contribution.as_ref() {
let delta = ProviderApiKeyUsageDelta::addition(after);
enqueue_provider_api_key_usage_delta_in_tx(
tx,
&usage.request_id,
after.key_id.as_str(),
&delta,
)
.await?;
}
}
}
Ok(stored)
}) as BoxFuture<'_, Result<StoredRequestUsageAudit, DataLayerError>>
})
.await
}
pub async fn flush_usage_counter_deltas(
&self,
batch_size: usize,
) -> Result<UsageCounterFlushSummary, DataLayerError> {
if batch_size == 0 {
return Ok(UsageCounterFlushSummary::default());
}
let batch_size_i64 = i64::try_from(batch_size).map_err(|_| {
DataLayerError::InvalidInput(format!(
"usage counter flush batch size is out of range: {batch_size}"
))
})?;
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
if !try_lock_usage_counter_flush_in_tx(tx).await? {
return Ok(UsageCounterFlushSummary::default());
}
let rows = claim_usage_counter_deltas_in_tx(tx, batch_size_i64).await?;
if rows.is_empty() {
return Ok(UsageCounterFlushSummary::default());
}
let row_ids = rows.iter().map(|row| row.id.clone()).collect::<Vec<_>>();
let aggregates = UsageCounterDeltaAggregates::from_rows(&rows)?;
for (api_key_id, delta) in &aggregates.api_keys {
apply_api_key_usage_delta_in_tx(tx, api_key_id.as_str(), delta).await?;
}
for (model, delta) in &aggregates.models {
apply_global_model_usage_delta_in_tx(tx, model.as_str(), delta).await?;
}
for (key_id, delta) in &aggregates.provider_api_keys {
apply_provider_api_key_main_usage_delta_in_tx(tx, key_id.as_str(), delta)
.await?;
}
for (provider_id, delta) in &aggregates.provider_monthly {
apply_provider_monthly_usage_delta_in_tx(tx, provider_id.as_str(), *delta)
.await?;
}
for (node_id, delta) in &aggregates.proxy_nodes {
apply_proxy_node_counter_delta_in_tx(tx, node_id.as_str(), delta).await?;
}
for (token_id, delta) in &aggregates.management_tokens {
apply_management_token_counter_delta_in_tx(tx, token_id.as_str(), delta)
.await?;
}
for (api_key_id, delta) in &aggregates.api_key_last_used {
apply_api_key_last_used_delta_in_tx(tx, api_key_id.as_str(), delta).await?;
}
mark_usage_counter_deltas_processed_in_tx(tx, &row_ids).await?;
Ok(UsageCounterFlushSummary {
rows_claimed: rows.len(),
api_key_targets: aggregates.api_keys.len(),
provider_api_key_targets: aggregates.provider_api_keys.len(),
model_targets: aggregates.models.len(),
provider_monthly_targets: aggregates.provider_monthly.len(),
proxy_node_targets: aggregates.proxy_nodes.len(),
management_token_targets: aggregates.management_tokens.len(),
api_key_last_used_targets: aggregates.api_key_last_used.len(),
})
})
as BoxFuture<'_, Result<UsageCounterFlushSummary, DataLayerError>>
})
.await
}
pub async fn enqueue_proxy_node_counter_delta(
&self,
delta: ProxyNodeCounterDelta,
) -> Result<bool, DataLayerError> {
if delta.is_noop() {
return Ok(false);
}
let node_id = delta.node_id.trim().to_string();
let request_id = format!("proxy_node:{node_id}:{}", Uuid::new_v4());
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id: &request_id,
kind: USAGE_COUNTER_KIND_PROXY_NODE,
target_id: &node_id,
request_count_delta: 0,
total_requests_delta: delta.total_requests_delta,
success_count_delta: 0,
error_count_delta: delta.failed_requests_delta,
dns_failures_delta: delta.dns_failures_delta,
stream_errors_delta: delta.stream_errors_delta,
total_tokens_delta: 0,
total_cost_usd_delta: 0.0,
total_response_time_ms_delta: 0,
last_used_at_unix_secs: None,
last_used_ip: None,
candidate_last_used_at_unix_secs: None,
removed_last_used_at_unix_secs: None,
usage_created_at_unix_secs: None,
},
)
.await?;
Ok(true)
}) as BoxFuture<'_, Result<bool, DataLayerError>>
})
.await
}
pub async fn enqueue_management_token_counter_delta(
&self,
delta: ManagementTokenCounterDelta,
) -> Result<bool, DataLayerError> {
if delta.is_noop() {
return Ok(false);
}
let token_id = delta.token_id.trim().to_string();
let last_used_ip = delta
.last_used_ip
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned);
let last_used_at = delta
.last_used_at_unix_secs
.unwrap_or_else(|| chrono::Utc::now().timestamp().max(0) as u64);
let request_id = format!("management_token:{token_id}:{}", Uuid::new_v4());
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id: &request_id,
kind: USAGE_COUNTER_KIND_MANAGEMENT_TOKEN,
target_id: &token_id,
request_count_delta: delta.usage_count_delta,
total_requests_delta: 0,
success_count_delta: 0,
error_count_delta: 0,
dns_failures_delta: 0,
stream_errors_delta: 0,
total_tokens_delta: 0,
total_cost_usd_delta: 0.0,
total_response_time_ms_delta: 0,
last_used_at_unix_secs: Some(last_used_at),
last_used_ip: last_used_ip.as_deref(),
candidate_last_used_at_unix_secs: None,
removed_last_used_at_unix_secs: None,
usage_created_at_unix_secs: None,
},
)
.await?;
Ok(true)
}) as BoxFuture<'_, Result<bool, DataLayerError>>
})
.await
}
pub async fn enqueue_api_key_last_used_delta(
&self,
delta: ApiKeyLastUsedDelta,
) -> Result<bool, DataLayerError> {
if delta.is_noop() {
return Ok(false);
}
let api_key_id = delta.api_key_id.trim().to_string();
let request_id = format!("api_key_last_used:{api_key_id}:{}", Uuid::new_v4());
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id: &request_id,
kind: USAGE_COUNTER_KIND_API_KEY_LAST_USED,
target_id: &api_key_id,
request_count_delta: 0,
total_requests_delta: 0,
success_count_delta: 0,
error_count_delta: 0,
dns_failures_delta: 0,
stream_errors_delta: 0,
total_tokens_delta: 0,
total_cost_usd_delta: 0.0,
total_response_time_ms_delta: 0,
last_used_at_unix_secs: Some(delta.last_used_at_unix_secs),
last_used_ip: None,
candidate_last_used_at_unix_secs: None,
removed_last_used_at_unix_secs: None,
usage_created_at_unix_secs: None,
},
)
.await?;
Ok(true)
}) as BoxFuture<'_, Result<bool, DataLayerError>>
})
.await
}
pub async fn cleanup_processed_usage_counter_deltas(
&self,
cutoff_unix_secs: u64,
batch_size: usize,
) -> Result<usize, DataLayerError> {
cleanup_processed_usage_counter_deltas_with_pool(&self.pool, cutoff_unix_secs, batch_size)
.await
}
pub async fn cleanup_stale_pending_requests(
&self,
cutoff_unix_secs: u64,
now_unix_secs: u64,
timeout_minutes: u64,
batch_size: usize,
) -> Result<PendingUsageCleanupSummary, DataLayerError> {
if batch_size == 0 {
return Ok(PendingUsageCleanupSummary::default());
}
let cutoff_timestamp = i64::try_from(cutoff_unix_secs).map_err(|_| {
DataLayerError::InvalidInput(format!(
"invalid stale pending usage cutoff: {cutoff_unix_secs}"
))
})?;
let now_timestamp = i64::try_from(now_unix_secs).map_err(|_| {
DataLayerError::InvalidInput(format!(
"invalid stale pending usage timestamp: {now_unix_secs}"
))
})?;
let cutoff = DateTime::<Utc>::from_timestamp(cutoff_timestamp, 0).ok_or_else(|| {
DataLayerError::InvalidInput(format!(
"invalid stale pending usage cutoff: {cutoff_unix_secs}"
))
})?;
let now = DateTime::<Utc>::from_timestamp(now_timestamp, 0).ok_or_else(|| {
DataLayerError::InvalidInput(format!(
"invalid stale pending usage timestamp: {now_unix_secs}"
))
})?;
let mut summary = PendingUsageCleanupSummary::default();
let batch_size_i64 = i64::try_from(batch_size).map_err(|_| {
DataLayerError::InvalidInput(format!(
"invalid stale pending usage batch size: {batch_size}"
))
})?;
loop {
let mut tx = self.pool.begin().await.map_postgres_err()?;
let stale_rows = sqlx::query(SELECT_STALE_PENDING_USAGE_BATCH_SQL)
.bind(cutoff)
.bind(batch_size_i64)
.fetch_all(&mut *tx)
.await
.map_postgres_err()?;
if stale_rows.is_empty() {
tx.rollback().await.map_postgres_err()?;
break;
}
let stale_rows = stale_rows
.iter()
.map(|row| {
Ok(StalePendingUsageRow {
request_id: row.try_get("request_id").map_postgres_err()?,
status: row.try_get("status").map_postgres_err()?,
billing_status: row.try_get("billing_status").map_postgres_err()?,
})
})
.collect::<Result<Vec<_>, DataLayerError>>()?;
let request_ids = stale_rows
.iter()
.map(|row| row.request_id.clone())
.collect::<Vec<_>>();
let (completed_request_ids, failed_candidate_info) = if request_ids.is_empty() {
(Vec::new(), std::collections::HashMap::new())
} else {
let completed = sqlx::query(SELECT_COMPLETED_PENDING_REQUEST_IDS_SQL)
.bind(&request_ids)
.fetch_all(&mut *tx)
.await
.map_postgres_err()?
.iter()
.map(|row| row.try_get("request_id").map_postgres_err())
.collect::<Result<Vec<String>, DataLayerError>>()?;
let failed_rows =
sqlx::query(SELECT_LATEST_FAILED_CANDIDATE_FOR_STALE_REQUESTS_SQL)
.bind(&request_ids)
.fetch_all(&mut *tx)
.await
.map_postgres_err()?;
let mut failed_map = std::collections::HashMap::new();
for row in failed_rows {
let request_id: String = row.try_get("request_id").map_postgres_err()?;
let status_code = row
.try_get::<Option<i32>, _>("status_code")
.map_postgres_err()?
.and_then(|value| u16::try_from(value).ok());
let error_message = row
.try_get::<Option<String>, _>("error_message")
.map_postgres_err()?
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty());
failed_map.insert(
request_id,
FailedCandidateCleanupInfo {
status_code,
error_message,
},
);
}
(completed, failed_map)
};
for row in stale_rows {
if completed_request_ids.contains(&row.request_id) {
sqlx::query(UPDATE_RECOVERED_STALE_USAGE_SQL)
.bind(&row.request_id)
.execute(&mut *tx)
.await
.map_postgres_err()?;
sqlx::query(UPDATE_RECOVERED_STREAMING_CANDIDATES_SQL)
.bind(&row.request_id)
.bind(now)
.execute(&mut *tx)
.await
.map_postgres_err()?;
summary.recovered += 1;
continue;
}
let candidate_info = failed_candidate_info.get(&row.request_id);
let (status_code, error_message) =
resolve_stale_pending_failure(candidate_info, &row.status, timeout_minutes);
let status_code_i32 = i32::from(status_code);
if row.billing_status == "pending" {
sqlx::query(UPDATE_FAILED_VOID_STALE_USAGE_SQL)
.bind(&row.request_id)
.bind(&error_message)
.bind(now)
.bind(status_code_i32)
.execute(&mut *tx)
.await
.map_postgres_err()?;
} else {
sqlx::query(UPDATE_FAILED_STALE_USAGE_SQL)
.bind(&row.request_id)
.bind(&error_message)
.bind(status_code_i32)
.execute(&mut *tx)
.await
.map_postgres_err()?;
}
sqlx::query(UPDATE_FAILED_PENDING_CANDIDATES_SQL)
.bind(&row.request_id)
.bind(now)
.execute(&mut *tx)
.await
.map_postgres_err()?;
summary.failed += 1;
}
tx.commit().await.map_postgres_err()?;
}
Ok(summary)
}
pub async fn read_usage_counter_health(
&self,
) -> Result<UsageCounterHealthSnapshot, DataLayerError> {
let row = sqlx::query(READ_USAGE_COUNTER_HEALTH_SQL)
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
let mut snapshot = UsageCounterHealthSnapshot {
pending_rows: row
.try_get::<i64, _>("pending_rows")
.map_postgres_err()?
.max(0) as u64,
processed_rows: row
.try_get::<i64, _>("processed_rows")
.map_postgres_err()?
.max(0) as u64,
oldest_pending_created_at_unix_secs: row
.try_get::<Option<i64>, _>("oldest_pending_created_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
latest_processed_at_unix_secs: row
.try_get::<Option<i64>, _>("latest_processed_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64),
pending_by_kind: BTreeMap::new(),
};
let pending_by_kind_rows = sqlx::query(READ_PENDING_USAGE_COUNTER_DELTAS_BY_KIND_SQL)
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
for row in pending_by_kind_rows {
let kind = row.try_get::<String, _>("kind").map_postgres_err()?;
let pending_rows = row
.try_get::<i64, _>("pending_rows")
.map_postgres_err()?
.max(0) as u64;
snapshot.pending_by_kind.insert(kind, pending_rows);
}
Ok(snapshot)
}
pub async fn rebuild_api_key_usage_stats(&self) -> Result<u64, DataLayerError> {
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
sqlx::query(RESET_API_KEY_USAGE_STATS_SQL)
.execute(&mut **tx)
.await
.map_postgres_err()?;
let rows_affected = sqlx::query(REBUILD_API_KEY_USAGE_STATS_SQL)
.execute(&mut **tx)
.await
.map_postgres_err()?
.rows_affected();
Ok(rows_affected)
}) as BoxFuture<'_, Result<u64, DataLayerError>>
})
.await
}
pub async fn rebuild_provider_api_key_usage_stats(&self) -> Result<u64, DataLayerError> {
self.tx_runner
.run_read_write(|tx| {
Box::pin(async move {
sqlx::query(RESET_PROVIDER_API_KEY_USAGE_STATS_SQL)
.execute(&mut **tx)
.await
.map_postgres_err()?;
let rows_affected = sqlx::query(REBUILD_PROVIDER_API_KEY_USAGE_STATS_SQL)
.execute(&mut **tx)
.await
.map_postgres_err()?
.rows_affected();
sqlx::query(REBUILD_PROVIDER_API_KEY_CODEX_WINDOW_USAGE_STATS_SQL)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(rows_affected)
}) as BoxFuture<'_, Result<u64, DataLayerError>>
})
.await
}
}
#[async_trait]
impl UsageReadRepository for SqlxUsageReadRepository {
async fn find_by_id(
&self,
id: &str,
) -> Result<Option<StoredRequestUsageAudit>, DataLayerError> {
Self::find_by_id(self, id).await
}
async fn list_by_ids(
&self,
ids: &[String],
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
Self::list_by_ids(self, ids).await
}
async fn find_by_request_id(
&self,
request_id: &str,
) -> Result<Option<StoredRequestUsageAudit>, DataLayerError> {
Self::find_by_request_id(self, request_id).await
}
async fn resolve_body_ref(&self, body_ref: &str) -> Result<Option<Value>, DataLayerError> {
Self::resolve_body_ref(self, body_ref).await
}
async fn list_usage_audits(
&self,
query: &UsageAuditListQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
Self::list_usage_audits(self, query).await
}
async fn count_usage_audits(&self, query: &UsageAuditListQuery) -> Result<u64, DataLayerError> {
Self::count_usage_audits(self, query).await
}
async fn list_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
Self::list_usage_audits_by_keyword_search(self, query).await
}
async fn count_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<u64, DataLayerError> {
Self::count_usage_audits_by_keyword_search(self, query).await
}
async fn aggregate_usage_audits(
&self,
query: &UsageAuditAggregationQuery,
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
Self::aggregate_usage_audits(self, query).await
}
async fn summarize_usage_audits(
&self,
query: &UsageAuditSummaryQuery,
) -> Result<StoredUsageAuditSummary, DataLayerError> {
Self::summarize_usage_audits(self, query).await
}
async fn summarize_usage_cache_hit_summary(
&self,
query: &UsageCacheHitSummaryQuery,
) -> Result<StoredUsageCacheHitSummary, DataLayerError> {
Self::summarize_usage_cache_hit_summary(self, query).await
}
async fn summarize_usage_settled_cost(
&self,
query: &UsageSettledCostSummaryQuery,
) -> Result<StoredUsageSettledCostSummary, DataLayerError> {
Self::summarize_usage_settled_cost(self, query).await
}
async fn summarize_usage_cache_affinity_hit_summary(
&self,
query: &UsageCacheAffinityHitSummaryQuery,
) -> Result<StoredUsageCacheAffinityHitSummary, DataLayerError> {
Self::summarize_usage_cache_affinity_hit_summary(self, query).await
}
async fn list_usage_cache_affinity_intervals(
&self,
query: &UsageCacheAffinityIntervalQuery,
) -> Result<Vec<StoredUsageCacheAffinityIntervalRow>, DataLayerError> {
Self::list_usage_cache_affinity_intervals(self, query).await
}
async fn summarize_dashboard_usage(
&self,
query: &UsageDashboardSummaryQuery,
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
Self::summarize_dashboard_usage(self, query).await
}
async fn list_dashboard_daily_breakdown(
&self,
query: &UsageDashboardDailyBreakdownQuery,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, DataLayerError> {
Self::list_dashboard_daily_breakdown(self, query).await
}
async fn summarize_dashboard_provider_counts(
&self,
query: &UsageDashboardProviderCountsQuery,
) -> Result<Vec<StoredUsageDashboardProviderCount>, DataLayerError> {
Self::summarize_dashboard_provider_counts(self, query).await
}
async fn summarize_usage_breakdown(
&self,
query: &UsageBreakdownSummaryQuery,
) -> Result<Vec<StoredUsageBreakdownSummaryRow>, DataLayerError> {
Self::summarize_usage_breakdown(self, query).await
}
async fn count_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorCountQuery,
) -> Result<u64, DataLayerError> {
Self::count_monitoring_usage_errors(self, query).await
}
async fn list_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorListQuery,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
Self::list_monitoring_usage_errors(self, query).await
}
async fn summarize_usage_error_distribution(
&self,
query: &UsageErrorDistributionQuery,
) -> Result<Vec<StoredUsageErrorDistributionRow>, DataLayerError> {
Self::summarize_usage_error_distribution(self, query).await
}
async fn summarize_usage_performance_percentiles(
&self,
query: &UsagePerformancePercentilesQuery,
) -> Result<Vec<StoredUsagePerformancePercentilesRow>, DataLayerError> {
Self::summarize_usage_performance_percentiles(self, query).await
}
async fn summarize_usage_provider_performance(
&self,
query: &UsageProviderPerformanceQuery,
) -> Result<StoredUsageProviderPerformance, DataLayerError> {
Self::summarize_usage_provider_performance(self, query).await
}
async fn summarize_usage_cost_savings(
&self,
query: &UsageCostSavingsSummaryQuery,
) -> Result<StoredUsageCostSavingsSummary, DataLayerError> {
Self::summarize_usage_cost_savings(self, query).await
}
async fn summarize_usage_time_series(
&self,
query: &UsageTimeSeriesQuery,
) -> Result<Vec<StoredUsageTimeSeriesBucket>, DataLayerError> {
Self::summarize_usage_time_series(self, query).await
}
async fn summarize_usage_leaderboard(
&self,
query: &UsageLeaderboardQuery,
) -> Result<Vec<StoredUsageLeaderboardSummary>, DataLayerError> {
Self::summarize_usage_leaderboard(self, query).await
}
async fn list_recent_usage_audits(
&self,
user_id: Option<&str>,
limit: usize,
) -> Result<Vec<StoredRequestUsageAudit>, DataLayerError> {
Self::list_recent_usage_audits(self, user_id, limit).await
}
async fn summarize_total_tokens_by_api_key_ids(
&self,
api_key_ids: &[String],
) -> Result<std::collections::BTreeMap<String, u64>, DataLayerError> {
Self::summarize_total_tokens_by_api_key_ids(self, api_key_ids).await
}
async fn summarize_usage_totals_by_user_ids(
&self,
user_ids: &[String],
) -> Result<Vec<StoredUsageUserTotals>, DataLayerError> {
Self::summarize_usage_totals_by_user_ids(self, user_ids).await
}
async fn summarize_usage_by_provider_api_key_ids(
&self,
provider_api_key_ids: &[String],
) -> Result<std::collections::BTreeMap<String, StoredProviderApiKeyUsageSummary>, DataLayerError>
{
Self::summarize_usage_by_provider_api_key_ids(self, provider_api_key_ids).await
}
async fn summarize_usage_by_provider_api_key_windows(
&self,
requests: &[ProviderApiKeyWindowUsageRequest],
) -> Result<Vec<StoredProviderApiKeyWindowUsageSummary>, DataLayerError> {
Self::summarize_usage_by_provider_api_key_windows(self, requests).await
}
async fn summarize_provider_usage_since(
&self,
provider_id: &str,
since_unix_secs: u64,
) -> Result<StoredProviderUsageSummary, DataLayerError> {
Self::summarize_provider_usage_since(self, provider_id, since_unix_secs).await
}
async fn summarize_usage_daily_heatmap(
&self,
query: &UsageDailyHeatmapQuery,
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
Self::summarize_usage_daily_heatmap(self, query).await
}
async fn read_usage_counter_health(
&self,
) -> Result<UsageCounterHealthSnapshot, DataLayerError> {
Self::read_usage_counter_health(self).await
}
}
#[async_trait]
impl UsageWriteRepository for SqlxUsageReadRepository {
async fn upsert(
&self,
usage: UpsertUsageRecord,
) -> Result<StoredRequestUsageAudit, DataLayerError> {
Self::upsert(self, usage).await
}
async fn rebuild_api_key_usage_stats(&self) -> Result<u64, DataLayerError> {
Self::rebuild_api_key_usage_stats(self).await
}
async fn rebuild_provider_api_key_usage_stats(&self) -> Result<u64, DataLayerError> {
Self::rebuild_provider_api_key_usage_stats(self).await
}
async fn cleanup_stale_pending_requests(
&self,
cutoff_unix_secs: u64,
now_unix_secs: u64,
timeout_minutes: u64,
batch_size: usize,
) -> Result<PendingUsageCleanupSummary, DataLayerError> {
Self::cleanup_stale_pending_requests(
self,
cutoff_unix_secs,
now_unix_secs,
timeout_minutes,
batch_size,
)
.await
}
async fn flush_usage_counter_deltas(
&self,
batch_size: usize,
) -> Result<UsageCounterFlushSummary, DataLayerError> {
Self::flush_usage_counter_deltas(self, batch_size).await
}
async fn enqueue_proxy_node_counter_delta(
&self,
delta: ProxyNodeCounterDelta,
) -> Result<bool, DataLayerError> {
Self::enqueue_proxy_node_counter_delta(self, delta).await
}
async fn enqueue_management_token_counter_delta(
&self,
delta: ManagementTokenCounterDelta,
) -> Result<bool, DataLayerError> {
Self::enqueue_management_token_counter_delta(self, delta).await
}
async fn enqueue_api_key_last_used_delta(
&self,
delta: ApiKeyLastUsedDelta,
) -> Result<bool, DataLayerError> {
Self::enqueue_api_key_last_used_delta(self, delta).await
}
async fn cleanup_processed_usage_counter_deltas(
&self,
cutoff_unix_secs: u64,
batch_size: usize,
) -> Result<usize, DataLayerError> {
Self::cleanup_processed_usage_counter_deltas(self, cutoff_unix_secs, batch_size).await
}
async fn cleanup_usage(
&self,
window: &UsageCleanupWindow,
batch_size: usize,
auto_delete_expired_keys: bool,
targets: UsageCleanupTargets,
mode: UsageCleanupExecutionMode,
) -> Result<UsageCleanupSummary, DataLayerError> {
Self::cleanup_usage(
self,
window,
batch_size,
auto_delete_expired_keys,
targets,
mode,
)
.await
}
async fn preview_usage_cleanup(
&self,
window: &UsageCleanupWindow,
targets: UsageCleanupTargets,
mode: UsageCleanupExecutionMode,
) -> Result<aether_data_contracts::repository::usage::UsageCleanupPreviewCounts, DataLayerError>
{
crate::repository::usage::postgres::cleanup::preview_usage_cleanup_impl(
&self.pool, window, targets, mode,
)
.await
}
}
struct StalePendingUsageRow {
request_id: String,
status: String,
billing_status: String,
}
struct FailedCandidateCleanupInfo {
status_code: Option<u16>,
error_message: Option<String>,
}
fn stale_pending_error_message(status: &str, timeout_minutes: u64) -> String {
format!("请求超时: 状态 '{status}' 超过 {timeout_minutes} 分钟未完成")
}
fn resolve_stale_pending_failure(
candidate: Option<&FailedCandidateCleanupInfo>,
status: &str,
timeout_minutes: u64,
) -> (u16, String) {
match candidate {
Some(info) => (
info.status_code.unwrap_or(502),
info.error_message
.clone()
.unwrap_or_else(|| stale_pending_error_message(status, timeout_minutes)),
),
None => (504, stale_pending_error_message(status, timeout_minutes)),
}
}
async fn find_usage_by_request_id_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
request_id: &str,
) -> Result<Option<StoredRequestUsageAudit>, DataLayerError> {
sqlx::query(FIND_BY_REQUEST_ID_SQL)
.bind(request_id)
.fetch_optional(&mut **tx)
.await
.map_postgres_err()?
.map(|row| map_usage_row(&row, false))
.transpose()
}
async fn lock_usage_request_id_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
request_id: &str,
) -> Result<(), DataLayerError> {
sqlx::query(LOCK_USAGE_REQUEST_ID_SQL)
.bind(request_id)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
struct UsageCounterDeltaRow {
id: String,
kind: String,
target_id: String,
request_count_delta: i64,
total_requests_delta: i64,
success_count_delta: i64,
error_count_delta: i64,
dns_failures_delta: i64,
stream_errors_delta: i64,
total_tokens_delta: i64,
total_cost_usd_delta: f64,
total_response_time_ms_delta: i64,
last_used_at_unix_secs: Option<u64>,
last_used_ip: Option<String>,
candidate_last_used_at_unix_secs: Option<u64>,
removed_last_used_at_unix_secs: Option<u64>,
usage_created_at_unix_secs: Option<u64>,
}
#[derive(Default)]
struct UsageCounterDeltaAggregates {
api_keys: BTreeMap<String, ApiKeyUsageDelta>,
provider_api_keys: BTreeMap<String, ProviderApiKeyUsageDelta>,
models: BTreeMap<String, ModelUsageDelta>,
provider_monthly: BTreeMap<String, f64>,
proxy_nodes: BTreeMap<String, ProxyNodeCounterDelta>,
management_tokens: BTreeMap<String, ManagementTokenCounterDelta>,
api_key_last_used: BTreeMap<String, ApiKeyLastUsedDelta>,
}
impl UsageCounterDeltaAggregates {
fn from_rows(rows: &[UsageCounterDeltaRow]) -> Result<Self, DataLayerError> {
let mut aggregates = Self::default();
for row in rows {
if !row.total_cost_usd_delta.is_finite() {
return Err(DataLayerError::UnexpectedValue(format!(
"usage_counter_deltas.total_cost_usd_delta is not finite for {}",
row.id
)));
}
match row.kind.as_str() {
USAGE_COUNTER_KIND_API_KEY => {
let entry = aggregates
.api_keys
.entry(row.target_id.clone())
.or_default();
entry.total_requests += row.total_requests_delta;
entry.total_tokens += row.total_tokens_delta;
entry.total_cost_usd += row.total_cost_usd_delta;
merge_optional_max(
&mut entry.candidate_last_used_at_unix_secs,
row.candidate_last_used_at_unix_secs,
);
merge_optional_max(
&mut entry.removed_last_used_at_unix_secs,
row.removed_last_used_at_unix_secs,
);
}
USAGE_COUNTER_KIND_PROVIDER_API_KEY => {
let entry = aggregates
.provider_api_keys
.entry(row.target_id.clone())
.or_default();
entry.request_count += row.request_count_delta;
entry.success_count += row.success_count_delta;
entry.error_count += row.error_count_delta;
entry.total_tokens += row.total_tokens_delta;
entry.total_cost_usd += row.total_cost_usd_delta;
entry.total_response_time_ms += row.total_response_time_ms_delta;
merge_optional_max(
&mut entry.candidate_last_used_at_unix_secs,
row.candidate_last_used_at_unix_secs,
);
merge_optional_max(
&mut entry.removed_last_used_at_unix_secs,
row.removed_last_used_at_unix_secs,
);
merge_optional_max(
&mut entry.usage_created_at_unix_secs,
row.usage_created_at_unix_secs,
);
}
USAGE_COUNTER_KIND_MODEL => {
let entry = aggregates.models.entry(row.target_id.clone()).or_default();
entry.request_count += row.request_count_delta;
}
USAGE_COUNTER_KIND_PROVIDER_MONTHLY => {
let entry = aggregates
.provider_monthly
.entry(row.target_id.clone())
.or_default();
*entry += row.total_cost_usd_delta;
}
USAGE_COUNTER_KIND_PROXY_NODE => {
let entry = aggregates
.proxy_nodes
.entry(row.target_id.clone())
.or_insert(ProxyNodeCounterDelta {
node_id: row.target_id.clone(),
total_requests_delta: 0,
failed_requests_delta: 0,
dns_failures_delta: 0,
stream_errors_delta: 0,
});
entry.total_requests_delta += row.total_requests_delta;
entry.failed_requests_delta += row.error_count_delta;
entry.dns_failures_delta += row.dns_failures_delta;
entry.stream_errors_delta += row.stream_errors_delta;
}
USAGE_COUNTER_KIND_MANAGEMENT_TOKEN => {
let entry = aggregates
.management_tokens
.entry(row.target_id.clone())
.or_insert(ManagementTokenCounterDelta {
token_id: row.target_id.clone(),
usage_count_delta: 0,
last_used_at_unix_secs: None,
last_used_ip: None,
});
entry.usage_count_delta += row.request_count_delta;
merge_latest_optional_timestamp_with_value(
&mut entry.last_used_at_unix_secs,
&mut entry.last_used_ip,
row.last_used_at_unix_secs,
row.last_used_ip.clone(),
);
}
USAGE_COUNTER_KIND_API_KEY_LAST_USED => {
let Some(last_used_at_unix_secs) = row.last_used_at_unix_secs else {
continue;
};
let entry = aggregates
.api_key_last_used
.entry(row.target_id.clone())
.or_insert(ApiKeyLastUsedDelta {
api_key_id: row.target_id.clone(),
last_used_at_unix_secs,
});
if last_used_at_unix_secs > entry.last_used_at_unix_secs {
entry.last_used_at_unix_secs = last_used_at_unix_secs;
}
}
other => {
return Err(DataLayerError::UnexpectedValue(format!(
"unknown usage counter delta kind: {other}"
)));
}
}
}
Ok(aggregates)
}
}
fn merge_optional_max(target: &mut Option<u64>, value: Option<u64>) {
if let Some(value) = value {
if target.is_none_or(|current| value > current) {
*target = Some(value);
}
}
}
fn merge_latest_optional_timestamp_with_value(
target_timestamp: &mut Option<u64>,
target_value: &mut Option<String>,
timestamp: Option<u64>,
value: Option<String>,
) {
let Some(timestamp) = timestamp else {
return;
};
if target_timestamp.is_none_or(|current| timestamp >= current) {
*target_timestamp = Some(timestamp);
if value
.as_deref()
.map(str::trim)
.is_some_and(|v| !v.is_empty())
{
*target_value = value;
}
}
}
fn optional_unix_secs_to_i64(
field_name: &str,
value: Option<u64>,
) -> Result<Option<i64>, DataLayerError> {
value
.map(|value| {
i64::try_from(value).map_err(|_| {
DataLayerError::UnexpectedValue(format!("{field_name} exceeds i64: {value}"))
})
})
.transpose()
}
fn optional_i64_to_unix_secs(
field_name: &str,
value: Option<i64>,
) -> Result<Option<u64>, DataLayerError> {
value
.map(|value| {
u64::try_from(value).map_err(|_| {
DataLayerError::UnexpectedValue(format!("{field_name} is negative: {value}"))
})
})
.transpose()
}
async fn enqueue_api_key_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
request_id: &str,
api_key_id: &str,
delta: &ApiKeyUsageDelta,
) -> Result<(), DataLayerError> {
if api_key_id.trim().is_empty() || delta.is_noop() {
return Ok(());
}
let total_cost_usd_delta = if delta.total_cost_usd.is_finite() {
delta.total_cost_usd
} else {
0.0
};
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id,
kind: USAGE_COUNTER_KIND_API_KEY,
target_id: api_key_id,
request_count_delta: 0,
total_requests_delta: delta.total_requests,
success_count_delta: 0,
error_count_delta: 0,
dns_failures_delta: 0,
stream_errors_delta: 0,
total_tokens_delta: delta.total_tokens,
total_cost_usd_delta,
total_response_time_ms_delta: 0,
last_used_at_unix_secs: None,
last_used_ip: None,
candidate_last_used_at_unix_secs: delta.candidate_last_used_at_unix_secs,
removed_last_used_at_unix_secs: delta.removed_last_used_at_unix_secs,
usage_created_at_unix_secs: None,
},
)
.await
}
async fn enqueue_model_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
request_id: &str,
model: &str,
delta: &ModelUsageDelta,
) -> Result<(), DataLayerError> {
if model.trim().is_empty() || delta.is_noop() {
return Ok(());
}
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id,
kind: USAGE_COUNTER_KIND_MODEL,
target_id: model,
request_count_delta: delta.request_count,
total_requests_delta: 0,
success_count_delta: 0,
error_count_delta: 0,
dns_failures_delta: 0,
stream_errors_delta: 0,
total_tokens_delta: 0,
total_cost_usd_delta: 0.0,
total_response_time_ms_delta: 0,
last_used_at_unix_secs: None,
last_used_ip: None,
candidate_last_used_at_unix_secs: None,
removed_last_used_at_unix_secs: None,
usage_created_at_unix_secs: None,
},
)
.await
}
async fn enqueue_provider_api_key_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
request_id: &str,
key_id: &str,
delta: &ProviderApiKeyUsageDelta,
) -> Result<(), DataLayerError> {
if key_id.trim().is_empty() || delta.is_noop() {
return Ok(());
}
let total_cost_usd_delta = if delta.total_cost_usd.is_finite() {
delta.total_cost_usd
} else {
0.0
};
insert_usage_counter_delta_in_tx(
tx,
UsageCounterDeltaInsert {
request_id,
kind: USAGE_COUNTER_KIND_PROVIDER_API_KEY,
target_id: key_id,
request_count_delta: delta.request_count,
total_requests_delta: 0,
success_count_delta: delta.success_count,
error_count_delta: delta.error_count,
dns_failures_delta: 0,
stream_errors_delta: 0,
total_tokens_delta: delta.total_tokens,
total_cost_usd_delta,
total_response_time_ms_delta: delta.total_response_time_ms,
last_used_at_unix_secs: None,
last_used_ip: None,
candidate_last_used_at_unix_secs: delta.candidate_last_used_at_unix_secs,
removed_last_used_at_unix_secs: delta.removed_last_used_at_unix_secs,
usage_created_at_unix_secs: delta.usage_created_at_unix_secs,
},
)
.await
}
struct UsageCounterDeltaInsert<'a> {
request_id: &'a str,
kind: &'a str,
target_id: &'a str,
request_count_delta: i64,
total_requests_delta: i64,
success_count_delta: i64,
error_count_delta: i64,
dns_failures_delta: i64,
stream_errors_delta: i64,
total_tokens_delta: i64,
total_cost_usd_delta: f64,
total_response_time_ms_delta: i64,
last_used_at_unix_secs: Option<u64>,
last_used_ip: Option<&'a str>,
candidate_last_used_at_unix_secs: Option<u64>,
removed_last_used_at_unix_secs: Option<u64>,
usage_created_at_unix_secs: Option<u64>,
}
async fn insert_usage_counter_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
input: UsageCounterDeltaInsert<'_>,
) -> Result<(), DataLayerError> {
let request_id = input.request_id.trim();
let target_id = input.target_id.trim();
if request_id.is_empty() || target_id.is_empty() {
return Ok(());
}
let candidate_last_used_at_unix_secs = optional_unix_secs_to_i64(
"usage counter candidate_last_used_at_unix_secs",
input.candidate_last_used_at_unix_secs,
)?;
let removed_last_used_at_unix_secs = optional_unix_secs_to_i64(
"usage counter removed_last_used_at_unix_secs",
input.removed_last_used_at_unix_secs,
)?;
let usage_created_at_unix_secs = optional_unix_secs_to_i64(
"usage counter usage_created_at_unix_secs",
input.usage_created_at_unix_secs,
)?;
let last_used_at_unix_secs = optional_unix_secs_to_i64(
"usage counter last_used_at_unix_secs",
input.last_used_at_unix_secs,
)?;
sqlx::query(INSERT_USAGE_COUNTER_DELTA_SQL)
.bind(Uuid::new_v4().to_string())
.bind(request_id)
.bind(input.kind)
.bind(target_id)
.bind(input.request_count_delta)
.bind(input.total_requests_delta)
.bind(input.success_count_delta)
.bind(input.error_count_delta)
.bind(input.dns_failures_delta)
.bind(input.stream_errors_delta)
.bind(input.total_tokens_delta)
.bind(input.total_cost_usd_delta)
.bind(input.total_response_time_ms_delta)
.bind(last_used_at_unix_secs)
.bind(
input
.last_used_ip
.map(str::trim)
.filter(|value| !value.is_empty()),
)
.bind(candidate_last_used_at_unix_secs)
.bind(removed_last_used_at_unix_secs)
.bind(usage_created_at_unix_secs)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn claim_usage_counter_deltas_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
batch_size: i64,
) -> Result<Vec<UsageCounterDeltaRow>, DataLayerError> {
let rows = sqlx::query(CLAIM_USAGE_COUNTER_DELTAS_SQL)
.bind(batch_size)
.fetch_all(&mut **tx)
.await
.map_postgres_err()?;
rows.iter().map(map_usage_counter_delta_row).collect()
}
async fn try_lock_usage_counter_flush_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
) -> Result<bool, DataLayerError> {
sqlx::query_scalar::<_, bool>(TRY_LOCK_USAGE_COUNTER_FLUSH_SQL)
.fetch_one(&mut **tx)
.await
.map_postgres_err()
}
fn map_usage_counter_delta_row(row: &PgRow) -> Result<UsageCounterDeltaRow, DataLayerError> {
Ok(UsageCounterDeltaRow {
id: row.try_get::<String, _>("id").map_postgres_err()?,
kind: row.try_get::<String, _>("kind").map_postgres_err()?,
target_id: row.try_get::<String, _>("target_id").map_postgres_err()?,
request_count_delta: row
.try_get::<i64, _>("request_count_delta")
.map_postgres_err()?,
total_requests_delta: row
.try_get::<i64, _>("total_requests_delta")
.map_postgres_err()?,
success_count_delta: row
.try_get::<i64, _>("success_count_delta")
.map_postgres_err()?,
error_count_delta: row
.try_get::<i64, _>("error_count_delta")
.map_postgres_err()?,
dns_failures_delta: row
.try_get::<i64, _>("dns_failures_delta")
.map_postgres_err()?,
stream_errors_delta: row
.try_get::<i64, _>("stream_errors_delta")
.map_postgres_err()?,
total_tokens_delta: row
.try_get::<i64, _>("total_tokens_delta")
.map_postgres_err()?,
total_cost_usd_delta: row
.try_get::<f64, _>("total_cost_usd_delta")
.map_postgres_err()?,
total_response_time_ms_delta: row
.try_get::<i64, _>("total_response_time_ms_delta")
.map_postgres_err()?,
last_used_at_unix_secs: optional_i64_to_unix_secs(
"usage_counter_deltas.last_used_at_unix_secs",
row.try_get::<Option<i64>, _>("last_used_at_unix_secs")
.map_postgres_err()?,
)?,
last_used_ip: row
.try_get::<Option<String>, _>("last_used_ip")
.map_postgres_err()?,
candidate_last_used_at_unix_secs: optional_i64_to_unix_secs(
"usage_counter_deltas.candidate_last_used_at_unix_secs",
row.try_get::<Option<i64>, _>("candidate_last_used_at_unix_secs")
.map_postgres_err()?,
)?,
removed_last_used_at_unix_secs: optional_i64_to_unix_secs(
"usage_counter_deltas.removed_last_used_at_unix_secs",
row.try_get::<Option<i64>, _>("removed_last_used_at_unix_secs")
.map_postgres_err()?,
)?,
usage_created_at_unix_secs: optional_i64_to_unix_secs(
"usage_counter_deltas.usage_created_at_unix_secs",
row.try_get::<Option<i64>, _>("usage_created_at_unix_secs")
.map_postgres_err()?,
)?,
})
}
async fn mark_usage_counter_deltas_processed_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
row_ids: &[String],
) -> Result<(), DataLayerError> {
if row_ids.is_empty() {
return Ok(());
}
sqlx::query(MARK_USAGE_COUNTER_DELTAS_PROCESSED_SQL)
.bind(row_ids)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn cleanup_processed_usage_counter_deltas_with_pool(
pool: &PgPool,
cutoff_unix_secs: u64,
batch_size: usize,
) -> Result<usize, DataLayerError> {
if batch_size == 0 {
return Ok(0);
}
let cutoff = i64::try_from(cutoff_unix_secs).map_err(|_| {
DataLayerError::InvalidInput(format!(
"usage counter cleanup cutoff exceeds i64: {cutoff_unix_secs}"
))
})?;
let limit = i64::try_from(batch_size).map_err(|_| {
DataLayerError::InvalidInput(format!(
"usage counter cleanup batch size is out of range: {batch_size}"
))
})?;
let deleted = sqlx::query(DELETE_PROCESSED_USAGE_COUNTER_DELTAS_SQL)
.bind(cutoff)
.bind(limit)
.execute(pool)
.await
.map_postgres_err()?
.rows_affected();
Ok(usize::try_from(deleted).unwrap_or(usize::MAX))
}
async fn apply_api_key_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
api_key_id: &str,
delta: &ApiKeyUsageDelta,
) -> Result<(), DataLayerError> {
if api_key_id.trim().is_empty() {
return Ok(());
}
if delta.is_noop() {
return Ok(());
}
let total_cost_usd_delta = if delta.total_cost_usd.is_finite() {
delta.total_cost_usd
} else {
0.0
};
sqlx::query(APPLY_API_KEY_USAGE_DELTA_SQL)
.bind(api_key_id)
.bind(delta.total_requests)
.bind(delta.total_tokens)
.bind(total_cost_usd_delta)
.bind(
delta
.candidate_last_used_at_unix_secs
.map(|value| value as f64),
)
.bind(
delta
.removed_last_used_at_unix_secs
.map(|value| value as f64),
)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_global_model_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
model: &str,
delta: &ModelUsageDelta,
) -> Result<(), DataLayerError> {
if model.trim().is_empty() {
return Ok(());
}
if delta.is_noop() {
return Ok(());
}
sqlx::query(APPLY_GLOBAL_MODEL_USAGE_DELTA_SQL)
.bind(model)
.bind(delta.request_count)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_provider_api_key_main_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
key_id: &str,
delta: &ProviderApiKeyUsageDelta,
) -> Result<(), DataLayerError> {
if key_id.trim().is_empty() {
return Ok(());
}
if delta.is_noop() {
return Ok(());
}
let total_cost_usd_delta = if delta.total_cost_usd.is_finite() {
delta.total_cost_usd
} else {
0.0
};
sqlx::query(APPLY_PROVIDER_API_KEY_USAGE_DELTA_SQL)
.bind(key_id)
.bind(delta.request_count)
.bind(delta.success_count)
.bind(delta.error_count)
.bind(delta.total_tokens)
.bind(total_cost_usd_delta)
.bind(delta.total_response_time_ms)
.bind(
delta
.candidate_last_used_at_unix_secs
.map(|value| value as f64),
)
.bind(
delta
.removed_last_used_at_unix_secs
.map(|value| value as f64),
)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_provider_monthly_usage_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
provider_id: &str,
total_cost_usd_delta: f64,
) -> Result<(), DataLayerError> {
if provider_id.trim().is_empty() || total_cost_usd_delta == 0.0 {
return Ok(());
}
if !total_cost_usd_delta.is_finite() {
return Err(DataLayerError::UnexpectedValue(format!(
"providers.monthly_used_usd delta is not finite for {provider_id}"
)));
}
sqlx::query(APPLY_PROVIDER_MONTHLY_USAGE_DELTA_SQL)
.bind(provider_id)
.bind(total_cost_usd_delta)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_proxy_node_counter_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
node_id: &str,
delta: &ProxyNodeCounterDelta,
) -> Result<(), DataLayerError> {
if delta.is_noop() || node_id.trim().is_empty() {
return Ok(());
}
sqlx::query(APPLY_PROXY_NODE_COUNTER_DELTA_SQL)
.bind(node_id)
.bind(delta.total_requests_delta)
.bind(delta.failed_requests_delta)
.bind(delta.dns_failures_delta)
.bind(delta.stream_errors_delta)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_management_token_counter_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
token_id: &str,
delta: &ManagementTokenCounterDelta,
) -> Result<(), DataLayerError> {
if delta.is_noop() || token_id.trim().is_empty() {
return Ok(());
}
let last_used_at = delta.last_used_at_unix_secs.map(|value| value as f64);
sqlx::query(APPLY_MANAGEMENT_TOKEN_COUNTER_DELTA_SQL)
.bind(token_id)
.bind(delta.usage_count_delta)
.bind(last_used_at)
.bind(delta.last_used_ip.as_deref())
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
async fn apply_api_key_last_used_delta_in_tx(
tx: &mut sqlx::Transaction<'_, Postgres>,
api_key_id: &str,
delta: &ApiKeyLastUsedDelta,
) -> Result<(), DataLayerError> {
if delta.is_noop() || api_key_id.trim().is_empty() {
return Ok(());
}
sqlx::query(APPLY_API_KEY_LAST_USED_DELTA_SQL)
.bind(api_key_id)
.bind(delta.last_used_at_unix_secs as f64)
.execute(&mut **tx)
.await
.map_postgres_err()?;
Ok(())
}
// Build the usage read model from the split storage layout.
//
// Query projections already prefer the newer audit/snapshot owners and only fall back to
// deprecated `public.usage` mirror columns for historical rows that predate the split schema.
//
// Some read paths intentionally project only the core usage fields. For the newer adjunct
// audit/snapshot columns, treat a missing projection the same as SQL NULL so older callers and
// partial rollouts do not fail the whole read.
fn row_try_get_optional<T>(
row: &sqlx::postgres::PgRow,
column: &str,
) -> Result<Option<T>, DataLayerError>
where
for<'r> T: sqlx::Decode<'r, sqlx::Postgres> + sqlx::Type<sqlx::Postgres>,
{
match row.try_get::<Option<T>, _>(column) {
Ok(value) => Ok(value),
Err(sqlx::Error::ColumnNotFound(_)) => Ok(None),
Err(error) => Err(postgres_error(error)),
}
}
fn map_usage_row(
row: &sqlx::postgres::PgRow,
resolve_compressed_bodies: bool,
) -> Result<StoredRequestUsageAudit, DataLayerError> {
let mut usage = StoredRequestUsageAudit::new(
row.try_get("id").map_postgres_err()?,
row.try_get("request_id").map_postgres_err()?,
row.try_get("user_id").map_postgres_err()?,
row.try_get("api_key_id").map_postgres_err()?,
row.try_get("username").map_postgres_err()?,
row.try_get("api_key_name").map_postgres_err()?,
row.try_get("provider_name").map_postgres_err()?,
row.try_get("model").map_postgres_err()?,
row.try_get("target_model").map_postgres_err()?,
row.try_get("provider_id").map_postgres_err()?,
row.try_get("provider_endpoint_id").map_postgres_err()?,
row.try_get("provider_api_key_id").map_postgres_err()?,
row.try_get("request_type").map_postgres_err()?,
row.try_get("api_format").map_postgres_err()?,
row.try_get("api_family").map_postgres_err()?,
row.try_get("endpoint_kind").map_postgres_err()?,
row.try_get("endpoint_api_format").map_postgres_err()?,
row.try_get("provider_api_family").map_postgres_err()?,
row.try_get("provider_endpoint_kind").map_postgres_err()?,
row.try_get("has_format_conversion").map_postgres_err()?,
row.try_get("is_stream").map_postgres_err()?,
row.try_get("input_tokens").map_postgres_err()?,
row.try_get("output_tokens").map_postgres_err()?,
row.try_get("total_tokens").map_postgres_err()?,
row.try_get("total_cost_usd").map_postgres_err()?,
row.try_get("actual_total_cost_usd").map_postgres_err()?,
row.try_get("status_code").map_postgres_err()?,
row.try_get("error_message").map_postgres_err()?,
row.try_get("error_category").map_postgres_err()?,
row.try_get("response_time_ms").map_postgres_err()?,
row.try_get("first_byte_time_ms").map_postgres_err()?,
row.try_get("status").map_postgres_err()?,
row.try_get("billing_status").map_postgres_err()?,
row.try_get("created_at_unix_ms").map_postgres_err()?,
row.try_get("updated_at_unix_secs").map_postgres_err()?,
row.try_get("finalized_at_unix_secs").map_postgres_err()?,
)?;
usage.cache_creation_input_tokens = row
.try_get::<Option<i32>, _>("cache_creation_input_tokens")
.map_postgres_err()?
.map(|value| to_u64(value, "usage.cache_creation_input_tokens"))
.transpose()?
.unwrap_or_default();
usage.cache_creation_ephemeral_5m_input_tokens = row
.try_get::<Option<i32>, _>("cache_creation_ephemeral_5m_input_tokens")
.map_postgres_err()?
.map(|value| to_u64(value, "usage.cache_creation_ephemeral_5m_input_tokens"))
.transpose()?
.unwrap_or_default();
usage.cache_creation_ephemeral_1h_input_tokens = row
.try_get::<Option<i32>, _>("cache_creation_ephemeral_1h_input_tokens")
.map_postgres_err()?
.map(|value| to_u64(value, "usage.cache_creation_ephemeral_1h_input_tokens"))
.transpose()?
.unwrap_or_default();
usage.cache_read_input_tokens = row
.try_get::<Option<i32>, _>("cache_read_input_tokens")
.map_postgres_err()?
.map(|value| to_u64(value, "usage.cache_read_input_tokens"))
.transpose()?
.unwrap_or_default();
usage.cache_creation_cost_usd = row
.try_get::<f64, _>("cache_creation_cost_usd")
.map_postgres_err()?;
usage.cache_read_cost_usd = row
.try_get::<f64, _>("cache_read_cost_usd")
.map_postgres_err()?;
usage.output_price_per_1m = row.try_get("output_price_per_1m").map_postgres_err()?;
usage.client_family = row
.try_get::<Option<String>, _>("client_family")
.map_postgres_err()?;
usage.request_headers = row.try_get("request_headers").map_postgres_err()?;
let request_body = usage_json_column(
row,
"request_body",
"request_body_compressed",
resolve_compressed_bodies,
)?;
usage.provider_request_headers = row.try_get("provider_request_headers").map_postgres_err()?;
let provider_request_body = usage_json_column(
row,
"provider_request_body",
"provider_request_body_compressed",
resolve_compressed_bodies,
)?;
usage.response_headers = row.try_get("response_headers").map_postgres_err()?;
let response_body = usage_json_column(
row,
"response_body",
"response_body_compressed",
resolve_compressed_bodies,
)?;
usage.client_response_headers = row.try_get("client_response_headers").map_postgres_err()?;
let client_response_body = usage_json_column(
row,
"client_response_body",
"client_response_body_compressed",
resolve_compressed_bodies,
)?;
let request_metadata: Option<Value> = row.try_get("request_metadata").map_postgres_err()?;
let http_audit_refs = UsageHttpAuditRefs {
request_body_ref: row_try_get_optional(row, "http_request_body_ref")?,
provider_request_body_ref: row_try_get_optional(row, "http_provider_request_body_ref")?,
response_body_ref: row_try_get_optional(row, "http_response_body_ref")?,
client_response_body_ref: row_try_get_optional(row, "http_client_response_body_ref")?,
};
let http_audit_states = UsageHttpAuditStates {
request_body_state: row_try_get_optional::<String>(row, "http_request_body_state")?
.as_deref()
.and_then(parse_usage_body_capture_state),
provider_request_body_state: row_try_get_optional::<String>(
row,
"http_provider_request_body_state",
)?
.as_deref()
.and_then(parse_usage_body_capture_state),
response_body_state: row_try_get_optional::<String>(row, "http_response_body_state")?
.as_deref()
.and_then(parse_usage_body_capture_state),
client_response_body_state: row_try_get_optional::<String>(
row,
"http_client_response_body_state",
)?
.as_deref()
.and_then(parse_usage_body_capture_state),
};
let routing_snapshot = usage_routing_snapshot_from_row(row)?;
let settlement_pricing_snapshot = usage_settlement_pricing_snapshot_from_row(row)?;
usage.request_body = request_body.value;
usage.provider_request_body = provider_request_body.value;
usage.response_body = response_body.value;
usage.client_response_body = client_response_body.value;
let request_metadata_object = request_metadata.as_ref().and_then(Value::as_object);
usage.request_body_ref = resolved_read_usage_body_ref(
None,
request_metadata_object,
&usage.request_id,
UsageBodyField::RequestBody,
request_body.has_compressed_storage,
http_audit_refs.request_body_ref.as_deref(),
);
usage.provider_request_body_ref = resolved_read_usage_body_ref(
None,
request_metadata_object,
&usage.request_id,
UsageBodyField::ProviderRequestBody,
provider_request_body.has_compressed_storage,
http_audit_refs.provider_request_body_ref.as_deref(),
);
usage.response_body_ref = resolved_read_usage_body_ref(
None,
request_metadata_object,
&usage.request_id,
UsageBodyField::ResponseBody,
response_body.has_compressed_storage,
http_audit_refs.response_body_ref.as_deref(),
);
usage.client_response_body_ref = resolved_read_usage_body_ref(
None,
request_metadata_object,
&usage.request_id,
UsageBodyField::ClientResponseBody,
client_response_body.has_compressed_storage,
http_audit_refs.client_response_body_ref.as_deref(),
);
usage.request_body_state = http_audit_states.request_body_state;
usage.provider_request_body_state = http_audit_states.provider_request_body_state;
usage.response_body_state = http_audit_states.response_body_state;
usage.client_response_body_state = http_audit_states.client_response_body_state;
usage.candidate_id = routing_snapshot.candidate_id.clone();
usage.candidate_index = routing_snapshot.candidate_index;
usage.key_name = routing_snapshot.key_name.clone();
usage.planner_kind = routing_snapshot.planner_kind.clone();
usage.route_family = routing_snapshot.route_family.clone();
usage.route_kind = routing_snapshot.route_kind.clone();
usage.execution_path = routing_snapshot.execution_path.clone();
usage.local_execution_runtime_miss_reason =
routing_snapshot.local_execution_runtime_miss_reason.clone();
usage.request_metadata = attach_usage_settlement_pricing_snapshot_metadata(
request_metadata,
&settlement_pricing_snapshot,
);
Ok(usage)
}
fn to_i32(value: u64) -> Result<i32, DataLayerError> {
i32::try_from(value).map_err(|_| {
DataLayerError::UnexpectedValue(format!("invalid usage integer value: {value}"))
})
}
fn to_u64(value: i32, field_name: &str) -> Result<u64, DataLayerError> {
u64::try_from(value)
.map_err(|_| DataLayerError::UnexpectedValue(format!("invalid {field_name}: {value}")))
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct UsageBodyStorage {
inline_json: Option<String>,
detached_blob_bytes: Option<Vec<u8>>,
}
impl UsageBodyStorage {
fn has_detached_blob(&self) -> bool {
self.detached_blob_bytes.is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
struct UsageBodyColumn {
value: Option<Value>,
has_compressed_storage: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct UsageHttpAuditRefs {
request_body_ref: Option<String>,
provider_request_body_ref: Option<String>,
response_body_ref: Option<String>,
client_response_body_ref: Option<String>,
}
impl UsageHttpAuditRefs {
fn any_present(&self) -> bool {
self.request_body_ref.is_some()
|| self.provider_request_body_ref.is_some()
|| self.response_body_ref.is_some()
|| self.client_response_body_ref.is_some()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct UsageHttpAuditStates {
request_body_state: Option<UsageBodyCaptureState>,
provider_request_body_state: Option<UsageBodyCaptureState>,
response_body_state: Option<UsageBodyCaptureState>,
client_response_body_state: Option<UsageBodyCaptureState>,
}
impl UsageHttpAuditStates {
fn any_present(&self) -> bool {
self.request_body_state.is_some()
|| self.provider_request_body_state.is_some()
|| self.response_body_state.is_some()
|| self.client_response_body_state.is_some()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct UsageHttpAuditHeaders<'a> {
request_headers_json: Option<&'a str>,
provider_request_headers_json: Option<&'a str>,
response_headers_json: Option<&'a str>,
client_response_headers_json: Option<&'a str>,
}
impl UsageHttpAuditHeaders<'_> {
fn any_present(&self) -> bool {
self.request_headers_json.is_some()
|| self.provider_request_headers_json.is_some()
|| self.response_headers_json.is_some()
|| self.client_response_headers_json.is_some()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct UsageRoutingSnapshot {
candidate_id: Option<String>,
candidate_index: Option<u64>,
key_name: Option<String>,
planner_kind: Option<String>,
route_family: Option<String>,
route_kind: Option<String>,
execution_path: Option<String>,
local_execution_runtime_miss_reason: Option<String>,
selected_provider_id: Option<String>,
selected_endpoint_id: Option<String>,
selected_provider_api_key_id: Option<String>,
has_format_conversion: Option<bool>,
}
impl UsageRoutingSnapshot {
fn has_metadata_fields(&self) -> bool {
self.candidate_id.is_some()
|| self.candidate_index.is_some()
|| self.key_name.is_some()
|| self.planner_kind.is_some()
|| self.route_family.is_some()
|| self.route_kind.is_some()
|| self.execution_path.is_some()
|| self.local_execution_runtime_miss_reason.is_some()
}
fn any_present(&self) -> bool {
self.has_metadata_fields()
|| self.selected_provider_id.is_some()
|| self.selected_endpoint_id.is_some()
|| self.selected_provider_api_key_id.is_some()
|| self.has_format_conversion.is_some()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
struct UsageSettlementPricingSnapshot {
billing_status: Option<String>,
billing_snapshot_schema_version: Option<String>,
billing_snapshot_status: Option<String>,
settlement_snapshot_schema_version: Option<String>,
settlement_snapshot: Option<Value>,
billing_dimensions: Option<Value>,
billing_input_tokens: Option<i64>,
billing_effective_input_tokens: Option<i64>,
billing_output_tokens: Option<i64>,
billing_cache_creation_tokens: Option<i64>,
billing_cache_creation_5m_tokens: Option<i64>,
billing_cache_creation_1h_tokens: Option<i64>,
billing_cache_read_tokens: Option<i64>,
billing_total_input_context: Option<i64>,
billing_cache_creation_cost_usd: Option<f64>,
billing_cache_read_cost_usd: Option<f64>,
billing_total_cost_usd: Option<f64>,
billing_actual_total_cost_usd: Option<f64>,
billing_pricing_source: Option<String>,
billing_rule_id: Option<String>,
billing_rule_version: Option<String>,
rate_multiplier: Option<f64>,
is_free_tier: Option<bool>,
input_price_per_1m: Option<f64>,
output_price_per_1m: Option<f64>,
cache_creation_price_per_1m: Option<f64>,
cache_read_price_per_1m: Option<f64>,
price_per_request: Option<f64>,
}
impl UsageSettlementPricingSnapshot {
fn any_present(&self) -> bool {
self.billing_status.is_some()
|| self.billing_snapshot_schema_version.is_some()
|| self.billing_snapshot_status.is_some()
|| self.settlement_snapshot_schema_version.is_some()
|| self.settlement_snapshot.is_some()
|| self.billing_dimensions.is_some()
|| self.billing_input_tokens.is_some()
|| self.billing_effective_input_tokens.is_some()
|| self.billing_output_tokens.is_some()
|| self.billing_cache_creation_tokens.is_some()
|| self.billing_cache_creation_5m_tokens.is_some()
|| self.billing_cache_creation_1h_tokens.is_some()
|| self.billing_cache_read_tokens.is_some()
|| self.billing_total_input_context.is_some()
|| self.billing_cache_creation_cost_usd.is_some()
|| self.billing_cache_read_cost_usd.is_some()
|| self.billing_total_cost_usd.is_some()
|| self.billing_actual_total_cost_usd.is_some()
|| self.billing_pricing_source.is_some()
|| self.billing_rule_id.is_some()
|| self.billing_rule_version.is_some()
|| self.rate_multiplier.is_some()
|| self.is_free_tier.is_some()
|| self.input_price_per_1m.is_some()
|| self.output_price_per_1m.is_some()
|| self.cache_creation_price_per_1m.is_some()
|| self.cache_read_price_per_1m.is_some()
|| self.price_per_request.is_some()
}
}
fn prepare_usage_body_storage(value: Option<&Value>) -> Result<UsageBodyStorage, DataLayerError> {
let Some(value) = value else {
return Ok(UsageBodyStorage {
inline_json: None,
detached_blob_bytes: None,
});
};
let bytes = serde_json::to_vec(value).map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to serialize usage json: {err}"))
})?;
if bytes.len() == MAX_INLINE_USAGE_BODY_BYTES {
return Ok(UsageBodyStorage {
inline_json: Some(String::from_utf8(bytes).map_err(|err| {
DataLayerError::UnexpectedValue(format!(
"failed to encode inline usage body as utf-8: {err}"
))
})?),
detached_blob_bytes: None,
});
}
let mut encoder = GzEncoder::new(Vec::new(), Compression::new(6));
encoder.write_all(&bytes).map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to compress usage json: {err}"))
})?;
let detached_blob_bytes = encoder.finish().map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to finish usage json compression: {err}"))
})?;
Ok(UsageBodyStorage {
inline_json: None,
detached_blob_bytes: Some(detached_blob_bytes),
})
}
fn json_bind_text(value: Option<&Value>) -> Result<Option<String>, DataLayerError> {
value
.map(|value| {
serde_json::to_string(value).map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to serialize usage json: {err}"))
})
})
.transpose()
}
fn usage_body_capture_state_bind_text(
value: Option<UsageBodyCaptureState>,
) -> Option<&'static str> {
value.map(UsageBodyCaptureState::as_str)
}
fn parse_usage_body_capture_state(value: &str) -> Option<UsageBodyCaptureState> {
match value.trim() {
"none" => Some(UsageBodyCaptureState::None),
"inline" => Some(UsageBodyCaptureState::Inline),
"reference" => Some(UsageBodyCaptureState::Reference),
"truncated" => Some(UsageBodyCaptureState::Truncated),
"disabled" => Some(UsageBodyCaptureState::Disabled),
"unavailable" => Some(UsageBodyCaptureState::Unavailable),
_ => None,
}
}
#[cfg(test)]
fn usage_http_audit_body_refs(metadata: Option<&Value>) -> UsageHttpAuditRefs {
let object = metadata.and_then(Value::as_object);
UsageHttpAuditRefs {
request_body_ref: metadata_ref_value(object, "request_body_ref"),
provider_request_body_ref: metadata_ref_value(object, "provider_request_body_ref"),
response_body_ref: metadata_ref_value(object, "response_body_ref"),
client_response_body_ref: metadata_ref_value(object, "client_response_body_ref"),
}
}
fn resolved_read_usage_body_ref(
explicit_ref: Option<&str>,
metadata: Option<&serde_json::Map<String, Value>>,
request_id: &str,
field: UsageBodyField,
has_compressed_storage: bool,
http_audit_ref: Option<&str>,
) -> Option<String> {
explicit_ref
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.or_else(|| {
http_audit_ref
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
})
.or_else(|| has_compressed_storage.then(|| usage_body_ref(request_id, field)))
.or_else(|| metadata_usage_body_ref_value(metadata, request_id, field))
}
fn resolved_write_usage_body_ref(
explicit_ref: Option<&str>,
request_id: &str,
field: UsageBodyField,
has_compressed_storage: bool,
http_audit_ref: Option<&str>,
) -> Option<String> {
explicit_ref
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.or_else(|| has_compressed_storage.then(|| usage_body_ref(request_id, field)))
.or_else(|| {
http_audit_ref
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
})
}
fn metadata_ref_value(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<String> {
metadata
.and_then(|object| object.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn metadata_usage_body_ref_value(
metadata: Option<&serde_json::Map<String, Value>>,
request_id: &str,
field: UsageBodyField,
) -> Option<String> {
metadata_ref_value(metadata, field.as_ref_key())
.and_then(|value| parse_usage_body_ref(&value))
.filter(|(parsed_request_id, parsed_field)| {
parsed_request_id == request_id && *parsed_field == field
})
.map(|(parsed_request_id, parsed_field)| usage_body_ref(&parsed_request_id, parsed_field))
}
fn metadata_number_value(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<f64> {
metadata
.and_then(|object| object.get(key))
.and_then(Value::as_f64)
.filter(|value| value.is_finite())
}
fn metadata_u64_value(metadata: Option<&serde_json::Map<String, Value>>, key: &str) -> Option<u64> {
metadata.and_then(|object| {
object.get(key).and_then(|value| {
value
.as_u64()
.or_else(|| value.as_i64().and_then(|number| u64::try_from(number).ok()))
})
})
}
fn metadata_bool_value(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<bool> {
metadata
.and_then(|object| object.get(key))
.and_then(Value::as_bool)
}
fn billing_snapshot_object(
metadata: Option<&serde_json::Map<String, Value>>,
) -> Option<&serde_json::Map<String, Value>> {
metadata
.and_then(|object| object.get("billing_snapshot"))
.and_then(Value::as_object)
}
fn billing_snapshot_string_value(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<String> {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn billing_snapshot_resolved_number(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<f64> {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get("resolved_variables"))
.and_then(Value::as_object)
.and_then(|variables| variables.get(key))
.and_then(Value::as_f64)
.filter(|value| value.is_finite())
}
fn settlement_snapshot_object(
metadata: Option<&serde_json::Map<String, Value>>,
) -> Option<&serde_json::Map<String, Value>> {
metadata
.and_then(|object| object.get("settlement_snapshot"))
.and_then(Value::as_object)
}
fn settlement_snapshot_schema_version(
metadata: Option<&serde_json::Map<String, Value>>,
) -> Option<String> {
metadata_ref_value(metadata, "settlement_snapshot_schema_version").or_else(|| {
settlement_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get("schema_version"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
})
}
fn settlement_snapshot_value(metadata: Option<&serde_json::Map<String, Value>>) -> Option<Value> {
metadata
.and_then(|object| object.get("settlement_snapshot"))
.cloned()
}
fn settlement_snapshot_child_value<'a>(
metadata: Option<&'a serde_json::Map<String, Value>>,
child: &str,
) -> Option<&'a Value> {
settlement_snapshot_object(metadata).and_then(|snapshot| snapshot.get(child))
}
fn settlement_snapshot_child_object<'a>(
metadata: Option<&'a serde_json::Map<String, Value>>,
child: &str,
) -> Option<&'a serde_json::Map<String, Value>> {
settlement_snapshot_child_value(metadata, child).and_then(Value::as_object)
}
fn metadata_or_snapshot_dimensions(
metadata: Option<&serde_json::Map<String, Value>>,
) -> Option<Value> {
metadata
.and_then(|object| object.get("billing_dimensions"))
.cloned()
.or_else(|| settlement_snapshot_child_value(metadata, "resolved_dimensions").cloned())
.or_else(|| {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get("resolved_dimensions"))
.cloned()
})
}
fn json_i64_value(value: &Value) -> Option<i64> {
value
.as_i64()
.or_else(|| value.as_u64().and_then(|number| i64::try_from(number).ok()))
}
fn billing_dimension_i64(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<i64> {
metadata_or_snapshot_dimensions(metadata)
.and_then(|dimensions| dimensions.get(key).and_then(json_i64_value))
.filter(|value| *value >= 0)
}
fn settlement_snapshot_number(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<f64> {
settlement_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get(key))
.and_then(Value::as_f64)
.filter(|value| value.is_finite())
}
fn billing_snapshot_number(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<f64> {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get(key))
.and_then(Value::as_f64)
.filter(|value| value.is_finite())
}
fn settlement_cost_breakdown_number(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<f64> {
settlement_snapshot_child_object(metadata, "cost_breakdown")
.or_else(|| {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get("cost_breakdown"))
.and_then(Value::as_object)
})
.and_then(|breakdown| breakdown.get(key))
.and_then(Value::as_f64)
.filter(|value| value.is_finite())
}
fn settlement_cache_creation_cost(
metadata: Option<&serde_json::Map<String, Value>>,
) -> Option<f64> {
let keys = [
"cache_creation_uncategorized_cost",
"cache_creation_ephemeral_5m_cost",
"cache_creation_ephemeral_1h_cost",
"cache_creation_cost",
];
let mut found = false;
let total = keys.into_iter().fold(0.0, |sum, key| {
if let Some(value) = settlement_cost_breakdown_number(metadata, key) {
found = true;
sum + value
} else {
sum
}
});
found.then_some(total)
}
fn settlement_snapshot_nested_string(
metadata: Option<&serde_json::Map<String, Value>>,
child: &str,
key: &str,
) -> Option<String> {
settlement_snapshot_child_object(metadata, child)
.and_then(|object| object.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn billing_snapshot_string_field(
metadata: Option<&serde_json::Map<String, Value>>,
key: &str,
) -> Option<String> {
billing_snapshot_object(metadata)
.and_then(|snapshot| snapshot.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn usage_http_audit_capture_mode(
refs: &UsageHttpAuditRefs,
body_values: [Option<&Value>; 4],
) -> &'static str {
if refs.any_present() {
return "ref_backed";
}
if body_values.iter().any(Option::is_some) {
return "inline_legacy";
}
"none"
}
fn usage_routing_snapshot_from_usage(
usage: &UpsertUsageRecord,
metadata: Option<&Value>,
) -> UsageRoutingSnapshot {
let object = metadata.and_then(Value::as_object);
let mut snapshot = UsageRoutingSnapshot {
candidate_id: usage
.candidate_id
.clone()
.or_else(|| metadata_ref_value(object, "candidate_id")),
candidate_index: usage
.candidate_index
.or_else(|| metadata_u64_value(object, "candidate_index")),
key_name: usage
.key_name
.clone()
.or_else(|| metadata_ref_value(object, "key_name")),
planner_kind: usage
.planner_kind
.clone()
.or_else(|| metadata_ref_value(object, "planner_kind")),
route_family: usage
.route_family
.clone()
.or_else(|| metadata_ref_value(object, "route_family")),
route_kind: usage
.route_kind
.clone()
.or_else(|| metadata_ref_value(object, "route_kind")),
execution_path: usage
.execution_path
.clone()
.or_else(|| metadata_ref_value(object, "execution_path")),
local_execution_runtime_miss_reason: usage
.local_execution_runtime_miss_reason
.clone()
.or_else(|| metadata_ref_value(object, "local_execution_runtime_miss_reason")),
selected_provider_id: None,
selected_endpoint_id: None,
selected_provider_api_key_id: None,
has_format_conversion: None,
};
if !snapshot.has_metadata_fields() {
return snapshot;
}
snapshot.selected_provider_id = usage.provider_id.clone();
snapshot.selected_endpoint_id = usage.provider_endpoint_id.clone();
snapshot.selected_provider_api_key_id = usage.provider_api_key_id.clone();
snapshot.has_format_conversion = usage.has_format_conversion;
snapshot
}
fn usage_optional_i64(value: Option<u64>, field_name: &str) -> Result<Option<i64>, DataLayerError> {
value
.map(|value| {
i64::try_from(value).map_err(|_| {
DataLayerError::UnexpectedValue(format!(
"usage {field_name} exceeds bigint: {value}"
))
})
})
.transpose()
}
fn usage_cache_creation_tokens_from_parts(
uncategorized: Option<i64>,
ephemeral_5m: Option<i64>,
ephemeral_1h: Option<i64>,
) -> Option<i64> {
let categorized = ephemeral_5m
.unwrap_or_default()
.saturating_add(ephemeral_1h.unwrap_or_default());
match uncategorized {
Some(0) if categorized > 0 => Some(categorized),
Some(value) => Some(value),
None if categorized > 0 => Some(categorized),
None => None,
}
}
fn usage_normalized_api_family(usage: &UpsertUsageRecord) -> String {
usage
.endpoint_api_format
.as_deref()
.or(usage.api_format.as_deref())
.unwrap_or_default()
.split(':')
.next()
.unwrap_or_default()
.trim()
.to_ascii_lowercase()
}
fn usage_effective_input_tokens(
input_tokens: Option<i64>,
cache_read_tokens: Option<i64>,
api_family: &str,
) -> Option<i64> {
let input_tokens = input_tokens?;
let cache_read_tokens = cache_read_tokens.unwrap_or_default();
if matches!(api_family, "openai" | "gemini" | "google")
&& input_tokens > 0
&& cache_read_tokens > 0
{
return Some(input_tokens.saturating_sub(cache_read_tokens));
}
Some(input_tokens)
}
fn usage_total_input_context(
input_tokens: Option<i64>,
effective_input_tokens: Option<i64>,
cache_creation_tokens: Option<i64>,
cache_read_tokens: Option<i64>,
api_family: &str,
) -> Option<i64> {
if input_tokens.is_none()
&& effective_input_tokens.is_none()
&& cache_creation_tokens.is_none()
&& cache_read_tokens.is_none()
{
return None;
}
let input_tokens = input_tokens.unwrap_or_default();
let effective_input_tokens = effective_input_tokens.unwrap_or(input_tokens);
let cache_creation_tokens = cache_creation_tokens.unwrap_or_default();
let cache_read_tokens = cache_read_tokens.unwrap_or_default();
match api_family {
"claude" | "anthropic" => Some(
input_tokens
.saturating_add(cache_creation_tokens)
.saturating_add(cache_read_tokens),
),
"openai" | "gemini" | "google" => {
Some(effective_input_tokens.saturating_add(cache_read_tokens))
}
_ => Some(
input_tokens
.saturating_add(cache_creation_tokens)
.saturating_add(cache_read_tokens),
),
}
}
fn usage_settlement_pricing_snapshot_from_usage(
usage: &UpsertUsageRecord,
metadata: Option<&Value>,
) -> Result<UsageSettlementPricingSnapshot, DataLayerError> {
let object = metadata.and_then(Value::as_object);
let billing_dimensions = metadata_or_snapshot_dimensions(object);
let has_billing_dimensions = billing_dimensions.is_some();
let usage_input_tokens = usage_optional_i64(usage.input_tokens, "input_tokens")?;
let usage_output_tokens = usage_optional_i64(usage.output_tokens, "output_tokens")?;
let usage_cache_creation_uncategorized_tokens = usage_optional_i64(
usage.cache_creation_input_tokens,
"cache_creation_input_tokens",
)?;
let usage_cache_creation_5m_tokens = usage_optional_i64(
usage.cache_creation_ephemeral_5m_input_tokens,
"cache_creation_ephemeral_5m_input_tokens",
)?;
let usage_cache_creation_1h_tokens = usage_optional_i64(
usage.cache_creation_ephemeral_1h_input_tokens,
"cache_creation_ephemeral_1h_input_tokens",
)?;
let usage_cache_read_tokens =
usage_optional_i64(usage.cache_read_input_tokens, "cache_read_input_tokens")?;
let usage_cache_creation_tokens = usage_cache_creation_tokens_from_parts(
usage_cache_creation_uncategorized_tokens,
usage_cache_creation_5m_tokens,
usage_cache_creation_1h_tokens,
);
let billing_cache_creation_tokens = billing_dimension_i64(object, "cache_creation_tokens")
.or_else(|| {
usage_cache_creation_tokens_from_parts(
billing_dimension_i64(object, "cache_creation_uncategorized_tokens"),
billing_dimension_i64(object, "cache_creation_ephemeral_5m_tokens"),
billing_dimension_i64(object, "cache_creation_ephemeral_1h_tokens"),
)
})
.or(usage_cache_creation_tokens);
let billing_cache_creation_5m_tokens =
billing_dimension_i64(object, "cache_creation_ephemeral_5m_tokens")
.or(usage_cache_creation_5m_tokens);
let billing_cache_creation_1h_tokens =
billing_dimension_i64(object, "cache_creation_ephemeral_1h_tokens")
.or(usage_cache_creation_1h_tokens);
let billing_input_tokens = billing_dimension_i64(object, "input_tokens").or(usage_input_tokens);
let billing_output_tokens =
billing_dimension_i64(object, "output_tokens").or(usage_output_tokens);
let billing_cache_read_tokens =
billing_dimension_i64(object, "cache_read_tokens").or(usage_cache_read_tokens);
let api_family = usage_normalized_api_family(usage);
let billing_effective_input_tokens = billing_dimension_i64(object, "effective_input_tokens")
.or_else(|| {
has_billing_dimensions
.then(|| billing_dimension_i64(object, "input_tokens"))
.flatten()
})
.or_else(|| {
usage_effective_input_tokens(
billing_input_tokens,
billing_cache_read_tokens,
api_family.as_str(),
)
});
let billing_total_input_context =
billing_dimension_i64(object, "total_input_context").or_else(|| {
usage_total_input_context(
billing_input_tokens,
billing_effective_input_tokens,
billing_cache_creation_tokens,
billing_cache_read_tokens,
api_family.as_str(),
)
});
let snapshot = UsageSettlementPricingSnapshot {
billing_status: Some(usage.billing_status.clone()),
billing_snapshot_schema_version: metadata_ref_value(
object,
"billing_snapshot_schema_version",
)
.or_else(|| billing_snapshot_string_value(object, "schema_version")),
billing_snapshot_status: metadata_ref_value(object, "billing_snapshot_status")
.or_else(|| billing_snapshot_string_value(object, "status")),
settlement_snapshot_schema_version: settlement_snapshot_schema_version(object),
settlement_snapshot: settlement_snapshot_value(object),
billing_dimensions,
billing_input_tokens,
billing_effective_input_tokens,
billing_output_tokens,
billing_cache_creation_tokens,
billing_cache_creation_5m_tokens,
billing_cache_creation_1h_tokens,
billing_cache_read_tokens,
billing_total_input_context,
billing_cache_creation_cost_usd: settlement_cache_creation_cost(object)
.or(usage.cache_creation_cost_usd),
billing_cache_read_cost_usd: settlement_cost_breakdown_number(object, "cache_read_cost")
.or(usage.cache_read_cost_usd),
billing_total_cost_usd: settlement_snapshot_number(object, "total_cost")
.or_else(|| billing_snapshot_number(object, "total_cost"))
.or(usage.total_cost_usd),
billing_actual_total_cost_usd: settlement_snapshot_number(object, "actual_total_cost")
.or(usage.actual_total_cost_usd),
billing_pricing_source: settlement_snapshot_nested_string(
object,
"pricing_snapshot",
"pricing_source",
),
billing_rule_id: settlement_snapshot_nested_string(
object,
"billing_plan_snapshot",
"rule_id",
)
.or_else(|| billing_snapshot_string_field(object, "rule_id")),
billing_rule_version: settlement_snapshot_nested_string(
object,
"billing_plan_snapshot",
"rule_version",
),
rate_multiplier: metadata_number_value(object, "rate_multiplier"),
is_free_tier: metadata_bool_value(object, "is_free_tier"),
input_price_per_1m: metadata_number_value(object, "input_price_per_1m")
.or_else(|| billing_snapshot_resolved_number(object, "input_price_per_1m")),
output_price_per_1m: metadata_number_value(object, "output_price_per_1m")
.or_else(|| billing_snapshot_resolved_number(object, "output_price_per_1m"))
.or(usage.output_price_per_1m),
cache_creation_price_per_1m: metadata_number_value(object, "cache_creation_price_per_1m")
.or_else(|| billing_snapshot_resolved_number(object, "cache_creation_price_per_1m")),
cache_read_price_per_1m: metadata_number_value(object, "cache_read_price_per_1m")
.or_else(|| billing_snapshot_resolved_number(object, "cache_read_price_per_1m")),
price_per_request: metadata_number_value(object, "price_per_request")
.or_else(|| billing_snapshot_resolved_number(object, "price_per_request")),
};
Ok(if snapshot.any_present() {
snapshot
} else {
UsageSettlementPricingSnapshot::default()
})
}
// Decode deprecated inline/compressed body columns from `public.usage`.
//
// New writes keep these columns empty by forcing body storage through `usage_body_blobs` and
// `usage_http_audits`; this helper exists only so older rows remain readable without backfill.
fn usage_json_column(
row: &sqlx::postgres::PgRow,
inline_column: &str,
compressed_column: &str,
resolve_compressed: bool,
) -> Result<UsageBodyColumn, DataLayerError> {
let inline = row
.try_get::<Option<Value>, _>(inline_column)
.map_postgres_err()?;
if inline.is_some() {
return Ok(UsageBodyColumn {
value: inline,
has_compressed_storage: false,
});
}
let compressed = row
.try_get::<Option<Vec<u8>>, _>(compressed_column)
.map_postgres_err()?;
let has_compressed_storage = compressed.is_some();
let value = if resolve_compressed {
compressed
.map(|bytes| inflate_usage_json_value(&bytes))
.transpose()?
} else {
None
};
Ok(UsageBodyColumn {
value,
has_compressed_storage,
})
}
fn inflate_usage_json_value(bytes: &[u8]) -> Result<Value, DataLayerError> {
let mut decoder = GzDecoder::new(bytes);
let mut json_bytes = Vec::new();
decoder.read_to_end(&mut json_bytes).map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to decompress usage json: {err}"))
})?;
serde_json::from_slice(&json_bytes).map_err(|err| {
DataLayerError::UnexpectedValue(format!("failed to parse decompressed usage json: {err}"))
})
}
#[cfg(test)]
fn attach_compressed_body_refs(
request_id: &str,
metadata: Option<Value>,
has_request_body_compressed: bool,
has_provider_request_body_compressed: bool,
has_response_body_compressed: bool,
has_client_response_body_compressed: bool,
) -> Option<Value> {
let mut metadata = match metadata {
Some(Value::Object(object)) => object,
Some(value) => return Some(value),
None => Map::new(),
};
maybe_insert_usage_body_ref(
&mut metadata,
"request_body_ref",
request_id,
"request_body",
has_request_body_compressed,
);
maybe_insert_usage_body_ref(
&mut metadata,
"provider_request_body_ref",
request_id,
"provider_request_body",
has_provider_request_body_compressed,
);
maybe_insert_usage_body_ref(
&mut metadata,
"response_body_ref",
request_id,
"response_body",
has_response_body_compressed,
);
maybe_insert_usage_body_ref(
&mut metadata,
"client_response_body_ref",
request_id,
"client_response_body",
has_client_response_body_compressed,
);
(!metadata.is_empty()).then_some(Value::Object(metadata))
}
#[cfg(test)]
fn attach_usage_http_audit_body_refs(
metadata: Option<Value>,
refs: &UsageHttpAuditRefs,
) -> Option<Value> {
if !refs.any_present() {
return metadata;
}
let mut metadata = match metadata {
Some(Value::Object(object)) => object,
Some(value) => return Some(value),
None => Map::new(),
};
maybe_insert_string_value(
&mut metadata,
"request_body_ref",
refs.request_body_ref.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"provider_request_body_ref",
refs.provider_request_body_ref.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"response_body_ref",
refs.response_body_ref.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"client_response_body_ref",
refs.client_response_body_ref.as_deref(),
);
(!metadata.is_empty()).then_some(Value::Object(metadata))
}
#[cfg(test)]
fn attach_usage_routing_snapshot_metadata(
metadata: Option<Value>,
snapshot: &UsageRoutingSnapshot,
) -> Option<Value> {
if !snapshot.has_metadata_fields() {
return metadata;
}
let mut metadata = match metadata {
Some(Value::Object(object)) => object,
Some(value) => return Some(value),
None => Map::new(),
};
maybe_insert_string_value(
&mut metadata,
"candidate_id",
snapshot.candidate_id.as_deref(),
);
maybe_insert_string_value(&mut metadata, "key_name", snapshot.key_name.as_deref());
maybe_insert_string_value(
&mut metadata,
"planner_kind",
snapshot.planner_kind.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"route_family",
snapshot.route_family.as_deref(),
);
maybe_insert_string_value(&mut metadata, "route_kind", snapshot.route_kind.as_deref());
maybe_insert_string_value(
&mut metadata,
"execution_path",
snapshot.execution_path.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"local_execution_runtime_miss_reason",
snapshot.local_execution_runtime_miss_reason.as_deref(),
);
(!metadata.is_empty()).then_some(Value::Object(metadata))
}
fn prepare_request_metadata_for_body_storage<const N: usize>(
metadata: Option<Value>,
body_fields: [(
UsageBodyField,
&UsageBodyStorage,
Option<&Value>,
Option<&str>,
); N],
) -> Option<Value> {
let mut metadata = match metadata {
Some(Value::Object(object)) => object,
Some(value) => {
let mut object = Map::new();
object.insert("request_metadata".to_string(), value);
object
}
None => Map::new(),
};
let should_replace = !metadata.is_empty()
|| body_fields.iter().any(|(_, storage, value, explicit_ref)| {
storage.has_detached_blob() || value.is_some() || explicit_ref.is_some()
});
if !should_replace {
return None;
}
for (field, storage, value, explicit_ref) in body_fields {
if storage.has_detached_blob() || value.is_some() || explicit_ref.is_some() {
let ref_key = field.as_ref_key();
metadata.remove(ref_key);
}
}
Some(Value::Object(metadata))
}
async fn sync_usage_body_blob_storage<'e, E>(
executor: E,
request_id: &str,
field: UsageBodyField,
value: Option<&Value>,
storage: &UsageBodyStorage,
) -> Result<(), DataLayerError>
where
E: sqlx::Executor<'e, Database = Postgres>,
{
let body_ref = usage_body_ref(request_id, field);
if let Some(payload_gzip) = storage.detached_blob_bytes.as_ref() {
sqlx::query(UPSERT_USAGE_BODY_BLOB_SQL)
.bind(&body_ref)
.bind(request_id)
.bind(field.as_storage_field())
.bind(payload_gzip)
.execute(executor)
.await
.map_postgres_err()?;
return Ok(());
}
if value.is_some() {
sqlx::query(DELETE_USAGE_BODY_BLOB_SQL)
.bind(&body_ref)
.execute(executor)
.await
.map_postgres_err()?;
}
Ok(())
}
async fn sync_usage_http_audit_storage<'e, E>(
executor: E,
request_id: &str,
headers: &UsageHttpAuditHeaders<'_>,
refs: &UsageHttpAuditRefs,
states: &UsageHttpAuditStates,
body_capture_mode: &str,
) -> Result<(), DataLayerError>
where
E: sqlx::Executor<'e, Database = Postgres>,
{
if !headers.any_present()
&& !refs.any_present()
&& !states.any_present()
&& body_capture_mode == "none"
{
return Ok(());
}
sqlx::query(UPSERT_USAGE_HTTP_AUDIT_SQL)
.bind(request_id)
.bind(headers.request_headers_json)
.bind(headers.provider_request_headers_json)
.bind(headers.response_headers_json)
.bind(headers.client_response_headers_json)
.bind(refs.request_body_ref.as_deref())
.bind(refs.provider_request_body_ref.as_deref())
.bind(refs.response_body_ref.as_deref())
.bind(refs.client_response_body_ref.as_deref())
.bind(usage_body_capture_state_bind_text(
states.request_body_state,
))
.bind(usage_body_capture_state_bind_text(
states.provider_request_body_state,
))
.bind(usage_body_capture_state_bind_text(
states.response_body_state,
))
.bind(usage_body_capture_state_bind_text(
states.client_response_body_state,
))
.bind(body_capture_mode)
.execute(executor)
.await
.map_postgres_err()?;
Ok(())
}
async fn sync_usage_routing_snapshot_storage<'e, E>(
executor: E,
request_id: &str,
snapshot: &UsageRoutingSnapshot,
) -> Result<(), DataLayerError>
where
E: sqlx::Executor<'e, Database = Postgres>,
{
if !snapshot.any_present() {
return Ok(());
}
sqlx::query(UPSERT_USAGE_ROUTING_SNAPSHOT_SQL)
.bind(request_id)
.bind(snapshot.candidate_id.as_deref())
.bind(snapshot.candidate_index.map(to_i32).transpose()?)
.bind(snapshot.key_name.as_deref())
.bind(snapshot.planner_kind.as_deref())
.bind(snapshot.route_family.as_deref())
.bind(snapshot.route_kind.as_deref())
.bind(snapshot.execution_path.as_deref())
.bind(snapshot.local_execution_runtime_miss_reason.as_deref())
.bind(snapshot.selected_provider_id.as_deref())
.bind(snapshot.selected_endpoint_id.as_deref())
.bind(snapshot.selected_provider_api_key_id.as_deref())
.bind(snapshot.has_format_conversion)
.execute(executor)
.await
.map_postgres_err()?;
Ok(())
}
async fn sync_usage_settlement_pricing_snapshot_storage<'e, E>(
executor: E,
request_id: &str,
snapshot: &UsageSettlementPricingSnapshot,
) -> Result<(), DataLayerError>
where
E: sqlx::Executor<'e, Database = Postgres>,
{
if !snapshot.any_present() {
return Ok(());
}
sqlx::query(UPSERT_USAGE_SETTLEMENT_PRICING_SNAPSHOT_SQL)
.bind(request_id)
.bind(snapshot.billing_status.as_deref().unwrap_or("pending"))
.bind(snapshot.billing_snapshot_schema_version.as_deref())
.bind(snapshot.billing_snapshot_status.as_deref())
.bind(snapshot.settlement_snapshot_schema_version.as_deref())
.bind(snapshot.settlement_snapshot.as_ref())
.bind(snapshot.billing_dimensions.as_ref())
.bind(snapshot.billing_input_tokens)
.bind(snapshot.billing_effective_input_tokens)
.bind(snapshot.billing_output_tokens)
.bind(snapshot.billing_cache_creation_tokens)
.bind(snapshot.billing_cache_creation_5m_tokens)
.bind(snapshot.billing_cache_creation_1h_tokens)
.bind(snapshot.billing_cache_read_tokens)
.bind(snapshot.billing_total_input_context)
.bind(snapshot.billing_cache_creation_cost_usd)
.bind(snapshot.billing_cache_read_cost_usd)
.bind(snapshot.billing_total_cost_usd)
.bind(snapshot.billing_actual_total_cost_usd)
.bind(snapshot.billing_pricing_source.as_deref())
.bind(snapshot.billing_rule_id.as_deref())
.bind(snapshot.billing_rule_version.as_deref())
.bind(snapshot.rate_multiplier)
.bind(snapshot.is_free_tier)
.bind(snapshot.input_price_per_1m)
.bind(snapshot.output_price_per_1m)
.bind(snapshot.cache_creation_price_per_1m)
.bind(snapshot.cache_read_price_per_1m)
.bind(snapshot.price_per_request)
.execute(executor)
.await
.map_postgres_err()?;
Ok(())
}
#[cfg(test)]
fn maybe_insert_usage_body_ref(
metadata: &mut Map<String, Value>,
key: &str,
request_id: &str,
field: &str,
should_insert: bool,
) {
if !should_insert || metadata.contains_key(key) {
return;
}
metadata.insert(
key.to_string(),
Value::String(usage_body_ref(
request_id,
UsageBodyField::from_storage_field(field).expect("known usage body field"),
)),
);
}
fn maybe_insert_string_value(metadata: &mut Map<String, Value>, key: &str, value: Option<&str>) {
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
return;
};
if metadata.contains_key(key) {
return;
}
metadata.insert(key.to_string(), Value::String(value.to_string()));
}
fn maybe_insert_number_value(metadata: &mut Map<String, Value>, key: &str, value: Option<f64>) {
let Some(value) = value.filter(|value| value.is_finite()) else {
return;
};
if metadata.contains_key(key) {
return;
}
let Some(number) = serde_json::Number::from_f64(value) else {
return;
};
metadata.insert(key.to_string(), Value::Number(number));
}
fn maybe_insert_bool_value(metadata: &mut Map<String, Value>, key: &str, value: Option<bool>) {
let Some(value) = value else {
return;
};
if metadata.contains_key(key) {
return;
}
metadata.insert(key.to_string(), Value::Bool(value));
}
fn usage_routing_snapshot_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<UsageRoutingSnapshot, DataLayerError> {
Ok(UsageRoutingSnapshot {
candidate_id: row_try_get_optional(row, "routing_candidate_id")?,
candidate_index: row_try_get_optional::<i32>(row, "routing_candidate_index")?
.map(|value| to_u64(value, "usage_routing_snapshots.candidate_index"))
.transpose()?,
key_name: row_try_get_optional(row, "routing_key_name")?,
planner_kind: row_try_get_optional(row, "routing_planner_kind")?,
route_family: row_try_get_optional(row, "routing_route_family")?,
route_kind: row_try_get_optional(row, "routing_route_kind")?,
execution_path: row_try_get_optional(row, "routing_execution_path")?,
local_execution_runtime_miss_reason: row_try_get_optional(
row,
"routing_local_execution_runtime_miss_reason",
)?,
selected_provider_id: None,
selected_endpoint_id: None,
selected_provider_api_key_id: None,
has_format_conversion: None,
})
}
fn usage_settlement_pricing_snapshot_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<UsageSettlementPricingSnapshot, DataLayerError> {
Ok(UsageSettlementPricingSnapshot {
billing_status: None,
billing_snapshot_schema_version: row_try_get_optional(
row,
"settlement_billing_snapshot_schema_version",
)?,
billing_snapshot_status: row_try_get_optional(row, "settlement_billing_snapshot_status")?,
settlement_snapshot_schema_version: row_try_get_optional(
row,
"settlement_snapshot_schema_version",
)?,
settlement_snapshot: row_try_get_optional(row, "settlement_snapshot")?,
billing_dimensions: row_try_get_optional(row, "settlement_billing_dimensions")?,
billing_input_tokens: row_try_get_optional(row, "settlement_billing_input_tokens")?,
billing_effective_input_tokens: row_try_get_optional(
row,
"settlement_billing_effective_input_tokens",
)?,
billing_output_tokens: row_try_get_optional(row, "settlement_billing_output_tokens")?,
billing_cache_creation_tokens: row_try_get_optional(
row,
"settlement_billing_cache_creation_tokens",
)?,
billing_cache_creation_5m_tokens: row_try_get_optional(
row,
"settlement_billing_cache_creation_5m_tokens",
)?,
billing_cache_creation_1h_tokens: row_try_get_optional(
row,
"settlement_billing_cache_creation_1h_tokens",
)?,
billing_cache_read_tokens: row_try_get_optional(
row,
"settlement_billing_cache_read_tokens",
)?,
billing_total_input_context: row_try_get_optional(
row,
"settlement_billing_total_input_context",
)?,
billing_cache_creation_cost_usd: row_try_get_optional(
row,
"settlement_billing_cache_creation_cost_usd",
)?,
billing_cache_read_cost_usd: row_try_get_optional(
row,
"settlement_billing_cache_read_cost_usd",
)?,
billing_total_cost_usd: row_try_get_optional(row, "settlement_billing_total_cost_usd")?,
billing_actual_total_cost_usd: row_try_get_optional(
row,
"settlement_billing_actual_total_cost_usd",
)?,
billing_pricing_source: row_try_get_optional(row, "settlement_billing_pricing_source")?,
billing_rule_id: row_try_get_optional(row, "settlement_billing_rule_id")?,
billing_rule_version: row_try_get_optional(row, "settlement_billing_rule_version")?,
rate_multiplier: row_try_get_optional(row, "settlement_rate_multiplier")?,
is_free_tier: row_try_get_optional(row, "settlement_is_free_tier")?,
input_price_per_1m: row_try_get_optional(row, "settlement_input_price_per_1m")?,
output_price_per_1m: row_try_get_optional(row, "settlement_output_price_per_1m")?,
cache_creation_price_per_1m: row_try_get_optional(
row,
"settlement_cache_creation_price_per_1m",
)?,
cache_read_price_per_1m: row_try_get_optional(row, "settlement_cache_read_price_per_1m")?,
price_per_request: row_try_get_optional(row, "settlement_price_per_request")?,
})
}
fn attach_usage_settlement_pricing_snapshot_metadata(
metadata: Option<Value>,
snapshot: &UsageSettlementPricingSnapshot,
) -> Option<Value> {
if !snapshot.any_present() {
return metadata;
}
let mut metadata = match metadata {
Some(Value::Object(object)) => object,
Some(value) => return Some(value),
None => Map::new(),
};
maybe_insert_string_value(
&mut metadata,
"billing_snapshot_schema_version",
snapshot.billing_snapshot_schema_version.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"billing_snapshot_status",
snapshot.billing_snapshot_status.as_deref(),
);
maybe_insert_string_value(
&mut metadata,
"settlement_snapshot_schema_version",
snapshot.settlement_snapshot_schema_version.as_deref(),
);
if !metadata.contains_key("settlement_snapshot") {
if let Some(value) = snapshot.settlement_snapshot.clone() {
metadata.insert("settlement_snapshot".to_string(), value);
}
}
if !metadata.contains_key("billing_dimensions") {
if let Some(value) = snapshot.billing_dimensions.clone() {
metadata.insert("billing_dimensions".to_string(), value);
}
}
maybe_insert_number_value(&mut metadata, "rate_multiplier", snapshot.rate_multiplier);
maybe_insert_bool_value(&mut metadata, "is_free_tier", snapshot.is_free_tier);
maybe_insert_number_value(
&mut metadata,
"input_price_per_1m",
snapshot.input_price_per_1m,
);
maybe_insert_number_value(
&mut metadata,
"output_price_per_1m",
snapshot.output_price_per_1m,
);
maybe_insert_number_value(
&mut metadata,
"cache_creation_price_per_1m",
snapshot.cache_creation_price_per_1m,
);
maybe_insert_number_value(
&mut metadata,
"cache_read_price_per_1m",
snapshot.cache_read_price_per_1m,
);
maybe_insert_number_value(
&mut metadata,
"price_per_request",
snapshot.price_per_request,
);
(!metadata.is_empty()).then_some(Value::Object(metadata))
}
fn usage_body_sql_columns(field: UsageBodyField) -> (&'static str, &'static str) {
match field {
UsageBodyField::RequestBody => ("request_body", "request_body_compressed"),
UsageBodyField::ProviderRequestBody => {
("provider_request_body", "provider_request_body_compressed")
}
UsageBodyField::ResponseBody => ("response_body", "response_body_compressed"),
UsageBodyField::ClientResponseBody => {
("client_response_body", "client_response_body_compressed")
}
}
}
#[cfg(test)]
mod tests;