1use std::sync::Arc;
2use std::sync::atomic::AtomicU64;
3use std::sync::atomic::Ordering;
4
5use ursula_shard::BucketStreamId;
6use ursula_shard::CoreId;
7use ursula_shard::RaftGroupId;
8use ursula_shard::ShardPlacement;
9use ursula_stream::StreamErrorCode;
10use ursula_stream::StreamErrorContext;
11
12use crate::engine::GroupEngine;
13use crate::engine::GroupEngineError;
14use crate::error::RuntimeError;
15use crate::request::AppendBatchRequest;
16use crate::request::ColdWriteAdmission;
17use crate::rt::time::Instant;
18
19pub(crate) const GROUP_ACTOR_MAX_WRITE_BATCH: usize = 64;
20pub(crate) const COLD_FLUSH_GROUP_BATCH_MAX_CHUNKS: usize = 4096;
21
22#[derive(Debug, Clone)]
23pub struct RuntimeMetrics {
24 pub(crate) inner: Arc<RuntimeMetricsInner>,
25}
26
27impl RuntimeMetrics {
28 pub fn record_raft_snapshot_pressure(&self, groups: u64) {
30 self.inner.record_raft_snapshot_pressure(groups);
31 }
32}
33
34macro_rules! runtime_metrics {
55 (@munch
56 ctx { $ir:ident $cc:ident $gc:ident }
57 inner { $($inner:tt)* }
58 new { $($new:tt)* }
59 snap { $($snap:tt)* }
60 fields { $($fields:tt)* }
61 names { $($names:ident)* }
62 rest { sum $global:ident: core $core:ident, group $group:ident; $($rest:tt)* }
63 ) => {
64 runtime_metrics! {
65 @munch
66 ctx { $ir $cc $gc }
67 inner {
68 $($inner)*
69 pub(crate) $core: Vec<PaddedAtomicU64>,
70 pub(crate) $group: Vec<PaddedAtomicU64>,
71 }
72 new {
73 $($new)*
74 $core: zeroed_counters($cc),
75 $group: zeroed_counters($gc),
76 }
77 snap {
78 $($snap)*
79 let $core = load_counters(&$ir.$core);
80 let $global: u64 = $core.iter().sum();
81 let $group = load_counters(&$ir.$group);
82 }
83 fields {
84 $($fields)*
85 pub $global: u64,
86 pub $core: Vec<u64>,
87 pub $group: Vec<u64>,
88 }
89 names { $($names)* $global $core $group }
90 rest { $($rest)* }
91 }
92 };
93 (@munch
94 ctx { $ir:ident $cc:ident $gc:ident }
95 inner { $($inner:tt)* }
96 new { $($new:tt)* }
97 snap { $($snap:tt)* }
98 fields { $($fields:tt)* }
99 names { $($names:ident)* }
100 rest { sum $global:ident: core $core:ident; $($rest:tt)* }
101 ) => {
102 runtime_metrics! {
103 @munch
104 ctx { $ir $cc $gc }
105 inner {
106 $($inner)*
107 pub(crate) $core: Vec<PaddedAtomicU64>,
108 }
109 new {
110 $($new)*
111 $core: zeroed_counters($cc),
112 }
113 snap {
114 $($snap)*
115 let $core = load_counters(&$ir.$core);
116 let $global: u64 = $core.iter().sum();
117 }
118 fields {
119 $($fields)*
120 pub $global: u64,
121 pub $core: Vec<u64>,
122 }
123 names { $($names)* $global $core }
124 rest { $($rest)* }
125 }
126 };
127 (@munch
128 ctx { $ir:ident $cc:ident $gc:ident }
129 inner { $($inner:tt)* }
130 new { $($new:tt)* }
131 snap { $($snap:tt)* }
132 fields { $($fields:tt)* }
133 names { $($names:ident)* }
134 rest { sum $global:ident: group $group:ident; $($rest:tt)* }
135 ) => {
136 runtime_metrics! {
137 @munch
138 ctx { $ir $cc $gc }
139 inner {
140 $($inner)*
141 pub(crate) $group: Vec<PaddedAtomicU64>,
142 }
143 new {
144 $($new)*
145 $group: zeroed_counters($gc),
146 }
147 snap {
148 $($snap)*
149 let $group = load_counters(&$ir.$group);
150 let $global: u64 = $group.iter().sum();
151 }
152 fields {
153 $($fields)*
154 pub $global: u64,
155 pub $group: Vec<u64>,
156 }
157 names { $($names)* $global $group }
158 rest { $($rest)* }
159 }
160 };
161 (@munch
162 ctx { $ir:ident $cc:ident $gc:ident }
163 inner { $($inner:tt)* }
164 new { $($new:tt)* }
165 snap { $($snap:tt)* }
166 fields { $($fields:tt)* }
167 names { $($names:ident)* }
168 rest { max $global:ident: group $group:ident; $($rest:tt)* }
169 ) => {
170 runtime_metrics! {
171 @munch
172 ctx { $ir $cc $gc }
173 inner {
174 $($inner)*
175 pub(crate) $group: Vec<PaddedAtomicU64>,
176 }
177 new {
178 $($new)*
179 $group: zeroed_counters($gc),
180 }
181 snap {
182 $($snap)*
183 let $group = load_counters(&$ir.$group);
184 let $global = max_or_zero(&$group);
185 }
186 fields {
187 $($fields)*
188 pub $global: u64,
189 pub $group: Vec<u64>,
190 }
191 names { $($names)* $global $group }
192 rest { $($rest)* }
193 }
194 };
195 (@munch
196 ctx { $ir:ident $cc:ident $gc:ident }
197 inner { $($inner:tt)* }
198 new { $($new:tt)* }
199 snap { $($snap:tt)* }
200 fields { $($fields:tt)* }
201 names { $($names:ident)* }
202 rest { summax $sum:ident, $max:ident: group $group:ident; $($rest:tt)* }
203 ) => {
204 runtime_metrics! {
205 @munch
206 ctx { $ir $cc $gc }
207 inner {
208 $($inner)*
209 pub(crate) $group: Vec<PaddedAtomicU64>,
210 }
211 new {
212 $($new)*
213 $group: zeroed_counters($gc),
214 }
215 snap {
216 $($snap)*
217 let $group = load_counters(&$ir.$group);
218 let $sum: u64 = $group.iter().sum();
219 let $max = max_or_zero(&$group);
220 }
221 fields {
222 $($fields)*
223 pub $sum: u64,
224 pub $max: u64,
225 pub $group: Vec<u64>,
226 }
227 names { $($names)* $sum $max $group }
228 rest { $($rest)* }
229 }
230 };
231 (@munch
232 ctx { $ir:ident $cc:ident $gc:ident }
233 inner { $($inner:tt)* }
234 new { $($new:tt)* }
235 snap { $($snap:tt)* }
236 fields { $($fields:tt)* }
237 names { $($names:ident)* }
238 rest { counter $global:ident; $($rest:tt)* }
239 ) => {
240 runtime_metrics! {
241 @munch
242 ctx { $ir $cc $gc }
243 inner {
244 $($inner)*
245 pub(crate) $global: PaddedAtomicU64,
246 }
247 new {
248 $($new)*
249 $global: PaddedAtomicU64::new(0),
250 }
251 snap {
252 $($snap)*
253 let $global = $ir.$global.load_relaxed();
254 }
255 fields {
256 $($fields)*
257 pub $global: u64,
258 }
259 names { $($names)* $global }
260 rest { $($rest)* }
261 }
262 };
263 (@munch
264 ctx { $ir:ident $cc:ident $gc:ident }
265 inner { $($inner:tt)* }
266 new { $($new:tt)* }
267 snap { $($snap:tt)* }
268 fields { $($fields:tt)* }
269 names { $($names:ident)* }
270 rest { }
271 ) => {
272 #[derive(Debug)]
273 pub(crate) struct RuntimeMetricsInner {
274 $($inner)*
275 }
276
277 impl RuntimeMetricsInner {
278 pub(crate) fn new($cc: usize, $gc: usize) -> Self {
279 Self { $($new)* }
280 }
281 }
282
283 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
284 pub struct RuntimeMetricsSnapshot {
285 $($fields)*
286 }
287
288 impl RuntimeMetrics {
289 pub fn snapshot(&self) -> RuntimeMetricsSnapshot {
290 let $ir = &self.inner;
291 $($snap)*
292 RuntimeMetricsSnapshot { $($names),* }
293 }
294 }
295 };
296 ( $($manifest:tt)* ) => {
297 runtime_metrics! {
298 @munch
299 ctx { inner_counters core_count raft_group_count }
300 inner {}
301 new {}
302 snap {}
303 fields {}
304 names {}
305 rest { $($manifest)* }
306 }
307 };
308}
309
310fn zeroed_counters(len: usize) -> Vec<PaddedAtomicU64> {
311 (0..len).map(|_| PaddedAtomicU64::new(0)).collect()
312}
313
314fn load_counters(counters: &[PaddedAtomicU64]) -> Vec<u64> {
315 counters.iter().map(PaddedAtomicU64::load_relaxed).collect()
316}
317
318fn max_or_zero(values: &[u64]) -> u64 {
319 values.iter().copied().max().unwrap_or(0)
320}
321
322runtime_metrics! {
323 sum accepted_appends: core per_core_appends, group per_group_appends;
324 sum applied_mutations:
325 core per_core_applied_mutations, group per_group_applied_mutations;
326 sum mutation_apply_ns:
327 core per_core_mutation_apply_ns, group per_group_mutation_apply_ns;
328 sum append_post_commit_ns:
329 core per_core_append_post_commit_ns, group per_group_append_post_commit_ns;
330 sum read_watcher_notify_calls:
331 core per_core_read_watcher_notify_calls, group per_group_read_watcher_notify_calls;
332 sum read_watcher_notify_ns:
333 core per_core_read_watcher_notify_ns, group per_group_read_watcher_notify_ns;
334 sum read_watcher_replans:
335 core per_core_read_watcher_replans, group per_group_read_watcher_replans;
336 sum group_lock_wait_ns:
337 core per_core_group_lock_wait_ns, group per_group_group_lock_wait_ns;
338 sum group_engine_exec_ns:
339 core per_core_group_engine_exec_ns, group per_group_group_engine_exec_ns;
340 sum group_mailbox_depth: group per_group_group_mailbox_depth;
341 max group_mailbox_max_depth: group per_group_group_mailbox_max_depth;
342 sum group_mailbox_full_events: group per_group_group_mailbox_full_events;
343 sum raft_write_many_batches:
344 core per_core_raft_write_many_batches, group per_group_raft_write_many_batches;
345 sum raft_write_many_commands:
346 core per_core_raft_write_many_commands, group per_group_raft_write_many_commands;
347 sum raft_write_many_logical_commands:
348 core per_core_raft_write_many_logical_commands,
349 group per_group_raft_write_many_logical_commands;
350 sum raft_write_many_responses:
351 core per_core_raft_write_many_responses, group per_group_raft_write_many_responses;
352 sum raft_write_many_submit_ns:
353 core per_core_raft_write_many_submit_ns, group per_group_raft_write_many_submit_ns;
354 sum raft_write_many_response_ns:
355 core per_core_raft_write_many_response_ns, group per_group_raft_write_many_response_ns;
356 sum raft_apply_entries: core per_core_raft_apply_entries, group per_group_raft_apply_entries;
357 sum raft_apply_ns: core per_core_raft_apply_ns, group per_group_raft_apply_ns;
358 sum raft_snapshot_builds: group per_group_raft_snapshot_builds;
359 sum raft_snapshot_build_ns: group per_group_raft_snapshot_build_ns;
360 summax raft_snapshot_body_bytes, raft_snapshot_body_bytes_max:
361 group per_group_raft_snapshot_body_bytes;
362 summax raft_snapshot_pointer_bytes, raft_snapshot_pointer_bytes_max:
363 group per_group_raft_snapshot_pointer_bytes;
364 summax raft_snapshot_streams, raft_snapshot_streams_max:
365 group per_group_raft_snapshot_streams;
366 sum raft_snapshot_external_uploads: group per_group_raft_snapshot_external_uploads;
367 sum raft_snapshot_inline_fallbacks: group per_group_raft_snapshot_inline_fallbacks;
368 sum live_read_waiters: core per_core_live_read_waiters;
369 sum live_read_backpressure_events: core per_core_live_read_backpressure_events;
370 sum routed_requests: core per_core_routed_requests;
371 sum mailbox_send_wait_ns: core per_core_mailbox_send_wait_ns;
372 sum mailbox_full_events: core per_core_mailbox_full_events;
373 sum wal_batches: core per_core_wal_batches, group per_group_wal_batches;
374 sum wal_records: core per_core_wal_records, group per_group_wal_records;
375 sum wal_write_ns: core per_core_wal_write_ns, group per_group_wal_write_ns;
376 sum wal_sync_ns: core per_core_wal_sync_ns, group per_group_wal_sync_ns;
377 counter cold_flush_uploads;
378 counter cold_flush_upload_bytes;
379 counter cold_flush_upload_ns;
380 counter cold_pack_uploads;
381 counter cold_pack_bytes;
382 counter cold_pack_slices;
383 counter cold_flush_publishes;
384 counter cold_flush_publish_bytes;
385 counter cold_flush_publish_ns;
386 counter cold_orphan_cleanup_attempts;
387 counter cold_orphan_cleanup_errors;
388 counter cold_orphan_bytes;
389 counter cold_gc_reclaimed;
390 counter cold_gc_errors;
391 counter cold_flush_write_errors;
392 counter cold_pressure_flush_passes;
393 counter cold_pressure_flush_candidates;
394 counter raft_snapshot_pressure_passes;
395 counter raft_snapshot_pressure_groups;
396 sum cold_hot_bytes: group per_group_cold_hot_bytes;
397 max cold_hot_group_bytes_max: group per_group_cold_hot_bytes_current_max;
401 max cold_hot_group_bytes_high_watermark: group per_group_cold_hot_bytes_max;
402 counter cold_hot_stream_bytes_max;
403 sum cold_backpressure_events:
404 core per_core_cold_backpressure_events, group per_group_cold_backpressure_events;
405 counter cold_backpressure_bytes;
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct RuntimeMailboxSnapshot {
410 pub depths: Vec<usize>,
411 pub capacities: Vec<usize>,
412}
413
414#[derive(Debug, Clone, Copy)]
415pub(crate) struct RaftWriteManySample {
416 pub(crate) command_count: u64,
417 pub(crate) logical_command_count: u64,
418 pub(crate) response_count: u64,
419 pub(crate) submit_ns: u64,
420 pub(crate) response_ns: u64,
421}
422
423#[derive(Debug, Clone, Copy)]
424pub(crate) struct RaftSnapshotBuildSample {
425 pub(crate) streams: u64,
426 pub(crate) body_bytes: u64,
427 pub(crate) pointer_bytes: u64,
428 pub(crate) build_ns: u64,
429 pub(crate) external_upload: bool,
430 pub(crate) inline_fallback: bool,
431}
432
433impl RuntimeMetricsInner {
434 pub(crate) fn record_routed_request(&self, core_id: CoreId, mailbox_send_wait_ns: u64) {
435 let index = usize::from(core_id.0);
436 self.per_core_routed_requests[index].fetch_add_relaxed(1);
437 self.per_core_mailbox_send_wait_ns[index].fetch_add_relaxed(mailbox_send_wait_ns);
438 }
439
440 pub(crate) fn record_mailbox_full(&self, core_id: CoreId) {
441 self.per_core_mailbox_full_events[usize::from(core_id.0)].fetch_add_relaxed(1);
442 }
443
444 pub(crate) fn cold_hot_bytes(&self) -> u64 {
445 self.per_group_cold_hot_bytes
446 .iter()
447 .map(PaddedAtomicU64::load_relaxed)
448 .sum()
449 }
450
451 pub(crate) fn record_cold_pressure_flush(&self, candidates: usize) {
452 self.cold_pressure_flush_passes.fetch_add_relaxed(1);
453 self.cold_pressure_flush_candidates
454 .fetch_add_relaxed(u64::try_from(candidates).unwrap_or(u64::MAX));
455 }
456
457 pub(crate) fn record_append(&self, core_id: CoreId, group_id: RaftGroupId) {
458 self.record_append_batch(core_id, group_id, 1);
459 }
460
461 pub(crate) fn record_append_batch(&self, core_id: CoreId, group_id: RaftGroupId, count: u64) {
462 self.per_core_appends[usize::from(core_id.0)].fetch_add_relaxed(count);
463 self.per_group_appends[usize::try_from(group_id.0).expect("u32 fits usize")]
464 .fetch_add_relaxed(count);
465 }
466
467 pub(crate) fn record_applied_mutation(
468 &self,
469 core_id: CoreId,
470 group_id: RaftGroupId,
471 apply_ns: u64,
472 ) {
473 self.record_applied_mutation_batch(core_id, group_id, 1, apply_ns);
474 }
475
476 pub(crate) fn record_applied_mutation_batch(
477 &self,
478 core_id: CoreId,
479 group_id: RaftGroupId,
480 count: u64,
481 apply_ns: u64,
482 ) {
483 let core_index = usize::from(core_id.0);
484 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
485 self.per_core_applied_mutations[core_index].fetch_add_relaxed(count);
486 self.per_group_applied_mutations[group_index].fetch_add_relaxed(count);
487 self.per_core_mutation_apply_ns[core_index].fetch_add_relaxed(apply_ns);
488 self.per_group_mutation_apply_ns[group_index].fetch_add_relaxed(apply_ns);
489 }
490
491 pub(crate) fn record_group_engine_exec(
492 &self,
493 core_id: CoreId,
494 group_id: RaftGroupId,
495 exec_ns: u64,
496 ) {
497 let core_index = usize::from(core_id.0);
498 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
499 self.per_core_group_engine_exec_ns[core_index].fetch_add_relaxed(exec_ns);
500 self.per_group_group_engine_exec_ns[group_index].fetch_add_relaxed(exec_ns);
501 }
502
503 pub(crate) fn record_append_post_commit(
504 &self,
505 core_id: CoreId,
506 group_id: RaftGroupId,
507 elapsed_ns: u64,
508 ) {
509 let core_index = usize::from(core_id.0);
510 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
511 self.per_core_append_post_commit_ns[core_index].fetch_add_relaxed(elapsed_ns);
512 self.per_group_append_post_commit_ns[group_index].fetch_add_relaxed(elapsed_ns);
513 }
514
515 pub(crate) fn record_read_watcher_notify(
516 &self,
517 core_id: CoreId,
518 group_id: RaftGroupId,
519 replans: usize,
520 elapsed_ns: u64,
521 ) {
522 let core_index = usize::from(core_id.0);
523 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
524 let replans = u64::try_from(replans).unwrap_or(u64::MAX);
525 self.per_core_read_watcher_notify_calls[core_index].fetch_add_relaxed(1);
526 self.per_group_read_watcher_notify_calls[group_index].fetch_add_relaxed(1);
527 self.per_core_read_watcher_notify_ns[core_index].fetch_add_relaxed(elapsed_ns);
528 self.per_group_read_watcher_notify_ns[group_index].fetch_add_relaxed(elapsed_ns);
529 self.per_core_read_watcher_replans[core_index].fetch_add_relaxed(replans);
530 self.per_group_read_watcher_replans[group_index].fetch_add_relaxed(replans);
531 }
532
533 pub(crate) fn record_group_mailbox_enqueued(&self, group_id: RaftGroupId) {
534 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
535 let depth = self.per_group_group_mailbox_depth[group_index]
536 .fetch_add_relaxed(1)
537 .saturating_add(1);
538 self.per_group_group_mailbox_max_depth[group_index].fetch_max_relaxed(depth);
539 }
540
541 pub(crate) fn record_group_mailbox_dequeued(&self, group_id: RaftGroupId) {
542 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
543 self.per_group_group_mailbox_depth[group_index].fetch_sub_saturating_relaxed(1);
544 }
545
546 pub(crate) fn record_group_mailbox_full(&self, group_id: RaftGroupId) {
547 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
548 self.per_group_group_mailbox_full_events[group_index].fetch_add_relaxed(1);
549 }
550
551 pub(crate) fn record_raft_write_many(
552 &self,
553 core_id: CoreId,
554 group_id: RaftGroupId,
555 sample: RaftWriteManySample,
556 ) {
557 let core_index = usize::from(core_id.0);
558 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
559 self.per_core_raft_write_many_batches[core_index].fetch_add_relaxed(1);
560 self.per_group_raft_write_many_batches[group_index].fetch_add_relaxed(1);
561 self.per_core_raft_write_many_commands[core_index].fetch_add_relaxed(sample.command_count);
562 self.per_group_raft_write_many_commands[group_index]
563 .fetch_add_relaxed(sample.command_count);
564 self.per_core_raft_write_many_logical_commands[core_index]
565 .fetch_add_relaxed(sample.logical_command_count);
566 self.per_group_raft_write_many_logical_commands[group_index]
567 .fetch_add_relaxed(sample.logical_command_count);
568 self.per_core_raft_write_many_responses[core_index]
569 .fetch_add_relaxed(sample.response_count);
570 self.per_group_raft_write_many_responses[group_index]
571 .fetch_add_relaxed(sample.response_count);
572 self.per_core_raft_write_many_submit_ns[core_index].fetch_add_relaxed(sample.submit_ns);
573 self.per_group_raft_write_many_submit_ns[group_index].fetch_add_relaxed(sample.submit_ns);
574 self.per_core_raft_write_many_response_ns[core_index].fetch_add_relaxed(sample.response_ns);
575 self.per_group_raft_write_many_response_ns[group_index]
576 .fetch_add_relaxed(sample.response_ns);
577 }
578
579 pub(crate) fn record_raft_apply_batch(
580 &self,
581 core_id: CoreId,
582 group_id: RaftGroupId,
583 entry_count: u64,
584 apply_ns: u64,
585 ) {
586 let core_index = usize::from(core_id.0);
587 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
588 self.per_core_raft_apply_entries[core_index].fetch_add_relaxed(entry_count);
589 self.per_group_raft_apply_entries[group_index].fetch_add_relaxed(entry_count);
590 self.per_core_raft_apply_ns[core_index].fetch_add_relaxed(apply_ns);
591 self.per_group_raft_apply_ns[group_index].fetch_add_relaxed(apply_ns);
592 }
593
594 pub(crate) fn record_raft_snapshot_build(
595 &self,
596 group_id: RaftGroupId,
597 sample: RaftSnapshotBuildSample,
598 ) {
599 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
600 self.per_group_raft_snapshot_builds[group_index].fetch_add_relaxed(1);
601 self.per_group_raft_snapshot_build_ns[group_index].fetch_add_relaxed(sample.build_ns);
602 self.per_group_raft_snapshot_body_bytes[group_index].store_relaxed(sample.body_bytes);
603 self.per_group_raft_snapshot_pointer_bytes[group_index].store_relaxed(sample.pointer_bytes);
604 self.per_group_raft_snapshot_streams[group_index].store_relaxed(sample.streams);
605 if sample.external_upload {
606 self.per_group_raft_snapshot_external_uploads[group_index].fetch_add_relaxed(1);
607 }
608 if sample.inline_fallback {
609 self.per_group_raft_snapshot_inline_fallbacks[group_index].fetch_add_relaxed(1);
610 }
611 }
612
613 pub(crate) fn record_wal_batch(
614 &self,
615 core_id: CoreId,
616 group_id: RaftGroupId,
617 record_count: u64,
618 write_ns: u64,
619 sync_ns: u64,
620 ) {
621 let core_index = usize::from(core_id.0);
622 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
623 self.per_core_wal_batches[core_index].fetch_add_relaxed(1);
624 self.per_group_wal_batches[group_index].fetch_add_relaxed(1);
625 self.per_core_wal_records[core_index].fetch_add_relaxed(record_count);
626 self.per_group_wal_records[group_index].fetch_add_relaxed(record_count);
627 self.per_core_wal_write_ns[core_index].fetch_add_relaxed(write_ns);
628 self.per_group_wal_write_ns[group_index].fetch_add_relaxed(write_ns);
629 self.per_core_wal_sync_ns[core_index].fetch_add_relaxed(sync_ns);
630 self.per_group_wal_sync_ns[group_index].fetch_add_relaxed(sync_ns);
631 }
632
633 pub(crate) fn record_cold_upload(&self, bytes: u64, upload_ns: u64) {
634 self.cold_flush_uploads.fetch_add_relaxed(1);
635 self.cold_flush_upload_bytes.fetch_add_relaxed(bytes);
636 self.cold_flush_upload_ns.fetch_add_relaxed(upload_ns);
637 }
638
639 fn record_raft_snapshot_pressure(&self, groups: u64) {
640 self.raft_snapshot_pressure_passes.fetch_add_relaxed(1);
641 self.raft_snapshot_pressure_groups.fetch_add_relaxed(groups);
642 }
643
644 pub(crate) fn record_cold_pack(&self, bytes: u64, slices: u64) {
645 self.cold_pack_uploads.fetch_add_relaxed(1);
646 self.cold_pack_bytes.fetch_add_relaxed(bytes);
647 self.cold_pack_slices.fetch_add_relaxed(slices);
648 }
649
650 pub(crate) fn record_cold_publish(&self, bytes: u64, publish_ns: u64) {
651 self.cold_flush_publishes.fetch_add_relaxed(1);
652 self.cold_flush_publish_bytes.fetch_add_relaxed(bytes);
653 self.cold_flush_publish_ns.fetch_add_relaxed(publish_ns);
654 }
655
656 pub(crate) fn record_cold_gc_reclaimed(&self, entries: u64) {
657 self.cold_gc_reclaimed.fetch_add_relaxed(entries);
658 }
659
660 pub(crate) fn record_cold_flush_write_error(&self) {
661 self.cold_flush_write_errors.fetch_add_relaxed(1);
662 }
663
664 pub(crate) fn record_cold_gc_error(&self) {
665 self.cold_gc_errors.fetch_add_relaxed(1);
666 }
667
668 pub(crate) fn record_cold_hot_backlog(
669 &self,
670 group_id: RaftGroupId,
671 stream_hot_bytes: u64,
672 group_hot_bytes: u64,
673 ) {
674 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
675 self.per_group_cold_hot_bytes[group_index].store_relaxed(group_hot_bytes);
676 self.per_group_cold_hot_bytes_current_max[group_index].store_relaxed(group_hot_bytes);
677 self.per_group_cold_hot_bytes_max[group_index].fetch_max_relaxed(group_hot_bytes);
678 self.cold_hot_stream_bytes_max
679 .fetch_max_relaxed(stream_hot_bytes);
680 }
681
682 pub(crate) fn record_cold_backpressure(
683 &self,
684 core_id: CoreId,
685 group_id: RaftGroupId,
686 incoming_bytes: u64,
687 _limit: u64,
688 ) {
689 let core_index = usize::from(core_id.0);
690 let group_index = usize::try_from(group_id.0).expect("u32 fits usize");
691 self.per_core_cold_backpressure_events[core_index].fetch_add_relaxed(1);
692 self.per_group_cold_backpressure_events[group_index].fetch_add_relaxed(1);
693 self.cold_backpressure_bytes
694 .fetch_add_relaxed(incoming_bytes);
695 }
696
697 pub(crate) fn record_read_watcher_added(&self, core_id: CoreId) {
698 self.record_read_watchers_added(core_id, 1);
699 }
700
701 pub(crate) fn record_read_watchers_added(&self, core_id: CoreId, count: usize) {
702 self.per_core_live_read_waiters[usize::from(core_id.0)]
703 .fetch_add_relaxed(u64::try_from(count).expect("watcher count fits u64"));
704 }
705
706 pub(crate) fn record_read_watchers_removed(&self, core_id: CoreId, count: usize) {
707 self.per_core_live_read_waiters[usize::from(core_id.0)]
708 .fetch_sub_relaxed(u64::try_from(count).expect("watcher count fits u64"));
709 }
710
711 pub(crate) fn record_live_read_backpressure(&self, core_id: CoreId) {
712 self.per_core_live_read_backpressure_events[usize::from(core_id.0)].fetch_add_relaxed(1);
713 }
714}
715
716pub(crate) fn elapsed_ns(started_at: Instant) -> u64 {
717 u64::try_from(started_at.elapsed().as_nanos()).unwrap_or(u64::MAX)
718}
719
720pub(crate) fn append_batch_payload_bytes(request: &AppendBatchRequest) -> u64 {
721 request
722 .payloads
723 .iter()
724 .map(|payload| u64::try_from(payload.len()).expect("payload len fits u64"))
725 .sum()
726}
727
728pub(crate) fn record_cold_backpressure_error(
729 metrics: &RuntimeMetricsInner,
730 placement: ShardPlacement,
731 incoming_bytes: u64,
732 admission: ColdWriteAdmission,
733 err: &GroupEngineError,
734) {
735 if !err.is_cold_backpressure() {
736 return;
737 }
738 metrics.record_cold_backpressure(
739 placement.core_id,
740 placement.raft_group_id,
741 incoming_bytes,
742 admission.max_hot_bytes_per_group.unwrap_or(0),
743 );
744}
745
746pub(crate) fn is_stale_cold_flush_candidate_error(err: &RuntimeError) -> bool {
747 match err.stream_error_code() {
748 Some(StreamErrorCode::StreamGone | StreamErrorCode::StreamNotFound) => true,
749 Some(StreamErrorCode::InvalidColdFlush) => err
750 .stream_error_context()
751 .iter()
752 .any(|context| matches!(context, StreamErrorContext::StaleColdFlushCandidate)),
753 _ => false,
754 }
755}
756
757pub(crate) async fn record_cold_hot_backlog(
758 group: &mut Box<dyn GroupEngine>,
759 metrics: &RuntimeMetricsInner,
760 stream_id: BucketStreamId,
761 placement: ShardPlacement,
762) {
763 if let Ok(backlog) = group.cold_hot_backlog(stream_id, placement).await {
764 metrics.record_cold_hot_backlog(
765 placement.raft_group_id,
766 backlog.stream_hot_bytes,
767 backlog.group_hot_bytes,
768 );
769 }
770}
771
772#[derive(Debug)]
773#[repr(align(128))]
774pub(crate) struct PaddedAtomicU64 {
775 value: AtomicU64,
776}
777
778impl PaddedAtomicU64 {
779 pub(crate) fn new(value: u64) -> Self {
780 Self {
781 value: AtomicU64::new(value),
782 }
783 }
784
785 pub(crate) fn load_relaxed(&self) -> u64 {
786 self.value.load(Ordering::Relaxed)
787 }
788
789 pub(crate) fn fetch_add_relaxed(&self, value: u64) -> u64 {
790 self.value.fetch_add(value, Ordering::Relaxed)
791 }
792
793 pub(crate) fn fetch_sub_relaxed(&self, value: u64) {
794 self.value.fetch_sub(value, Ordering::Relaxed);
795 }
796
797 pub(crate) fn fetch_sub_saturating_relaxed(&self, value: u64) {
798 let mut current = self.value.load(Ordering::Relaxed);
799 loop {
800 let next = current.saturating_sub(value);
801 match self.value.compare_exchange_weak(
802 current,
803 next,
804 Ordering::Relaxed,
805 Ordering::Relaxed,
806 ) {
807 Ok(_) => return,
808 Err(observed) => current = observed,
809 }
810 }
811 }
812
813 pub(crate) fn fetch_max_relaxed(&self, value: u64) {
814 self.value.fetch_max(value, Ordering::Relaxed);
815 }
816
817 pub(crate) fn store_relaxed(&self, value: u64) {
818 self.value.store(value, Ordering::Relaxed);
819 }
820}
821
822#[cfg(test)]
823mod metric_manifest_tests {
824 use std::sync::Arc;
825
826 use crate::metrics::RuntimeMetrics;
827 use crate::metrics::RuntimeMetricsInner;
828
829 const EXPECTED_SNAPSHOT_KEYS: [&str; 126] = [
833 "accepted_appends",
834 "per_core_appends",
835 "per_group_appends",
836 "applied_mutations",
837 "per_core_applied_mutations",
838 "per_group_applied_mutations",
839 "mutation_apply_ns",
840 "per_core_mutation_apply_ns",
841 "per_group_mutation_apply_ns",
842 "append_post_commit_ns",
843 "per_core_append_post_commit_ns",
844 "per_group_append_post_commit_ns",
845 "read_watcher_notify_calls",
846 "per_core_read_watcher_notify_calls",
847 "per_group_read_watcher_notify_calls",
848 "read_watcher_notify_ns",
849 "per_core_read_watcher_notify_ns",
850 "per_group_read_watcher_notify_ns",
851 "read_watcher_replans",
852 "per_core_read_watcher_replans",
853 "per_group_read_watcher_replans",
854 "group_lock_wait_ns",
855 "per_core_group_lock_wait_ns",
856 "per_group_group_lock_wait_ns",
857 "group_engine_exec_ns",
858 "per_core_group_engine_exec_ns",
859 "per_group_group_engine_exec_ns",
860 "group_mailbox_depth",
861 "per_group_group_mailbox_depth",
862 "group_mailbox_max_depth",
863 "per_group_group_mailbox_max_depth",
864 "group_mailbox_full_events",
865 "per_group_group_mailbox_full_events",
866 "raft_write_many_batches",
867 "per_core_raft_write_many_batches",
868 "per_group_raft_write_many_batches",
869 "raft_write_many_commands",
870 "per_core_raft_write_many_commands",
871 "per_group_raft_write_many_commands",
872 "raft_write_many_logical_commands",
873 "per_core_raft_write_many_logical_commands",
874 "per_group_raft_write_many_logical_commands",
875 "raft_write_many_responses",
876 "per_core_raft_write_many_responses",
877 "per_group_raft_write_many_responses",
878 "raft_write_many_submit_ns",
879 "per_core_raft_write_many_submit_ns",
880 "per_group_raft_write_many_submit_ns",
881 "raft_write_many_response_ns",
882 "per_core_raft_write_many_response_ns",
883 "per_group_raft_write_many_response_ns",
884 "raft_apply_entries",
885 "per_core_raft_apply_entries",
886 "per_group_raft_apply_entries",
887 "raft_apply_ns",
888 "per_core_raft_apply_ns",
889 "per_group_raft_apply_ns",
890 "raft_snapshot_builds",
891 "per_group_raft_snapshot_builds",
892 "raft_snapshot_build_ns",
893 "per_group_raft_snapshot_build_ns",
894 "raft_snapshot_body_bytes",
895 "raft_snapshot_body_bytes_max",
896 "per_group_raft_snapshot_body_bytes",
897 "raft_snapshot_pointer_bytes",
898 "raft_snapshot_pointer_bytes_max",
899 "per_group_raft_snapshot_pointer_bytes",
900 "raft_snapshot_streams",
901 "raft_snapshot_streams_max",
902 "per_group_raft_snapshot_streams",
903 "raft_snapshot_external_uploads",
904 "per_group_raft_snapshot_external_uploads",
905 "raft_snapshot_inline_fallbacks",
906 "per_group_raft_snapshot_inline_fallbacks",
907 "live_read_waiters",
908 "per_core_live_read_waiters",
909 "live_read_backpressure_events",
910 "per_core_live_read_backpressure_events",
911 "routed_requests",
912 "per_core_routed_requests",
913 "mailbox_send_wait_ns",
914 "per_core_mailbox_send_wait_ns",
915 "mailbox_full_events",
916 "per_core_mailbox_full_events",
917 "wal_batches",
918 "per_core_wal_batches",
919 "per_group_wal_batches",
920 "wal_records",
921 "per_core_wal_records",
922 "per_group_wal_records",
923 "wal_write_ns",
924 "per_core_wal_write_ns",
925 "per_group_wal_write_ns",
926 "wal_sync_ns",
927 "per_core_wal_sync_ns",
928 "per_group_wal_sync_ns",
929 "cold_flush_uploads",
930 "cold_flush_upload_bytes",
931 "cold_flush_upload_ns",
932 "cold_pack_uploads",
933 "cold_pack_bytes",
934 "cold_pack_slices",
935 "cold_flush_publishes",
936 "cold_flush_publish_bytes",
937 "cold_flush_publish_ns",
938 "cold_orphan_cleanup_attempts",
939 "cold_orphan_cleanup_errors",
940 "cold_orphan_bytes",
941 "cold_gc_reclaimed",
942 "cold_gc_errors",
943 "cold_flush_write_errors",
944 "cold_pressure_flush_passes",
945 "cold_pressure_flush_candidates",
946 "raft_snapshot_pressure_passes",
947 "raft_snapshot_pressure_groups",
948 "cold_hot_bytes",
949 "per_group_cold_hot_bytes",
950 "cold_hot_group_bytes_max",
951 "per_group_cold_hot_bytes_current_max",
952 "cold_hot_group_bytes_high_watermark",
953 "per_group_cold_hot_bytes_max",
954 "cold_hot_stream_bytes_max",
955 "cold_backpressure_events",
956 "per_core_cold_backpressure_events",
957 "per_group_cold_backpressure_events",
958 "cold_backpressure_bytes",
959 ];
960
961 fn metrics_for_test() -> RuntimeMetrics {
962 RuntimeMetrics {
963 inner: Arc::new(RuntimeMetricsInner::new(2, 3)),
964 }
965 }
966
967 #[test]
968 fn snapshot_serializes_expected_field_names_in_order() {
969 let json =
970 serde_json::to_string(&metrics_for_test().snapshot()).expect("snapshot serializes");
971 assert_eq!(
974 json.matches("\":").count(),
975 EXPECTED_SNAPSHOT_KEYS.len(),
976 "unexpected number of serialized fields: {json}"
977 );
978 let mut last_position = None;
979 for name in EXPECTED_SNAPSHOT_KEYS {
980 let needle = format!("\"{name}\":");
981 let position = json
982 .find(&needle)
983 .unwrap_or_else(|| panic!("missing serialized key {name}"));
984 assert!(
985 last_position < Some(position),
986 "serialized key {name} out of declaration order"
987 );
988 last_position = Some(position);
989 }
990 }
991
992 #[test]
993 fn snapshot_vector_lengths_follow_metric_scope() {
994 let snapshot = metrics_for_test().snapshot();
995 assert_eq!(snapshot.per_core_appends.len(), 2);
996 assert_eq!(snapshot.per_group_appends.len(), 3);
997 assert_eq!(snapshot.per_core_routed_requests.len(), 2);
998 assert_eq!(snapshot.per_group_raft_snapshot_streams.len(), 3);
999 }
1000
1001 #[test]
1002 fn snapshot_aggregates_sum_and_max_per_manifest() {
1003 let metrics = metrics_for_test();
1004 metrics.inner.per_core_appends[0].fetch_add_relaxed(3);
1005 metrics.inner.per_core_appends[1].fetch_add_relaxed(4);
1006 metrics.inner.per_group_group_mailbox_max_depth[1].fetch_max_relaxed(9);
1007 metrics.inner.per_group_raft_snapshot_body_bytes[0].store_relaxed(5);
1008 metrics.inner.per_group_raft_snapshot_body_bytes[2].store_relaxed(11);
1009 let snapshot = metrics.snapshot();
1010 assert_eq!(snapshot.accepted_appends, 7);
1011 assert_eq!(snapshot.group_mailbox_max_depth, 9);
1012 assert_eq!(snapshot.raft_snapshot_body_bytes, 16);
1013 assert_eq!(snapshot.raft_snapshot_body_bytes_max, 11);
1014 }
1015}