Skip to main content

reifydb_profiler/
category.rs

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