1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! # nm - nanometer
//!
//! Collect metrics about observed events with low overhead even in
//! highly multithreaded applications running on 100+ processors.
//!
//! Using arbitrary development hardware, we measure between 2 and 20 nanoseconds per
//! observation, depending on how the event is configured. Benchmarks are included.
//!
//! # Collected metrics
//!
//! For each defined event, the following metrics are collected:
//!
//! * Count of observations (`u64`).
//! * Mean magnitude of observations (`i64`).
//! * (Optional) Histogram of magnitudes, with configurable bucket boundaries (`[i64]`).
//!
//! # Defining events
//!
//! Use thread-local static variables to define the events to observe:
//!
//! ```
//! use nm::Event;
//!
//! thread_local! {
//! static PACKAGES_RECEIVED: Event = Event::builder()
//! .name("packages_received")
//! .build();
//!
//! static PACKAGES_SENT: Event = Event::builder()
//! .name("packages_sent")
//! .build();
//! }
//! ```
//!
//! Recommended event name format: `big_medium_small_units`.
//!
//! The above two events are merely counters, so there are no units in the name.
//!
//! When only an event name is provided to the builder, only the count and mean magnitude of
//! observations will be recorded. If you want to capture more information about the distribution
//! of event magnitudes, you must specify the histogram buckets to use.
//!
//! ```
//! use nm::{Event, Magnitude};
//!
//! const PACKAGE_WEIGHT_GRAMS_BUCKETS: &[Magnitude] = &[0, 100, 200, 500, 1000, 2000, 5000, 10000];
//!
//! thread_local! {
//! static PACKAGES_RECEIVED_WEIGHT_GRAMS: Event = Event::builder()
//! .name("packages_received_weight_grams")
//! .histogram(PACKAGE_WEIGHT_GRAMS_BUCKETS)
//! .build();
//!
//! static PACKAGES_SENT_WEIGHT_GRAMS: Event = Event::builder()
//! .name("packages_sent_weight_grams")
//! .histogram(PACKAGE_WEIGHT_GRAMS_BUCKETS)
//! .build();
//! }
//! ```
//!
//! Choose the bucket boundaries based on the expected distribution of magnitudes.
//!
//! # Capturing observations
//!
//! To capture an observation, call `observe()` on the event.
//! Different variants of this method are provided to capture observations with different
//! characteristics:
//!
//! ```
//! # use nm::{Event, Magnitude};
//! #
//! # const PACKAGE_WEIGHT_GRAMS_BUCKETS: &[Magnitude] = &[0, 100, 200, 500, 1000, 2000, 5000, 10000];
//! #
//! # thread_local! {
//! # static PACKAGES_RECEIVED: Event = Event::builder()
//! # .name("packages_received")
//! # .build();
//! #
//! # static PACKAGES_RECEIVED_WEIGHT_GRAMS: Event = Event::builder()
//! # .name("packages_received_weight_grams")
//! # .histogram(PACKAGE_WEIGHT_GRAMS_BUCKETS)
//! # .build();
//! #
//! # static PACKAGE_SEND_DURATION_MS: Event = Event::builder()
//! # .name("package_send_duration_ms")
//! # .build();
//! # }
//! use std::time::Duration;
//!
//! // observe(x) observes an event with a magnitude of `x`.
//! PACKAGES_RECEIVED_WEIGHT_GRAMS.with(|e| e.observe(900));
//!
//! // observe_once() observes an event with a nominal magnitude of 1, to clearly express that
//! // this event has no concept of magnitude and we use 1 as a nominal placeholder.
//! PACKAGES_RECEIVED.with(|e| e.observe_once());
//!
//! // observe_millis(x) observes an event with a magnitude of `x` in milliseconds while
//! // ensuring that any data type conversions respect the crate panic and mathematics policies.
//! let send_duration = Duration::from_millis(150);
//! PACKAGE_SEND_DURATION_MS.with(|e| e.observe_millis(send_duration));
//!
//! // batch(count) allows you to observe `count` occurrences in one call,
//! // each with the same magnitude, for greater efficiency in batch operations.
//! PACKAGES_RECEIVED_WEIGHT_GRAMS.with(|e| e.batch(500).observe(8));
//! PACKAGES_RECEIVED.with(|e| e.batch(500).observe_once());
//! PACKAGE_SEND_DURATION_MS.with(|e| e.batch(500).observe_millis(send_duration));
//! ```
//!
//! ## Observing durations of operations
//!
//! You can efficiently capture the duration of function calls via `observe_duration_millis()`:
//!
//! ```
//! use nm::{Event, Magnitude};
//!
//! const CONNECT_TIME_MS_BUCKETS: &[Magnitude] = &[0, 10, 20, 50, 100, 200, 500, 1000];
//!
//! thread_local! {
//! static CONNECT_TIME_MS: Event = Event::builder()
//! .name("net_http_connect_time_ms")
//! .histogram(CONNECT_TIME_MS_BUCKETS)
//! .build();
//! }
//!
//! pub fn http_connect() {
//! CONNECT_TIME_MS.with(|e| {
//! e.observe_duration_millis(|| {
//! do_http_connect();
//! })
//! });
//! }
//! # http_connect();
//! # fn do_http_connect() {}
//! ```
//!
//! This captures the duration of the function call in milliseconds using a low-precision
//! clock optimized for high-frequency capture. The measurement has a granularity of
//! roughly 1-20 ms. Durations shorter than the granularity may appear as zero.
//!
//! It is not practical to measure the duration of individual operations at a finer level of
//! precision because the measurement overhead becomes prohibitive. If you are observing
//! operations that last nanoseconds or microseconds, you should only measure them in
//! aggregate (e.g. duration per batch of 10000).
//!
//! # Reporting to terminal
//!
//! To collect a report of all observations, call `Report::collect()`. This implements the
//! `Display` trait, so you can print it to the terminal:
//!
//! ```
//! use nm::Report;
//!
//! let report = Report::collect();
//! println!("{report}");
//! ```
//!
//! # Reporting to external systems
//!
//! A report can be inspected to extract the data within and deliver it to an external system,
//! such as an OpenTelemetry exporter for storage in a metrics database.
//!
//! ```
//! use nm::Report;
//!
//! let report = Report::collect();
//!
//! for event in report.events() {
//! println!(
//! "Event {} has occurred {} times with a total magnitude of {}",
//! event.name(),
//! event.count(),
//! event.sum()
//! );
//! }
//! ```
//!
//! Note that the report accumulates data from the start of the process. This means the
//! data does not reset between reports. If you only want to record differences, you need
//! to account for the previous state of the event yourself.
//!
//! # Minimizing overhead by on-demand publishing
//!
//! The ultimate goal of the metrics collected by an [`Event`] is to end up in a [`Report`].
//!
//! There are two models by which this can happen:
//!
//! - **Pull** model - the reporting system queries each event in the process for its latest data
//! set when generating a report. This is the default and requires no action from you.
//! - **Push** model - data from an event only flows to a thread-local [`MetricsPusher`], which
//! publishes the data into the reporting system on demand. This requires you to periodically
//! trigger the publishing via [`MetricsPusher::push()`][MetricsPusher::push].
//!
//! The push model has lower measurement overhead due to a more optimal internal data layout
//! but requires action from you to ensure that data is published. If you never push the data,
//! it will never show up in a report.
//!
//! The previous examples all use the default pull model. Here is an example with the push model:
//!
//! ```
//! use nm::{Event, MetricsPusher, Push};
//!
//! thread_local! {
//! static HTTP_EVENTS_PUSHER: MetricsPusher = MetricsPusher::new();
//!
//! static CONNECT_TIME_MS: Event<Push> = Event::builder()
//! .name("net_http_connect_time_ms")
//! .pusher_local(&HTTP_EVENTS_PUSHER)
//! .build();
//! }
//!
//! pub fn http_connect() {
//! CONNECT_TIME_MS.with(|e| {
//! e.observe_duration_millis(|| {
//! do_http_connect();
//! })
//! });
//! }
//!
//! loop {
//! http_connect();
//!
//! // Periodically push the data to the reporting system.
//! if is_time_to_push() {
//! HTTP_EVENTS_PUSHER.with(MetricsPusher::push);
//! }
//! # break; // Avoid infinite loop when running example.
//! }
//! # fn do_http_connect() {}
//! # fn is_time_to_push() -> bool { true }
//! ```
//!
//! You should consider using the push model when an event is only used under controlled conditions
//! where you are certain that every thread that will be observing an event is guaranteed to call
//! [`MetricsPusher::push()`][MetricsPusher::push] at some point.
//!
//! The choice of publishing model can be made separately for each event.
//!
//! # Dynamically registered events
//!
//! It is not strictly required to define events as thread-local statics. You can also create
//! instances of `Event` on the fly using the same `Event::builder()` mechanism. This can be useful
//! if you do not know at compile time which events you will need, such as when creating one event
//! per item defined in a configuration file.
//!
//! Note, however, that each event (each unique event name) can only be registered once per thread.
//! Any attempt to register an event two times with the same name on the same thread will result
//! in a panic.
//!
//! # Panic policy
//!
//! This crate may panic when registering events if an invalid configuration
//! is supplied for the event.
//!
//! This crate will not panic for "mathematical" reasons during observation of events,
//! such as overflow or underflow due to excessively large event counts or magnitudes.
//!
//! # Mathematics policy
//!
//! Attempting to use excessively large values, either instantaneous or cumulative, may result in
//! mangled data. For example, attempting to observe events with magnitudes near `i64::MAX`. There
//! is no guarantee made about what the specific outcome will be in this case (though the panic
//! policy above still applies). Do not stray near `i64` boundaries and you should be fine.
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;