matrix_sdk_common/tracing_timer.rs
1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use ruma::time::Instant;
16use tracing::{Callsite as _, callsite::DefaultCallsite};
17
18/// A named RAII that will show on `Drop` how long its covered section took to
19/// execute.
20pub struct TracingTimer {
21 id: String,
22 callsite: &'static DefaultCallsite,
23 start: Instant,
24 level: tracing::Level,
25}
26
27#[cfg(not(tarpaulin_include))]
28impl std::fmt::Debug for TracingTimer {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("TracingTimer").field("id", &self.id).field("start", &self.start).finish()
31 }
32}
33
34impl Drop for TracingTimer {
35 fn drop(&mut self) {
36 let elapsed = self.start.elapsed();
37
38 let enabled = tracing::level_enabled!(self.level) && {
39 let interest = self.callsite.interest();
40 !interest.is_never()
41 && tracing::__macro_support::__is_enabled(self.callsite.metadata(), interest)
42 };
43
44 if !enabled {
45 return;
46 }
47
48 let message = format!("_{}_ finished in {:?}", self.id, elapsed);
49
50 let metadata = self.callsite.metadata();
51 let fields = metadata.fields();
52 let message_field = fields.field("message").unwrap();
53 #[allow(trivial_casts)] // The compiler is lying, it can't infer this cast
54 let values = [(&message_field, Some(&message as &dyn tracing::Value))];
55
56 // This function is hidden from docs, but we have to use it
57 // because there is no other way of obtaining a `ValueSet`.
58 // It's not entirely clear why it is private. See this issue:
59 // https://github.com/tokio-rs/tracing/issues/2363
60 let values = fields.value_set(&values);
61
62 tracing::Event::dispatch(metadata, &values);
63 }
64}
65
66impl TracingTimer {
67 /// Create a new `TracingTimer`.
68 pub fn new(callsite: &'static DefaultCallsite, id: String, level: tracing::Level) -> Self {
69 Self { id, callsite, start: Instant::now(), level }
70 }
71}
72
73/// Macro to create a RAII timer that will log on `Drop` how long its covered
74/// section took to execute.
75///
76/// The tracing level can be specified as a first argument, but it's optional.
77/// If it's missing, this will use the debug level.
78///
79/// ```rust,no_run
80/// # fn do_long_computation(_x: u32) {}
81/// # fn main() {
82/// use matrix_sdk_common::timer;
83///
84/// // It's possible to specify the tracing level we want to be used for the log message on drop.
85/// {
86/// let _timer = timer!(tracing::Level::TRACE, "do long computation");
87/// // But it's optional; by default it's set to `DEBUG`.
88/// let _debug_timer = timer!("do long computation but time it in DEBUG");
89/// // The macro doesn't support formatting / structured events (yet?), but you can use
90/// // `format!()` for that.
91/// let other_timer = timer!(format!("do long computation for parameter = {}", 123));
92/// do_long_computation(123);
93/// } // The log statements will happen here.
94/// # }
95/// ```
96#[macro_export]
97macro_rules! timer {
98 ($level:expr, $string:expr) => {{
99 static __CALLSITE: tracing::callsite::DefaultCallsite = tracing::callsite2! {
100 name: tracing::__macro_support::concat!(
101 "event ",
102 file!(),
103 ":",
104 line!(),
105 ),
106 kind: tracing::metadata::Kind::EVENT,
107 target: module_path!(),
108 level: $level,
109 fields: []
110 };
111
112 $crate::tracing_timer::TracingTimer::new(&__CALLSITE, $string.into(), $level)
113 }};
114
115 ($string:expr) => {
116 $crate::timer!(tracing::Level::DEBUG, $string)
117 };
118}
119
120#[cfg(test)]
121mod tests {
122 #[cfg(not(target_family = "wasm"))]
123 #[matrix_sdk_test_macros::async_test]
124 async fn test_timer_name() {
125 use tracing::{Level, span};
126
127 tracing::warn!("Starting test...");
128
129 mod time123 {
130 pub async fn run() {
131 let _timer_guard = timer!(tracing::Level::DEBUG, "test");
132 tokio::time::sleep(ruma::time::Duration::from_millis(123)).await;
133 // Displays: 2023-08-25T15:18:31.169498Z DEBUG
134 // matrix_sdk_common::tracing_timer::tests: _test_ finished in
135 // 124ms
136 }
137 }
138
139 time123::run().await;
140
141 let span = span!(Level::DEBUG, "le 256ms span");
142 let _guard = span.enter();
143
144 let _timer_guard = timer!("in span");
145 tokio::time::sleep(ruma::time::Duration::from_millis(256)).await;
146
147 tracing::warn!("Test about to finish.");
148 // Displays: 2023-08-25T15:18:31.427070Z DEBUG le 256ms span:
149 // matrix_sdk_common::tracing_timer::tests: in span finished in 257ms
150 }
151}