Skip to main content

metrique_writer_core/entry/
boxed.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{any::Any, borrow::Cow, time::SystemTime};
5
6use smallvec::SmallVec;
7
8use crate::{
9    Entry, EntryWriter, Observation, Unit, ValidationError, Value, ValueWriter, value::MetricFlags,
10};
11
12use super::EntryConfig;
13
14/// A heap-allocated [`Entry`] wrapper that uses dynamic dispatch.
15///
16/// While somewhat slower than a statically-dispatched entries, an [`crate::EntrySink`] of boxed
17/// entries can be heterogeneous rather than requiring all entries to be the same type. This is
18/// especially useful for "global" background queues that will consume entries from many
19/// different components.
20pub struct BoxEntry(Box<dyn DynEntry>);
21
22impl BoxEntry {
23    /// Move the entry to the heap and enable dynamic dispatch.
24    pub fn new(entry: impl Entry + Send + 'static) -> Self {
25        Self(Box::new(entry))
26    }
27
28    /// Returns a reference to the inner [`Entry`] value, which can be used with
29    /// [`Any`] to extract a typed reference.
30    pub fn inner(&self) -> &(dyn Any + Send + 'static) {
31        &self.0
32    }
33
34    /// Returns a mutable reference to the inner [`Entry`] value, which can be used
35    /// with [`Any`] to extract a typed reference.
36    pub fn inner_mut(&mut self) -> &mut (dyn Any + Send + 'static) {
37        &mut self.0
38    }
39}
40
41// Behind the scenes, we use a double dispatch method to make each layer of traits (Entry <=>
42// EntryWriter, Value <=> ValueWriter) object safe.
43impl Entry for BoxEntry {
44    fn write<'a>(&'a self, writer: &mut impl EntryWriter<'a>) {
45        self.0.write(&mut EntryWriterToDyn(writer))
46    }
47
48    fn sample_group(&self) -> impl Iterator<Item = (Cow<'static, str>, Cow<'static, str>)> {
49        self.0.sample_group().into_iter()
50    }
51}
52
53// Each Dyn* trait is the object-safe equivalent of its partner
54
55trait DynEntry: Any + Send + 'static {
56    fn write<'a>(&'a self, writer: &mut dyn DynEntryWriter<'a>);
57    fn sample_group(&self) -> SmallVec<[(Cow<'static, str>, Cow<'static, str>); 2]>;
58}
59
60trait DynEntryWriter<'a> {
61    fn timestamp(&mut self, timestamp: SystemTime);
62    fn value(&mut self, name: Cow<'a, str>, value: &dyn DynValue);
63    fn config(&mut self, config: &'a dyn EntryConfig);
64}
65
66trait DynValue {
67    fn write(&self, writer: &mut dyn DynValueWriter);
68}
69
70trait DynValueWriter {
71    fn string(&mut self, value: &str);
72
73    fn metric<'a>(
74        &mut self,
75        distribution: &[Observation],
76        unit: Unit,
77        dimensions: &[(&'a str, &'a str)],
78        flags: MetricFlags<'_>,
79    );
80
81    fn error(&mut self, error: ValidationError);
82
83    fn values_str(&mut self, values: &[&str]);
84}
85
86impl<E: Entry + Send + 'static> DynEntry for E {
87    fn write<'a>(&'a self, writer: &mut dyn DynEntryWriter<'a>) {
88        Entry::write(self, &mut EntryWriterFromDyn(writer));
89    }
90
91    fn sample_group(&self) -> SmallVec<[(Cow<'static, str>, Cow<'static, str>); 2]> {
92        Entry::sample_group(self).collect()
93    }
94}
95
96struct EntryWriterToDyn<W>(W);
97struct EntryWriterFromDyn<'a, 'w>(&'w mut dyn DynEntryWriter<'a>);
98
99impl<'a, W: EntryWriter<'a>> DynEntryWriter<'a> for EntryWriterToDyn<W> {
100    fn timestamp(&mut self, timestamp: SystemTime) {
101        self.0.timestamp(timestamp)
102    }
103
104    fn value(&mut self, name: Cow<'a, str>, value: &dyn DynValue) {
105        self.0.value(name, &ValueFromDyn(value));
106    }
107
108    fn config(&mut self, config: &'a dyn EntryConfig) {
109        self.0.config(config);
110    }
111}
112
113impl<'a> EntryWriter<'a> for EntryWriterFromDyn<'a, '_> {
114    fn timestamp(&mut self, timestamp: SystemTime) {
115        self.0.timestamp(timestamp)
116    }
117
118    fn value(&mut self, name: impl Into<Cow<'a, str>>, value: &(impl Value + ?Sized)) {
119        self.0.value(name.into(), &ValueToDyn(value))
120    }
121
122    fn config(&mut self, config: &'a dyn EntryConfig) {
123        self.0.config(config)
124    }
125}
126
127struct ValueToDyn<'a, V: ?Sized>(&'a V);
128struct ValueFromDyn<'a>(&'a dyn DynValue);
129
130impl<V: Value + ?Sized> DynValue for ValueToDyn<'_, V> {
131    fn write(&self, writer: &mut dyn DynValueWriter) {
132        self.0.write(ValueWriterFromDyn(writer));
133    }
134}
135
136impl Value for ValueFromDyn<'_> {
137    fn write(&self, writer: impl ValueWriter) {
138        self.0.write(&mut ValueWriterToDyn(Some(writer)));
139    }
140}
141
142struct ValueWriterToDyn<W>(Option<W>);
143struct ValueWriterFromDyn<'a>(&'a mut dyn DynValueWriter);
144
145impl<W: ValueWriter> DynValueWriter for ValueWriterToDyn<W> {
146    fn string(&mut self, value: &str) {
147        self.0.take().unwrap().string(value)
148    }
149
150    fn metric<'a>(
151        &mut self,
152        distribution: &[Observation],
153        unit: Unit,
154        dimensions: &[(&'a str, &'a str)],
155        flags: MetricFlags<'_>,
156    ) {
157        self.0.take().unwrap().metric(
158            distribution.iter().copied(),
159            unit,
160            dimensions.iter().copied(),
161            flags,
162        )
163    }
164
165    fn error(&mut self, error: ValidationError) {
166        self.0.take().unwrap().error(error)
167    }
168
169    fn values_str(&mut self, values: &[&str]) {
170        self.0.take().unwrap().values(values.iter())
171    }
172}
173
174impl ValueWriter for ValueWriterFromDyn<'_> {
175    fn string(self, value: &str) {
176        self.0.string(value)
177    }
178
179    fn metric<'a>(
180        self,
181        distribution: impl IntoIterator<Item = Observation>,
182        unit: Unit,
183        dimensions: impl IntoIterator<Item = (&'a str, &'a str)>,
184        flags: MetricFlags<'_>,
185    ) {
186        self.0.metric(
187            distribution
188                .into_iter()
189                .collect::<SmallVec<[_; 2]>>()
190                .as_slice(),
191            unit,
192            dimensions
193                .into_iter()
194                .collect::<SmallVec<[_; 1]>>()
195                .as_slice(),
196            flags,
197        )
198    }
199
200    fn error(self, error: ValidationError) {
201        self.0.error(error)
202    }
203
204    fn values<'a, V: Value + 'a>(self, values: impl IntoIterator<Item = &'a V>) {
205        let strs: SmallVec<[String; 8]> = values
206            .into_iter()
207            .filter_map(|v| {
208                let mut s = String::new();
209                v.write(crate::value::StringCapture(&mut s));
210                if s.is_empty() { None } else { Some(s) }
211            })
212            .collect();
213        let refs: SmallVec<[&str; 8]> = strs.iter().map(|s| s.as_str()).collect();
214        self.0.values_str(&refs)
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::{
222        EntryWriter, MetricValue as _, test_stream::DummyEntryWriter, value::WithDimensions,
223    };
224    use std::time::{Duration, SystemTime};
225
226    #[test]
227    fn dummy() {
228        struct TestEntry;
229        impl Entry for TestEntry {
230            fn write<'a>(&'a self, writer: &mut impl EntryWriter<'a>) {
231                writer.timestamp(SystemTime::UNIX_EPOCH + Duration::from_secs_f64(1.5));
232                writer.value("Time", &Duration::from_millis(42));
233                writer.value("StringProp", "some string value");
234                writer.value("BasicIntCount", &1234u64);
235                writer.value(
236                    "BasicIntCountWithDimensions",
237                    &(1234u64.with_dimensions([("A", "x"), ("B", "y")]) as WithDimensions<_, 2>),
238                );
239                writer.value("BasicFloatCount", &5.4321f64);
240                writer.value("SomeDuration", &Duration::from_micros(12345678));
241            }
242        }
243
244        let mut writer = DummyEntryWriter::default();
245        <BoxEntry as Entry>::write(&TestEntry.boxed(), &mut writer);
246        assert_eq!(
247            writer.0,
248            vec![
249                ("timestamp".to_string(), "1.5".to_string()),
250                (
251                    "Time".to_string(),
252                    "[Floating(42.0)] Milliseconds []".to_string()
253                ),
254                ("StringProp".to_string(), "some string value".to_string()),
255                (
256                    "BasicIntCount".to_string(),
257                    "[Unsigned(1234)] None []".to_string()
258                ),
259                (
260                    "BasicIntCountWithDimensions".to_string(),
261                    "[Unsigned(1234)] None [(\"A\", \"x\"), (\"B\", \"y\")]".to_string()
262                ),
263                (
264                    "BasicFloatCount".to_string(),
265                    "[Floating(5.4321)] None []".to_string()
266                ),
267                (
268                    "SomeDuration".to_string(),
269                    "[Floating(12345.678)] Milliseconds []".to_string()
270                ),
271            ]
272        );
273    }
274}