fast_telemetry/span/
ids.rs1use std::cell::RefCell;
7use std::fmt;
8
9use crate::thread_id;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub struct TraceId(pub(crate) [u8; 16]);
18
19impl TraceId {
20 pub const INVALID: Self = Self([0; 16]);
22
23 pub fn random() -> Self {
25 let (a, b) = rng_next_u128();
26 let mut bytes = [0u8; 16];
27 bytes[..8].copy_from_slice(&a.to_le_bytes());
28 bytes[8..].copy_from_slice(&b.to_le_bytes());
29 Self(bytes)
30 }
31
32 pub fn from_hex(hex: &str) -> Option<Self> {
34 if hex.len() != 32 {
35 return None;
36 }
37 let mut bytes = [0u8; 16];
38 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
39 bytes[i] = hex_byte(chunk[0], chunk[1])?;
40 }
41 Some(Self(bytes))
42 }
43
44 pub fn is_invalid(self) -> bool {
46 self == Self::INVALID
47 }
48
49 pub fn as_bytes(&self) -> &[u8; 16] {
51 &self.0
52 }
53}
54
55impl fmt::Display for TraceId {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 for b in &self.0 {
58 write!(f, "{:02x}", b)?;
59 }
60 Ok(())
61 }
62}
63
64impl fmt::Debug for TraceId {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "TraceId({})", self)
67 }
68}
69
70#[derive(Clone, Copy, PartialEq, Eq, Hash)]
76pub struct SpanId(pub(crate) [u8; 8]);
77
78impl SpanId {
79 pub const INVALID: Self = Self([0; 8]);
81
82 pub fn random() -> Self {
84 Self(rng_next_u64().to_le_bytes())
85 }
86
87 pub fn from_hex(hex: &str) -> Option<Self> {
89 if hex.len() != 16 {
90 return None;
91 }
92 let mut bytes = [0u8; 8];
93 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
94 bytes[i] = hex_byte(chunk[0], chunk[1])?;
95 }
96 Some(Self(bytes))
97 }
98
99 pub fn is_invalid(self) -> bool {
101 self == Self::INVALID
102 }
103
104 pub fn as_bytes(&self) -> &[u8; 8] {
106 &self.0
107 }
108}
109
110impl fmt::Display for SpanId {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 for b in &self.0 {
113 write!(f, "{:02x}", b)?;
114 }
115 Ok(())
116 }
117}
118
119impl fmt::Debug for SpanId {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "SpanId({})", self)
122 }
123}
124
125struct Xorshift128Plus {
130 s0: u64,
131 s1: u64,
132}
133
134impl Xorshift128Plus {
135 fn next(&mut self) -> u64 {
136 let mut s1 = self.s0;
137 let s0 = self.s1;
138 self.s0 = s0;
139 s1 ^= s1 << 23;
140 s1 ^= s1 >> 17;
141 s1 ^= s0;
142 s1 ^= s0 >> 26;
143 self.s1 = s1;
144 s0.wrapping_add(s1)
145 }
146}
147
148thread_local! {
149 static RNG: RefCell<Xorshift128Plus> = RefCell::new({
150 let nanos = std::time::SystemTime::now()
152 .duration_since(std::time::UNIX_EPOCH)
153 .unwrap_or_default()
154 .as_nanos() as u64;
155 let tid = thread_id::thread_id() as u64;
156
157 let s0 = nanos ^ (tid.wrapping_mul(0x9E37_79B9_7F4A_7C15));
159 let s1 = nanos.wrapping_mul(0x6C62_272E_07BB_0142) ^ tid;
160
161 let s0 = if s0 == 0 { 0xDEAD_BEEF_CAFE_BABE } else { s0 };
163 let s1 = if s1 == 0 { 0x0123_4567_89AB_CDEF } else { s1 };
164
165 Xorshift128Plus { s0, s1 }
166 });
167}
168
169fn rng_next_u128() -> (u64, u64) {
171 RNG.with(|rng| {
172 let mut rng = rng.borrow_mut();
173 let a = rng.next();
174 let b = rng.next();
175 (a, b)
176 })
177}
178
179#[inline]
181fn rng_next_u64() -> u64 {
182 RNG.with(|rng| rng.borrow_mut().next())
183}
184
185fn hex_digit(c: u8) -> Option<u8> {
190 match c {
191 b'0'..=b'9' => Some(c - b'0'),
192 b'a'..=b'f' => Some(c - b'a' + 10),
193 b'A'..=b'F' => Some(c - b'A' + 10),
194 _ => None,
195 }
196}
197
198fn hex_byte(hi: u8, lo: u8) -> Option<u8> {
199 Some(hex_digit(hi)? << 4 | hex_digit(lo)?)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::collections::HashSet;
206
207 #[test]
208 fn trace_id_random_is_non_zero() {
209 let id = TraceId::random();
210 assert_ne!(id, TraceId::INVALID);
211 }
212
213 #[test]
214 fn span_id_random_is_non_zero() {
215 for _ in 0..100 {
217 let id = SpanId::random();
218 assert_ne!(id, SpanId::INVALID);
219 }
220 }
221
222 #[test]
223 fn trace_id_uniqueness() {
224 let mut set = HashSet::new();
225 for _ in 0..10_000 {
226 assert!(set.insert(TraceId::random()));
227 }
228 }
229
230 #[test]
231 fn span_id_uniqueness() {
232 let mut set = HashSet::new();
233 for _ in 0..10_000 {
234 assert!(set.insert(SpanId::random()));
235 }
236 }
237
238 #[test]
239 fn trace_id_hex_roundtrip() {
240 let id = TraceId::random();
241 let hex = id.to_string();
242 assert_eq!(hex.len(), 32);
243 let parsed = TraceId::from_hex(&hex).expect("valid hex");
244 assert_eq!(parsed, id);
245 }
246
247 #[test]
248 fn span_id_hex_roundtrip() {
249 let id = SpanId::random();
250 let hex = id.to_string();
251 assert_eq!(hex.len(), 16);
252 let parsed = SpanId::from_hex(&hex).expect("valid hex");
253 assert_eq!(parsed, id);
254 }
255
256 #[test]
257 fn trace_id_from_hex_known() {
258 let id = TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e4736").expect("valid");
259 assert_eq!(id.to_string(), "4bf92f3577b34da6a3ce929d0e0e4736");
260 }
261
262 #[test]
263 fn span_id_from_hex_known() {
264 let id = SpanId::from_hex("00f067aa0ba902b7").expect("valid");
265 assert_eq!(id.to_string(), "00f067aa0ba902b7");
266 }
267
268 #[test]
269 fn trace_id_from_hex_rejects_bad_input() {
270 assert!(TraceId::from_hex("too_short").is_none());
271 assert!(TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e473x").is_none()); assert!(TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e47").is_none()); }
274
275 #[test]
276 fn span_id_from_hex_rejects_bad_input() {
277 assert!(SpanId::from_hex("short").is_none());
278 assert!(SpanId::from_hex("00f067aa0ba902bx").is_none());
279 }
280
281 #[test]
282 fn invalid_sentinels() {
283 assert!(TraceId::INVALID.is_invalid());
284 assert!(SpanId::INVALID.is_invalid());
285 assert!(!TraceId::random().is_invalid());
286 }
287
288 #[test]
289 fn cross_thread_uniqueness() {
290 use std::sync::Arc;
291 use std::sync::Mutex;
292
293 let ids: Arc<Mutex<Vec<TraceId>>> = Arc::new(Mutex::new(Vec::new()));
294 let mut handles = Vec::new();
295
296 for _ in 0..4 {
297 let ids = Arc::clone(&ids);
298 handles.push(std::thread::spawn(move || {
299 let local: Vec<TraceId> = (0..1000).map(|_| TraceId::random()).collect();
300 ids.lock().expect("lock").extend(local);
301 }));
302 }
303 for h in handles {
304 h.join().expect("thread join");
305 }
306
307 let all = ids.lock().expect("lock");
308 let set: HashSet<_> = all.iter().collect();
309 assert_eq!(set.len(), all.len(), "duplicate trace IDs across threads");
310 }
311}