custom_labels/lib.rs
1//! # Custom labels for profilers.
2//!
3//! ## Overview
4//!
5//! This library provides Rust bindings to [v0 of the Custom Labels ABI](../custom-labels-v0.md).
6//!
7//! It allows time ranges within a thread's execution to be annotated with labels (key/value pairs) in such a way
8//! that the labels are visible to a CPU profiler that may
9//! interrupt the running process at any time. The profiler can then report the labels
10//! that were active at any given time in the profiles it produces.
11//!
12//! The main interface is [`with_label`], which sets a label to a provided value,
13//! executes a function, and then resets the label to its old value (or removes it if there was no old value).
14//!
15//! For example, imagine a program that performs database queries on behalf of a user. It might have a function
16//! like the following:
17//!
18//! ```rust
19//! # struct DbResultSet;
20//! #
21//! # fn do_query(sql: &str) -> DbResultSet {
22//! # DbResultSet
23//! # }
24//! #
25//! fn query_for_user(username: &str, sql: &str) -> DbResultSet {
26//! custom_labels::with_label("username", username, || do_query(sql))
27//! }
28//! ```
29//!
30//! If two users named "Marta" and "Petros" repeatedly query the database, a profiler might produce a
31//! CPU profile like the following:
32//! ```text
33//! * all (13.4s)
34//! |
35//! +---- username: Marta (4.9s)
36//! | |
37//! | +---- query_for_user (4.9s)
38//! | |
39//! | + ---- custom_labels::with_label (4.9s)
40//! | |
41//! | + ---- do_query (4.9s)
42//! |
43//! +---- username: Petros (8.5s)
44//! |
45//! +---- query_for_user (8.5s)
46//! |
47//! + ---- custom_labels::with_label (8.5s)
48//! |
49//! + ---- do_query (8.5s)
50//! ```
51//!
52//! ## Profiler Support
53//!
54//! The following profilers can make use of the labels set with this library:
55//! * [Parca](parca.dev) (when using parca-agent v0.33.0 and later)
56//! * [Polar Signals Cloud](https://www.polarsignals.com) (when using parca-agent v0.33.0 and later).
57//!
58//! If you work on another profiler that also supports this format, [send us a PR](https://github.com/polarsignals/custom-labels)
59//! to update this list!
60
61/// Low-level interface to the underlying C library.
62pub mod sys {
63 mod c {
64 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
65 }
66
67 pub use c::custom_labels_label_t as Label;
68 pub use c::custom_labels_string_t as String;
69
70 impl<'a> From<&'a [u8]> for self::String {
71 fn from(value: &'a [u8]) -> Self {
72 Self {
73 len: value.len(),
74 buf: value.as_ptr(),
75 }
76 }
77 }
78
79 impl Clone for self::String {
80 fn clone(&self) -> Self {
81 unsafe {
82 let buf = libc::malloc(self.len);
83 if buf.is_null() {
84 panic!("Out of memory");
85 }
86 libc::memcpy(buf, self.buf as *const _, self.len);
87 Self {
88 len: self.len,
89 buf: buf as *mut _,
90 }
91 }
92 }
93 }
94
95 impl Drop for self::String {
96 fn drop(&mut self) {
97 unsafe {
98 libc::free(self.buf as *mut _);
99 }
100 }
101 }
102
103 pub use c::custom_labels_delete as delete;
104 pub use c::custom_labels_get as get;
105 pub use c::custom_labels_set as set;
106}
107
108/// Utilities for build scripts
109pub mod build {
110 /// Emit the instructions required for an
111 /// executable to expose custom labels data.
112 pub fn emit_build_instructions() {
113 let dlist_path = format!("{}/dlist", std::env::var("OUT_DIR").unwrap());
114 std::fs::write(&dlist_path, include_str!("../dlist")).unwrap();
115 println!("cargo:rustc-link-arg=-Wl,--dynamic-list={}", dlist_path);
116 }
117}
118
119/// Set the label for the specified key to the specified
120/// value while the given function is running.
121///
122/// All labels are thread-local: setting a label on one thread
123/// has no effect on its value on any other thread.
124pub fn with_label<K, V, F, Ret>(k: K, v: V, f: F) -> Ret
125where
126 K: AsRef<[u8]>,
127 V: AsRef<[u8]>,
128 F: FnOnce() -> Ret,
129{
130 struct Guard<'a> {
131 k: &'a [u8],
132 old_v: Option<sys::String>,
133 }
134
135 impl<'a> Drop for Guard<'a> {
136 fn drop(&mut self) {
137 if let Some(old_v) = std::mem::take(&mut self.old_v) {
138 let errno = unsafe { sys::set(self.k.into(), old_v) };
139 if errno != 0 {
140 panic!("corruption in custom labels library: errno {errno}");
141 }
142 } else {
143 unsafe { sys::delete(self.k.into()) };
144 }
145 }
146 }
147
148 let old_v = unsafe { sys::get(k.as_ref().into()).as_ref() }.map(|lbl| lbl.value.clone());
149 let _g = Guard {
150 k: k.as_ref(),
151 old_v,
152 };
153
154 let errno = unsafe { sys::set(k.as_ref().into(), v.as_ref().into()) };
155 if errno != 0 {
156 panic!("corruption in custom labels library: errno {errno}")
157 }
158
159 f()
160}
161
162/// Set the labels for the specified keys to the specified
163/// values while the given function is running.
164///
165/// `i` is an iterator of key-value pairs.
166///
167/// The effect is the same as repeatedly nesting calls to the singular [`with_label`].
168pub fn with_labels<I, K, V, F, Ret>(i: I, f: F) -> Ret
169where
170 I: IntoIterator<Item = (K, V)>,
171 K: AsRef<[u8]>,
172 V: AsRef<[u8]>,
173 F: FnOnce() -> Ret,
174{
175 let mut i = i.into_iter();
176 if let Some((k, v)) = i.next() {
177 with_label(k, v, || with_labels(i, f))
178 } else {
179 f()
180 }
181}