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	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		// flow::ffi:: must beat the broader flow:: -> Flow rule so FFI boundary cost is attributed to Ffi.
311		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		// catalog:: was moved out of Plan into its own Catalog category so Plan reflects
318		// real query planning (rql::) rather than being dominated by metadata lookups.
319		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		// cache::, shape_store::/row_shape_registry::, api::, actor:: were previously unmapped and
329		// their spans silently dropped; each now buckets into its own selectable category.
330		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		// row_shape_registry:: is the same row-shape subsystem as shape_store::, so it shares Shape.
333		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		// Flow was widened from `flow::engine::` to `flow::` so the already-instrumented
341		// coordinator/pool/worker spans are captured, not just the engine internals.
342		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}