@@ -569,6 +569,34 @@ pub async fn get_performance_alerts(
569569 } ) )
570570}
571571
572+ #[ derive( serde:: Deserialize ) ]
573+ pub struct PerfRecordBody {
574+ pub route : String ,
575+ pub method : String ,
576+ pub status : u16 ,
577+ pub duration_ms : u64 ,
578+ }
579+
580+ /// POST /performance/record — ingest a single APM sample (used by middleware / external agents).
581+ pub async fn record_performance_sample (
582+ State ( state) : State < AppState > ,
583+ Json ( body) : Json < PerfRecordBody > ,
584+ ) -> StatusCode {
585+ state
586+ . performance_service
587+ . record ( & body. route , & body. method , body. status , body. duration_ms )
588+ . await ;
589+ StatusCode :: NO_CONTENT
590+ }
591+
592+ /// GET /performance/history — hourly rollup from the materialized view.
593+ pub async fn get_performance_history (
594+ State ( state) : State < AppState > ,
595+ ) -> Result < Json < serde_json:: Value > , AppError > {
596+ let rows = state. database . get_perf_hourly_rollup ( 24 ) . await ?;
597+ Ok ( Json ( serde_json:: json!( { "hours" : rows } ) ) )
598+ }
599+
572600// =============================================================================
573601// Analytics Handlers
574602// =============================================================================
@@ -854,11 +882,42 @@ pub async fn get_monitoring_alerts(
854882 Ok ( Json ( serde_json:: to_value ( & alerts) . unwrap_or_default ( ) ) )
855883}
856884
857- /// GET /monitoring/metrics — Prometheus-format metrics.
885+ /// GET /monitoring/metrics — Prometheus-format metrics (monitoring + APM) .
858886pub async fn get_prometheus_metrics (
859887 State ( state) : State < AppState > ,
860888) -> axum:: response:: Response < String > {
861- let body = state. monitoring_service . prometheus_metrics ( ) ;
889+ let mut body = state. monitoring_service . prometheus_metrics ( ) ;
890+
891+ // Append APM metrics from the performance service
892+ let dash = state. performance_service . dashboard ( ) . await ;
893+ let apm = format ! (
894+ "\n # HELP stellar_escrow_api_avg_response_ms Average API response time in ms\n \
895+ # TYPE stellar_escrow_api_avg_response_ms gauge\n \
896+ stellar_escrow_api_avg_response_ms {avg}\n \
897+ # HELP stellar_escrow_api_p95_response_ms P95 API response time in ms\n \
898+ # TYPE stellar_escrow_api_p95_response_ms gauge\n \
899+ stellar_escrow_api_p95_response_ms {p95}\n \
900+ # HELP stellar_escrow_api_p99_response_ms P99 API response time in ms\n \
901+ # TYPE stellar_escrow_api_p99_response_ms gauge\n \
902+ stellar_escrow_api_p99_response_ms {p99}\n \
903+ # HELP stellar_escrow_api_requests_total Total API requests recorded\n \
904+ # TYPE stellar_escrow_api_requests_total counter\n \
905+ stellar_escrow_api_requests_total {total}\n \
906+ # HELP stellar_escrow_api_requests_per_minute API requests per minute\n \
907+ # TYPE stellar_escrow_api_requests_per_minute gauge\n \
908+ stellar_escrow_api_requests_per_minute {rpm}\n \
909+ # HELP stellar_escrow_active_perf_alerts Number of active performance alerts\n \
910+ # TYPE stellar_escrow_active_perf_alerts gauge\n \
911+ stellar_escrow_active_perf_alerts {alerts}\n ",
912+ avg = dash. overall. avg_ms,
913+ p95 = dash. overall. p95_ms,
914+ p99 = dash. overall. p99_ms,
915+ total = dash. overall. total_requests,
916+ rpm = dash. overall. requests_per_minute,
917+ alerts = dash. active_alerts. len( ) ,
918+ ) ;
919+ body. push_str ( & apm) ;
920+
862921 axum:: response:: Response :: builder ( )
863922 . status ( 200 )
864923 . header ( "Content-Type" , "text/plain; version=0.0.4" )
0 commit comments