1use std::sync::atomic::{AtomicU64, Ordering};
26use std::time::Instant;
27
28use crate::{integer, string};
29
30struct Counters {
32 offers: AtomicU64,
33 kept: AtomicU64,
34 nanos: AtomicU64,
35}
36
37impl Counters {
38 const fn zero() -> Self {
39 Self { offers: AtomicU64::new(0), kept: AtomicU64::new(0), nanos: AtomicU64::new(0) }
40 }
41}
42
43static INTEGERS: [Counters; 8] = [const { Counters::zero() }; 8];
45static STRINGS: [Counters; 7] = [const { Counters::zero() }; 7];
46
47pub const CHOOSING: &str = "(choosing)";
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Codec {
57 pub family: &'static str,
59 pub name: &'static str,
61 pub offers: u64,
63 pub kept: u64,
65 pub nanos: u64,
67}
68
69#[must_use]
71pub fn codecs() -> Vec<Codec> {
72 let integers = integer::Kind::ALL.iter().map(|kind| kind.name()).chain([CHOOSING]);
73 let strings = string::Kind::ALL.iter().map(|kind| kind.name()).chain([CHOOSING]);
74 let integers = integers.zip(&INTEGERS).map(|(name, counters)| ("integer", name, counters));
75 let strings = strings.zip(&STRINGS).map(|(name, counters)| ("string", name, counters));
76 integers
77 .chain(strings)
78 .map(|(family, name, counters)| Codec {
79 family,
80 name,
81 offers: counters.offers.load(Ordering::Relaxed),
82 kept: counters.kept.load(Ordering::Relaxed),
83 nanos: counters.nanos.load(Ordering::Relaxed),
84 })
85 .collect()
86}
87
88#[derive(Debug, Clone, Copy)]
90pub(crate) enum Family {
91 Integer,
92 String,
93}
94
95impl Family {
96 fn slots(self) -> &'static [Counters] {
97 match self {
98 Self::Integer => &INTEGERS,
99 Self::String => &STRINGS,
100 }
101 }
102}
103
104fn since(started: Instant) -> u64 {
106 u64::try_from(started.elapsed().as_nanos()).unwrap_or(u64::MAX)
107}
108
109pub(crate) fn chose(family: Family, started: Instant) {
111 if let Some(counters) = family.slots().last() {
112 counters.nanos.fetch_add(since(started), Ordering::Relaxed);
113 }
114}
115
116pub(crate) fn offer<T>(family: Family, tag: u8, encode: impl FnOnce() -> T) -> T {
118 let started = Instant::now();
119 let out = encode();
120 if let Some(counters) = family.slots().get(usize::from(tag)) {
121 counters.offers.fetch_add(1, Ordering::Relaxed);
122 counters.nanos.fetch_add(since(started), Ordering::Relaxed);
123 }
124 out
125}
126
127pub(crate) fn kept(family: Family, tag: u8) {
129 if let Some(counters) = family.slots().get(usize::from(tag)) {
130 counters.kept.fetch_add(1, Ordering::Relaxed);
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn of(family: &str, name: &str) -> Codec {
139 codecs().into_iter().find(|codec| codec.family == family && codec.name == name).unwrap()
140 }
141
142 #[test]
143 fn a_top_level_chunk_counts_its_offers_and_the_one_it_kept() {
144 let kept = |family: &str| -> u64 {
145 codecs().iter().filter(|codec| codec.family == family).map(|codec| codec.kept).sum()
146 };
147 let before = (of("integer", "FOR+BITPACK"), of("integer", "RLE"), of("string", "PLAIN"));
148 let kept_before = (kept("integer"), kept("string"));
149 let runs: Vec<i64> = (0..4096).map(|at| at / 512).collect();
150 integer::encode(&runs).unwrap();
151 string::encode(&[b"a".as_slice(), b"b", b"c"]).unwrap();
152 let after = (of("integer", "FOR+BITPACK"), of("integer", "RLE"), of("string", "PLAIN"));
153 assert!(after.0.offers > before.0.offers);
155 assert!(after.1.offers > before.1.offers);
156 assert!(after.2.offers > before.2.offers);
157 assert!(kept("integer") > kept_before.0);
158 assert!(kept("string") > kept_before.1);
159 }
160
161 #[test]
162 fn every_codec_of_both_families_has_a_row() {
163 let rows = codecs();
164 assert_eq!(rows.len(), integer::Kind::ALL.len() + string::Kind::ALL.len() + 2);
165 assert!(rows.iter().any(|codec| codec.family == "string" && codec.name == "LZ"));
166 }
167}