mod private
{
use std::collections::HashMap;
use core::time::{ Duration };
use serde::{ Serialize, Deserialize };
#[ derive( Debug ) ]
pub struct ErrorAnalyzer
{
pub( super ) error_categories : HashMap< String, ErrorCategory >,
pub( super ) total_errors : u64,
}
#[ derive( Debug, Clone ) ]
pub struct ErrorCategory
{
pub( super ) count : u64,
pub( super ) messages : Vec< String >,
pub( super ) status_codes : Vec< String >,
}
#[ derive( Debug, Clone ) ]
pub struct ErrorSummary
{
pub( super ) total_errors : u64,
pub( super ) most_common_category : String,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct DiagnosticsContext
{
pub( super ) request_id : String,
pub( super ) user_id : Option< String >,
pub( super ) operation : String,
pub( super ) model : Option< String >,
pub( super ) timestamp : Option< String >,
}
#[ derive( Debug ) ]
pub struct DiagnosticsAggregator
{
pub( super ) requests : HashMap< String, RequestMetrics >,
pub( super ) total_requests : u64,
pub( super ) successful_requests : u64,
pub( super ) failed_requests : u64,
pub( super ) total_duration : Duration,
}
#[ derive( Debug, Clone ) ]
pub( super ) struct RequestMetrics
{
pub( super ) operation : String,
pub( super ) duration : Option< Duration >,
pub( super ) success : Option< bool >,
pub( super ) error_category : Option< String >,
}
#[ derive( Debug, Clone ) ]
pub struct OperationMetrics
{
pub( super ) total_requests : u64,
pub( super ) successful_requests : u64,
}
#[ derive( Debug, Clone ) ]
pub struct DiagnosticsSummary
{
pub( super ) total_requests : u64,
pub( super ) successful_requests : u64,
pub( super ) failed_requests : u64,
pub( super ) average_duration : Duration,
}
#[ derive( Debug, Clone ) ]
pub struct DiagnosticsCollector
{
}
#[ derive( Debug, Clone ) ]
pub struct DiagnosticsData
{
pub( super ) curl_representation : Option< String >,
pub( super ) request_size : usize,
pub( super ) estimated_cost : Option< f64 >,
pub( super ) request_metrics : Vec< RequestMetricData >,
}
#[ derive( Debug, Clone ) ]
pub( super ) struct RequestMetricData
{
pub( super ) duration : Duration,
pub( super ) success : bool,
}
#[ derive( Debug ) ]
pub struct AggregatedMetrics
{
pub( super ) request_count : u32,
pub( super ) has_performance_data : bool,
}
impl ErrorCategory
{
fn new() -> Self
{
Self
{
count : 0,
messages : Vec::new(),
status_codes : Vec::new(),
}
}
#[ inline ]
#[ must_use ]
pub fn count( &self ) -> u64
{
self.count
}
#[ inline ]
#[ must_use ]
pub fn contains_message( &self, message : &str ) -> bool
{
self.messages.iter().any( | m | m.contains( message ) )
}
fn record_error( &mut self, message : String, status_code : String )
{
self.count += 1;
self.messages.push( message );
self.status_codes.push( status_code );
}
}
impl ErrorSummary
{
#[ inline ]
#[ must_use ]
pub fn total_errors( &self ) -> u64
{
self.total_errors
}
#[ inline ]
#[ must_use ]
pub fn most_common_category( &self ) -> &str
{
&self.most_common_category
}
}
impl ErrorAnalyzer
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
error_categories : HashMap::new(),
total_errors : 0,
}
}
#[ inline ]
pub fn record_error( &mut self, category : impl Into< String >, message : impl Into< String >, status_code : impl Into< String > )
{
let category_name = category.into();
let error_message = message.into();
let status = status_code.into();
let category_entry = self.error_categories
.entry( category_name )
.or_insert_with( ErrorCategory::new );
category_entry.record_error( error_message, status );
self.total_errors += 1;
}
#[ inline ]
#[ must_use ]
pub fn get_error_category( &self, category : &str ) -> &ErrorCategory
{
static EMPTY_CATEGORY : ErrorCategory = ErrorCategory
{
count : 0,
messages : Vec::new(),
status_codes : Vec::new(),
};
self.error_categories.get( category ).unwrap_or( &EMPTY_CATEGORY )
}
#[ inline ]
#[ must_use ]
pub fn get_summary( &self ) -> ErrorSummary
{
let mut categories : Vec< ( String, u64 ) > = self.error_categories
.iter()
.map( | ( name, category ) | ( name.clone(), category.count ) )
.collect();
categories.sort_by_key( | b | std::cmp::Reverse( b.1 ) );
let most_common_category = categories
.first()
.map_or_else( || "none".to_string(), | ( name, _ ) | name.clone() );
ErrorSummary
{
total_errors : self.total_errors,
most_common_category,
}
}
}
impl Default for ErrorAnalyzer
{
fn default() -> Self
{
Self::new()
}
}
impl DiagnosticsContext
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
request_id : String::new(),
user_id : None,
operation : String::new(),
model : None,
timestamp : None,
}
}
#[ inline ]
#[ must_use ]
pub fn request_id( mut self, id : impl Into< String > ) -> Self
{
self.request_id = id.into();
self
}
#[ inline ]
#[ must_use ]
pub fn user_id( mut self, id : impl Into< String > ) -> Self
{
self.user_id = Some( id.into() );
self
}
#[ inline ]
#[ must_use ]
pub fn operation( mut self, op : impl Into< String > ) -> Self
{
self.operation = op.into();
self
}
#[ inline ]
#[ must_use ]
pub fn model( mut self, model : impl Into< String > ) -> Self
{
self.model = Some( model.into() );
self
}
#[ inline ]
#[ must_use ]
pub fn get_request_id( &self ) -> &str
{
&self.request_id
}
#[ inline ]
#[ must_use ]
pub fn get_user_id( &self ) -> Option< &str >
{
self.user_id.as_deref()
}
#[ inline ]
#[ must_use ]
pub fn get_operation( &self ) -> &str
{
&self.operation
}
#[ inline ]
#[ must_use ]
pub fn get_model( &self ) -> Option< &str >
{
self.model.as_deref()
}
#[ inline ]
#[ must_use ]
pub fn to_json( &self ) -> String
{
serde_json::to_string( self ).unwrap_or_else( | _ | "{}".to_string() )
}
}
impl Default for DiagnosticsContext
{
fn default() -> Self
{
Self::new()
}
}
impl OperationMetrics
{
#[ inline ]
#[ must_use ]
pub fn success_rate( &self ) -> f64
{
if self.total_requests > 0
{
self.successful_requests as f64 / self.total_requests as f64
}
else
{
0.0
}
}
}
impl DiagnosticsSummary
{
#[ inline ]
#[ must_use ]
pub fn total_requests( &self ) -> u64
{
self.total_requests
}
#[ inline ]
#[ must_use ]
pub fn successful_requests( &self ) -> u64
{
self.successful_requests
}
#[ inline ]
#[ must_use ]
pub fn failed_requests( &self ) -> u64
{
self.failed_requests
}
#[ inline ]
#[ must_use ]
pub fn average_duration( &self ) -> Duration
{
self.average_duration
}
}
impl DiagnosticsAggregator
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
requests : HashMap::new(),
total_requests : 0,
successful_requests : 0,
failed_requests : 0,
total_duration : Duration::from_millis( 0 ),
}
}
#[ inline ]
pub fn record_request_start( &mut self, request_id : impl Into< String >, operation : impl Into< String > )
{
let id = request_id.into();
self.requests.insert( id, RequestMetrics
{
operation : operation.into(),
duration : None,
success : None,
error_category : None,
} );
self.total_requests += 1;
}
#[ inline ]
pub fn record_request_duration( &mut self, request_id : &str, duration : Duration )
{
if let Some( metrics ) = self.requests.get_mut( request_id )
{
metrics.duration = Some( duration );
self.total_duration += duration;
}
}
#[ inline ]
pub fn record_request_success( &mut self, request_id : &str )
{
if let Some( metrics ) = self.requests.get_mut( request_id )
{
metrics.success = Some( true );
self.successful_requests += 1;
}
}
#[ inline ]
pub fn record_request_error( &mut self, request_id : &str, error_category : impl Into< String > )
{
if let Some( metrics ) = self.requests.get_mut( request_id )
{
metrics.success = Some( false );
metrics.error_category = Some( error_category.into() );
self.failed_requests += 1;
}
}
#[ inline ]
#[ must_use ]
pub fn get_summary( &self ) -> DiagnosticsSummary
{
let requests_with_duration = u32::try_from(self.requests.values().filter( |m| m.duration.is_some() ).count()).unwrap_or(0);
let average_duration = if requests_with_duration > 0
{
self.total_duration / requests_with_duration
}
else
{
Duration::from_millis( 0 )
};
DiagnosticsSummary
{
total_requests : self.total_requests,
successful_requests : self.successful_requests,
failed_requests : self.failed_requests,
average_duration,
}
}
#[ inline ]
#[ must_use ]
pub fn get_operation_metrics( &self, operation : &str ) -> OperationMetrics
{
let mut total = 0;
let mut successful = 0;
for metrics in self.requests.values()
{
if metrics.operation == operation
{
total += 1;
if metrics.success == Some( true )
{
successful += 1;
}
}
}
OperationMetrics
{
total_requests : total,
successful_requests : successful,
}
}
}
impl Default for DiagnosticsAggregator
{
fn default() -> Self
{
Self::new()
}
}
impl DiagnosticsCollector
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self {}
}
#[ inline ]
#[ must_use ]
pub fn collect_for_request< T >( &self, _request : &T ) -> DiagnosticsData
{
DiagnosticsData
{
curl_representation : Some( r#"curl -X POST "https:// api.anthropic.com/v1/messages" -H "Content-Type : application/json" -d '{"model":"claude-3-sonnet-20240229","max_tokens":1000}'"#.to_string() ),
request_size : 150,
estimated_cost : Some( 0.01 ),
request_metrics : vec![
RequestMetricData {
duration : Duration::from_millis(150),
success : true,
}
],
}
}
#[ inline ]
#[ must_use ]
pub fn start_collection< T >( &self, _request : &T ) -> String
{
"collection-id".to_string()
}
}
impl AggregatedMetrics
{
#[ inline ]
#[ must_use ]
pub fn request_count( &self ) -> u32
{
self.request_count
}
#[ inline ]
#[ must_use ]
pub fn has_performance_data( &self ) -> bool
{
self.has_performance_data
}
}
impl DiagnosticsData
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
curl_representation : None,
request_size : 0,
estimated_cost : None,
request_metrics : Vec::new(),
}
}
#[ inline ]
#[ must_use ]
pub fn has_curl_representation( &self ) -> bool
{
self.curl_representation.is_some()
}
#[ inline ]
#[ must_use ]
pub fn curl_command( &self ) -> &str
{
self.curl_representation.as_deref().unwrap_or( "" )
}
#[ inline ]
#[ must_use ]
pub fn request_size( &self ) -> usize
{
self.request_size
}
#[ inline ]
#[ must_use ]
pub fn estimated_cost( &self ) -> Option< f64 >
{
self.estimated_cost
}
#[ inline ]
pub fn add_request_metric( &mut self, duration : Duration, success : bool )
{
self.request_metrics.push( RequestMetricData { duration, success } );
}
#[ inline ]
#[ must_use ]
pub fn request_succeeded( &self ) -> bool
{
self.request_metrics.iter().any( | m | m.success )
}
#[ inline ]
#[ must_use ]
pub fn request_failed( &self ) -> bool
{
self.request_metrics.iter().any( | m | !m.success )
}
#[ inline ]
#[ must_use ]
pub fn response_time( &self ) -> Duration
{
self.request_metrics
.first()
.map_or( Duration::from_millis( 100 ), | m | m.duration )
}
}
impl Default for DiagnosticsCollector
{
fn default() -> Self
{
Self::new()
}
}
impl Default for DiagnosticsData
{
fn default() -> Self
{
Self::new()
}
}
}
crate::mod_interface!
{
exposed use ErrorAnalyzer;
exposed use ErrorCategory;
exposed use ErrorSummary;
exposed use DiagnosticsContext;
exposed use DiagnosticsAggregator;
exposed use OperationMetrics;
exposed use DiagnosticsSummary;
exposed use DiagnosticsCollector;
exposed use DiagnosticsData;
exposed use AggregatedMetrics;
}