1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
use std::{
collections::HashMap,
hash::Hash,
sync::{Arc, RwLock},
};
use derivative::Derivative;
use super::{InternalEventHandle, RegisterInternalEvent};
/// Metrics (eg. `component_sent_event_bytes_total`) may need to emit tags based on
/// values contained within the events. These tags can't be determined in advance.
///
/// Metrics need to be registered and the handle needs to be held onto in order to
/// prevent them from expiring and being dropped (this would result in the counter
/// resetting to zero).
/// `CachedEvent` is used to maintain a store of these registered metrics. When a
/// new event is emitted for a previously unseen set of tags an event is registered
/// and stored in the cache.
#[derive(Derivative)]
#[derivative(Clone(bound = "T: Clone"))]
pub struct RegisteredEventCache<T, Event: RegisterTaggedInternalEvent> {
fixed_tags: T,
cache: Arc<
RwLock<
HashMap<
<Event as RegisterTaggedInternalEvent>::Tags,
<Event as RegisterInternalEvent>::Handle,
>,
>,
>,
}
/// This trait must be implemented by events that emit dynamic tags. `register` must
/// be implemented to register an event based on the set of tags passed.
pub trait RegisterTaggedInternalEvent: RegisterInternalEvent {
/// The type that will contain the data necessary to extract the tags
/// that will be used when registering the event.
type Tags;
/// The type that contains data necessary to extract the tags that will
/// be fixed and only need setting up front when the cache is first created.
type Fixed;
fn register(fixed: Self::Fixed, tags: Self::Tags) -> <Self as RegisterInternalEvent>::Handle;
}
impl<Event, EventHandle, Data, Tags, FixedTags> RegisteredEventCache<FixedTags, Event>
where
Data: Sized,
EventHandle: InternalEventHandle<Data = Data>,
Tags: Clone + Eq + Hash,
FixedTags: Clone,
Event: RegisterInternalEvent<Handle = EventHandle>
+ RegisterTaggedInternalEvent<Tags = Tags, Fixed = FixedTags>,
{
/// Create a new event cache with a set of fixed tags. These tags are passed to
/// all registered events.
pub fn new(fixed_tags: FixedTags) -> Self {
Self {
fixed_tags,
cache: Arc::default(),
}
}
/// Emits the event with the given tags.
/// It will register the event and store in the cache if this has not already
/// been done.
///
/// # Panics
///
/// This will panic if the lock is poisoned.
pub fn emit(&self, tags: &Tags, value: Data) {
let read = self.cache.read().unwrap();
if let Some(event) = read.get(tags) {
event.emit(value);
} else {
let event = <Event as RegisterTaggedInternalEvent>::register(
self.fixed_tags.clone(),
tags.clone(),
);
event.emit(value);
// Ensure the read lock is dropped so we can write.
drop(read);
self.cache.write().unwrap().insert(tags.clone(), event);
}
}
}
#[cfg(test)]
mod tests {
#![allow(unreachable_pub)]
use metrics::{counter, Counter};
use super::*;
crate::registered_event!(
TestEvent {
fixed: String,
dynamic: String,
} => {
event: Counter = {
counter!("test_event_total", "fixed" => self.fixed, "dynamic" => self.dynamic)
},
}
fn emit(&self, count: u64) {
self.event.increment(count);
}
fn register(fixed: String, dynamic: String) {
crate::internal_event::register(TestEvent {
fixed,
dynamic,
})
}
);
#[test]
fn test_fixed_tag() {
let event: RegisteredEventCache<String, TestEvent> =
RegisteredEventCache::new("fixed".to_string());
for tag in 1..=5 {
event.emit(&format!("dynamic{tag}"), tag);
}
}
}