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