Rootfix usage counter outbox

This commit is contained in:
fawney19
2026-05-19 00:30:24 +08:00
parent 19c8688eb1
commit 5eed329916
60 changed files with 4910 additions and 362 deletions

View File

@@ -6,6 +6,7 @@ use super::route_filters::{
};
use crate::constants::INTERNAL_GATEWAY_PATH_PREFIXES;
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::handlers::admin::shared::build_admin_usage_counter_health_payload;
use crate::GatewayError;
use aether_admin::observability::monitoring::{
admin_monitoring_bad_request_response, admin_monitoring_user_behavior_user_id_from_path,
@@ -189,6 +190,13 @@ pub(super) async fn build_admin_monitoring_system_status_response(
)
.unwrap_or(usize::MAX);
let tunnel = state.tunnel.stats();
let usage_counter_snapshot = state
.data
.read_usage_counter_health()
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?;
let usage_counter =
build_admin_usage_counter_health_payload(&usage_counter_snapshot, now_unix_secs);
Ok(build_admin_monitoring_system_status_payload_response(
now,
@@ -206,5 +214,6 @@ pub(super) async fn build_admin_monitoring_system_status_response(
tunnel.active_streams,
INTERNAL_GATEWAY_PATH_PREFIXES,
recent_errors,
usage_counter,
))
}

View File

@@ -1,7 +1,9 @@
use super::range::{build_comparison_range, parse_bounded_u32};
use super::resolve_admin_usage_time_range;
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::handlers::admin::shared::{query_param_optional_bool, query_param_value};
use crate::handlers::admin::shared::{
build_admin_usage_counter_health_payload, query_param_optional_bool, query_param_value,
};
use crate::GatewayError;
use aether_admin::observability::stats::{
admin_stats_bad_request_response, admin_stats_comparison_empty_response,
@@ -21,6 +23,22 @@ use aether_data_contracts::repository::usage::{
};
use axum::{body::Body, http, response::Response};
async fn build_usage_counter_health_payload(
state: &AdminAppState<'_>,
) -> Result<serde_json::Value, GatewayError> {
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
let snapshot = state
.as_ref()
.data
.read_usage_counter_health()
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?;
Ok(build_admin_usage_counter_health_payload(
&snapshot,
now_unix_secs,
))
}
fn usage_summary_to_admin_stats_aggregate(
summary: &aether_data_contracts::repository::usage::StoredUsageAuditSummary,
) -> AdminStatsAggregate {
@@ -211,13 +229,18 @@ pub(super) async fn maybe_build_local_admin_stats_analytics_response(
Ok(value) => u64::from(value.unwrap_or(10_000)),
Err(detail) => return Ok(Some(admin_stats_bad_request_response(detail))),
};
let usage_counter = build_usage_counter_health_payload(state).await?;
if !state.has_usage_data_reader() {
return Ok(Some(admin_stats_provider_performance_empty_response()));
return Ok(Some(admin_stats_provider_performance_empty_response(
usage_counter,
)));
}
let Some((created_from_unix_secs, created_until_unix_secs)) = time_range.to_unix_bounds()
else {
return Ok(Some(admin_stats_provider_performance_empty_response()));
return Ok(Some(admin_stats_provider_performance_empty_response(
usage_counter,
)));
};
let performance = state
.summarize_usage_provider_performance(&UsageProviderPerformanceQuery {
@@ -240,6 +263,7 @@ pub(super) async fn maybe_build_local_admin_stats_analytics_response(
.await?;
return Ok(Some(build_admin_stats_provider_performance_response(
&performance,
usage_counter,
)));
}

View File

@@ -1,10 +1,12 @@
mod paths;
mod payloads;
mod proxy_errors;
mod usage_counter;
pub(crate) use self::paths::*;
pub(crate) use self::payloads::*;
pub(crate) use self::proxy_errors::build_proxy_error_response;
pub(crate) use self::usage_counter::build_admin_usage_counter_health_payload;
pub(crate) use crate::handlers::shared::{
attach_admin_audit_response, build_admin_provider_key_response,
decrypt_catalog_secret_with_fallbacks, default_provider_key_status_snapshot,

View File

@@ -0,0 +1,85 @@
use aether_data_contracts::repository::usage::UsageCounterHealthSnapshot;
use serde_json::{json, Value};
pub(crate) fn build_admin_usage_counter_health_payload(
snapshot: &UsageCounterHealthSnapshot,
now_unix_secs: u64,
) -> Value {
let oldest_pending_age_secs = snapshot
.oldest_pending_created_at_unix_secs
.map(|created_at| now_unix_secs.saturating_sub(created_at));
let status = match (snapshot.pending_rows, oldest_pending_age_secs) {
(0, _) => "idle",
(_, Some(age)) if age >= 60 => "backlogged",
_ => "catching_up",
};
json!({
"status": status,
"outbox_pending_rows": snapshot.pending_rows,
"outbox_processed_rows": snapshot.processed_rows,
"oldest_pending_created_at_unix_secs": snapshot.oldest_pending_created_at_unix_secs,
"oldest_pending_age_secs": oldest_pending_age_secs,
"latest_processed_at_unix_secs": snapshot.latest_processed_at_unix_secs,
"pending_by_kind": snapshot.pending_by_kind,
})
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use aether_data_contracts::repository::usage::UsageCounterHealthSnapshot;
use serde_json::json;
use super::build_admin_usage_counter_health_payload;
#[test]
fn usage_counter_health_payload_reports_idle_without_pending_rows() {
let snapshot = UsageCounterHealthSnapshot {
processed_rows: 42,
latest_processed_at_unix_secs: Some(1_000),
..UsageCounterHealthSnapshot::default()
};
let payload = build_admin_usage_counter_health_payload(&snapshot, 1_100);
assert_eq!(payload["status"], json!("idle"));
assert_eq!(payload["outbox_pending_rows"], json!(0));
assert_eq!(payload["outbox_processed_rows"], json!(42));
assert_eq!(payload["oldest_pending_age_secs"], json!(null));
assert_eq!(payload["latest_processed_at_unix_secs"], json!(1_000));
}
#[test]
fn usage_counter_health_payload_reports_catching_up_for_fresh_backlog() {
let mut pending_by_kind = BTreeMap::new();
pending_by_kind.insert("api_key".to_string(), 3);
let snapshot = UsageCounterHealthSnapshot {
pending_rows: 3,
oldest_pending_created_at_unix_secs: Some(1_050),
pending_by_kind,
..UsageCounterHealthSnapshot::default()
};
let payload = build_admin_usage_counter_health_payload(&snapshot, 1_100);
assert_eq!(payload["status"], json!("catching_up"));
assert_eq!(payload["oldest_pending_age_secs"], json!(50));
assert_eq!(payload["pending_by_kind"]["api_key"], json!(3));
}
#[test]
fn usage_counter_health_payload_reports_backlogged_for_old_backlog() {
let snapshot = UsageCounterHealthSnapshot {
pending_rows: 1,
oldest_pending_created_at_unix_secs: Some(1_000),
..UsageCounterHealthSnapshot::default()
};
let payload = build_admin_usage_counter_health_payload(&snapshot, 1_060);
assert_eq!(payload["status"], json!("backlogged"));
assert_eq!(payload["oldest_pending_age_secs"], json!(60));
}
}

View File

@@ -1,4 +1,5 @@
use crate::handlers::admin::request::AdminAppState;
use crate::handlers::admin::shared::build_admin_usage_counter_health_payload;
use crate::handlers::shared::{system_config_bool, system_config_string};
use crate::GatewayError;
use aether_admin::system::{
@@ -114,6 +115,15 @@ pub(crate) async fn build_admin_system_stats_payload(
.filter(|provider| provider.is_active)
.count() as u64;
let stats = state.read_admin_system_stats().await?;
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
let usage_counter_snapshot = state
.as_ref()
.data
.read_usage_counter_health()
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?;
let usage_counter =
build_admin_usage_counter_health_payload(&usage_counter_snapshot, now_unix_secs);
Ok(build_admin_system_stats_payload_pure(
stats.total_users,
@@ -122,6 +132,7 @@ pub(crate) async fn build_admin_system_stats_payload(
active_providers,
stats.total_api_keys,
stats.total_requests,
usage_counter,
))
}