1use reifydb_core::profiler::ProfilerCategoryId;
5use serde::{Deserialize, Serialize};
6use tracing::{Level, level_filters::LevelFilter};
7
8#[repr(u8)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum ProfilerCategory {
11 Query = 0,
12 Txn = 1,
13 Storage = 2,
14 Plan = 3,
15 Cdc = 4,
16 Flow = 5,
17 Subscription = 6,
18 Server = 7,
19 Wire = 8,
20 Auth = 9,
21 Catalog = 10,
22 Engine = 11,
23 Mutate = 12,
24 Transport = 13,
25 Task = 14,
26 Policy = 15,
27 Ffi = 16,
28 Cache = 17,
29 Shape = 18,
30 Api = 19,
31 Actor = 20,
32}
33
34pub const CATEGORY_COUNT: usize = 21;
35
36pub const ALL_CATEGORIES: [ProfilerCategory; CATEGORY_COUNT] = [
37 ProfilerCategory::Query,
38 ProfilerCategory::Txn,
39 ProfilerCategory::Storage,
40 ProfilerCategory::Plan,
41 ProfilerCategory::Cdc,
42 ProfilerCategory::Flow,
43 ProfilerCategory::Subscription,
44 ProfilerCategory::Server,
45 ProfilerCategory::Wire,
46 ProfilerCategory::Auth,
47 ProfilerCategory::Catalog,
48 ProfilerCategory::Engine,
49 ProfilerCategory::Mutate,
50 ProfilerCategory::Transport,
51 ProfilerCategory::Task,
52 ProfilerCategory::Policy,
53 ProfilerCategory::Ffi,
54 ProfilerCategory::Cache,
55 ProfilerCategory::Shape,
56 ProfilerCategory::Api,
57 ProfilerCategory::Actor,
58];
59
60impl ProfilerCategory {
61 pub const fn as_id(self) -> ProfilerCategoryId {
62 ProfilerCategoryId(self as u8)
63 }
64
65 pub const fn from_id(id: ProfilerCategoryId) -> Option<Self> {
66 match id.0 {
67 0 => Some(ProfilerCategory::Query),
68 1 => Some(ProfilerCategory::Txn),
69 2 => Some(ProfilerCategory::Storage),
70 3 => Some(ProfilerCategory::Plan),
71 4 => Some(ProfilerCategory::Cdc),
72 5 => Some(ProfilerCategory::Flow),
73 6 => Some(ProfilerCategory::Subscription),
74 7 => Some(ProfilerCategory::Server),
75 8 => Some(ProfilerCategory::Wire),
76 9 => Some(ProfilerCategory::Auth),
77 10 => Some(ProfilerCategory::Catalog),
78 11 => Some(ProfilerCategory::Engine),
79 12 => Some(ProfilerCategory::Mutate),
80 13 => Some(ProfilerCategory::Transport),
81 14 => Some(ProfilerCategory::Task),
82 15 => Some(ProfilerCategory::Policy),
83 16 => Some(ProfilerCategory::Ffi),
84 17 => Some(ProfilerCategory::Cache),
85 18 => Some(ProfilerCategory::Shape),
86 19 => Some(ProfilerCategory::Api),
87 20 => Some(ProfilerCategory::Actor),
88 _ => None,
89 }
90 }
91
92 pub fn from_span_name(name: &str) -> Option<Self> {
93 if name.starts_with("flow::ffi::") {
94 Some(ProfilerCategory::Ffi)
95 } else if name.starts_with("flow::") {
96 Some(ProfilerCategory::Flow)
97 } else if name.starts_with("transaction::") {
98 Some(ProfilerCategory::Txn)
99 } else if name.starts_with("store::single::")
100 || name.starts_with("store::multi::")
101 || name.starts_with("store::cdc::")
102 || name.starts_with("drop::")
103 {
104 Some(ProfilerCategory::Storage)
105 } else if name.starts_with("volcano::") || name.starts_with("vm::") {
106 Some(ProfilerCategory::Query)
107 } else if name.starts_with("rql::") {
108 Some(ProfilerCategory::Plan)
109 } else if name.starts_with("catalog::") {
110 Some(ProfilerCategory::Catalog)
111 } else if name.starts_with("cache::") {
112 Some(ProfilerCategory::Cache)
113 } else if name.starts_with("shape_store::") || name.starts_with("row_shape_registry::") {
114 Some(ProfilerCategory::Shape)
115 } else if name.starts_with("api::") {
116 Some(ProfilerCategory::Api)
117 } else if name.starts_with("actor::") {
118 Some(ProfilerCategory::Actor)
119 } else if name.starts_with("cdc::") {
120 Some(ProfilerCategory::Cdc)
121 } else if name.starts_with("subscription::") {
122 Some(ProfilerCategory::Subscription)
123 } else if name.starts_with("server::") {
124 Some(ProfilerCategory::Server)
125 } else if name.starts_with("wire::") {
126 Some(ProfilerCategory::Wire)
127 } else if name.starts_with("auth::") {
128 Some(ProfilerCategory::Auth)
129 } else if name.starts_with("engine::")
130 || name.starts_with("executor::")
131 || name.starts_with("session::")
132 {
133 Some(ProfilerCategory::Engine)
134 } else if name.starts_with("mutate::") {
135 Some(ProfilerCategory::Mutate)
136 } else if name.starts_with("http::") || name.starts_with("dispatch::") {
137 Some(ProfilerCategory::Transport)
138 } else if name.starts_with("task::") {
139 Some(ProfilerCategory::Task)
140 } else if name.starts_with("policy::") {
141 Some(ProfilerCategory::Policy)
142 } else if name.starts_with("ffi::")
143 || name.starts_with("procedure::")
144 || name.starts_with("transform::")
145 {
146 Some(ProfilerCategory::Ffi)
147 } else {
148 None
149 }
150 }
151
152 pub const fn name(self) -> &'static str {
153 match self {
154 ProfilerCategory::Query => "query",
155 ProfilerCategory::Txn => "txn",
156 ProfilerCategory::Storage => "storage",
157 ProfilerCategory::Plan => "plan",
158 ProfilerCategory::Cdc => "cdc",
159 ProfilerCategory::Flow => "flow",
160 ProfilerCategory::Subscription => "subscription",
161 ProfilerCategory::Server => "server",
162 ProfilerCategory::Wire => "wire",
163 ProfilerCategory::Auth => "auth",
164 ProfilerCategory::Catalog => "catalog",
165 ProfilerCategory::Engine => "engine",
166 ProfilerCategory::Mutate => "mutate",
167 ProfilerCategory::Transport => "transport",
168 ProfilerCategory::Task => "task",
169 ProfilerCategory::Policy => "policy",
170 ProfilerCategory::Ffi => "ffi",
171 ProfilerCategory::Cache => "cache",
172 ProfilerCategory::Shape => "shape",
173 ProfilerCategory::Api => "api",
174 ProfilerCategory::Actor => "actor",
175 }
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
180pub enum ProfilerLevel {
181 Trace,
182 Debug,
183 Info,
184 Warn,
185 Error,
186}
187
188impl ProfilerLevel {
189 pub fn as_level_filter(self) -> LevelFilter {
190 match self {
191 Self::Trace => LevelFilter::TRACE,
192 Self::Debug => LevelFilter::DEBUG,
193 Self::Info => LevelFilter::INFO,
194 Self::Warn => LevelFilter::WARN,
195 Self::Error => LevelFilter::ERROR,
196 }
197 }
198
199 pub fn admits(self, level: &Level) -> bool {
200 *level <= self.as_level_filter()
201 }
202}
203
204#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
205pub struct CategorySet {
206 levels: [Option<ProfilerLevel>; CATEGORY_COUNT],
207}
208
209impl CategorySet {
210 pub const fn empty() -> Self {
211 Self {
212 levels: [None; CATEGORY_COUNT],
213 }
214 }
215
216 pub const fn all() -> Self {
217 Self::all_at(ProfilerLevel::Trace)
218 }
219
220 pub const fn all_at(level: ProfilerLevel) -> Self {
221 Self {
222 levels: [Some(level); CATEGORY_COUNT],
223 }
224 }
225
226 pub fn contains(&self, c: ProfilerCategory) -> bool {
227 self.levels[c as usize].is_some()
228 }
229
230 pub fn level_for(&self, c: ProfilerCategory) -> Option<ProfilerLevel> {
231 self.levels[c as usize]
232 }
233
234 pub fn insert(&mut self, c: ProfilerCategory) {
235 self.insert_at(c, ProfilerLevel::Trace);
236 }
237
238 pub fn insert_at(&mut self, c: ProfilerCategory, level: ProfilerLevel) {
239 self.levels[c as usize] = Some(level);
240 }
241
242 pub fn remove(&mut self, c: ProfilerCategory) {
243 self.levels[c as usize] = None;
244 }
245
246 pub fn with(mut self, c: ProfilerCategory) -> Self {
247 self.insert(c);
248 self
249 }
250
251 pub fn with_level(mut self, c: ProfilerCategory, level: ProfilerLevel) -> Self {
252 self.insert_at(c, level);
253 self
254 }
255
256 pub fn without(mut self, c: ProfilerCategory) -> Self {
257 self.remove(c);
258 self
259 }
260
261 pub fn is_empty(&self) -> bool {
262 self.levels.iter().all(|l| l.is_none())
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn from_span_name_known_prefixes() {
272 assert_eq!(ProfilerCategory::from_span_name("flow::engine::apply"), Some(ProfilerCategory::Flow));
273 assert_eq!(
274 ProfilerCategory::from_span_name("flow::engine::process_batch"),
275 Some(ProfilerCategory::Flow)
276 );
277 assert_eq!(ProfilerCategory::from_span_name("transaction::commit"), Some(ProfilerCategory::Txn));
278 assert_eq!(ProfilerCategory::from_span_name("store::multi::write"), Some(ProfilerCategory::Storage));
279 assert_eq!(ProfilerCategory::from_span_name("store::single::scan"), Some(ProfilerCategory::Storage));
280 assert_eq!(
281 ProfilerCategory::from_span_name("store::cdc::sqlite::write"),
282 Some(ProfilerCategory::Storage)
283 );
284 assert_eq!(ProfilerCategory::from_span_name("drop::range"), Some(ProfilerCategory::Storage));
285 assert_eq!(ProfilerCategory::from_span_name("volcano::project"), Some(ProfilerCategory::Query));
286 assert_eq!(ProfilerCategory::from_span_name("vm::executor"), Some(ProfilerCategory::Query));
287 assert_eq!(ProfilerCategory::from_span_name("rql::parse"), Some(ProfilerCategory::Plan));
288 assert_eq!(ProfilerCategory::from_span_name("cdc::write"), Some(ProfilerCategory::Cdc));
289 assert_eq!(
290 ProfilerCategory::from_span_name("subscription::consume"),
291 Some(ProfilerCategory::Subscription)
292 );
293 assert_eq!(ProfilerCategory::from_span_name("server::deliver"), Some(ProfilerCategory::Server));
294 assert_eq!(ProfilerCategory::from_span_name("wire::encode_frames"), Some(ProfilerCategory::Wire));
295 assert_eq!(ProfilerCategory::from_span_name("auth::authenticate"), Some(ProfilerCategory::Auth));
296 assert_eq!(ProfilerCategory::from_span_name("engine::query_as"), Some(ProfilerCategory::Engine));
297 assert_eq!(ProfilerCategory::from_span_name("executor::compile"), Some(ProfilerCategory::Engine));
298 assert_eq!(ProfilerCategory::from_span_name("session::query"), Some(ProfilerCategory::Engine));
299 assert_eq!(ProfilerCategory::from_span_name("mutate::table::insert"), Some(ProfilerCategory::Mutate));
300 assert_eq!(ProfilerCategory::from_span_name("http::query"), Some(ProfilerCategory::Transport));
301 assert_eq!(
302 ProfilerCategory::from_span_name("dispatch::send_server_message"),
303 Some(ProfilerCategory::Transport)
304 );
305 assert_eq!(ProfilerCategory::from_span_name("task::spawn"), Some(ProfilerCategory::Task));
306 assert_eq!(ProfilerCategory::from_span_name("policy::enforce"), Some(ProfilerCategory::Policy));
307 assert_eq!(ProfilerCategory::from_span_name("ffi::callback"), Some(ProfilerCategory::Ffi));
308 assert_eq!(ProfilerCategory::from_span_name("procedure::ffi::execute"), Some(ProfilerCategory::Ffi));
309 assert_eq!(ProfilerCategory::from_span_name("transform::ffi::apply"), Some(ProfilerCategory::Ffi));
310 assert_eq!(ProfilerCategory::from_span_name("flow::ffi::vtable_call"), Some(ProfilerCategory::Ffi));
312 assert_eq!(ProfilerCategory::from_span_name("flow::engine::apply"), Some(ProfilerCategory::Flow));
313 }
314
315 #[test]
316 fn catalog_split_out_of_plan() {
317 assert_eq!(
320 ProfilerCategory::from_span_name("catalog::column::find_by_name"),
321 Some(ProfilerCategory::Catalog)
322 );
323 assert_eq!(ProfilerCategory::from_span_name("rql::plan"), Some(ProfilerCategory::Plan));
324 }
325
326 #[test]
327 fn from_span_name_new_subsystem_prefixes() {
328 assert_eq!(ProfilerCategory::from_span_name("cache::row_shape::load"), Some(ProfilerCategory::Cache));
331 assert_eq!(ProfilerCategory::from_span_name("shape_store::create"), Some(ProfilerCategory::Shape));
332 assert_eq!(ProfilerCategory::from_span_name("row_shape_registry::load"), Some(ProfilerCategory::Shape));
334 assert_eq!(ProfilerCategory::from_span_name("api::stop_fast"), Some(ProfilerCategory::Api));
335 assert_eq!(ProfilerCategory::from_span_name("actor::task_pool"), Some(ProfilerCategory::Actor));
336 }
337
338 #[test]
339 fn from_span_name_flow_covers_non_engine_prefixes() {
340 assert_eq!(
343 ProfilerCategory::from_span_name("flow::coordinator::consume"),
344 Some(ProfilerCategory::Flow)
345 );
346 assert_eq!(ProfilerCategory::from_span_name("flow::pool::submit"), Some(ProfilerCategory::Flow));
347 assert_eq!(ProfilerCategory::from_span_name("flow::actor::tick"), Some(ProfilerCategory::Flow));
348 assert_eq!(ProfilerCategory::from_span_name("flow::engine::apply"), Some(ProfilerCategory::Flow));
349 }
350
351 #[test]
352 fn from_span_name_unknown_returns_none() {
353 assert_eq!(ProfilerCategory::from_span_name("random::other::name"), None);
354 assert_eq!(ProfilerCategory::from_span_name(""), None);
355 assert_eq!(ProfilerCategory::from_span_name("tracing::subsystem::start"), None);
356 }
357
358 #[test]
359 fn id_roundtrip() {
360 for c in ALL_CATEGORIES {
361 assert_eq!(ProfilerCategory::from_id(c.as_id()), Some(c));
362 }
363 assert_eq!(ProfilerCategory::from_id(ProfilerCategoryId(CATEGORY_COUNT as u8)), None);
364 assert_eq!(ProfilerCategory::from_id(ProfilerCategoryId(255)), None);
365 }
366
367 #[test]
368 fn category_set_membership() {
369 let mut s = CategorySet::empty();
370 assert!(s.is_empty());
371 s.insert(ProfilerCategory::Flow);
372 assert!(s.contains(ProfilerCategory::Flow));
373 assert!(!s.contains(ProfilerCategory::Query));
374 s.insert(ProfilerCategory::Query);
375 assert!(s.contains(ProfilerCategory::Query));
376 s.remove(ProfilerCategory::Flow);
377 assert!(!s.contains(ProfilerCategory::Flow));
378
379 let all = CategorySet::all();
380 for c in ALL_CATEGORIES {
381 assert!(all.contains(c));
382 }
383 }
384
385 #[test]
386 fn category_set_default_insert_is_trace() {
387 let mut s = CategorySet::empty();
388 s.insert(ProfilerCategory::Flow);
389 assert_eq!(s.level_for(ProfilerCategory::Flow), Some(ProfilerLevel::Trace));
390 }
391
392 #[test]
393 fn category_set_per_category_level() {
394 let mut s = CategorySet::empty();
395 s.insert_at(ProfilerCategory::Flow, ProfilerLevel::Trace);
396 s.insert_at(ProfilerCategory::Query, ProfilerLevel::Debug);
397 s.insert_at(ProfilerCategory::Storage, ProfilerLevel::Info);
398
399 assert_eq!(s.level_for(ProfilerCategory::Flow), Some(ProfilerLevel::Trace));
400 assert_eq!(s.level_for(ProfilerCategory::Query), Some(ProfilerLevel::Debug));
401 assert_eq!(s.level_for(ProfilerCategory::Storage), Some(ProfilerLevel::Info));
402 assert_eq!(s.level_for(ProfilerCategory::Plan), None);
403
404 assert!(s.contains(ProfilerCategory::Flow));
405 assert!(s.contains(ProfilerCategory::Query));
406 assert!(s.contains(ProfilerCategory::Storage));
407 assert!(!s.contains(ProfilerCategory::Plan));
408 }
409
410 #[test]
411 fn category_set_with_level_round_trips() {
412 let s = CategorySet::empty()
413 .with_level(ProfilerCategory::Flow, ProfilerLevel::Trace)
414 .with_level(ProfilerCategory::Plan, ProfilerLevel::Debug);
415 assert_eq!(s.level_for(ProfilerCategory::Flow), Some(ProfilerLevel::Trace));
416 assert_eq!(s.level_for(ProfilerCategory::Plan), Some(ProfilerLevel::Debug));
417 assert_eq!(s.level_for(ProfilerCategory::Cdc), None);
418 }
419
420 #[test]
421 fn category_set_all_at_sets_uniform_level() {
422 let s = CategorySet::all_at(ProfilerLevel::Debug);
423 for c in ALL_CATEGORIES {
424 assert_eq!(s.level_for(c), Some(ProfilerLevel::Debug));
425 }
426 }
427
428 #[test]
429 fn profile_level_admits_at_or_less_verbose() {
430 assert!(ProfilerLevel::Debug.admits(&Level::DEBUG));
431 assert!(ProfilerLevel::Debug.admits(&Level::INFO));
432 assert!(ProfilerLevel::Debug.admits(&Level::WARN));
433 assert!(ProfilerLevel::Debug.admits(&Level::ERROR));
434 assert!(!ProfilerLevel::Debug.admits(&Level::TRACE));
435
436 assert!(ProfilerLevel::Trace.admits(&Level::TRACE));
437 assert!(!ProfilerLevel::Error.admits(&Level::WARN));
438 }
439}