Use header-only usage server timing

This commit is contained in:
Novick Yuan
2026-05-28 14:30:15 +08:00
parent 01c8592ca6
commit 6412294262
14 changed files with 425 additions and 75 deletions
@@ -9,9 +9,9 @@ use aether_admin::observability::usage::{
admin_usage_data_unavailable_response, admin_usage_has_fallback, admin_usage_is_failed,
admin_usage_matches_search, admin_usage_matches_username, admin_usage_parse_ids,
admin_usage_parse_limit, admin_usage_parse_offset, admin_usage_provider_key_name,
admin_usage_record_json, build_admin_usage_active_requests_response,
build_admin_usage_records_response, build_admin_usage_summary_stats_response_from_summary,
usage_server_now_unix_ms, ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
admin_usage_record_json, attach_usage_server_now_header,
build_admin_usage_active_requests_response, build_admin_usage_records_response,
build_admin_usage_summary_stats_response_from_summary, ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
};
use aether_data::repository::users::StoredUserSummary;
use aether_data_contracts::repository::{
@@ -314,14 +314,15 @@ fn build_admin_usage_records_response_with_attempt_flags(
})
.collect();
Json(json!({
"server_now_unix_ms": usage_server_now_unix_ms(),
"records": records,
"total": total,
"limit": limit,
"offset": offset,
}))
.into_response()
attach_usage_server_now_header(
Json(json!({
"records": records,
"total": total,
"limit": limit,
"offset": offset,
}))
.into_response(),
)
}
fn build_admin_usage_records_query(
@@ -4,6 +4,7 @@ use aether_ai_serving::UPSTREAM_IS_STREAM_KEY;
use aether_billing::{
normalize_input_tokens_for_billing, normalize_total_input_context_for_cache_hit_rate,
};
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use aether_data_contracts::repository::usage::{
StoredRequestUsageAudit, StoredUsageBreakdownSummaryRow, StoredUsageDailySummary,
UsageAuditKeywordSearchQuery, UsageAuditListQuery, UsageBreakdownGroupBy,
@@ -33,6 +34,17 @@ fn users_me_usage_server_now_unix_ms() -> u64 {
u64::try_from(Utc::now().timestamp_millis()).unwrap_or_default()
}
fn attach_users_me_usage_server_now_header(mut response: Response<Body>) -> Response<Body> {
if let Ok(value) = http::HeaderValue::from_str(&users_me_usage_server_now_unix_ms().to_string())
{
response.headers_mut().insert(
http::HeaderName::from_static(USAGE_SERVER_NOW_UNIX_MS_HEADER),
value,
);
}
response
}
fn build_users_me_usage_reader_unavailable_response() -> Response<Body> {
build_auth_error_response(
http::StatusCode::SERVICE_UNAVAILABLE,
@@ -1075,7 +1087,6 @@ pub(super) async fn handle_users_me_usage_get(
.flatten();
let mut payload = json!({
"server_now_unix_ms": users_me_usage_server_now_unix_ms(),
"total_requests": total_requests,
"total_input_tokens": total_input_tokens,
"total_output_tokens": total_output_tokens,
@@ -1099,7 +1110,7 @@ pub(super) async fn handle_users_me_usage_get(
&summary_by_provider
));
}
Json(payload).into_response()
attach_users_me_usage_server_now_header(Json(payload).into_response())
}
pub(super) async fn handle_users_me_usage_active_get(
@@ -1174,14 +1185,15 @@ pub(super) async fn handle_users_me_usage_active_get(
.collect::<Vec<_>>()
};
Json(json!({
"server_now_unix_ms": users_me_usage_server_now_unix_ms(),
"requests": items
.iter()
.map(build_users_me_usage_active_payload)
.collect::<Vec<_>>(),
}))
.into_response()
attach_users_me_usage_server_now_header(
Json(json!({
"requests": items
.iter()
.map(build_users_me_usage_active_payload)
.collect::<Vec<_>>(),
}))
.into_response(),
)
}
pub(super) async fn handle_users_me_usage_interval_timeline_get(
@@ -1370,14 +1382,21 @@ async fn build_usage_heatmap_summaries(
mod tests {
use std::collections::BTreeMap;
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use aether_data_contracts::repository::usage::StoredRequestUsageAudit;
use axum::{
body::Body,
response::{IntoResponse, Response},
Json,
};
use chrono::Utc;
use serde_json::json;
use super::{
build_users_me_usage_active_payload, build_users_me_usage_record_payload,
users_me_usage_client_is_stream, users_me_usage_is_failed,
users_me_usage_server_now_unix_ms, users_me_usage_upstream_is_stream,
attach_users_me_usage_server_now_header, build_users_me_usage_active_payload,
build_users_me_usage_record_payload, users_me_usage_client_is_stream,
users_me_usage_is_failed, users_me_usage_server_now_unix_ms,
users_me_usage_upstream_is_stream,
};
fn sample_usage(status: &str) -> StoredRequestUsageAudit {
@@ -1433,6 +1452,28 @@ mod tests {
assert!(value > 1_000_000_000_000);
}
fn assert_users_me_usage_server_now_header(response: &Response<Body>) {
let value = response
.headers()
.get(USAGE_SERVER_NOW_UNIX_MS_HEADER)
.expect("user usage response should include server now header")
.to_str()
.expect("server now header should be valid ASCII")
.parse::<u64>()
.expect("server now header should be epoch millis");
assert!(value > 1_000_000_000_000);
}
#[test]
fn users_me_usage_server_now_header_is_added_to_response() {
let response = attach_users_me_usage_server_now_header(
Json(json!({ "requests": [] })).into_response(),
);
assert_users_me_usage_server_now_header(&response);
}
#[test]
fn user_usage_record_payload_rehydrates_cache_creation_total_from_classified_fields() {
let item = StoredRequestUsageAudit {
@@ -1,3 +1,4 @@
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use axum::body::Body;
use axum::extract::{Request, State};
use axum::http::{self, HeaderValue, Response};
@@ -6,6 +7,8 @@ use axum::middleware::Next;
use crate::headers::header_value_str;
use crate::state::{AppState, FrontdoorCorsConfig};
const FRONTDOOR_CREDENTIALS_EXPOSE_HEADERS: &str = "*, x-aether-server-now-unix-ms";
fn append_vary(headers: &mut http::HeaderMap, value: &'static str) {
headers.append(http::header::VARY, HeaderValue::from_static(value));
}
@@ -31,7 +34,11 @@ fn apply_frontdoor_cors_headers(
);
headers.insert(
http::header::ACCESS_CONTROL_EXPOSE_HEADERS,
HeaderValue::from_static("*"),
HeaderValue::from_static(if cors.allow_credentials() {
FRONTDOOR_CREDENTIALS_EXPOSE_HEADERS
} else {
"*"
}),
);
if let Some(value) = requested_headers {
if let Ok(value) = HeaderValue::from_str(value) {
@@ -109,3 +116,52 @@ pub(crate) async fn frontdoor_cors_middleware(
);
response
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_exposes_header(value: &HeaderValue, expected: &str) {
let exposed_headers = value
.to_str()
.expect("expose headers should be valid ASCII");
assert!(
exposed_headers
.split(',')
.map(str::trim)
.any(|header| header.eq_ignore_ascii_case(expected)),
"{exposed_headers} should include {expected}"
);
}
#[test]
fn frontdoor_cors_explicitly_exposes_usage_server_time_for_credentials() {
let cors = FrontdoorCorsConfig::new(vec!["http://localhost:5173".to_string()], true)
.expect("cors config should build");
let mut headers = http::HeaderMap::new();
apply_frontdoor_cors_headers(&mut headers, &cors, "http://localhost:5173", None);
let expose_headers = headers
.get(http::header::ACCESS_CONTROL_EXPOSE_HEADERS)
.expect("expose headers should be set");
assert_exposes_header(expose_headers, "*");
assert_exposes_header(expose_headers, USAGE_SERVER_NOW_UNIX_MS_HEADER);
}
#[test]
fn frontdoor_cors_keeps_wildcard_expose_headers_without_credentials() {
let cors = FrontdoorCorsConfig::new(vec!["http://localhost:5173".to_string()], false)
.expect("cors config should build");
let mut headers = http::HeaderMap::new();
apply_frontdoor_cors_headers(&mut headers, &cors, "http://localhost:5173", None);
assert_eq!(
headers
.get(http::header::ACCESS_CONTROL_EXPOSE_HEADERS)
.expect("expose headers should be set"),
"*"
);
}
}
+14 -6
View File
@@ -7,6 +7,7 @@ use crate::tests::{
build_state_with_execution_runtime_override, json, start_server, AppState, Arc, Body,
FrontdoorCorsConfig, Mutex, Request, Router, StatusCode, FRONTDOOR_MANIFEST_PATH, READYZ_PATH,
};
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use aether_crypto::DEVELOPMENT_ENCRYPTION_KEY;
use aether_data::repository::auth::InMemoryAuthApiKeySnapshotRepository;
use aether_data::repository::candidate_selection::InMemoryMinimalCandidateSelectionReadRepository;
@@ -440,12 +441,19 @@ async fn gateway_adds_cors_headers_to_proxied_responses() {
.expect("allow origin header"),
"http://localhost:3000"
);
assert_eq!(
response_headers
.get("access-control-expose-headers")
.expect("expose headers header"),
"*"
);
let expose_headers = response_headers
.get("access-control-expose-headers")
.expect("expose headers header")
.to_str()
.expect("expose headers should be valid ASCII");
assert!(expose_headers
.split(',')
.map(str::trim)
.any(|header| header == "*"));
assert!(expose_headers
.split(',')
.map(str::trim)
.any(|header| header.eq_ignore_ascii_case(USAGE_SERVER_NOW_UNIX_MS_HEADER)));
assert_eq!(
*execution_runtime_hits.lock().expect("mutex should lock"),
1