1+ use std:: sync:: Once ;
12use std:: time:: Duration ;
2- #[ cfg( feature = "function_call_metrics" ) ]
3- use std:: time:: Instant ;
43
54use metrics_macro:: NamedMetric ;
65
@@ -10,33 +9,58 @@ mod metrics_macro;
109// These defines all types of metrics in this crate
1110define_metrics ! {
1211 CounterMetric {
13- GuestErrors { code: u64 } => "guest_errors_total" ,
14- GuestCancellations => "guest_cancellations_total" ,
12+ GuestErrors { code: u64 } => {
13+ name: "guest_errors_total" ,
14+ description: "Number of errors encountered by guests" ,
15+ unit: metrics:: Unit :: Count ,
16+ } ,
17+ GuestCancellations => {
18+ name: "guest_cancellations_total" ,
19+ description: "Number of times guest execution was cancelled due to timeout" ,
20+ unit: metrics:: Unit :: Count ,
21+ } ,
1522 }
1623 HistogramMetric {
17- GuestCallDuration { name: String , duration: Duration } => "guest_call_duration_seconds" ,
18- HostCallDuration { name: String , duration: Duration } => "host_call_duration_seconds" ,
24+ GuestCallDuration { name: String , duration: Duration } => {
25+ name: "guest_call_duration_seconds" ,
26+ description: "Duration of guest function calls" ,
27+ unit: metrics:: Unit :: Seconds ,
28+ } ,
29+ HostCallDuration { name: String , duration: Duration } => {
30+ name: "host_call_duration_seconds" ,
31+ description: "Duration of host function calls" ,
32+ unit: metrics:: Unit :: Seconds ,
33+ } ,
1934 }
2035}
2136
2237impl CounterMetric {
2338 /// Create a new guest error metric, ready to be emitted
2439 #[ must_use]
25- pub ( crate ) fn guest_error ( code : u64 ) -> CounterMetric {
40+ pub ( crate ) fn guest_error ( code : u64 ) -> Self {
2641 CounterMetric :: GuestErrors { code }
2742 }
2843 /// Create a new guest cancellation metric, ready to be emitted
2944 #[ must_use]
30- pub ( crate ) fn guest_cancellation ( ) -> CounterMetric {
45+ pub ( crate ) fn guest_cancellation ( ) -> Self {
3146 CounterMetric :: GuestCancellations
3247 }
3348}
3449
3550impl HistogramMetric {
36- /// Time a guest call and emit its duration as a metric
37- pub ( crate ) fn time_and_emit_guest_call < T , F : FnOnce ( ) -> T > ( name : String , f : F ) -> T {
51+ /// Measures the time to execute the given closure, and then emits the duration
52+ /// as a guest call metric.
53+ ///
54+ /// Note: If the `function_call_metrics` feature is not enabled, this function
55+ /// will simply execute the closure without measuring time or emitting metrics.
56+ pub ( crate ) fn time_and_emit_guest_call < T , F : FnOnce ( ) -> T > (
57+ #[ allow( unused_variables) ] name : String ,
58+ f : F ,
59+ ) -> T {
3860 cfg_if:: cfg_if! {
3961 if #[ cfg( feature = "function_call_metrics" ) ] {
62+ use std:: time:: Instant ;
63+
4064 let start = Instant :: now( ) ;
4165 let result = f( ) ;
4266 let duration = start. elapsed( ) ;
@@ -48,10 +72,19 @@ impl HistogramMetric {
4872 }
4973 }
5074
51- /// Time a host call and emit its duration as a metric
52- pub ( crate ) fn time_and_emit_host_call < T , F : FnOnce ( ) -> T > ( name : String , f : F ) -> T {
75+ /// Measures the time to execute the given closure, and then emits the duration
76+ /// as a host call metric.
77+ ///
78+ /// Note: If the `function_call_metrics` feature is not enabled, this function
79+ /// will simply execute the closure without measuring time or emitting metrics.
80+ pub ( crate ) fn time_and_emit_host_call < T , F : FnOnce ( ) -> T > (
81+ #[ allow( unused_variables) ] name : String ,
82+ f : F ,
83+ ) -> T {
5384 cfg_if:: cfg_if! {
5485 if #[ cfg( feature = "function_call_metrics" ) ] {
86+ use std:: time:: Instant ;
87+
5588 let start = Instant :: now( ) ;
5689 let result = f( ) ;
5790 let duration = start. elapsed( ) ;
@@ -78,36 +111,68 @@ impl HistogramMetric {
78111}
79112
80113/// A metric which can be emitted to the underlying metrics system
81- pub ( crate ) trait Metric {
82- /// Emits the metric to the underlying metrics system
114+ pub ( crate ) trait EmittableMetric {
115+ /// Emits the metric to the underlying metrics system.
116+ /// The first time this is called for a given metric variant, it will
117+ /// also describe the metric to the underlying metrics system.
83118 fn emit ( self ) ;
84119}
85120
86- impl Metric for CounterMetric {
121+ impl EmittableMetric for CounterMetric {
87122 /// Increases the counter represented by `self` by 1
88123 fn emit ( self ) {
89124 let name = self . name ( ) ;
125+ let unit = self . unit ( ) ;
126+ let description = self . description ( ) ;
127+
90128 match self {
91129 CounterMetric :: GuestErrors { code } => {
92- // label keys
93- static LABEL_ERROR_CODE : & str = "code" ;
130+ // Describe each metric variant only once
131+ static DESCRIBE : Once = Once :: new ( ) ;
132+ DESCRIBE . call_once ( || {
133+ metrics:: describe_counter!( name, unit, description) ;
134+ } ) ;
94135
136+ static LABEL_ERROR_CODE : & str = "code" ;
95137 metrics:: counter!( name, LABEL_ERROR_CODE => code. to_string( ) ) . increment ( 1 ) ;
96138 }
97139 CounterMetric :: GuestCancellations => {
140+ // Describe each metric variant only once
141+ static DESCRIBE : Once = Once :: new ( ) ;
142+ DESCRIBE . call_once ( || {
143+ metrics:: describe_counter!( name, unit, description) ;
144+ } ) ;
145+
98146 metrics:: counter!( name) . increment ( 1 ) ;
99147 }
100148 }
101149 }
102150}
103151
104- impl Metric for HistogramMetric {
152+ impl EmittableMetric for HistogramMetric {
105153 fn emit ( self ) {
106154 let metric_name = self . name ( ) ;
155+ let unit = self . unit ( ) ;
156+ let description = self . description ( ) ;
157+
158+ static LABEL_FUNCTION_NAME : & str = "function_name" ;
159+
107160 match self {
108- HistogramMetric :: GuestCallDuration { name, duration }
109- | HistogramMetric :: HostCallDuration { name, duration } => {
110- static LABEL_FUNCTION_NAME : & str = "function_name" ;
161+ HistogramMetric :: GuestCallDuration { name, duration } => {
162+ // Describe each metric variant only once
163+ static DESCRIBE : Once = Once :: new ( ) ;
164+ DESCRIBE . call_once ( || {
165+ metrics:: describe_histogram!( metric_name, unit, description) ;
166+ } ) ;
167+
168+ metrics:: histogram!( metric_name, LABEL_FUNCTION_NAME => name) . record ( duration) ;
169+ }
170+ HistogramMetric :: HostCallDuration { name, duration } => {
171+ // Describe each metric variant only once
172+ static DESCRIBE : Once = Once :: new ( ) ;
173+ DESCRIBE . call_once ( || {
174+ metrics:: describe_histogram!( metric_name, unit, description) ;
175+ } ) ;
111176
112177 metrics:: histogram!( metric_name, LABEL_FUNCTION_NAME => name) . record ( duration) ;
113178 }
@@ -117,7 +182,9 @@ impl Metric for HistogramMetric {
117182
118183#[ cfg( test) ]
119184mod tests {
120- use metrics:: { Key , Label } ;
185+ use std:: sync:: atomic:: AtomicU64 ;
186+
187+ use metrics:: { Counter , Gauge , Histogram , Key , Label } ;
121188 use metrics_util:: CompositeKey ;
122189
123190 use super :: * ;
@@ -207,4 +274,93 @@ mod tests {
207274 "Histogram metric does not match expected value"
208275 ) ;
209276 }
277+
278+ /// Makes sure that the description function is called only once for each metric variant.
279+ /// This helps performance
280+ #[ test]
281+ fn test_description_called_once ( ) {
282+ struct DescriptionCounterRecorder {
283+ num_descriptions : AtomicU64 ,
284+ }
285+
286+ impl metrics:: Recorder for DescriptionCounterRecorder {
287+ fn describe_counter (
288+ & self ,
289+ _key : metrics:: KeyName ,
290+ _unit : Option < metrics:: Unit > ,
291+ _description : metrics:: SharedString ,
292+ ) {
293+ self . num_descriptions
294+ . fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) ;
295+ }
296+
297+ fn describe_gauge (
298+ & self ,
299+ _key : metrics:: KeyName ,
300+ _unit : Option < metrics:: Unit > ,
301+ _description : metrics:: SharedString ,
302+ ) {
303+ self . num_descriptions
304+ . fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) ;
305+ }
306+
307+ fn describe_histogram (
308+ & self ,
309+ _key : metrics:: KeyName ,
310+ _unit : Option < metrics:: Unit > ,
311+ _description : metrics:: SharedString ,
312+ ) {
313+ self . num_descriptions
314+ . fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) ;
315+ }
316+
317+ fn register_counter (
318+ & self ,
319+ _key : & Key ,
320+ _metadata : & metrics:: Metadata < ' _ > ,
321+ ) -> metrics:: Counter {
322+ Counter :: noop ( )
323+ }
324+
325+ fn register_gauge (
326+ & self ,
327+ _key : & Key ,
328+ _metadata : & metrics:: Metadata < ' _ > ,
329+ ) -> metrics:: Gauge {
330+ Gauge :: noop ( )
331+ }
332+
333+ fn register_histogram (
334+ & self ,
335+ _key : & Key ,
336+ _metadata : & metrics:: Metadata < ' _ > ,
337+ ) -> metrics:: Histogram {
338+ Histogram :: noop ( )
339+ }
340+ }
341+
342+ let recorder = DescriptionCounterRecorder {
343+ num_descriptions : AtomicU64 :: new ( 0 ) ,
344+ } ;
345+
346+ metrics:: with_local_recorder ( & recorder, || {
347+ CounterMetric :: guest_error ( 404 ) . emit ( ) ;
348+ CounterMetric :: guest_error ( 500 ) . emit ( ) ;
349+ CounterMetric :: GuestCancellations . emit ( ) ;
350+ CounterMetric :: GuestCancellations . emit ( ) ;
351+ HistogramMetric :: guest_call ( "GuestFunc1" . to_string ( ) , Duration :: from_secs ( 1 ) ) . emit ( ) ;
352+ HistogramMetric :: guest_call ( "GuestFunc2" . to_string ( ) , Duration :: from_secs ( 2 ) ) . emit ( ) ;
353+ HistogramMetric :: host_call ( "HostFunc1" . to_string ( ) , Duration :: from_secs ( 3 ) ) . emit ( ) ;
354+ HistogramMetric :: host_call ( "HostFunc2" . to_string ( ) , Duration :: from_secs ( 4 ) ) . emit ( ) ;
355+ } ) ;
356+
357+ // Despite 8 emitted metrics above, we expect only 4 descriptions to be recorded
358+ assert_eq ! (
359+ recorder
360+ . num_descriptions
361+ . load( std:: sync:: atomic:: Ordering :: Relaxed ) ,
362+ 4 ,
363+ "Expected each metric variant to be described only once"
364+ ) ;
365+ }
210366}
0 commit comments