use crate::error::Result;
pub const COUNT_PROPERTY_PREFIX: &str = ":count_";
const COUNT_RESOLUTION: i64 = 100;
const COUNT_MAX: i64 = 10_000_000;
#[derive(Default)]
pub struct ApproximateCounter {
values: Vec<i64>,
}
impl ApproximateCounter {
pub fn record_one_insert(&mut self) {
if next_below(COUNT_RESOLUTION) != 0 {
return;
}
let max = self.maximum();
if max >= COUNT_MAX {
return;
}
let x = COUNT_RESOLUTION.max(max.saturating_mul(2)) / COUNT_RESOLUTION;
if next_below(x) > 0 {
return;
}
self.values.push(x * COUNT_RESOLUTION);
}
fn maximum(&self) -> i64 {
self.values.iter().copied().max().unwrap_or(0)
}
pub fn properties<Sink: crate::writer::record_writer::SegmentSink>(
&self,
writer: &mut crate::writer::record_writer::RecordWriter<Sink>,
) -> Result<Vec<crate::writer::record_writer::PropertyToWrite>> {
use crate::writer::record_writer::{PropertyToWrite, PropertyValuesToWrite};
let mut properties = Vec::with_capacity(self.values.len());
for value in &self.values {
let written = writer.write_string(&value.to_string())?;
properties.push(PropertyToWrite {
name: format!("{COUNT_PROPERTY_PREFIX}{}", random_identifier()),
property_type: crate::PropertyType::Long,
values: PropertyValuesToWrite::Single(written),
});
}
Ok(properties)
}
}
fn next_below(bound: i64) -> i64 {
if bound <= 1 {
return 0;
}
let bound = u64::try_from(bound).unwrap_or(1);
i64::try_from(u64::from(crate::writer::identifier_generator::random_u32()) % bound).unwrap_or(0)
}
fn random_identifier() -> String {
let identifier = crate::writer::identifier_generator::new_data_segment_identifier();
let most = identifier.most_significant_bits;
let least = (identifier.least_significant_bits & 0x3FFF_FFFF_FFFF_FFFF) | 0x8000_0000_0000_0000;
format!(
"{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
most >> 32,
(most >> 16) & 0xFFFF,
most & 0xFFFF,
least >> 48,
least & 0xFFFF_FFFF_FFFF,
)
}