use super::Metadata;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestMetrics {
#[serde(flatten)]
pub metadata: Metadata,
pub request_id: String,
pub user_id: Option<Uuid>,
pub team_id: Option<Uuid>,
pub api_key_id: Option<Uuid>,
pub model: String,
pub provider: String,
pub request_type: String,
pub status: RequestStatus,
pub status_code: u16,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub response_time_ms: u64,
pub queue_time_ms: u64,
pub provider_time_ms: u64,
pub token_usage: TokenUsage,
pub cost: CostInfo,
pub error: Option<ErrorInfo>,
pub cache: CacheMetrics,
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequestStatus {
Success,
Error,
Timeout,
RateLimit,
QuotaExceeded,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TokenUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
pub cached_tokens: Option<u32>,
pub reasoning_tokens: Option<u32>,
pub audio_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CostInfo {
pub input_cost: f64,
pub output_cost: f64,
pub total_cost: f64,
pub currency: String,
pub rates: CostRates,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CostRates {
pub input_cost_per_token: f64,
pub output_cost_per_token: f64,
pub cost_per_request: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorInfo {
pub code: String,
pub message: String,
pub error_type: String,
pub provider_code: Option<String>,
pub stack_trace: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CacheMetrics {
pub hit: bool,
pub cache_type: Option<String>,
pub cache_key: Option<String>,
pub similarity_score: Option<f32>,
pub cache_latency_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderMetrics {
#[serde(flatten)]
pub metadata: Metadata,
pub provider: String,
pub period_start: chrono::DateTime<chrono::Utc>,
pub period_end: chrono::DateTime<chrono::Utc>,
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub success_rate: f64,
pub avg_response_time_ms: f64,
pub p50_response_time_ms: f64,
pub p95_response_time_ms: f64,
pub p99_response_time_ms: f64,
pub total_tokens: u64,
pub total_cost: f64,
pub error_breakdown: HashMap<String, u64>,
pub model_breakdown: HashMap<String, ModelMetrics>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelMetrics {
pub model: String,
pub requests: u64,
pub successes: u64,
pub tokens: u64,
pub cost: f64,
pub avg_response_time_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemMetrics {
#[serde(flatten)]
pub metadata: Metadata,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub cpu_usage: f64,
pub memory_usage: u64,
pub memory_usage_percent: f64,
pub disk_usage: u64,
pub disk_usage_percent: f64,
pub network_io: NetworkIO,
pub active_connections: u32,
pub queue_sizes: HashMap<String, u32>,
pub thread_pool: ThreadPoolStats,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NetworkIO {
pub bytes_received: u64,
pub bytes_sent: u64,
pub packets_received: u64,
pub packets_sent: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ThreadPoolStats {
pub active_threads: u32,
pub total_threads: u32,
pub queued_tasks: u32,
pub completed_tasks: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsageAnalytics {
#[serde(flatten)]
pub metadata: Metadata,
pub period: TimePeriod,
pub user_id: Option<Uuid>,
pub team_id: Option<Uuid>,
pub total_requests: u64,
pub total_tokens: u64,
pub total_cost: f64,
pub model_usage: HashMap<String, ModelUsage>,
pub provider_usage: HashMap<String, ProviderUsage>,
pub daily_breakdown: Vec<DailyUsage>,
pub top_endpoints: Vec<EndpointUsage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimePeriod {
pub start: chrono::DateTime<chrono::Utc>,
pub end: chrono::DateTime<chrono::Utc>,
pub period_type: PeriodType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PeriodType {
Hour,
Day,
Week,
Month,
Year,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelUsage {
pub model: String,
pub requests: u64,
pub tokens: u64,
pub cost: f64,
pub success_rate: f64,
pub avg_response_time_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderUsage {
pub provider: String,
pub requests: u64,
pub tokens: u64,
pub cost: f64,
pub success_rate: f64,
pub avg_response_time_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyUsage {
pub date: chrono::NaiveDate,
pub requests: u64,
pub tokens: u64,
pub cost: f64,
pub unique_users: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointUsage {
pub endpoint: String,
pub requests: u64,
pub success_rate: f64,
pub avg_response_time_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertConfig {
#[serde(flatten)]
pub metadata: Metadata,
pub name: String,
pub description: Option<String>,
pub condition: AlertCondition,
pub threshold: f64,
pub severity: AlertSeverity,
pub channels: Vec<String>,
pub enabled: bool,
pub cooldown_seconds: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AlertCondition {
ErrorRateHigh,
ResponseTimeSlow,
RequestVolumeHigh,
CostHigh,
ProviderDown,
QuotaExceeded,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AlertSeverity {
Info,
Warning,
Error,
Critical,
}
impl RequestMetrics {
pub fn new(request_id: String, model: String, provider: String, request_type: String) -> Self {
Self {
metadata: Metadata::new(),
request_id,
user_id: None,
team_id: None,
api_key_id: None,
model,
provider,
request_type,
status: RequestStatus::Success,
status_code: 200,
timestamp: chrono::Utc::now(),
response_time_ms: 0,
queue_time_ms: 0,
provider_time_ms: 0,
token_usage: TokenUsage::default(),
cost: CostInfo::default(),
error: None,
cache: CacheMetrics::default(),
extra: HashMap::new(),
}
}
pub fn with_user(mut self, user_id: Uuid, team_id: Option<Uuid>) -> Self {
self.user_id = Some(user_id);
self.team_id = team_id;
self
}
pub fn with_api_key(mut self, api_key_id: Uuid) -> Self {
self.api_key_id = Some(api_key_id);
self
}
pub fn with_timing(
mut self,
response_time_ms: u64,
queue_time_ms: u64,
provider_time_ms: u64,
) -> Self {
self.response_time_ms = response_time_ms;
self.queue_time_ms = queue_time_ms;
self.provider_time_ms = provider_time_ms;
self
}
pub fn with_tokens(mut self, input_tokens: u32, output_tokens: u32) -> Self {
self.token_usage.input_tokens = input_tokens;
self.token_usage.output_tokens = output_tokens;
self.token_usage.total_tokens = input_tokens + output_tokens;
self
}
pub fn with_cost(mut self, input_cost: f64, output_cost: f64, currency: String) -> Self {
self.cost.input_cost = input_cost;
self.cost.output_cost = output_cost;
self.cost.total_cost = input_cost + output_cost;
self.cost.currency = currency;
self
}
pub fn with_error(mut self, error: ErrorInfo) -> Self {
self.status = RequestStatus::Error;
self.error = Some(error);
self
}
pub fn with_cache(mut self, cache: CacheMetrics) -> Self {
self.cache = cache;
self
}
}
impl TokenUsage {
pub fn new(input_tokens: u32, output_tokens: u32) -> Self {
Self {
input_tokens,
output_tokens,
total_tokens: input_tokens + output_tokens,
cached_tokens: None,
reasoning_tokens: None,
audio_tokens: None,
}
}
}
impl CostInfo {
pub fn new(input_cost: f64, output_cost: f64, currency: String) -> Self {
Self {
input_cost,
output_cost,
total_cost: input_cost + output_cost,
currency,
rates: CostRates::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_metrics_creation() {
let metrics = RequestMetrics::new(
"req-123".to_string(),
"gpt-4".to_string(),
"openai".to_string(),
"chat_completion".to_string(),
);
assert_eq!(metrics.request_id, "req-123");
assert_eq!(metrics.model, "gpt-4");
assert_eq!(metrics.provider, "openai");
assert!(matches!(metrics.status, RequestStatus::Success));
}
#[test]
fn test_token_usage() {
let usage = TokenUsage::new(100, 50);
assert_eq!(usage.input_tokens, 100);
assert_eq!(usage.output_tokens, 50);
assert_eq!(usage.total_tokens, 150);
}
#[test]
fn test_cost_calculation() {
let cost = CostInfo::new(0.01, 0.02, "USD".to_string());
assert_eq!(cost.input_cost, 0.01);
assert_eq!(cost.output_cost, 0.02);
assert_eq!(cost.total_cost, 0.03);
assert_eq!(cost.currency, "USD");
}
}