use super::payload::{Metric, PayloadMetrics};
pub enum ThreadLookup {
Found {
allocated_bytes: u64,
deallocated_bytes: Option<u64>,
},
MissingAllocatedBytes,
TidAbsent,
ExceedsCap,
}
pub const MAX_SCAN_INDEX: usize = 1024;
pub fn find_metric<'a>(metrics: &'a PayloadMetrics, key: &str) -> Option<&'a Metric> {
metrics.metrics.iter().find(|m| m.name == key)
}
pub fn has_metric(metrics: &PayloadMetrics, key: &str) -> bool {
find_metric(metrics, key).is_some()
}
pub fn find_metric_u64(metrics: &PayloadMetrics, key: &str) -> Option<u64> {
find_metric(metrics, key).map(|m| {
debug_assert!(
m.value.is_finite() && m.value >= 0.0 && m.value <= (1u64 << 53) as f64,
"metric {:?} value {} outside the f64→u64 lossless range \
[0, 2^53]; the `as u64` cast will truncate silently. \
Either range-check externally-sourced input before \
landing it in the flat metrics list, or consume the \
metric via `.value` (f64) instead of this u64 helper.",
m.name,
m.value,
);
m.value as u64
})
}
pub fn count_indexed_metrics<F>(metrics: &PayloadMetrics, cap: usize, key_fn: F) -> usize
where
F: Fn(usize) -> String,
{
let mut n = 0;
for i in 0..cap {
if find_metric(metrics, &key_fn(i)).is_some() {
n += 1;
} else {
break;
}
}
n
}
pub fn lookup_thread(metrics: &PayloadMetrics, worker_tid: i32) -> ThreadLookup {
let worker_tid_f64 = worker_tid as f64;
for i in 0..MAX_SCAN_INDEX {
let tid_key = format!("snapshots.0.threads.{i}.tid");
let tid_m = match find_metric(metrics, &tid_key) {
Some(m) => m,
None => return ThreadLookup::TidAbsent,
};
if tid_m.value == worker_tid_f64 {
let alloc_key = format!("snapshots.0.threads.{i}.allocated_bytes");
let dealloc_key = format!("snapshots.0.threads.{i}.deallocated_bytes");
let allocated_bytes = match find_metric(metrics, &alloc_key).map(|m| m.value as u64) {
Some(v) => v,
None => return ThreadLookup::MissingAllocatedBytes,
};
let deallocated_bytes = find_metric(metrics, &dealloc_key).map(|m| m.value as u64);
return ThreadLookup::Found {
allocated_bytes,
deallocated_bytes,
};
}
}
ThreadLookup::ExceedsCap
}
pub fn snapshot_worker_allocated(
metrics: &PayloadMetrics,
snap_idx: usize,
worker_tid: i32,
) -> ThreadLookup {
let worker_tid_f64 = worker_tid as f64;
for j in 0..MAX_SCAN_INDEX {
let tid_key = format!("snapshots.{snap_idx}.threads.{j}.tid");
let tid_m = match find_metric(metrics, &tid_key) {
Some(m) => m,
None => return ThreadLookup::TidAbsent,
};
if tid_m.value == worker_tid_f64 {
let alloc_key = format!("snapshots.{snap_idx}.threads.{j}.allocated_bytes");
let dealloc_key = format!("snapshots.{snap_idx}.threads.{j}.deallocated_bytes");
let allocated_bytes = match find_metric(metrics, &alloc_key).map(|m| m.value as u64) {
Some(v) => v,
None => return ThreadLookup::MissingAllocatedBytes,
};
let deallocated_bytes = find_metric(metrics, &dealloc_key).map(|m| m.value as u64);
return ThreadLookup::Found {
allocated_bytes,
deallocated_bytes,
};
}
}
ThreadLookup::ExceedsCap
}
pub fn thread_count(metrics: &PayloadMetrics) -> usize {
count_indexed_metrics(metrics, MAX_SCAN_INDEX, |i| {
format!("snapshots.0.threads.{i}.tid")
})
}
pub fn snapshot_count(metrics: &PayloadMetrics) -> usize {
count_indexed_metrics(metrics, MAX_SCAN_INDEX, |i| {
format!("snapshots.{i}.timestamp_unix_sec")
})
}
pub fn flat_metrics_dump(metrics: &PayloadMetrics) -> Vec<(&str, f64)> {
metrics
.metrics
.iter()
.map(|m| (m.name.as_str(), m.value))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn metric(name: &str, value: f64) -> Metric {
use super::super::payload::{MetricSource, MetricStream, Polarity};
Metric {
name: name.to_owned(),
value,
polarity: Polarity::Unknown,
unit: String::new(),
source: MetricSource::Json,
stream: MetricStream::Stdout,
}
}
fn empty_payload() -> PayloadMetrics {
PayloadMetrics {
payload_index: 0,
metrics: Vec::new(),
exit_code: 0,
}
}
fn push_tid(metrics: &mut PayloadMetrics, idx: usize, tid: f64) {
metrics
.metrics
.push(metric(&format!("snapshots.0.threads.{idx}.tid"), tid));
}
fn push_alloc(metrics: &mut PayloadMetrics, idx: usize, alloc: f64) {
metrics.metrics.push(metric(
&format!("snapshots.0.threads.{idx}.allocated_bytes"),
alloc,
));
}
#[test]
fn lookup_thread_empty_metrics_returns_tid_absent() {
let m = empty_payload();
assert!(matches!(lookup_thread(&m, 42), ThreadLookup::TidAbsent));
}
#[test]
fn lookup_thread_matching_tid_returns_found() {
let mut m = empty_payload();
push_tid(&mut m, 0, 42.0);
push_alloc(&mut m, 0, 1_048_576.0);
match lookup_thread(&m, 42) {
ThreadLookup::Found {
allocated_bytes,
deallocated_bytes,
} => {
assert_eq!(allocated_bytes, 1_048_576);
assert_eq!(deallocated_bytes, None);
}
_ => panic!("expected ThreadLookup::Found"),
}
}
#[test]
fn lookup_thread_missing_allocated_bytes_returns_missing_variant() {
let mut m = empty_payload();
push_tid(&mut m, 0, 42.0);
assert!(matches!(
lookup_thread(&m, 42),
ThreadLookup::MissingAllocatedBytes
));
}
#[test]
fn lookup_thread_contiguous_prefix_without_match_returns_tid_absent() {
let mut m = empty_payload();
for i in 0..10 {
push_tid(&mut m, i, (1000 + i) as f64);
}
assert!(matches!(lookup_thread(&m, 42), ThreadLookup::TidAbsent));
}
#[test]
fn lookup_thread_saturated_scan_without_match_returns_exceeds_cap() {
let mut m = empty_payload();
for i in 0..MAX_SCAN_INDEX {
push_tid(&mut m, i, (1_000_000 + i) as f64);
}
let target_tid: i32 = 42;
let outcome = lookup_thread(&m, target_tid);
assert!(
matches!(outcome, ThreadLookup::ExceedsCap),
"saturated scan without match must return ExceedsCap; got other variant"
);
}
#[test]
fn snapshot_worker_allocated_saturated_scan_returns_exceeds_cap() {
let mut m = empty_payload();
for i in 0..MAX_SCAN_INDEX {
push_tid(&mut m, i, (1_000_000 + i) as f64);
}
let outcome = snapshot_worker_allocated(&m, 0, 42);
assert!(
matches!(outcome, ThreadLookup::ExceedsCap),
"saturated multi-snapshot scan without match must return ExceedsCap"
);
}
#[test]
fn snapshot_worker_allocated_empty_returns_tid_absent() {
let m = empty_payload();
assert!(matches!(
snapshot_worker_allocated(&m, 0, 42),
ThreadLookup::TidAbsent
));
}
}