mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(billing): 引入 billing v3 settlement snapshot 及 usage_billing_facts 视图
- 新增迁移 20260424000000:为 usage_settlement_snapshots 添加 settlement_snapshot、 billing_dimensions、billing_input/output/cache tokens、billing_total_cost_usd 等列, 并创建 usage_billing_facts 视图(从 settlement snapshot 覆盖原始 usage token/cost 字段) - event_enrichment:构建结构化 settlement_snapshot(含 pricing_snapshot、billing_plan_snapshot、 resolved_dimensions、cost_breakdown 等),写入 request_metadata - pricing:新增 pricing_source() 方法区分 provider_override / global_default / unpriced - sql.rs:settlement snapshot 写入新列;用量汇总查询改用 usage_billing_facts 视图 - maintenance/runtime:所有统计查询的 FROM usage 替换为 FROM usage_billing_facts - admin observability:在 settlement 响应中暴露 settlement_snapshot / billing_dimensions, 并从 metadata 中剥离对应字段 - request_metadata:允许 settlement_snapshot / billing_dimensions 字段传播 - stream execution:在首个数据帧到达时记录 TTFB,补全流式请求的 streaming 事件
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::io::Error as IoError;
|
||||
use std::time::Instant;
|
||||
|
||||
use aether_contracts::{
|
||||
ExecutionPlan, ExecutionStreamTerminalSummary, ExecutionTelemetry, StreamFrame,
|
||||
@@ -304,6 +305,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
report_kind: Option<String>,
|
||||
mut report_context: Option<serde_json::Value>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let stream_started_at = Instant::now();
|
||||
ensure_execution_request_candidate_slot(state, &mut plan, &mut report_context).await;
|
||||
let lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
|
||||
let request_candidate_status_snapshot =
|
||||
@@ -391,6 +393,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
report_kind,
|
||||
report_context,
|
||||
candidate_started_unix_secs,
|
||||
stream_started_at,
|
||||
frame_stream,
|
||||
)
|
||||
.await;
|
||||
@@ -450,6 +453,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
report_kind,
|
||||
report_context,
|
||||
candidate_started_unix_secs,
|
||||
stream_started_at,
|
||||
frame_stream,
|
||||
)
|
||||
.await;
|
||||
@@ -534,6 +538,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
report_kind,
|
||||
report_context,
|
||||
candidate_started_unix_secs,
|
||||
stream_started_at,
|
||||
frame_stream,
|
||||
)
|
||||
.await;
|
||||
@@ -675,6 +680,7 @@ async fn execute_stream_from_frame_stream(
|
||||
report_kind: Option<String>,
|
||||
report_context: Option<serde_json::Value>,
|
||||
candidate_started_unix_secs: u64,
|
||||
stream_started_at: Instant,
|
||||
frame_stream: BoxStream<'static, Result<Bytes, IoError>>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let request_id = plan.request_id.as_str();
|
||||
@@ -1639,6 +1645,32 @@ async fn execute_stream_from_frame_stream(
|
||||
continue;
|
||||
}
|
||||
|
||||
if usage_stream_telemetry
|
||||
.as_ref()
|
||||
.and_then(|telemetry| telemetry.ttfb_ms)
|
||||
.is_none()
|
||||
{
|
||||
let first_data_elapsed_ms = stream_started_at
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
.min(u128::from(u64::MAX))
|
||||
as u64;
|
||||
let first_data_telemetry = ExecutionTelemetry {
|
||||
ttfb_ms: Some(first_data_elapsed_ms),
|
||||
elapsed_ms: Some(first_data_elapsed_ms),
|
||||
upstream_bytes: telemetry
|
||||
.as_ref()
|
||||
.and_then(|telemetry| telemetry.upstream_bytes),
|
||||
};
|
||||
state_for_report.usage_runtime.record_stream_started(
|
||||
state_for_report.data.as_ref(),
|
||||
&lifecycle_seed_for_report,
|
||||
status_code,
|
||||
Some(&first_data_telemetry),
|
||||
);
|
||||
usage_stream_telemetry = Some(first_data_telemetry);
|
||||
}
|
||||
|
||||
append_stream_capture_bytes(
|
||||
&mut buffered_body,
|
||||
&rewritten_chunk,
|
||||
@@ -2082,16 +2114,22 @@ fn apply_stream_summary_report_context(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, RequestBody};
|
||||
use axum::body::{to_bytes, Body};
|
||||
use aether_data::repository::usage::InMemoryUsageReadRepository;
|
||||
use aether_data_contracts::repository::usage::UsageReadRepository;
|
||||
use aether_usage_runtime::UsageRuntimeConfig;
|
||||
use async_stream::stream;
|
||||
use axum::body::{to_bytes, Body, Bytes};
|
||||
use axum::extract::ws::Message;
|
||||
use axum::extract::Request;
|
||||
use axum::routing::any;
|
||||
use axum::{http::header, http::HeaderValue, Router};
|
||||
use serde_json::{json, Value};
|
||||
use tokio::sync::watch;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
use super::{execute_execution_runtime_stream, should_skip_direct_finalize_prefetch};
|
||||
use crate::control::GatewayControlDecision;
|
||||
@@ -2168,6 +2206,153 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_execution_runtime_stream_records_first_data_as_streaming_before_terminal_telemetry(
|
||||
) {
|
||||
let listener = crate::test_support::bind_loopback_listener()
|
||||
.await
|
||||
.expect("listener should bind");
|
||||
let addr = listener.local_addr().expect("local addr should resolve");
|
||||
let first_data_seen = Arc::new(Notify::new());
|
||||
let release_terminal = Arc::new(Notify::new());
|
||||
let first_data_seen_for_route = Arc::clone(&first_data_seen);
|
||||
let release_terminal_for_route = Arc::clone(&release_terminal);
|
||||
let server = tokio::spawn(async move {
|
||||
let app = Router::new().route(
|
||||
"/v1/execute/stream",
|
||||
any(move |_request: Request| {
|
||||
let first_data_seen = Arc::clone(&first_data_seen_for_route);
|
||||
let release_terminal = Arc::clone(&release_terminal_for_route);
|
||||
async move {
|
||||
let frames = stream! {
|
||||
yield Ok::<Bytes, Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
));
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
yield Ok::<Bytes, Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.output_text.delta\\ndata: {\\\"type\\\":\\\"response.output_text.delta\\\",\\\"delta\\\":\\\"hi\\\"}\\n\\n\"}}\n",
|
||||
));
|
||||
first_data_seen.notify_one();
|
||||
release_terminal.notified().await;
|
||||
yield Ok::<Bytes, Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"ttfb_ms\":123,\"elapsed_ms\":456}}}\n",
|
||||
));
|
||||
yield Ok::<Bytes, Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n",
|
||||
));
|
||||
};
|
||||
let mut response = axum::http::Response::new(Body::from_stream(frames));
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/x-ndjson"),
|
||||
);
|
||||
response
|
||||
}
|
||||
}),
|
||||
);
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.expect("server should start");
|
||||
});
|
||||
|
||||
let usage_repository = Arc::new(InMemoryUsageReadRepository::default());
|
||||
let state = AppState::new()
|
||||
.expect("app state should build")
|
||||
.with_usage_data_repository_for_tests(Arc::clone(&usage_repository))
|
||||
.with_usage_runtime_for_tests(UsageRuntimeConfig {
|
||||
enabled: true,
|
||||
..UsageRuntimeConfig::default()
|
||||
})
|
||||
.with_execution_runtime_override_base_url(format!("http://{addr}"));
|
||||
let plan = ExecutionPlan {
|
||||
request_id: "req-live-stream-first-data".into(),
|
||||
candidate_id: Some("cand-live-stream-first-data".into()),
|
||||
provider_name: Some("openai".into()),
|
||||
provider_id: "prov-1".into(),
|
||||
endpoint_id: "ep-1".into(),
|
||||
key_id: "key-1".into(),
|
||||
method: "POST".into(),
|
||||
url: "https://chatgpt.com/backend-api/codex/responses".into(),
|
||||
headers: BTreeMap::from([
|
||||
("content-type".into(), "application/json".into()),
|
||||
("accept".into(), "text/event-stream".into()),
|
||||
]),
|
||||
content_type: Some("application/json".into()),
|
||||
content_encoding: None,
|
||||
body: RequestBody::from_json(json!({
|
||||
"model": "gpt-5.4",
|
||||
"input": "hello",
|
||||
"stream": true
|
||||
})),
|
||||
stream: true,
|
||||
client_api_format: "openai:cli".into(),
|
||||
provider_api_format: "openai:cli".into(),
|
||||
model_name: Some("gpt-5.4".into()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(5_000),
|
||||
total_ms: Some(5_000),
|
||||
..ExecutionTimeouts::default()
|
||||
}),
|
||||
};
|
||||
let decision = GatewayControlDecision::synthetic(
|
||||
"/v1/responses",
|
||||
Some("ai_public".to_string()),
|
||||
Some("openai".to_string()),
|
||||
Some("cli".to_string()),
|
||||
Some("openai:cli".to_string()),
|
||||
)
|
||||
.with_execution_runtime_candidate(true);
|
||||
|
||||
let response = execute_execution_runtime_stream(
|
||||
&state,
|
||||
plan,
|
||||
"trace-live-stream-first-data",
|
||||
&decision,
|
||||
"openai_cli_stream",
|
||||
None,
|
||||
Some(json!({
|
||||
"provider_api_format": "openai:cli",
|
||||
"client_api_format": "openai:cli",
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.expect("execution should succeed")
|
||||
.expect("execution should return a client response");
|
||||
|
||||
first_data_seen.notified().await;
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
|
||||
let live_usage = loop {
|
||||
let usage = usage_repository
|
||||
.find_by_request_id("req-live-stream-first-data")
|
||||
.await
|
||||
.expect("usage should read");
|
||||
if usage.as_ref().is_some_and(|usage| {
|
||||
usage.status == "streaming" && usage.first_byte_time_ms.is_some()
|
||||
}) {
|
||||
break usage.expect("live usage should exist");
|
||||
}
|
||||
assert!(
|
||||
tokio::time::Instant::now() < deadline,
|
||||
"usage should record streaming status with first byte before terminal telemetry"
|
||||
);
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
};
|
||||
|
||||
assert_eq!(live_usage.status, "streaming");
|
||||
assert!(live_usage.first_byte_time_ms.is_some());
|
||||
|
||||
release_terminal.notify_one();
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("response body should read");
|
||||
let text = String::from_utf8(body.to_vec()).expect("response body should be utf8");
|
||||
assert!(text.contains("response.output_text.delta"));
|
||||
|
||||
server.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_execution_runtime_stream_bridges_sync_json_body_from_remote_runtime_to_sse() {
|
||||
let listener = crate::test_support::bind_loopback_listener()
|
||||
|
||||
@@ -121,7 +121,7 @@ WITH aggregated AS (
|
||||
COALESCE(SUM(usage.cache_read_input_tokens), 0) AS cache_read_tokens,
|
||||
MIN(COALESCE(usage_settlement_snapshots.finalized_at, usage.finalized_at)) AS first_finalized_at,
|
||||
MAX(COALESCE(usage_settlement_snapshots.finalized_at, usage.finalized_at)) AS last_finalized_at
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage_settlement_snapshots.wallet_id IS NOT NULL
|
||||
@@ -184,7 +184,7 @@ WHERE ledgers.billing_date = $1
|
||||
AND ledgers.billing_timezone = $2
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage_settlement_snapshots.wallet_id = ledgers.wallet_id
|
||||
@@ -600,7 +600,7 @@ const SELECT_STATS_DAILY_AGGREGATE_SQL: &str = r#"
|
||||
SELECT
|
||||
(
|
||||
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
|
||||
FROM usage AS cache_hit_usage
|
||||
FROM usage_billing_facts AS cache_hit_usage
|
||||
WHERE cache_hit_usage.created_at >= $1
|
||||
AND cache_hit_usage.created_at < $2
|
||||
) AS cache_hit_total_requests,
|
||||
@@ -610,13 +610,13 @@ SELECT
|
||||
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS cache_hit_usage
|
||||
FROM usage_billing_facts AS cache_hit_usage
|
||||
WHERE cache_hit_usage.created_at >= $1
|
||||
AND cache_hit_usage.created_at < $2
|
||||
) AS cache_hit_requests,
|
||||
(
|
||||
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -627,7 +627,7 @@ SELECT
|
||||
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -636,7 +636,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -659,7 +659,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -671,7 +671,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -809,7 +809,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -826,7 +826,7 @@ SELECT
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -840,7 +840,7 @@ SELECT
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -850,7 +850,7 @@ SELECT
|
||||
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
||||
AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -858,7 +858,7 @@ SELECT
|
||||
) AS settled_total_cost,
|
||||
(
|
||||
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -868,7 +868,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -878,7 +878,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -891,7 +891,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -904,7 +904,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -912,7 +912,7 @@ SELECT
|
||||
) AS settled_cache_read_tokens,
|
||||
(
|
||||
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -920,7 +920,7 @@ SELECT
|
||||
) AS settled_first_finalized_at_unix_secs,
|
||||
(
|
||||
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -1081,7 +1081,7 @@ SELECT
|
||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms,
|
||||
CAST(COUNT(DISTINCT model) AS BIGINT) AS unique_models,
|
||||
CAST(COUNT(DISTINCT provider_name) AS BIGINT) AS unique_providers
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
@@ -1105,7 +1105,7 @@ SELECT
|
||||
CAST(percentile_cont(0.5) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p50,
|
||||
CAST(percentile_cont(0.9) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p90,
|
||||
CAST(percentile_cont(0.99) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p99
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status = 'completed'
|
||||
@@ -1118,7 +1118,7 @@ SELECT
|
||||
CAST(percentile_cont(0.5) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p50,
|
||||
CAST(percentile_cont(0.9) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p90,
|
||||
CAST(percentile_cont(0.99) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p99
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status = 'completed'
|
||||
@@ -1295,7 +1295,7 @@ WITH aggregated AS (
|
||||
) AS BIGINT
|
||||
) AS response_time_samples,
|
||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND model IS NOT NULL
|
||||
@@ -1365,7 +1365,7 @@ WITH aggregated AS (
|
||||
CAST(COALESCE(SUM(cache_creation_input_tokens), 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
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
@@ -1437,7 +1437,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND model IS NOT NULL
|
||||
@@ -1523,7 +1523,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -1589,7 +1589,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -1665,7 +1665,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -1742,7 +1742,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -1832,7 +1832,7 @@ WITH aggregated AS (
|
||||
CAST(COALESCE(SUM(cache_creation_input_tokens), 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
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND api_key_id IS NOT NULL
|
||||
@@ -1894,7 +1894,7 @@ WITH aggregated AS (
|
||||
provider_name,
|
||||
model,
|
||||
CAST(COUNT(id) AS BIGINT) AS total_count
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND error_category IS NOT NULL
|
||||
@@ -2183,7 +2183,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -2472,7 +2472,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS successful_response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -2740,7 +2740,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS successful_response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -3008,7 +3008,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS successful_response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -3122,7 +3122,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS BIGINT
|
||||
) AS response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -3218,7 +3218,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -3300,7 +3300,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -3386,7 +3386,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -3473,7 +3473,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
) AS estimated_full_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
LEFT JOIN usage_settlement_snapshots
|
||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||
WHERE usage.created_at >= $1
|
||||
@@ -3700,7 +3700,7 @@ WHERE is_complete IS TRUE
|
||||
"#;
|
||||
const SELECT_NEXT_STATS_DAILY_BUCKET_SQL: &str = r#"
|
||||
SELECT date_trunc('day', MIN(created_at)) AS next_bucket
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
@@ -3713,7 +3713,7 @@ WHERE is_complete IS TRUE
|
||||
"#;
|
||||
const SELECT_NEXT_STATS_HOURLY_BUCKET_SQL: &str = r#"
|
||||
SELECT date_trunc('hour', MIN(created_at)) AS next_bucket
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
@@ -3723,7 +3723,7 @@ const SELECT_STATS_HOURLY_AGGREGATE_SQL: &str = r#"
|
||||
SELECT
|
||||
(
|
||||
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
|
||||
FROM usage AS cache_hit_usage
|
||||
FROM usage_billing_facts AS cache_hit_usage
|
||||
WHERE cache_hit_usage.created_at >= $1
|
||||
AND cache_hit_usage.created_at < $2
|
||||
) AS cache_hit_total_requests,
|
||||
@@ -3733,13 +3733,13 @@ SELECT
|
||||
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS cache_hit_usage
|
||||
FROM usage_billing_facts AS cache_hit_usage
|
||||
WHERE cache_hit_usage.created_at >= $1
|
||||
AND cache_hit_usage.created_at < $2
|
||||
) AS cache_hit_requests,
|
||||
(
|
||||
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3750,7 +3750,7 @@ SELECT
|
||||
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3759,7 +3759,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3782,7 +3782,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3794,7 +3794,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3932,7 +3932,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3949,7 +3949,7 @@ SELECT
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3963,7 +3963,7 @@ SELECT
|
||||
0
|
||||
) AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS completed_usage
|
||||
FROM usage_billing_facts AS completed_usage
|
||||
WHERE completed_usage.created_at >= $1
|
||||
AND completed_usage.created_at < $2
|
||||
AND completed_usage.status = 'completed'
|
||||
@@ -3973,7 +3973,7 @@ SELECT
|
||||
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
||||
AS DOUBLE PRECISION
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -3981,7 +3981,7 @@ SELECT
|
||||
) AS settled_total_cost,
|
||||
(
|
||||
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -3991,7 +3991,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4001,7 +4001,7 @@ SELECT
|
||||
SELECT CAST(
|
||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4014,7 +4014,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4027,7 +4027,7 @@ SELECT
|
||||
0
|
||||
) AS BIGINT
|
||||
)
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4035,7 +4035,7 @@ SELECT
|
||||
) AS settled_cache_read_tokens,
|
||||
(
|
||||
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4043,7 +4043,7 @@ SELECT
|
||||
) AS settled_first_finalized_at_unix_secs,
|
||||
(
|
||||
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
||||
FROM usage AS settled_usage
|
||||
FROM usage_billing_facts AS settled_usage
|
||||
WHERE settled_usage.created_at >= $1
|
||||
AND settled_usage.created_at < $2
|
||||
AND settled_usage.billing_status = 'settled'
|
||||
@@ -4101,7 +4101,7 @@ SELECT
|
||||
0
|
||||
) AS response_time_samples,
|
||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND status NOT IN ('pending', 'streaming')
|
||||
@@ -4330,7 +4330,7 @@ WITH aggregated AS (
|
||||
),
|
||||
0
|
||||
) AS response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -4441,7 +4441,7 @@ WITH aggregated AS (
|
||||
0
|
||||
) AS response_time_samples,
|
||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND model IS NOT NULL
|
||||
@@ -4517,7 +4517,7 @@ WITH aggregated AS (
|
||||
),
|
||||
0
|
||||
) AS response_time_samples
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND user_id IS NOT NULL
|
||||
@@ -4573,7 +4573,7 @@ WITH aggregated AS (
|
||||
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
||||
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
|
||||
FROM usage
|
||||
FROM usage_billing_facts AS usage
|
||||
WHERE created_at >= $1
|
||||
AND created_at < $2
|
||||
AND provider_name IS NOT NULL
|
||||
|
||||
@@ -110,7 +110,7 @@ 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"
|
||||
FROM usage_billing_facts AS "usage"
|
||||
WHERE "usage".user_id = ANY($1::TEXT[])
|
||||
AND "usage".created_at >= $2
|
||||
AND "usage".status NOT IN ('pending', 'streaming')
|
||||
|
||||
Reference in New Issue
Block a user