Skip to main content

tuwunel_database/engine/
events.rs

1use rocksdb::{
2	Env,
3	event_listener::{
4		CompactionJobInfo, DBBackgroundErrorReason, DBWriteStallCondition, EventListener,
5		FlushJobInfo, IngestionInfo, MemTableInfo, MutableStatus, SubcompactionJobInfo,
6		WriteStallInfo,
7	},
8};
9use tuwunel_core::{Config, debug, debug::INFO_SPAN_LEVEL, debug_info, error, info, warn};
10
11/// Bridges RocksDB background events into structured tracing.
12///
13/// The listener records stalls, compactions, flushes, memtable seals, and
14/// external file ingestion without retaining per-event state.
15pub(super) struct Events;
16
17impl Events {
18	pub(super) fn new(_config: &Config, _env: &Env) -> Self { Self {} }
19}
20
21impl EventListener for Events {
22	#[tracing::instrument(name = "error", level = "error", skip_all)]
23	fn on_background_error(&self, reason: DBBackgroundErrorReason, _status: MutableStatus) {
24		error!(error = ?reason, "Critical RocksDB Error");
25	}
26
27	#[tracing::instrument(name = "stall", level = "warn", skip_all)]
28	fn on_stall_conditions_changed(&self, info: &WriteStallInfo) {
29		let col = info.cf_name();
30		let col = col
31			.as_deref()
32			.map(str::from_utf8)
33			.expect("column has a name")
34			.expect("column name is valid utf8");
35
36		let prev = info.prev();
37		match info.cur() {
38			| DBWriteStallCondition::KStopped => {
39				error!(?col, ?prev, "Database Stalled");
40			},
41			| DBWriteStallCondition::KDelayed if prev == DBWriteStallCondition::KStopped => {
42				warn!(?col, ?prev, "Database Stall Recovering");
43			},
44			| DBWriteStallCondition::KDelayed => {
45				warn!(?col, ?prev, "Database Stalling");
46			},
47			| DBWriteStallCondition::KNormal
48				if prev == DBWriteStallCondition::KStopped
49					|| prev == DBWriteStallCondition::KDelayed =>
50			{
51				info!(?col, ?prev, "Database Stall Recovered");
52			},
53			| DBWriteStallCondition::KNormal => {
54				debug!(?col, ?prev, "Database Normal");
55			},
56		}
57	}
58
59	#[tracing::instrument(
60		name = "compaction",
61		level = INFO_SPAN_LEVEL,
62		skip_all,
63	)]
64	fn on_compaction_begin(&self, info: &CompactionJobInfo) {
65		let col = info.cf_name();
66		let col = col
67			.as_deref()
68			.map(str::from_utf8)
69			.expect("column has a name")
70			.expect("column name is valid utf8");
71
72		let level = (info.base_input_level(), info.output_level());
73		let records = (info.input_records(), info.output_records());
74		let bytes = (info.total_input_bytes(), info.total_output_bytes());
75		let files = (
76			info.input_file_count(),
77			info.output_file_count(),
78			info.num_input_files_at_output_level(),
79		);
80
81		debug!(
82			status = ?info.status(),
83			?level,
84			?files,
85			?records,
86			?bytes,
87			micros = info.elapsed_micros(),
88			errs = info.num_corrupt_keys(),
89			reason = ?info.compaction_reason(),
90			?col,
91			"Compaction Starting",
92		);
93	}
94
95	#[tracing::instrument(
96		name = "compaction",
97		level = INFO_SPAN_LEVEL,
98		skip_all,
99	)]
100	fn on_compaction_completed(&self, info: &CompactionJobInfo) {
101		let col = info.cf_name();
102		let col = col
103			.as_deref()
104			.map(str::from_utf8)
105			.expect("column has a name")
106			.expect("column name is valid utf8");
107
108		let level = (info.base_input_level(), info.output_level());
109		let records = (info.input_records(), info.output_records());
110		let bytes = (info.total_input_bytes(), info.total_output_bytes());
111		let files = (
112			info.input_file_count(),
113			info.output_file_count(),
114			info.num_input_files_at_output_level(),
115		);
116
117		debug_info!(
118			status = ?info.status(),
119			?level,
120			?files,
121			?records,
122			?bytes,
123			micros = info.elapsed_micros(),
124			errs = info.num_corrupt_keys(),
125			reason = ?info.compaction_reason(),
126			?col,
127			"Compaction Complete",
128		);
129	}
130
131	#[tracing::instrument(name = "compaction", level = "debug", skip_all)]
132	fn on_subcompaction_begin(&self, info: &SubcompactionJobInfo) {
133		let col = info.cf_name();
134		let col = col
135			.as_deref()
136			.map(str::from_utf8)
137			.expect("column has a name")
138			.expect("column name is valid utf8");
139
140		let level = (info.base_input_level(), info.output_level());
141
142		debug!(
143			status = ?info.status(),
144			?level,
145			tid = info.thread_id(),
146			reason = ?info.compaction_reason(),
147			?col,
148			"Compaction Starting",
149		);
150	}
151
152	#[tracing::instrument(name = "compaction", level = "debug", skip_all)]
153	fn on_subcompaction_completed(&self, info: &SubcompactionJobInfo) {
154		let col = info.cf_name();
155		let col = col
156			.as_deref()
157			.map(str::from_utf8)
158			.expect("column has a name")
159			.expect("column name is valid utf8");
160
161		let level = (info.base_input_level(), info.output_level());
162
163		debug!(
164			status = ?info.status(),
165			?level,
166			tid = info.thread_id(),
167			reason = ?info.compaction_reason(),
168			?col,
169			"Compaction Complete",
170		);
171	}
172
173	#[tracing::instrument(
174		name = "flush",
175		level = INFO_SPAN_LEVEL,
176		skip_all,
177	)]
178	fn on_flush_begin(&self, info: &FlushJobInfo) {
179		let col = info.cf_name();
180		let col = col
181			.as_deref()
182			.map(str::from_utf8)
183			.expect("column has a name")
184			.expect("column name is valid utf8");
185
186		debug!(
187			seq_start = info.smallest_seqno(),
188			seq_end = info.largest_seqno(),
189			slow = info.triggered_writes_slowdown(),
190			stop = info.triggered_writes_stop(),
191			reason = ?info.flush_reason(),
192			?col,
193			"Flush Starting",
194		);
195	}
196
197	#[tracing::instrument(
198		name = "flush",
199		level = INFO_SPAN_LEVEL,
200		skip_all,
201	)]
202	fn on_flush_completed(&self, info: &FlushJobInfo) {
203		let col = info.cf_name();
204		let col = col
205			.as_deref()
206			.map(str::from_utf8)
207			.expect("column has a name")
208			.expect("column name is valid utf8");
209
210		debug_info!(
211			seq_start = info.smallest_seqno(),
212			seq_end = info.largest_seqno(),
213			slow = info.triggered_writes_slowdown(),
214			stop = info.triggered_writes_stop(),
215			reason = ?info.flush_reason(),
216			?col,
217			"Flush Complete",
218		);
219	}
220
221	#[tracing::instrument(
222		name = "memtable",
223		level = INFO_SPAN_LEVEL,
224		skip_all,
225	)]
226	fn on_memtable_sealed(&self, info: &MemTableInfo) {
227		let col = info.cf_name();
228		let col = col
229			.as_deref()
230			.map(str::from_utf8)
231			.expect("column has a name")
232			.expect("column name is valid utf8");
233
234		debug_info!(
235			seq_first = info.first_seqno(),
236			seq_early = info.earliest_seqno(),
237			ents = info.num_entries(),
238			dels = info.num_deletes(),
239			?col,
240			"Buffer Filled",
241		);
242	}
243
244	fn on_external_file_ingested(&self, _info: &IngestionInfo) {
245		unimplemented!();
246	}
247}