use serde::{Deserialize, Serialize};
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct MonotonicCount(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct MonotonicNs(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct ClockTicks(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct Bytes(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct DeadCounter(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct PeakNs(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct PeakBytes(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct GaugeNs(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct GaugeCount(pub u64);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct OrdinalI32(pub i32);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct OrdinalU32(pub u32);
#[repr(transparent)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct OrdinalU64(pub u64);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct CategoricalString(pub String);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct CpuSet(pub Vec<u32>);
macro_rules! impl_u64_newtype_from {
($t:ident) => {
impl From<u64> for $t {
fn from(v: u64) -> Self {
Self(v)
}
}
impl From<$t> for u64 {
fn from(v: $t) -> Self {
v.0
}
}
};
}
impl_u64_newtype_from!(MonotonicCount);
impl_u64_newtype_from!(MonotonicNs);
impl_u64_newtype_from!(ClockTicks);
impl_u64_newtype_from!(Bytes);
impl_u64_newtype_from!(DeadCounter);
impl_u64_newtype_from!(PeakNs);
impl_u64_newtype_from!(PeakBytes);
impl_u64_newtype_from!(GaugeNs);
impl_u64_newtype_from!(GaugeCount);
impl From<i32> for OrdinalI32 {
fn from(v: i32) -> Self {
Self(v)
}
}
impl From<OrdinalI32> for i32 {
fn from(v: OrdinalI32) -> Self {
v.0
}
}
impl From<u32> for OrdinalU32 {
fn from(v: u32) -> Self {
Self(v)
}
}
impl From<OrdinalU32> for u32 {
fn from(v: OrdinalU32) -> Self {
v.0
}
}
impl From<u64> for OrdinalU64 {
fn from(v: u64) -> Self {
Self(v)
}
}
impl From<OrdinalU64> for u64 {
fn from(v: OrdinalU64) -> Self {
v.0
}
}
impl From<String> for CategoricalString {
fn from(v: String) -> Self {
Self(v)
}
}
impl From<CategoricalString> for String {
fn from(v: CategoricalString) -> Self {
v.0
}
}
impl From<&str> for CategoricalString {
fn from(v: &str) -> Self {
Self(v.to_string())
}
}
impl From<Vec<u32>> for CpuSet {
fn from(v: Vec<u32>) -> Self {
Self(v)
}
}
impl From<CpuSet> for Vec<u32> {
fn from(v: CpuSet) -> Self {
v.0
}
}
macro_rules! impl_display_passthrough {
($t:ident) => {
impl std::fmt::Display for $t {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
};
}
impl_display_passthrough!(MonotonicCount);
impl_display_passthrough!(MonotonicNs);
impl_display_passthrough!(ClockTicks);
impl_display_passthrough!(Bytes);
impl_display_passthrough!(DeadCounter);
impl_display_passthrough!(PeakNs);
impl_display_passthrough!(PeakBytes);
impl_display_passthrough!(GaugeNs);
impl_display_passthrough!(GaugeCount);
impl_display_passthrough!(OrdinalI32);
impl_display_passthrough!(OrdinalU32);
impl_display_passthrough!(OrdinalU64);
impl_display_passthrough!(CategoricalString);
mod sealed {
pub trait SummableSealed {}
pub trait MaxableSealed {}
pub trait ModeableSealed {}
pub trait RangeableSealed {}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not Summable — summing it would conflate semantic categories \
or temporal windows",
label = "this metric type cannot be summed across a group",
note = "PeakNs (lifetime high-water): use Maxable::max_across; \
GaugeNs/GaugeCount (instantaneous samples — different temporal window than the \
lifetime accumulators): use Maxable::max_across; \
OrdinalI32/OrdinalU32/OrdinalU64 (bounded scalars): use Rangeable::range_across; \
CategoricalString: use Modeable::mode_across; CpuSet: use the \
AffinitySummary reduction in ctprof_compare; \
DeadCounter: kernel-side dead pointer — value is structurally zero; the \
registry must use a no-op aggregation arm (or omit the field) rather than \
sum across structural zeros"
)]
pub trait Summable: sealed::SummableSealed + Sized + Copy {
fn sum_across(items: impl IntoIterator<Item = Self>) -> Self;
fn try_sum_across(items: impl IntoIterator<Item = Self>) -> Option<Self> {
let mut it = items.into_iter();
let first = it.next()?;
Some(Self::sum_across(std::iter::once(first).chain(it)))
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not Maxable — `max` is undefined for this category",
label = "this metric type does not support max-across",
note = "MonotonicCount/MonotonicNs/ClockTicks/Bytes (Summable cumulative counters): \
use Summable::sum_across; max-across-snapshots on a lifetime accumulator \
reduces to the most-recent reading, not a worst window; \
OrdinalI32/OrdinalU32/OrdinalU64: use Rangeable::range_across; \
CategoricalString: use Modeable::mode_across; CpuSet: use the \
AffinitySummary reduction in ctprof_compare; \
DeadCounter: kernel-side dead pointer — value is structurally zero; the \
registry must use a no-op aggregation arm (or omit the field) rather than \
max across structural zeros"
)]
pub trait Maxable: sealed::MaxableSealed + Sized + Copy + Ord {
fn max_across(items: impl IntoIterator<Item = Self>) -> Option<Self>;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not Modeable — Modeable is reserved for CategoricalString in this codebase",
label = "this metric type does not support mode-across",
note = "CategoricalString is the only Modeable type today. Numeric types use Summable / \
Maxable / Rangeable depending on category. If a new categorical newtype \
needs mode-aggregation, add the impl in metric_types.rs."
)]
pub trait Modeable: sealed::ModeableSealed + Sized + Clone + Eq + Ord {
fn mode_across(items: impl IntoIterator<Item = Self>) -> Option<(Self, usize, usize)> {
use std::collections::BTreeMap;
let mut counts: BTreeMap<Self, usize> = BTreeMap::new();
let mut total = 0usize;
for item in items {
*counts.entry(item).or_default() += 1;
total += 1;
}
if total == 0 {
return None;
}
let (value, count) = counts
.into_iter()
.max_by(|a, b| a.1.cmp(&b.1).then(b.0.cmp(&a.0)))
.expect("non-empty inputs produce a non-empty count map");
Some((value, count, total))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Range<T: PartialOrd> {
min: T,
max: T,
}
impl<T: PartialOrd> Range<T> {
pub fn new(min: T, max: T) -> Self {
debug_assert!(
min.partial_cmp(&max) != Some(std::cmp::Ordering::Greater),
"Range::new requires min <= max — got a min that compares strictly greater"
);
Self { min, max }
}
pub fn min(&self) -> &T {
&self.min
}
pub fn max(&self) -> &T {
&self.max
}
pub fn into_tuple(self) -> (T, T) {
(self.min, self.max)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not Rangeable — Rangeable is reserved for bounded ordinals in this codebase",
label = "this metric type does not support range-across",
note = "Counters/peaks/gauges: use Summable::sum_across or Maxable::max_across; \
CategoricalString: use Modeable::mode_across; CpuSet: use the AffinitySummary \
reduction in ctprof_compare"
)]
pub trait Rangeable: sealed::RangeableSealed + Sized + Copy + Ord {
fn range_across(items: impl IntoIterator<Item = Self>) -> Option<Range<Self>> {
let mut it = items.into_iter();
let first = it.next()?;
let mut min = first;
let mut max = first;
for v in it {
if v < min {
min = v;
}
if v > max {
max = v;
}
}
Some(Range::new(min, max))
}
}
macro_rules! impl_summable_only_u64 {
($t:ident) => {
impl sealed::SummableSealed for $t {}
impl Summable for $t {
fn sum_across(items: impl IntoIterator<Item = Self>) -> Self {
let mut total: u64 = 0;
for v in items {
total = total.saturating_add(v.0);
}
Self(total)
}
}
};
}
impl_summable_only_u64!(MonotonicCount);
impl_summable_only_u64!(MonotonicNs);
impl_summable_only_u64!(ClockTicks);
impl_summable_only_u64!(Bytes);
macro_rules! impl_maxable_only_u64 {
($t:ident) => {
impl sealed::MaxableSealed for $t {}
impl Maxable for $t {
fn max_across(items: impl IntoIterator<Item = Self>) -> Option<Self> {
let mut it = items.into_iter();
let first = it.next()?;
let mut out = first;
for v in it {
if v > out {
out = v;
}
}
Some(out)
}
}
};
}
impl_maxable_only_u64!(PeakNs);
impl_maxable_only_u64!(PeakBytes);
impl_maxable_only_u64!(GaugeNs);
impl_maxable_only_u64!(GaugeCount);
impl sealed::RangeableSealed for OrdinalI32 {}
impl sealed::RangeableSealed for OrdinalU32 {}
impl sealed::RangeableSealed for OrdinalU64 {}
impl Rangeable for OrdinalI32 {}
impl Rangeable for OrdinalU32 {}
impl Rangeable for OrdinalU64 {}
impl sealed::ModeableSealed for CategoricalString {}
impl Modeable for CategoricalString {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn monotonic_count_from_u64_roundtrips() {
let v: MonotonicCount = 42u64.into();
assert_eq!(v.0, 42);
let back: u64 = v.into();
assert_eq!(back, 42);
}
#[test]
fn monotonic_ns_from_u64_roundtrips() {
let v: MonotonicNs = 1_000_000u64.into();
assert_eq!(v.0, 1_000_000);
let back: u64 = v.into();
assert_eq!(back, 1_000_000);
}
#[test]
fn clock_ticks_from_u64_roundtrips() {
let v: ClockTicks = 1234u64.into();
assert_eq!(v.0, 1234);
let back: u64 = v.into();
assert_eq!(back, 1234);
}
#[test]
fn bytes_from_u64_roundtrips() {
let v: Bytes = (1024 * 1024).into();
assert_eq!(v.0, 1024 * 1024);
let back: u64 = v.into();
assert_eq!(back, 1024 * 1024);
}
#[test]
fn peak_ns_from_u64_roundtrips() {
let v: PeakNs = 999u64.into();
assert_eq!(v.0, 999);
let back: u64 = v.into();
assert_eq!(back, 999);
}
#[test]
fn gauge_ns_from_u64_roundtrips() {
let v: GaugeNs = 7u64.into();
assert_eq!(v.0, 7);
let back: u64 = v.into();
assert_eq!(back, 7);
}
#[test]
fn ordinal_i32_from_i32_roundtrips() {
let v: OrdinalI32 = (-20).into();
assert_eq!(v.0, -20);
let back: i32 = v.into();
assert_eq!(back, -20);
}
#[test]
fn gauge_count_from_u64_roundtrips() {
let v: GaugeCount = 16u64.into();
assert_eq!(v.0, 16);
let back: u64 = v.into();
assert_eq!(back, 16);
}
#[test]
fn ordinal_u32_from_u32_roundtrips() {
let v: OrdinalU32 = 99u32.into();
assert_eq!(v.0, 99);
let back: u32 = v.into();
assert_eq!(back, 99);
}
#[test]
fn ordinal_u64_from_u64_roundtrips() {
let v: OrdinalU64 = 99u64.into();
assert_eq!(v.0, 99);
let back: u64 = v.into();
assert_eq!(back, 99);
}
#[test]
fn categorical_string_from_string_roundtrips() {
let v: CategoricalString = "SCHED_FIFO".to_string().into();
assert_eq!(v.0, "SCHED_FIFO");
let back: String = v.into();
assert_eq!(back, "SCHED_FIFO");
}
#[test]
fn categorical_string_from_str_works() {
let v: CategoricalString = "SCHED_OTHER".into();
assert_eq!(v.0, "SCHED_OTHER");
}
#[test]
fn cpuset_from_vec_roundtrips() {
let cpus = vec![0u32, 1, 2, 3];
let v: CpuSet = cpus.clone().into();
assert_eq!(v.0, cpus);
let back: Vec<u32> = v.into();
assert_eq!(back, cpus);
}
#[test]
fn summable_monotonic_count_sums_to_total() {
let xs = [MonotonicCount(10), MonotonicCount(20), MonotonicCount(30)];
let s = MonotonicCount::sum_across(xs);
assert_eq!(s, MonotonicCount(60));
}
#[test]
fn summable_monotonic_ns_saturates_on_overflow() {
let xs = [MonotonicNs(u64::MAX), MonotonicNs(5)];
let s = MonotonicNs::sum_across(xs);
assert_eq!(s, MonotonicNs(u64::MAX));
}
#[test]
fn summable_clock_ticks_sums() {
let xs = [ClockTicks(100), ClockTicks(50)];
let s = ClockTicks::sum_across(xs);
assert_eq!(s, ClockTicks(150));
}
#[test]
fn summable_bytes_sums() {
let xs = [Bytes(1024), Bytes(2048), Bytes(4096)];
let s = Bytes::sum_across(xs);
assert_eq!(s, Bytes(7168));
}
#[test]
fn summable_empty_iterator_returns_zero() {
let s = MonotonicCount::sum_across(std::iter::empty());
assert_eq!(s, MonotonicCount(0));
}
#[test]
fn summable_only_implemented_for_counters() {
fn assert_summable<T: Summable>() {}
assert_summable::<MonotonicCount>();
assert_summable::<MonotonicNs>();
assert_summable::<ClockTicks>();
assert_summable::<Bytes>();
}
#[test]
fn try_sum_across_empty_returns_none() {
let s = MonotonicCount::try_sum_across(std::iter::empty());
assert!(s.is_none());
}
#[test]
fn try_sum_across_non_empty_matches_sum_across() {
let xs = [MonotonicCount(10), MonotonicCount(20), MonotonicCount(30)];
let unchecked = MonotonicCount::sum_across(xs);
let tried = MonotonicCount::try_sum_across(xs).expect("non-empty");
assert_eq!(unchecked, tried);
assert_eq!(tried, MonotonicCount(60));
}
#[test]
fn try_sum_across_saturates_on_overflow() {
let xs = [MonotonicNs(u64::MAX), MonotonicNs(5)];
let s = MonotonicNs::try_sum_across(xs).expect("non-empty");
assert_eq!(s, MonotonicNs(u64::MAX));
}
#[test]
fn try_sum_across_singleton_returns_that_value() {
let s = MonotonicCount::try_sum_across([MonotonicCount(42)]).expect("non-empty");
assert_eq!(s, MonotonicCount(42));
}
#[test]
fn try_sum_across_available_on_every_summable() {
fn assert_try_sum<T: Summable>() {
let _ = T::try_sum_across(std::iter::empty());
}
assert_try_sum::<MonotonicCount>();
assert_try_sum::<MonotonicNs>();
assert_try_sum::<ClockTicks>();
assert_try_sum::<Bytes>();
}
#[test]
fn maxable_peak_ns_picks_largest() {
let xs = [PeakNs(100), PeakNs(500), PeakNs(200)];
let m = PeakNs::max_across(xs).expect("non-empty");
assert_eq!(m, PeakNs(500));
}
#[test]
fn maxable_gauge_ns_picks_largest() {
let xs = [GaugeNs(7), GaugeNs(99), GaugeNs(50)];
let m = GaugeNs::max_across(xs).expect("non-empty");
assert_eq!(m, GaugeNs(99));
}
#[test]
fn maxable_gauge_count_picks_largest() {
let xs = [GaugeCount(3), GaugeCount(11), GaugeCount(7)];
let m = GaugeCount::max_across(xs).expect("non-empty");
assert_eq!(m, GaugeCount(11));
}
#[test]
fn maxable_empty_iterator_returns_none() {
let m = PeakNs::max_across(std::iter::empty());
assert!(m.is_none());
}
#[test]
fn maxable_singleton_returns_that_value() {
let m = PeakNs::max_across([PeakNs(42)]).expect("non-empty");
assert_eq!(m, PeakNs(42));
}
#[test]
fn maxable_singleton_zero_returns_some_zero() {
let m = PeakNs::max_across([PeakNs(0)]).expect("non-empty");
assert_eq!(m, PeakNs(0));
}
#[test]
fn maxable_implemented_for_peaks_and_gauges() {
fn assert_maxable<T: Maxable>() {}
assert_maxable::<PeakNs>();
assert_maxable::<PeakBytes>();
assert_maxable::<GaugeNs>();
assert_maxable::<GaugeCount>();
}
#[test]
fn rangeable_ordinal_i32_finds_min_max() {
let xs = [
OrdinalI32(-5),
OrdinalI32(10),
OrdinalI32(0),
OrdinalI32(-20),
];
let r = OrdinalI32::range_across(xs).expect("non-empty");
assert_eq!(*r.min(), OrdinalI32(-20));
assert_eq!(*r.max(), OrdinalI32(10));
}
#[test]
fn rangeable_ordinal_u32_finds_min_max() {
let xs = [OrdinalU32(7), OrdinalU32(3), OrdinalU32(15)];
let r = OrdinalU32::range_across(xs).expect("non-empty");
assert_eq!(*r.min(), OrdinalU32(3));
assert_eq!(*r.max(), OrdinalU32(15));
}
#[test]
fn rangeable_ordinal_u64_finds_min_max() {
let xs = [
OrdinalU64(50),
OrdinalU64(99),
OrdinalU64(0),
OrdinalU64(25),
];
let r = OrdinalU64::range_across(xs).expect("non-empty");
assert_eq!(*r.min(), OrdinalU64(0));
assert_eq!(*r.max(), OrdinalU64(99));
}
#[test]
fn rangeable_singleton_min_eq_max() {
let r = OrdinalI32::range_across([OrdinalI32(42)]).expect("non-empty");
assert_eq!(*r.min(), OrdinalI32(42));
assert_eq!(*r.max(), OrdinalI32(42));
}
#[test]
fn rangeable_empty_iterator_returns_none() {
let r = OrdinalI32::range_across(std::iter::empty());
assert!(r.is_none());
}
#[test]
#[should_panic(expected = "min <= max")]
fn range_new_debug_asserts_min_le_max_when_swapped() {
let _ = Range::new(OrdinalI32(10), OrdinalI32(5));
}
#[test]
fn range_new_min_eq_max_is_allowed() {
let r = Range::new(OrdinalI32(42), OrdinalI32(42));
assert_eq!(*r.min(), OrdinalI32(42));
assert_eq!(*r.max(), OrdinalI32(42));
}
#[test]
fn range_into_tuple_preserves_pair() {
let r = Range::new(OrdinalU32(3), OrdinalU32(15));
let (min, max) = r.into_tuple();
assert_eq!(min, OrdinalU32(3));
assert_eq!(max, OrdinalU32(15));
}
#[test]
fn range_across_preserves_min_le_max_on_reversed_input() {
let xs = [
OrdinalI32(99),
OrdinalI32(10),
OrdinalI32(0),
OrdinalI32(-20),
];
let r = OrdinalI32::range_across(xs).expect("non-empty");
assert!(r.min() <= r.max());
assert_eq!(*r.min(), OrdinalI32(-20));
assert_eq!(*r.max(), OrdinalI32(99));
}
#[test]
fn dead_counter_from_u64_roundtrips() {
let v: DeadCounter = 0u64.into();
assert_eq!(v.0, 0);
let back: u64 = v.into();
assert_eq!(back, 0);
}
#[test]
fn dead_counter_serde_transparent() {
let v = DeadCounter(0);
let json = serde_json::to_string(&v).expect("serialize");
assert_eq!(json, "0");
let back: DeadCounter = serde_json::from_str(&json).expect("deserialize");
assert_eq!(v, back);
let nonzero = DeadCounter(42);
let nonzero_json = serde_json::to_string(&nonzero).expect("serialize");
assert_eq!(nonzero_json, "42");
let nonzero_back: DeadCounter = serde_json::from_str(&nonzero_json).expect("deserialize");
assert_eq!(nonzero, nonzero_back);
}
#[test]
fn dead_counter_default_is_zero() {
assert_eq!(DeadCounter::default(), DeadCounter(0));
}
#[test]
fn dead_counter_repr_transparent_size() {
use std::mem::size_of;
assert_eq!(size_of::<DeadCounter>(), size_of::<u64>());
}
#[test]
fn dead_counter_display_passthrough() {
assert_eq!(format!("{}", DeadCounter(0)), "0");
assert_eq!(format!("{}", DeadCounter(42)), "42");
}
#[test]
fn modeable_categorical_string_picks_most_frequent() {
let xs = [
CategoricalString::from("SCHED_OTHER"),
CategoricalString::from("SCHED_OTHER"),
CategoricalString::from("SCHED_FIFO"),
];
let (value, count, total) = CategoricalString::mode_across(xs).expect("non-empty");
assert_eq!(value, CategoricalString::from("SCHED_OTHER"));
assert_eq!(count, 2);
assert_eq!(total, 3);
}
#[test]
fn modeable_tie_break_is_lex_smallest() {
let xs = [
CategoricalString::from("SCHED_OTHER"),
CategoricalString::from("SCHED_FIFO"),
];
let (value, count, total) = CategoricalString::mode_across(xs).expect("non-empty");
assert_eq!(value, CategoricalString::from("SCHED_FIFO"));
assert_eq!(count, 1);
assert_eq!(total, 2);
}
#[test]
fn modeable_empty_iterator_returns_none() {
let r = CategoricalString::mode_across(std::iter::empty());
assert!(r.is_none());
}
#[test]
fn modeable_unanimous_returns_total() {
let xs = [
CategoricalString::from("R"),
CategoricalString::from("R"),
CategoricalString::from("R"),
];
let (value, count, total) = CategoricalString::mode_across(xs).expect("non-empty");
assert_eq!(value, CategoricalString::from("R"));
assert_eq!(count, 3);
assert_eq!(total, 3);
}
#[test]
fn serde_transparent_matches_primitive() {
let raw_count = MonotonicCount(123);
let raw_count_json = serde_json::to_string(&raw_count).expect("serialize");
assert_eq!(raw_count_json, "123");
let raw_ns = MonotonicNs(456_789);
let raw_ns_json = serde_json::to_string(&raw_ns).expect("serialize");
assert_eq!(raw_ns_json, "456789");
let raw_ticks = ClockTicks(2048);
let raw_ticks_json = serde_json::to_string(&raw_ticks).expect("serialize");
assert_eq!(raw_ticks_json, "2048");
let raw_bytes = Bytes(1024 * 1024);
let raw_bytes_json = serde_json::to_string(&raw_bytes).expect("serialize");
assert_eq!(raw_bytes_json, "1048576");
let raw_dead = DeadCounter(7);
let raw_dead_json = serde_json::to_string(&raw_dead).expect("serialize");
assert_eq!(raw_dead_json, "7");
let raw_peak = PeakNs(99);
let raw_peak_json = serde_json::to_string(&raw_peak).expect("serialize");
assert_eq!(raw_peak_json, "99");
let raw_peak_bytes = PeakBytes(2_097_152);
let raw_peak_bytes_json = serde_json::to_string(&raw_peak_bytes).expect("serialize");
assert_eq!(raw_peak_bytes_json, "2097152");
let raw_gauge = GaugeNs(7_500_000);
let raw_gauge_json = serde_json::to_string(&raw_gauge).expect("serialize");
assert_eq!(raw_gauge_json, "7500000");
let raw_gauge_count = GaugeCount(16);
let raw_gauge_count_json = serde_json::to_string(&raw_gauge_count).expect("serialize");
assert_eq!(raw_gauge_count_json, "16");
let raw_ordi = OrdinalI32(-5);
let raw_ordi_json = serde_json::to_string(&raw_ordi).expect("serialize");
assert_eq!(raw_ordi_json, "-5");
let raw_ordu32 = OrdinalU32(99);
let raw_ordu32_json = serde_json::to_string(&raw_ordu32).expect("serialize");
assert_eq!(raw_ordu32_json, "99");
let raw_ordu64 = OrdinalU64(99);
let raw_ordu64_json = serde_json::to_string(&raw_ordu64).expect("serialize");
assert_eq!(raw_ordu64_json, "99");
let raw_str = CategoricalString::from("R");
let raw_str_json = serde_json::to_string(&raw_str).expect("serialize");
assert_eq!(raw_str_json, "\"R\"");
let raw_cpus = CpuSet(vec![0, 2, 4]);
let raw_cpus_json = serde_json::to_string(&raw_cpus).expect("serialize");
assert_eq!(raw_cpus_json, "[0,2,4]");
}
#[test]
fn serde_round_trip_through_json() {
let v = MonotonicNs(987_654_321);
let json = serde_json::to_string(&v).expect("serialize");
let back: MonotonicNs = serde_json::from_str(&json).expect("deserialize");
assert_eq!(v, back);
let s = CategoricalString::from("SCHED_DEADLINE");
let json = serde_json::to_string(&s).expect("serialize");
let back: CategoricalString = serde_json::from_str(&json).expect("deserialize");
assert_eq!(s, back);
}
#[test]
fn repr_transparent_matches_primitive_size() {
use std::mem::size_of;
assert_eq!(size_of::<MonotonicCount>(), size_of::<u64>());
assert_eq!(size_of::<MonotonicNs>(), size_of::<u64>());
assert_eq!(size_of::<ClockTicks>(), size_of::<u64>());
assert_eq!(size_of::<Bytes>(), size_of::<u64>());
assert_eq!(size_of::<DeadCounter>(), size_of::<u64>());
assert_eq!(size_of::<PeakNs>(), size_of::<u64>());
assert_eq!(size_of::<PeakBytes>(), size_of::<u64>());
assert_eq!(size_of::<GaugeNs>(), size_of::<u64>());
assert_eq!(size_of::<GaugeCount>(), size_of::<u64>());
assert_eq!(size_of::<OrdinalI32>(), size_of::<i32>());
assert_eq!(size_of::<OrdinalU32>(), size_of::<u32>());
assert_eq!(size_of::<OrdinalU64>(), size_of::<u64>());
}
#[test]
fn defaults_are_zero_or_empty() {
assert_eq!(MonotonicCount::default(), MonotonicCount(0));
assert_eq!(MonotonicNs::default(), MonotonicNs(0));
assert_eq!(ClockTicks::default(), ClockTicks(0));
assert_eq!(Bytes::default(), Bytes(0));
assert_eq!(DeadCounter::default(), DeadCounter(0));
assert_eq!(PeakNs::default(), PeakNs(0));
assert_eq!(PeakBytes::default(), PeakBytes(0));
assert_eq!(GaugeNs::default(), GaugeNs(0));
assert_eq!(GaugeCount::default(), GaugeCount(0));
assert_eq!(OrdinalI32::default(), OrdinalI32(0));
assert_eq!(OrdinalU32::default(), OrdinalU32(0));
assert_eq!(OrdinalU64::default(), OrdinalU64(0));
assert_eq!(CategoricalString::default(), CategoricalString::from(""));
assert_eq!(CpuSet::default(), CpuSet(vec![]));
}
}