use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{Event, Level};
#[derive(Debug, Clone)]
pub struct StatsSnapshot {
pub location_stats: HashMap<Location, LocationStats>,
pub module_stats: HashMap<String, ModuleStats>,
pub level_event_counts: HashMap<(Level, EventType), u64>,
pub raw_stats: HashMap<StatKey, StatEntrySnapshot>,
pub total_counters: HashMap<(EventType, Level), u64>,
pub config: StatsConfig,
pub total_entries: usize,
pub location_count: usize,
pub module_count: usize,
pub snapshot_time: SystemTime,
}
#[derive(Debug, Clone)]
pub struct StatEntrySnapshot {
pub key: StatKey,
pub count: u64,
pub last_seen: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatsConfig {
pub track_by_location: bool,
pub track_by_module: bool,
pub track_by_level: bool,
pub max_locations: usize,
pub max_modules: usize,
}
impl Default for StatsConfig {
fn default() -> Self {
Self {
track_by_location: true,
track_by_module: true,
track_by_level: true,
max_locations: 10_000,
max_modules: 1_000,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Location {
pub file: String,
pub line: u32,
}
impl Location {
pub fn from_trace_data(data: &Event) -> Option<Self> {
if let (Some(file), Some(line)) = (&data.file, data.line) {
Some(Location {
file: file.clone(),
line,
})
} else {
None
}
}
}
impl std::fmt::Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.file, self.line)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventType {
Captured,
Silenced,
Dropped,
}
impl std::fmt::Display for EventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventType::Captured => write!(f, "Captured"),
EventType::Silenced => write!(f, "Silenced"),
EventType::Dropped => write!(f, "Dropped"),
}
}
}
#[derive(Debug)]
pub struct AtomicCounter {
count: AtomicU64,
}
impl AtomicCounter {
pub fn new() -> Self {
Self {
count: AtomicU64::new(0),
}
}
pub fn increment(&self) -> u64 {
self.count.fetch_add(1, Ordering::Relaxed)
}
pub fn get(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
pub fn reset(&self) {
self.count.store(0, Ordering::Relaxed);
}
}
impl Default for AtomicCounter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct StatEntry {
pub counter: AtomicCounter,
pub last_seen: AtomicU64,
}
impl StatEntry {
pub fn new() -> Self {
Self {
counter: AtomicCounter::new(),
last_seen: AtomicU64::new(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
),
}
}
pub fn record_event(&self) {
self.counter.increment();
self.last_seen.store(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
Ordering::Relaxed,
);
}
pub fn get_count(&self) -> u64 {
self.counter.get()
}
pub fn get_last_seen(&self) -> SystemTime {
let nanos = self.last_seen.load(Ordering::Relaxed);
UNIX_EPOCH + std::time::Duration::from_nanos(nanos)
}
}
impl Default for StatEntry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct StatKey {
pub location: Option<Location>,
pub module: Option<String>,
pub level: Level,
pub event_type: EventType,
}
#[derive(Debug)]
pub struct StatsTracker {
config: StatsConfig,
stats: RwLock<HashMap<StatKey, Arc<StatEntry>>>,
total_counters: HashMap<(EventType, Level), AtomicCounter>,
location_count: AtomicU64,
module_count: AtomicU64,
}
impl StatsTracker {
pub fn new(config: StatsConfig) -> Self {
let mut total_counters = HashMap::new();
for event_type in [EventType::Captured, EventType::Silenced, EventType::Dropped] {
for level in [
Level(tracing::Level::ERROR),
Level(tracing::Level::WARN),
Level(tracing::Level::INFO),
Level(tracing::Level::DEBUG),
Level(tracing::Level::TRACE),
] {
total_counters.insert((event_type, level), AtomicCounter::new());
}
}
Self {
config,
stats: RwLock::new(HashMap::new()),
total_counters,
location_count: AtomicU64::new(0),
module_count: AtomicU64::new(0),
}
}
pub fn record_event(&self, event_type: EventType, trace_data: &Event) {
if let Some(counter) = self.total_counters.get(&(event_type, trace_data.level)) {
counter.increment();
}
if !self.config.track_by_location && !self.config.track_by_module {
return;
}
let location = if self.config.track_by_location {
Location::from_trace_data(trace_data)
} else {
None
};
let module = if self.config.track_by_module {
trace_data.module_path.clone()
} else {
None
};
let key = StatKey {
location,
module,
level: trace_data.level,
event_type,
};
{
let stats = self.stats.read().unwrap();
if let Some(entry) = stats.get(&key) {
entry.record_event();
return;
}
}
{
let mut stats = self.stats.write().unwrap();
if let Some(entry) = stats.get(&key) {
entry.record_event();
return;
}
let location_count = self.location_count.load(Ordering::Relaxed) as usize;
let module_count = self.module_count.load(Ordering::Relaxed) as usize;
if (key.location.is_some() && location_count >= self.config.max_locations)
|| (key.module.is_some() && module_count >= self.config.max_modules)
{
return;
}
let entry = Arc::new(StatEntry::new());
entry.record_event();
stats.insert(key.clone(), entry);
if key.location.is_some() {
self.location_count.fetch_add(1, Ordering::Relaxed);
}
if key.module.is_some() {
self.module_count.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn get_total_count(&self, event_type: EventType, level: Level) -> u64 {
self.total_counters
.get(&(event_type, level))
.map(|c| c.get())
.unwrap_or(0)
}
pub fn get_total_count_by_type(&self, event_type: EventType) -> u64 {
self.total_counters
.iter()
.filter(|((et, _), _)| *et == event_type)
.map(|(_, counter)| counter.get())
.sum()
}
pub fn get_snapshot(&self) -> StatsSnapshot {
let stats = self.stats.read().unwrap();
let mut location_stats = HashMap::new();
let mut module_stats = HashMap::new();
let mut level_event_counts = HashMap::new();
let mut raw_stats = HashMap::new();
for (key, entry) in stats.iter() {
let count = entry.get_count();
let last_seen = entry.get_last_seen();
raw_stats.insert(
key.clone(),
StatEntrySnapshot {
key: key.clone(),
count,
last_seen,
},
);
if let Some(location) = &key.location {
let entry = location_stats
.entry(location.clone())
.or_insert_with(|| LocationStats::new(location.clone()));
entry.add_event(key.event_type, key.level, count, last_seen);
}
if let Some(module) = &key.module {
let entry = module_stats
.entry(module.clone())
.or_insert_with(|| ModuleStats::new(module.clone()));
entry.add_event(key.event_type, key.level, count, last_seen);
}
let entry = level_event_counts
.entry((key.level, key.event_type))
.or_insert(0);
*entry += count;
}
let mut total_counters = HashMap::new();
for ((event_type, level), counter) in &self.total_counters {
total_counters.insert((*event_type, *level), counter.get());
}
StatsSnapshot {
location_stats,
module_stats,
level_event_counts,
raw_stats,
total_counters,
config: self.config.clone(),
total_entries: stats.len(),
location_count: self.location_count.load(Ordering::Relaxed) as usize,
module_count: self.module_count.load(Ordering::Relaxed) as usize,
snapshot_time: SystemTime::now(),
}
}
pub fn clear(&self) {
let mut stats = self.stats.write().unwrap();
stats.clear();
for counter in self.total_counters.values() {
counter.reset();
}
self.location_count.store(0, Ordering::Relaxed);
self.module_count.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
pub struct LocationStats {
pub location: Location,
pub events_by_type_and_level: HashMap<(EventType, Level), u64>,
pub last_activity: SystemTime,
}
impl LocationStats {
fn new(location: Location) -> Self {
Self {
location,
events_by_type_and_level: HashMap::new(),
last_activity: UNIX_EPOCH,
}
}
fn add_event(
&mut self,
event_type: EventType,
level: Level,
count: u64,
last_seen: SystemTime,
) {
*self
.events_by_type_and_level
.entry((event_type, level))
.or_insert(0) += count;
if last_seen > self.last_activity {
self.last_activity = last_seen;
}
}
pub fn get_total_for_type(&self, event_type: EventType) -> u64 {
self.events_by_type_and_level
.iter()
.filter(|((et, _), _)| *et == event_type)
.map(|(_, count)| *count)
.sum()
}
pub fn get_total_events(&self) -> u64 {
self.events_by_type_and_level.values().sum()
}
}
#[derive(Debug, Clone)]
pub struct ModuleStats {
pub module: String,
pub events_by_type_and_level: HashMap<(EventType, Level), u64>,
pub last_activity: SystemTime,
}
impl ModuleStats {
fn new(module: String) -> Self {
Self {
module,
events_by_type_and_level: HashMap::new(),
last_activity: UNIX_EPOCH,
}
}
fn add_event(
&mut self,
event_type: EventType,
level: Level,
count: u64,
last_seen: SystemTime,
) {
*self
.events_by_type_and_level
.entry((event_type, level))
.or_insert(0) += count;
if last_seen > self.last_activity {
self.last_activity = last_seen;
}
}
pub fn get_total_for_type(&self, event_type: EventType) -> u64 {
self.events_by_type_and_level
.iter()
.filter(|((et, _), _)| *et == event_type)
.map(|(_, count)| *count)
.sum()
}
pub fn get_total_events(&self) -> u64 {
self.events_by_type_and_level.values().sum()
}
}