mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
refactor(workspace): enforce layered crate boundaries
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::Row;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::backend::PostgresBackend;
|
||||
use crate::{
|
||||
error::postgres_error, DataLayerError, StatsHourlyAggregationInput,
|
||||
StatsHourlyAggregationSummary,
|
||||
};
|
||||
|
||||
mod sql;
|
||||
|
||||
use self::sql::*;
|
||||
|
||||
impl PostgresBackend {
|
||||
pub async fn aggregate_stats_hourly(
|
||||
&self,
|
||||
input: &StatsHourlyAggregationInput,
|
||||
) -> Result<Option<StatsHourlyAggregationSummary>, DataLayerError> {
|
||||
let Some(hour_utc) = next_stats_hourly_bucket(self.pool(), input.target_hour_utc)
|
||||
.await
|
||||
.map_err(postgres_error)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
perform_stats_hourly_aggregation_for_hour(self.pool(), hour_utc, input.aggregated_at)
|
||||
.await
|
||||
.map(Some)
|
||||
.map_err(postgres_error)
|
||||
}
|
||||
}
|
||||
|
||||
async fn next_stats_hourly_bucket(
|
||||
pool: &crate::driver::postgres::PostgresPool,
|
||||
target_hour_utc: DateTime<Utc>,
|
||||
) -> Result<Option<DateTime<Utc>>, sqlx::Error> {
|
||||
let latest_row = sqlx::query(SELECT_LATEST_STATS_HOURLY_HOUR_SQL)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
let latest_hour = latest_row.try_get::<Option<DateTime<Utc>>, _>("latest_hour")?;
|
||||
let search_from = latest_hour
|
||||
.map(|value| value + chrono::Duration::hours(1))
|
||||
.unwrap_or_else(|| {
|
||||
DateTime::<Utc>::from_timestamp(0, 0).expect("unix epoch should be valid")
|
||||
});
|
||||
let search_until = target_hour_utc + chrono::Duration::hours(1);
|
||||
if search_from >= search_until {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let next_row = sqlx::query(SELECT_NEXT_STATS_HOURLY_BUCKET_SQL)
|
||||
.bind(search_from)
|
||||
.bind(search_until)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
let next_bucket = next_row.try_get::<Option<DateTime<Utc>>, _>("next_bucket")?;
|
||||
Ok(next_bucket.filter(|value| *value <= target_hour_utc))
|
||||
}
|
||||
|
||||
async fn perform_stats_hourly_aggregation_for_hour(
|
||||
pool: &crate::driver::postgres::PostgresPool,
|
||||
hour_utc: DateTime<Utc>,
|
||||
aggregated_at: DateTime<Utc>,
|
||||
) -> Result<StatsHourlyAggregationSummary, sqlx::Error> {
|
||||
let hour_end = hour_utc + chrono::Duration::hours(1);
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
let row = sqlx::query(SELECT_STATS_HOURLY_AGGREGATE_SQL)
|
||||
.bind(hour_utc)
|
||||
.bind(hour_end)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
let total_requests = row.try_get::<i64, _>("total_requests")?;
|
||||
let error_requests = row.try_get::<i64, _>("error_requests")?;
|
||||
let success_requests = total_requests.saturating_sub(error_requests);
|
||||
sqlx::query(UPSERT_STATS_HOURLY_SQL)
|
||||
.bind(Uuid::new_v4().to_string())
|
||||
.bind(hour_utc)
|
||||
.bind(total_requests)
|
||||
.bind(row.try_get::<i64, _>("cache_hit_total_requests")?)
|
||||
.bind(row.try_get::<i64, _>("cache_hit_requests")?)
|
||||
.bind(row.try_get::<i64, _>("completed_total_requests")?)
|
||||
.bind(row.try_get::<i64, _>("completed_cache_hit_requests")?)
|
||||
.bind(row.try_get::<i64, _>("completed_input_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("completed_cache_creation_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("completed_cache_read_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("completed_total_input_context")?)
|
||||
.bind(row.try_get::<f64, _>("completed_cache_creation_cost")?)
|
||||
.bind(row.try_get::<f64, _>("completed_cache_read_cost")?)
|
||||
.bind(row.try_get::<f64, _>("settled_total_cost")?)
|
||||
.bind(row.try_get::<i64, _>("settled_total_requests")?)
|
||||
.bind(row.try_get::<i64, _>("settled_input_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("settled_output_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("settled_cache_creation_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("settled_cache_read_tokens")?)
|
||||
.bind(row.try_get::<Option<i64>, _>("settled_first_finalized_at_unix_secs")?)
|
||||
.bind(row.try_get::<Option<i64>, _>("settled_last_finalized_at_unix_secs")?)
|
||||
.bind(success_requests)
|
||||
.bind(error_requests)
|
||||
.bind(row.try_get::<i64, _>("input_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("output_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("cache_creation_tokens")?)
|
||||
.bind(row.try_get::<i64, _>("cache_read_tokens")?)
|
||||
.bind(row.try_get::<f64, _>("total_cost")?)
|
||||
.bind(row.try_get::<f64, _>("actual_total_cost")?)
|
||||
.bind(row.try_get::<f64, _>("response_time_sum_ms")?)
|
||||
.bind(row.try_get::<i64, _>("response_time_samples")?)
|
||||
.bind(row.try_get::<f64, _>("avg_response_time_ms")?)
|
||||
.bind(true)
|
||||
.bind(aggregated_at)
|
||||
.bind(aggregated_at)
|
||||
.bind(aggregated_at)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let user_rows =
|
||||
upsert_stats_hourly_user_rows(&mut tx, hour_utc, hour_end, aggregated_at).await?;
|
||||
let user_model_rows =
|
||||
upsert_stats_hourly_user_model_rows(&mut tx, hour_utc, hour_end, aggregated_at).await?;
|
||||
let model_rows =
|
||||
upsert_stats_hourly_model_rows(&mut tx, hour_utc, hour_end, aggregated_at).await?;
|
||||
let provider_rows =
|
||||
upsert_stats_hourly_provider_rows(&mut tx, hour_utc, hour_end, aggregated_at).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(StatsHourlyAggregationSummary {
|
||||
hour_utc,
|
||||
total_requests,
|
||||
user_rows,
|
||||
user_model_rows,
|
||||
model_rows,
|
||||
provider_rows,
|
||||
})
|
||||
}
|
||||
|
||||
async fn upsert_stats_hourly_user_rows(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
hour_utc: DateTime<Utc>,
|
||||
hour_end: DateTime<Utc>,
|
||||
now_utc: DateTime<Utc>,
|
||||
) -> Result<usize, sqlx::Error> {
|
||||
let rows_affected = sqlx::query(UPSERT_STATS_HOURLY_USER_SQL)
|
||||
.bind(hour_utc)
|
||||
.bind(hour_end)
|
||||
.bind(now_utc)
|
||||
.execute(&mut **tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
Ok(usize::try_from(rows_affected).unwrap_or(usize::MAX))
|
||||
}
|
||||
|
||||
async fn upsert_stats_hourly_model_rows(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
hour_utc: DateTime<Utc>,
|
||||
hour_end: DateTime<Utc>,
|
||||
now_utc: DateTime<Utc>,
|
||||
) -> Result<usize, sqlx::Error> {
|
||||
let rows_affected = sqlx::query(UPSERT_STATS_HOURLY_MODEL_SQL)
|
||||
.bind(hour_utc)
|
||||
.bind(hour_end)
|
||||
.bind(now_utc)
|
||||
.execute(&mut **tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
Ok(usize::try_from(rows_affected).unwrap_or(usize::MAX))
|
||||
}
|
||||
|
||||
async fn upsert_stats_hourly_user_model_rows(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
hour_utc: DateTime<Utc>,
|
||||
hour_end: DateTime<Utc>,
|
||||
now_utc: DateTime<Utc>,
|
||||
) -> Result<usize, sqlx::Error> {
|
||||
let rows_affected = sqlx::query(UPSERT_STATS_HOURLY_USER_MODEL_SQL)
|
||||
.bind(hour_utc)
|
||||
.bind(hour_end)
|
||||
.bind(now_utc)
|
||||
.execute(&mut **tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
Ok(usize::try_from(rows_affected).unwrap_or(usize::MAX))
|
||||
}
|
||||
|
||||
async fn upsert_stats_hourly_provider_rows(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
hour_utc: DateTime<Utc>,
|
||||
hour_end: DateTime<Utc>,
|
||||
now_utc: DateTime<Utc>,
|
||||
) -> Result<usize, sqlx::Error> {
|
||||
let rows_affected = sqlx::query(UPSERT_STATS_HOURLY_PROVIDER_SQL)
|
||||
.bind(hour_utc)
|
||||
.bind(hour_end)
|
||||
.bind(now_utc)
|
||||
.execute(&mut **tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
Ok(usize::try_from(rows_affected).unwrap_or(usize::MAX))
|
||||
}
|
||||
@@ -0,0 +1,941 @@
|
||||
pub(super) const SELECT_LATEST_STATS_HOURLY_HOUR_SQL: &str = r#"
|
||||
SELECT MAX(hour_utc) AS latest_hour
|
||||
FROM stats_hourly
|
||||
WHERE is_complete IS TRUE
|
||||
"#;
|
||||
pub(super) const SELECT_NEXT_STATS_HOURLY_BUCKET_SQL: &str = r#"
|
||||
SELECT date_trunc('hour', MIN(created_at)) AS next_bucket
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
AND provider_name NOT IN ('unknown', 'pending')
|
||||
"#;
|
||||
pub(super) const SELECT_STATS_HOURLY_AGGREGATE_SQL: &str = r#"
|
||||
SELECT
|
||||
CAST(COUNT(usage.id) AS BIGINT) AS cache_hit_total_requests,
|
||||
CAST(
|
||||
COUNT(usage.id) FILTER (
|
||||
WHERE GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
) AS cache_hit_requests,
|
||||
CAST(
|
||||
COUNT(usage.id) FILTER (WHERE usage.status = 'completed') AS BIGINT
|
||||
) AS completed_total_requests,
|
||||
CAST(
|
||||
COUNT(usage.id) FILTER (
|
||||
WHERE usage.status = 'completed'
|
||||
AND GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
) AS completed_cache_hit_requests,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.input_tokens, 0), 0))
|
||||
FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS completed_input_tokens,
|
||||
CAST(
|
||||
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
|
||||
) FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS completed_cache_creation_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0))
|
||||
FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS completed_cache_read_tokens,
|
||||
CAST(
|
||||
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
|
||||
) FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS completed_total_input_context,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(
|
||||
COALESCE(CAST(usage.cache_creation_cost_usd AS DOUBLE PRECISION), 0)
|
||||
) FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS completed_cache_creation_cost,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(COALESCE(CAST(usage.cache_read_cost_usd AS DOUBLE PRECISION), 0))
|
||||
FILTER (WHERE usage.status = 'completed'),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS completed_cache_read_cost,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS settled_total_cost,
|
||||
CAST(
|
||||
COUNT(usage.id) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
) AS BIGINT
|
||||
) AS settled_total_requests,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.input_tokens, 0), 0)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS settled_input_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.output_tokens, 0), 0)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS settled_output_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.cache_creation_input_tokens, 0), 0)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS settled_cache_creation_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(GREATEST(COALESCE(usage.cache_read_input_tokens, 0), 0)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS settled_cache_read_tokens,
|
||||
MIN(CAST(EXTRACT(EPOCH FROM usage.finalized_at) AS BIGINT)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
) AS settled_first_finalized_at_unix_secs,
|
||||
MAX(CAST(EXTRACT(EPOCH FROM usage.finalized_at) AS BIGINT)) FILTER (
|
||||
WHERE usage.billing_status = 'settled'
|
||||
AND COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
) AS settled_last_finalized_at_unix_secs,
|
||||
CAST(
|
||||
COUNT(usage.id) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
) AS BIGINT
|
||||
) AS total_requests,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN usage.status_code >= 400
|
||||
OR lower(COALESCE(usage.status, '')) = 'failed'
|
||||
OR usage.error_message IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS error_requests,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(usage.input_tokens) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS input_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(usage.output_tokens) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS output_tokens,
|
||||
CAST(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
|
||||
) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS cache_creation_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(usage.cache_read_input_tokens) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS cache_read_tokens,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(usage.total_cost_usd) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS total_cost,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(usage.actual_total_cost_usd) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS actual_total_cost,
|
||||
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
|
||||
) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS response_time_sum_ms,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN usage.response_time_ms IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS response_time_samples,
|
||||
CAST(
|
||||
COALESCE(
|
||||
AVG(usage.response_time_ms) FILTER (
|
||||
WHERE usage.status NOT IN ('pending', 'streaming')
|
||||
AND usage.provider_name NOT IN ('unknown', 'pending')
|
||||
),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS avg_response_time_ms
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE usage.created_at >= $1
|
||||
AND usage.created_at < $2
|
||||
"#;
|
||||
pub(super) const UPSERT_STATS_HOURLY_SQL: &str = r#"
|
||||
INSERT INTO stats_hourly (
|
||||
id,
|
||||
hour_utc,
|
||||
total_requests,
|
||||
cache_hit_total_requests,
|
||||
cache_hit_requests,
|
||||
completed_total_requests,
|
||||
completed_cache_hit_requests,
|
||||
completed_input_tokens,
|
||||
completed_cache_creation_tokens,
|
||||
completed_cache_read_tokens,
|
||||
completed_total_input_context,
|
||||
completed_cache_creation_cost,
|
||||
completed_cache_read_cost,
|
||||
settled_total_cost,
|
||||
settled_total_requests,
|
||||
settled_input_tokens,
|
||||
settled_output_tokens,
|
||||
settled_cache_creation_tokens,
|
||||
settled_cache_read_tokens,
|
||||
settled_first_finalized_at_unix_secs,
|
||||
settled_last_finalized_at_unix_secs,
|
||||
success_requests,
|
||||
error_requests,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
cache_creation_tokens,
|
||||
cache_read_tokens,
|
||||
total_cost,
|
||||
actual_total_cost,
|
||||
response_time_sum_ms,
|
||||
response_time_samples,
|
||||
avg_response_time_ms,
|
||||
is_complete,
|
||||
aggregated_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8,
|
||||
$9, $10, $11, $12, $13, $14, $15, $16,
|
||||
$17, $18, $19, $20, $21, $22, $23, $24,
|
||||
$25, $26, $27, $28, $29, $30, $31, $32,
|
||||
$33, $34, $35, $36
|
||||
)
|
||||
ON CONFLICT (hour_utc)
|
||||
DO UPDATE SET
|
||||
total_requests = EXCLUDED.total_requests,
|
||||
cache_hit_total_requests = EXCLUDED.cache_hit_total_requests,
|
||||
cache_hit_requests = EXCLUDED.cache_hit_requests,
|
||||
completed_total_requests = EXCLUDED.completed_total_requests,
|
||||
completed_cache_hit_requests = EXCLUDED.completed_cache_hit_requests,
|
||||
completed_input_tokens = EXCLUDED.completed_input_tokens,
|
||||
completed_cache_creation_tokens = EXCLUDED.completed_cache_creation_tokens,
|
||||
completed_cache_read_tokens = EXCLUDED.completed_cache_read_tokens,
|
||||
completed_total_input_context = EXCLUDED.completed_total_input_context,
|
||||
completed_cache_creation_cost = EXCLUDED.completed_cache_creation_cost,
|
||||
completed_cache_read_cost = EXCLUDED.completed_cache_read_cost,
|
||||
settled_total_cost = EXCLUDED.settled_total_cost,
|
||||
settled_total_requests = EXCLUDED.settled_total_requests,
|
||||
settled_input_tokens = EXCLUDED.settled_input_tokens,
|
||||
settled_output_tokens = EXCLUDED.settled_output_tokens,
|
||||
settled_cache_creation_tokens = EXCLUDED.settled_cache_creation_tokens,
|
||||
settled_cache_read_tokens = EXCLUDED.settled_cache_read_tokens,
|
||||
settled_first_finalized_at_unix_secs = EXCLUDED.settled_first_finalized_at_unix_secs,
|
||||
settled_last_finalized_at_unix_secs = EXCLUDED.settled_last_finalized_at_unix_secs,
|
||||
success_requests = EXCLUDED.success_requests,
|
||||
error_requests = EXCLUDED.error_requests,
|
||||
input_tokens = EXCLUDED.input_tokens,
|
||||
output_tokens = EXCLUDED.output_tokens,
|
||||
cache_creation_tokens = EXCLUDED.cache_creation_tokens,
|
||||
cache_read_tokens = EXCLUDED.cache_read_tokens,
|
||||
total_cost = EXCLUDED.total_cost,
|
||||
actual_total_cost = EXCLUDED.actual_total_cost,
|
||||
response_time_sum_ms = EXCLUDED.response_time_sum_ms,
|
||||
response_time_samples = EXCLUDED.response_time_samples,
|
||||
avg_response_time_ms = EXCLUDED.avg_response_time_ms,
|
||||
is_complete = EXCLUDED.is_complete,
|
||||
aggregated_at = EXCLUDED.aggregated_at,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
"#;
|
||||
pub(super) const UPSERT_STATS_HOURLY_USER_SQL: &str = r#"
|
||||
WITH aggregated AS (
|
||||
SELECT
|
||||
user_id,
|
||||
CAST(COUNT(id) AS BIGINT) AS total_requests,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN status_code >= 400
|
||||
OR lower(COALESCE(status, '')) = 'failed'
|
||||
OR error_message IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS error_requests,
|
||||
CAST(COALESCE(SUM(input_tokens), 0) AS BIGINT) AS input_tokens,
|
||||
CAST(COALESCE(SUM(output_tokens), 0) AS BIGINT) AS output_tokens,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN COALESCE(cache_creation_input_tokens, 0) = 0
|
||||
AND (
|
||||
COALESCE(cache_creation_input_tokens_5m, 0)
|
||||
+ COALESCE(cache_creation_input_tokens_1h, 0)
|
||||
) > 0
|
||||
THEN COALESCE(cache_creation_input_tokens_5m, 0)
|
||||
+ COALESCE(cache_creation_input_tokens_1h, 0)
|
||||
ELSE COALESCE(cache_creation_input_tokens, 0)
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS cache_creation_tokens,
|
||||
CAST(COALESCE(SUM(cache_read_input_tokens), 0) AS BIGINT) AS cache_read_tokens,
|
||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost,
|
||||
CAST(COALESCE(SUM(actual_total_cost_usd), 0) AS DOUBLE PRECISION) AS actual_total_cost,
|
||||
CAST(
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0)
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS settled_total_cost,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS settled_total_requests,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN GREATEST(COALESCE(input_tokens, 0), 0)
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS settled_input_tokens,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN GREATEST(COALESCE(output_tokens, 0), 0)
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS settled_output_tokens,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN GREATEST(COALESCE(cache_creation_input_tokens, 0), 0)
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS settled_cache_creation_tokens,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
THEN GREATEST(COALESCE(cache_read_input_tokens, 0), 0)
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS settled_cache_read_tokens,
|
||||
MIN(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
AND finalized_at IS NOT NULL
|
||||
THEN CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT)
|
||||
ELSE NULL
|
||||
END
|
||||
) AS settled_first_finalized_at_unix_secs,
|
||||
MAX(
|
||||
CASE
|
||||
WHEN billing_status = 'settled'
|
||||
AND COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) > 0
|
||||
AND finalized_at IS NOT NULL
|
||||
THEN CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT)
|
||||
ELSE NULL
|
||||
END
|
||||
) AS settled_last_finalized_at_unix_secs,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL
|
||||
THEN GREATEST(COALESCE(response_time_ms, 0), 0)::DOUBLE PRECISION
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS response_time_sum_ms,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS response_time_samples
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
AND provider_name NOT IN ('unknown', 'pending')
|
||||
GROUP BY user_id
|
||||
)
|
||||
INSERT INTO stats_hourly_user (
|
||||
id,
|
||||
hour_utc,
|
||||
user_id,
|
||||
total_requests,
|
||||
success_requests,
|
||||
error_requests,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
cache_creation_tokens,
|
||||
cache_read_tokens,
|
||||
total_cost,
|
||||
actual_total_cost,
|
||||
settled_total_cost,
|
||||
settled_total_requests,
|
||||
settled_input_tokens,
|
||||
settled_output_tokens,
|
||||
settled_cache_creation_tokens,
|
||||
settled_cache_read_tokens,
|
||||
settled_first_finalized_at_unix_secs,
|
||||
settled_last_finalized_at_unix_secs,
|
||||
response_time_sum_ms,
|
||||
response_time_samples,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
md5(CONCAT('stats-hourly-user:', aggregated.user_id, ':', CAST($1 AS TEXT))),
|
||||
$1,
|
||||
aggregated.user_id,
|
||||
aggregated.total_requests,
|
||||
GREATEST(aggregated.total_requests - aggregated.error_requests, 0),
|
||||
aggregated.error_requests,
|
||||
aggregated.input_tokens,
|
||||
aggregated.output_tokens,
|
||||
aggregated.cache_creation_tokens,
|
||||
aggregated.cache_read_tokens,
|
||||
aggregated.total_cost,
|
||||
aggregated.actual_total_cost,
|
||||
aggregated.settled_total_cost,
|
||||
aggregated.settled_total_requests,
|
||||
aggregated.settled_input_tokens,
|
||||
aggregated.settled_output_tokens,
|
||||
aggregated.settled_cache_creation_tokens,
|
||||
aggregated.settled_cache_read_tokens,
|
||||
aggregated.settled_first_finalized_at_unix_secs,
|
||||
aggregated.settled_last_finalized_at_unix_secs,
|
||||
aggregated.response_time_sum_ms,
|
||||
aggregated.response_time_samples,
|
||||
$3,
|
||||
$3
|
||||
FROM aggregated
|
||||
ON CONFLICT (hour_utc, user_id)
|
||||
DO UPDATE SET
|
||||
total_requests = EXCLUDED.total_requests,
|
||||
success_requests = EXCLUDED.success_requests,
|
||||
error_requests = EXCLUDED.error_requests,
|
||||
input_tokens = EXCLUDED.input_tokens,
|
||||
output_tokens = EXCLUDED.output_tokens,
|
||||
cache_creation_tokens = EXCLUDED.cache_creation_tokens,
|
||||
cache_read_tokens = EXCLUDED.cache_read_tokens,
|
||||
total_cost = EXCLUDED.total_cost,
|
||||
actual_total_cost = EXCLUDED.actual_total_cost,
|
||||
settled_total_cost = EXCLUDED.settled_total_cost,
|
||||
settled_total_requests = EXCLUDED.settled_total_requests,
|
||||
settled_input_tokens = EXCLUDED.settled_input_tokens,
|
||||
settled_output_tokens = EXCLUDED.settled_output_tokens,
|
||||
settled_cache_creation_tokens = EXCLUDED.settled_cache_creation_tokens,
|
||||
settled_cache_read_tokens = EXCLUDED.settled_cache_read_tokens,
|
||||
settled_first_finalized_at_unix_secs = EXCLUDED.settled_first_finalized_at_unix_secs,
|
||||
settled_last_finalized_at_unix_secs = EXCLUDED.settled_last_finalized_at_unix_secs,
|
||||
response_time_sum_ms = EXCLUDED.response_time_sum_ms,
|
||||
response_time_samples = EXCLUDED.response_time_samples,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
"#;
|
||||
pub(super) const UPSERT_STATS_HOURLY_MODEL_SQL: &str = r#"
|
||||
WITH aggregated AS (
|
||||
SELECT
|
||||
model,
|
||||
CAST(COUNT(id) AS BIGINT) AS total_requests,
|
||||
CAST(COALESCE(SUM(input_tokens), 0) AS BIGINT) AS input_tokens,
|
||||
CAST(COALESCE(SUM(output_tokens), 0) AS BIGINT) AS output_tokens,
|
||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL
|
||||
THEN GREATEST(COALESCE(response_time_ms, 0), 0)::DOUBLE PRECISION
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS response_time_sum_ms,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS response_time_samples,
|
||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND model IS NOT NULL
|
||||
AND model <> ''
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
AND provider_name NOT IN ('unknown', 'pending')
|
||||
GROUP BY model
|
||||
)
|
||||
INSERT INTO stats_hourly_model (
|
||||
id,
|
||||
hour_utc,
|
||||
model,
|
||||
total_requests,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
total_cost,
|
||||
response_time_sum_ms,
|
||||
response_time_samples,
|
||||
avg_response_time_ms,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
md5(CONCAT('stats-hourly-model:', aggregated.model, ':', CAST($1 AS TEXT))),
|
||||
$1,
|
||||
aggregated.model,
|
||||
aggregated.total_requests,
|
||||
aggregated.input_tokens,
|
||||
aggregated.output_tokens,
|
||||
aggregated.total_cost,
|
||||
aggregated.response_time_sum_ms,
|
||||
aggregated.response_time_samples,
|
||||
aggregated.avg_response_time_ms,
|
||||
$3,
|
||||
$3
|
||||
FROM aggregated
|
||||
ON CONFLICT (hour_utc, model)
|
||||
DO UPDATE SET
|
||||
total_requests = EXCLUDED.total_requests,
|
||||
input_tokens = EXCLUDED.input_tokens,
|
||||
output_tokens = EXCLUDED.output_tokens,
|
||||
total_cost = EXCLUDED.total_cost,
|
||||
response_time_sum_ms = EXCLUDED.response_time_sum_ms,
|
||||
response_time_samples = EXCLUDED.response_time_samples,
|
||||
avg_response_time_ms = EXCLUDED.avg_response_time_ms,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
"#;
|
||||
pub(super) const UPSERT_STATS_HOURLY_USER_MODEL_SQL: &str = r#"
|
||||
WITH aggregated AS (
|
||||
SELECT
|
||||
user_id,
|
||||
model,
|
||||
CAST(COUNT(id) AS BIGINT) AS total_requests,
|
||||
CAST(COALESCE(SUM(input_tokens), 0) AS BIGINT) AS input_tokens,
|
||||
CAST(COALESCE(SUM(output_tokens), 0) AS BIGINT) AS output_tokens,
|
||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL
|
||||
THEN GREATEST(COALESCE(response_time_ms, 0), 0)::DOUBLE PRECISION
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS response_time_sum_ms,
|
||||
CAST(COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN response_time_ms IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS BIGINT) AS response_time_samples
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
AND model IS NOT NULL
|
||||
AND model <> ''
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
AND provider_name NOT IN ('unknown', 'pending')
|
||||
GROUP BY user_id, model
|
||||
)
|
||||
INSERT INTO stats_hourly_user_model (
|
||||
id,
|
||||
hour_utc,
|
||||
user_id,
|
||||
model,
|
||||
total_requests,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
total_cost,
|
||||
response_time_sum_ms,
|
||||
response_time_samples,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
md5(CONCAT('stats-hourly-user-model:', aggregated.user_id, ':', aggregated.model, ':', CAST($1 AS TEXT))),
|
||||
$1,
|
||||
aggregated.user_id,
|
||||
aggregated.model,
|
||||
aggregated.total_requests,
|
||||
aggregated.input_tokens,
|
||||
aggregated.output_tokens,
|
||||
aggregated.total_cost,
|
||||
aggregated.response_time_sum_ms,
|
||||
aggregated.response_time_samples,
|
||||
$3,
|
||||
$3
|
||||
FROM aggregated
|
||||
ON CONFLICT (hour_utc, user_id, model)
|
||||
DO UPDATE SET
|
||||
total_requests = EXCLUDED.total_requests,
|
||||
input_tokens = EXCLUDED.input_tokens,
|
||||
output_tokens = EXCLUDED.output_tokens,
|
||||
total_cost = EXCLUDED.total_cost,
|
||||
response_time_sum_ms = EXCLUDED.response_time_sum_ms,
|
||||
response_time_samples = EXCLUDED.response_time_samples,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
"#;
|
||||
pub(super) const UPSERT_STATS_HOURLY_PROVIDER_SQL: &str = r#"
|
||||
WITH aggregated AS (
|
||||
SELECT
|
||||
provider_name,
|
||||
CAST(COUNT(id) AS BIGINT) AS total_requests,
|
||||
CAST(COALESCE(SUM(input_tokens), 0) AS BIGINT) AS input_tokens,
|
||||
CAST(COALESCE(SUM(output_tokens), 0) AS BIGINT) AS output_tokens,
|
||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND provider_name IS NOT NULL
|
||||
AND provider_name <> ''
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
AND provider_name NOT IN ('unknown', 'pending')
|
||||
GROUP BY provider_name
|
||||
)
|
||||
INSERT INTO stats_hourly_provider (
|
||||
id,
|
||||
hour_utc,
|
||||
provider_name,
|
||||
total_requests,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
total_cost,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
md5(CONCAT('stats-hourly-provider:', aggregated.provider_name, ':', CAST($1 AS TEXT))),
|
||||
$1,
|
||||
aggregated.provider_name,
|
||||
aggregated.total_requests,
|
||||
aggregated.input_tokens,
|
||||
aggregated.output_tokens,
|
||||
aggregated.total_cost,
|
||||
$3,
|
||||
$3
|
||||
FROM aggregated
|
||||
ON CONFLICT (hour_utc, provider_name)
|
||||
DO UPDATE SET
|
||||
total_requests = EXCLUDED.total_requests,
|
||||
input_tokens = EXCLUDED.input_tokens,
|
||||
output_tokens = EXCLUDED.output_tokens,
|
||||
total_cost = EXCLUDED.total_cost,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
"#;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SELECT_STATS_HOURLY_AGGREGATE_SQL;
|
||||
|
||||
#[test]
|
||||
fn hourly_aggregate_uses_one_time_bounded_base_table_scan() {
|
||||
let sql = SELECT_STATS_HOURLY_AGGREGATE_SQL;
|
||||
|
||||
assert_eq!(sql.matches("FROM usage_billing_facts AS usage").count(), 1);
|
||||
assert_eq!(sql.matches("WHERE usage.created_at >= $1").count(), 1);
|
||||
assert_eq!(sql.matches("AND usage.created_at < $2").count(), 1);
|
||||
assert!(!sql.contains("MATERIALIZED"));
|
||||
assert!(!sql.contains("bucket_usage"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hourly_aggregate_keeps_independent_row_populations() {
|
||||
let sql = SELECT_STATS_HOURLY_AGGREGATE_SQL;
|
||||
|
||||
assert_eq!(sql.matches("usage.status = 'completed'").count(), 8);
|
||||
assert_eq!(sql.matches("usage.billing_status = 'settled'").count(), 8);
|
||||
assert_eq!(
|
||||
sql.matches("COALESCE(CAST(usage.total_cost_usd AS DOUBLE PRECISION), 0) > 0")
|
||||
.count(),
|
||||
8
|
||||
);
|
||||
assert_eq!(
|
||||
sql.matches("usage.status NOT IN ('pending', 'streaming')")
|
||||
.count(),
|
||||
11
|
||||
);
|
||||
assert_eq!(
|
||||
sql.matches("usage.provider_name NOT IN ('unknown', 'pending')")
|
||||
.count(),
|
||||
11
|
||||
);
|
||||
|
||||
let first_aggregate = sql
|
||||
.trim_start()
|
||||
.strip_prefix("SELECT\n")
|
||||
.expect("hourly aggregate should remain a direct SELECT");
|
||||
assert!(first_aggregate
|
||||
.starts_with(" CAST(COUNT(usage.id) AS BIGINT) AS cache_hit_total_requests,"));
|
||||
assert!(
|
||||
sql.contains("MIN(CAST(EXTRACT(EPOCH FROM usage.finalized_at) AS BIGINT)) FILTER (")
|
||||
);
|
||||
assert!(
|
||||
sql.contains("MAX(CAST(EXTRACT(EPOCH FROM usage.finalized_at) AS BIGINT)) FILTER (")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hourly_aggregate_preserves_the_decoder_column_contract() {
|
||||
let sql = SELECT_STATS_HOURLY_AGGREGATE_SQL;
|
||||
let aliases = [
|
||||
"cache_hit_total_requests",
|
||||
"cache_hit_requests",
|
||||
"completed_total_requests",
|
||||
"completed_cache_hit_requests",
|
||||
"completed_input_tokens",
|
||||
"completed_cache_creation_tokens",
|
||||
"completed_cache_read_tokens",
|
||||
"completed_total_input_context",
|
||||
"completed_cache_creation_cost",
|
||||
"completed_cache_read_cost",
|
||||
"settled_total_cost",
|
||||
"settled_total_requests",
|
||||
"settled_input_tokens",
|
||||
"settled_output_tokens",
|
||||
"settled_cache_creation_tokens",
|
||||
"settled_cache_read_tokens",
|
||||
"settled_first_finalized_at_unix_secs",
|
||||
"settled_last_finalized_at_unix_secs",
|
||||
"total_requests",
|
||||
"error_requests",
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"cache_creation_tokens",
|
||||
"cache_read_tokens",
|
||||
"total_cost",
|
||||
"actual_total_cost",
|
||||
"response_time_sum_ms",
|
||||
"response_time_samples",
|
||||
"avg_response_time_ms",
|
||||
];
|
||||
|
||||
for alias in aliases {
|
||||
assert_eq!(
|
||||
sql.matches(&format!(" AS {alias}")).count(),
|
||||
1,
|
||||
"hourly aggregate alias {alias} must appear exactly once"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user