use std::collections::HashMap;
use std::sync::OnceLock;
use histogram::{Config, Error, Histogram};
use parking_lot::RwLock;
use super::metadata::GroupMetadata;
use crate::{HistogramGroupMetric, Metric, Value};
pub struct HistogramGroup {
inner: OnceLock<RwLock<Vec<histogram::AtomicHistogram>>>,
metadata: GroupMetadata,
config: Config,
entries: usize,
}
impl HistogramGroup {
pub const fn new(entries: usize, grouping_power: u8, max_value_power: u8) -> Self {
let config = match Config::new(grouping_power, max_value_power) {
Ok(c) => c,
Err(_) => panic!("invalid histogram config"),
};
Self {
inner: OnceLock::new(),
metadata: GroupMetadata::new(),
config,
entries,
}
}
pub fn entries(&self) -> usize {
self.entries
}
pub fn config(&self) -> Config {
self.config
}
fn get_or_init(&self) -> &RwLock<Vec<histogram::AtomicHistogram>> {
self.inner.get_or_init(|| {
let mut v = Vec::with_capacity(self.entries);
for _ in 0..self.entries {
v.push(histogram::AtomicHistogram::with_config(&self.config));
}
RwLock::new(v)
})
}
pub fn increment(&self, idx: usize, value: u64) -> Result<bool, Error> {
if idx >= self.entries {
return Ok(false);
}
let inner = self.get_or_init().read();
inner[idx].increment(value)?;
Ok(true)
}
pub fn load(&self, idx: usize) -> Option<Histogram> {
if idx >= self.entries {
return None;
}
self.inner.get().map(|v| v.read()[idx].load())
}
pub fn load_all(&self) -> Option<Vec<Histogram>> {
self.inner
.get()
.map(|v| v.read().iter().map(|h| h.load()).collect())
}
pub fn set_metadata(&self, idx: usize, metadata: HashMap<String, String>) {
if idx < self.entries {
self.metadata.insert(idx, metadata);
}
}
pub fn insert_metadata(&self, idx: usize, key: String, value: String) {
if idx < self.entries {
self.metadata.insert_kv(idx, key, value);
}
}
pub fn load_metadata(&self, idx: usize) -> Option<HashMap<String, String>> {
self.metadata.load(idx)
}
pub fn clear_metadata(&self, idx: usize) {
self.metadata.remove(idx);
}
pub fn metadata_snapshot(&self) -> Vec<(usize, HashMap<String, String>)> {
self.metadata.snapshot()
}
}
impl HistogramGroupMetric for HistogramGroup {
fn entries(&self) -> usize {
self.entries
}
fn config(&self) -> Config {
self.config
}
fn load_histogram(&self, idx: usize) -> Option<Histogram> {
self.load(idx)
}
fn load_all_histograms(&self) -> Option<Vec<Histogram>> {
self.load_all()
}
fn load_metadata(&self, idx: usize) -> Option<HashMap<String, String>> {
self.metadata.load(idx)
}
fn metadata_snapshot(&self) -> Vec<(usize, HashMap<String, String>)> {
self.metadata.snapshot()
}
}
impl Metric for HistogramGroup {
fn as_any(&self) -> Option<&dyn std::any::Any> {
Some(self)
}
fn value(&self) -> Option<Value<'_>> {
Some(Value::HistogramGroup(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_operations() {
static GROUP: HistogramGroup = HistogramGroup::new(4, 7, 64);
assert!(GROUP.load(0).is_none()); assert!(GROUP.increment(0, 100).unwrap());
assert!(GROUP.load(0).is_some());
assert!(!GROUP.increment(4, 100).unwrap());
assert!(GROUP.load(4).is_none());
}
#[test]
fn load_all() {
static GROUP: HistogramGroup = HistogramGroup::new(2, 7, 64);
let _ = GROUP.increment(0, 100);
let _ = GROUP.increment(1, 200);
let all = GROUP.load_all().unwrap();
assert_eq!(all.len(), 2);
}
#[test]
fn metadata() {
static GROUP: HistogramGroup = HistogramGroup::new(4, 7, 64);
GROUP.insert_metadata(0, "op".into(), "read".into());
let meta = GROUP.load_metadata(0).unwrap();
assert_eq!(meta.get("op").unwrap(), "read");
assert!(GROUP.load_metadata(1).is_none());
}
#[test]
fn config() {
static GROUP: HistogramGroup = HistogramGroup::new(2, 7, 64);
let config = GROUP.config();
assert_eq!(config, Config::new(7, 64).unwrap());
}
}