1717 */
1818
1919pub mod prom_utils;
20+ use std:: sync:: OnceLock ;
21+
2022use crate :: {
2123 handlers:: { TelemetryType , http:: metrics_path} ,
2224 stats:: FullStats ,
@@ -25,7 +27,10 @@ use actix_web::Responder;
2527use actix_web_prometheus:: { PrometheusMetrics , PrometheusMetricsBuilder } ;
2628use error:: MetricsError ;
2729use once_cell:: sync:: Lazy ;
28- use prometheus:: { HistogramOpts , HistogramVec , IntCounterVec , IntGaugeVec , Opts , Registry } ;
30+ use prometheus:: {
31+ Gauge , HistogramOpts , HistogramVec , IntCounterVec , IntGaugeVec , Opts , Registry ,
32+ core:: { Atomic , AtomicF64 } ,
33+ } ;
2934
3035pub const METRICS_NAMESPACE : & str = env ! ( "CARGO_PKG_NAME" ) ;
3136
@@ -175,6 +180,97 @@ pub static STAGING_FILES: Lazy<IntGaugeVec> = Lazy::new(|| {
175180 . expect ( "metric can be created" )
176181} ) ;
177182
183+ pub static PROCESS_CPU_USAGE_PERCENT_AVG : Lazy < Gauge > = Lazy :: new ( || {
184+ Gauge :: with_opts (
185+ Opts :: new (
186+ "process_cpu_usage_percent_avg" ,
187+ "Lifetime average CPU usage percent for this Parseable process" ,
188+ )
189+ . namespace ( METRICS_NAMESPACE ) ,
190+ )
191+ . expect ( "metric can be created" )
192+ } ) ;
193+
194+ pub static PROCESS_MEMORY_BYTES_AVG : Lazy < Gauge > = Lazy :: new ( || {
195+ Gauge :: with_opts (
196+ Opts :: new (
197+ "process_memory_bytes_avg" ,
198+ "Lifetime average resident memory used by this Parseable process in bytes" ,
199+ )
200+ . namespace ( METRICS_NAMESPACE ) ,
201+ )
202+ . expect ( "metric can be created" )
203+ } ) ;
204+ pub static PROCESS_METRICS_INIT : OnceLock < ( f64 , u64 ) > = OnceLock :: new ( ) ;
205+ struct ProcessMetricsAccumulator {
206+ cpu_usage_avg : AtomicF64 ,
207+ memory_bytes_avg : AtomicF64 ,
208+ }
209+
210+ impl Default for ProcessMetricsAccumulator {
211+ fn default ( ) -> Self {
212+ // PROCESS_METRICS_INIT must be initialized by now
213+ let ( cpu, mem) = * PROCESS_METRICS_INIT . get ( ) . unwrap ( ) ;
214+ Self {
215+ cpu_usage_avg : AtomicF64 :: new ( cpu) ,
216+ memory_bytes_avg : AtomicF64 :: new ( mem as f64 ) ,
217+ }
218+ }
219+ }
220+
221+ impl ProcessMetricsAccumulator {
222+ fn record ( & self , cpu_usage_percent : f64 , memory_bytes : u64 ) -> ( f64 , f64 ) {
223+ // Exponentially Weighted Moving Average is better than
224+ // a lifetime average
225+ // A spike which occurred 5 days ago should not affect the average utilization
226+ // for the last minute
227+ // α = 1 - exp(-Δt / τ) = 1 - exp(-5/60) ≈ 0.0800
228+ // S_new = S_old + α * (x_new - S_old)
229+ let s_cpu_old = self . cpu_usage_avg . get ( ) ;
230+ let s_cpu_new = s_cpu_old + 0.08 * ( cpu_usage_percent - s_cpu_old) ;
231+
232+ let s_mem_old = self . memory_bytes_avg . get ( ) ;
233+ let s_mem_new = s_mem_old + 0.08 * ( memory_bytes as f64 - s_mem_old) ;
234+
235+ // update accumulator
236+ self . cpu_usage_avg . set ( s_cpu_new) ;
237+ self . memory_bytes_avg . set ( s_mem_new) ;
238+
239+ ( s_cpu_new, s_mem_new)
240+ }
241+ }
242+
243+ static PROCESS_METRICS_ACCUMULATOR : Lazy < ProcessMetricsAccumulator > =
244+ Lazy :: new ( ProcessMetricsAccumulator :: default) ;
245+
246+ pub fn record_process_metrics_sample ( cpu_usage_percent : f64 , memory_bytes : u64 ) {
247+ if PROCESS_METRICS_INIT . get ( ) . is_none ( ) {
248+ // first measurement
249+ let _ = PROCESS_METRICS_INIT . set ( ( cpu_usage_percent, memory_bytes) ) ;
250+ }
251+ let ( average_cpu_usage, average_memory_bytes) =
252+ PROCESS_METRICS_ACCUMULATOR . record ( cpu_usage_percent, memory_bytes) ;
253+ PROCESS_CPU_USAGE_PERCENT_AVG . set ( average_cpu_usage) ;
254+ PROCESS_MEMORY_BYTES_AVG . set ( average_memory_bytes) ;
255+ }
256+
257+ #[ cfg( test) ]
258+ mod process_metrics_tests {
259+ use crate :: metrics:: PROCESS_METRICS_INIT ;
260+
261+ use super :: ProcessMetricsAccumulator ;
262+
263+ #[ test]
264+ fn averages_process_metric_samples ( ) {
265+ // init PROCESS_METRICS_INIT
266+ PROCESS_METRICS_INIT . get_or_init ( || ( 10.0 , 100 ) ) ;
267+ let accumulator = ProcessMetricsAccumulator :: default ( ) ;
268+
269+ assert_eq ! ( accumulator. record( 10.0 , 100 ) , ( 10.0 , 100.0 ) ) ;
270+ assert_eq ! ( accumulator. record( 20.0 , 300 ) , ( 10.8 , 116.0 ) ) ;
271+ }
272+ }
273+
178274pub static QUERY_EXECUTE_TIME : Lazy < HistogramVec > = Lazy :: new ( || {
179275 HistogramVec :: new (
180276 HistogramOpts :: new ( "query_execute_time" , "Query execute time" ) . namespace ( METRICS_NAMESPACE ) ,
@@ -663,6 +759,12 @@ fn custom_metrics(registry: &Registry) {
663759 registry
664760 . register ( Box :: new ( STAGING_FILES . clone ( ) ) )
665761 . expect ( "metric can be registered" ) ;
762+ registry
763+ . register ( Box :: new ( PROCESS_CPU_USAGE_PERCENT_AVG . clone ( ) ) )
764+ . expect ( "metric can be registered" ) ;
765+ registry
766+ . register ( Box :: new ( PROCESS_MEMORY_BYTES_AVG . clone ( ) ) )
767+ . expect ( "metric can be registered" ) ;
666768 registry
667769 . register ( Box :: new ( QUERY_EXECUTE_TIME . clone ( ) ) )
668770 . expect ( "metric can be registered" ) ;
0 commit comments