Skip to main content

micromegas_tracing/
property_set.rs

1//! Interned collection of PropertySet instances. Each PropertySet contains properties where the names and the values are statically allocated.
2//! The user is expected to manage the cardinality: this holds for `key = static expr` values
3//! too, since every distinct combination is interned and never freed.
4use crate::{dyn_deps::static_ref_str, static_string_ref::StaticStringRef};
5use internment::Intern;
6use micromegas_transit::{HeterogeneousQueue, StaticStringDependency, UserDefinedType, prelude::*};
7use rustc_hash::FxHashSet;
8use std::{
9    borrow::Cow,
10    hash::Hash,
11    sync::{Arc, OnceLock},
12};
13
14lazy_static::lazy_static! {
15    pub static ref PROPERTY_SET_DEP_TYPE_NAME: Arc<String> = Arc::new("PropertySetDependency".into());
16}
17
18#[derive(Debug, Eq, PartialEq, Hash, Clone, TransitReflect)]
19pub struct Property {
20    pub name: StaticStringRef,
21    pub value: StaticStringRef,
22}
23
24impl Property {
25    pub fn new(name: &'static str, value: &'static str) -> Self {
26        Self {
27            name: name.into(),
28            value: value.into(),
29        }
30    }
31}
32
33/// A property whose value is not `'static`. The name is either `'static` (`new`, referenced by
34/// address) or block-scoped (`with_dyn_name`, copied into the block like a dynamic value).
35/// Names from outside Rust, or otherwise unbounded, belong in `with_dyn_name`.
36///
37/// For `log!` and metric macros, analytics treats some names as column overrides:
38/// `level`/`target` (logs) and `target`/`name`/`unit` (metrics). The macros reject those names
39/// as keys, so an override arrives through a `..set` spread, a slice of `DynProperty` built at
40/// runtime, or the `imetric_dyn!`/`fmetric_dyn!` macros, which set `name` and `unit`. A dynamic
41/// `level` must be a valid level name or the block fails to parse. Interop logs (the `tracing`
42/// layer) keep their level and target unchanged.
43#[derive(Debug, Clone, Copy)]
44pub struct DynProperty<'a> {
45    name: &'a str,
46    value: &'a str,
47    // Private: a `true` flag on a non-`'static` name would make `static_ref_str` read freed memory.
48    static_name: bool,
49}
50
51impl<'a> DynProperty<'a> {
52    pub const fn new(name: &'static str, value: &'a str) -> Self {
53        Self {
54            name,
55            value,
56            static_name: true,
57        }
58    }
59
60    pub const fn with_dyn_name(name: &'a str, value: &'a str) -> Self {
61        Self {
62            name,
63            value,
64            static_name: false,
65        }
66    }
67
68    pub fn name(&self) -> &'a str {
69        self.name
70    }
71
72    pub fn value(&self) -> &'a str {
73        self.value
74    }
75
76    pub(crate) fn is_static_name(&self) -> bool {
77        self.static_name
78    }
79}
80
81/// Interns the literal properties of one macro callsite on first use. `cell` is a `static`
82/// emitted by the macro, so each callsite pays one atomic load afterwards.
83///
84/// Duplicate names are resolved the way `merge_properties` does: case-insensitive, last wins.
85pub fn callsite_set(
86    cell: &'static OnceLock<&'static PropertySet>,
87    pairs: &'static [(&'static str, &'static str)],
88) -> &'static PropertySet {
89    cell.get_or_init(|| {
90        let mut properties: Vec<Property> = Vec::with_capacity(pairs.len());
91        apply_pairs(&mut properties, pairs.iter().copied());
92        PropertySet::find_or_create(properties)
93    })
94}
95
96/// Adds `pairs` in order, each one replacing an earlier property of the same name
97/// (case-insensitive), so the last one wins.
98pub fn apply_pairs(
99    properties: &mut Vec<Property>,
100    pairs: impl IntoIterator<Item = (&'static str, &'static str)>,
101) {
102    for (name, value) in pairs {
103        properties.retain(|p| !p.name.as_str().eq_ignore_ascii_case(name));
104        properties.push(Property::new(name, value));
105    }
106}
107
108/// Identity, but only accepts a `&'static str`: a borrowed value passed to a `key = static expr`
109/// property is a lifetime error at the call site.
110#[inline(always)]
111pub fn static_value(v: &'static str) -> &'static str {
112    v
113}
114
115pub fn dyn_property_get<'a>(properties: &[DynProperty<'a>], name: &str) -> Option<&'a str> {
116    properties
117        .iter()
118        .find(|p| p.name.eq_ignore_ascii_case(name))
119        .map(|p| p.value)
120}
121
122pub fn property_get<'a>(properties: &'a [Property], name: &str) -> Option<&'a str> {
123    properties.iter().find_map(|p| {
124        if p.name.as_str().eq_ignore_ascii_case(name) {
125            Some(p.value.as_str())
126        } else {
127            None
128        }
129    })
130}
131
132#[derive(Debug, Clone)]
133pub struct PropertySet {
134    pub(crate) properties: Vec<Property>,
135    // Same order as `properties`. Lets sinks get a slice without a per-call allocation.
136    dyn_view: Box<[DynProperty<'static>]>,
137}
138
139// Identity is the properties alone, so the cached view never affects interning.
140impl PartialEq for PropertySet {
141    fn eq(&self, other: &Self) -> bool {
142        self.properties == other.properties
143    }
144}
145
146impl Eq for PropertySet {}
147
148impl Hash for PropertySet {
149    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
150        self.properties.hash(state);
151    }
152}
153
154/// Canonical property order, so that equal sets hash equally.
155pub(crate) fn sort_properties(properties: &mut [Property]) {
156    properties.sort_by(|a, b| b.name.as_str().cmp(a.name.as_str()));
157}
158
159/// Same order as `sort_properties`, so a merged list matches the dynamic set built from it.
160pub(crate) fn cmp_dyn_properties(a: &DynProperty<'_>, b: &DynProperty<'_>) -> std::cmp::Ordering {
161    b.name.cmp(a.name)
162}
163
164impl PropertySet {
165    pub fn find_or_create(mut properties: Vec<Property>) -> &'static Self {
166        sort_properties(&mut properties);
167        // SAFETY: the refs were built from `&'static str` by `Property::new`, and they don't
168        // point into the set, so moving it into `Intern` keeps them valid.
169        let dyn_view = properties
170            .iter()
171            .map(|p| unsafe { DynProperty::new(static_ref_str(&p.name), static_ref_str(&p.value)) })
172            .collect();
173        let set = PropertySet {
174            properties,
175            dyn_view,
176        };
177        Intern::<PropertySet>::new(set).as_ref()
178    }
179
180    /// The interned set with no properties, for calls that only carry dynamic ones.
181    pub fn empty() -> &'static Self {
182        lazy_static::lazy_static! {
183            static ref EMPTY: &'static PropertySet = PropertySet::find_or_create(vec![]);
184        }
185        *EMPTY
186    }
187
188    pub fn get_properties(&self) -> &[Property] {
189        &self.properties
190    }
191
192    pub fn dyn_properties(&'static self) -> &'static [DynProperty<'static>] {
193        &self.dyn_view
194    }
195}
196
197/// Identifies a property set within one block's events: the address of an interned set, or
198/// the id of a record in the block's dynamic dependency queue.
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200#[repr(transparent)]
201pub struct PropertySetId(pub(crate) u64);
202
203// Dynamic set ids have bit 0 set, which is how they are told apart from interned addresses.
204const _: () = assert!(std::mem::align_of::<PropertySet>() >= 2);
205
206impl PropertySetId {
207    pub fn interned(set: &'static PropertySet) -> Self {
208        Self(set as *const PropertySet as u64)
209    }
210
211    /// `None` for the id of a dynamic set, which only its block's `DynDeps` can resolve.
212    pub fn as_interned(self) -> Option<&'static PropertySet> {
213        if self.0 & 1 != 0 {
214            return None;
215        }
216        // SAFETY: ids with bit 0 clear are only built by `interned`.
217        Some(unsafe { &*(self.0 as *const PropertySet) })
218    }
219}
220
221/// A dependency queue that can receive string records.
222pub trait StringDepsQueue: HeterogeneousQueue {
223    fn push_string(&mut self, dep: StaticStringDependency);
224}
225
226/// A dependency queue that can receive the records of a property set.
227pub trait PropertyDepsQueue: StringDepsQueue {
228    fn push_set(&mut self, dep: PropertySetDependency);
229}
230
231#[derive(Debug)]
232pub struct PropertySetDependency {
233    pub id: u64,
234    pub properties: Cow<'static, [Property]>,
235}
236
237/// Emits the dependencies of an interned property set once per block.
238pub(crate) fn record_properties<D: PropertyDepsQueue>(
239    set: &'static PropertySet,
240    recorded_deps: &mut FxHashSet<u64>,
241    deps: &mut D,
242) {
243    let id = PropertySetId::interned(set).0;
244    if recorded_deps.insert(id) {
245        for prop in set.get_properties() {
246            if recorded_deps.insert(prop.name.id()) {
247                deps.push_string(prop.name.into_dependency());
248            }
249            if recorded_deps.insert(prop.value.id()) {
250                deps.push_string(prop.value.into_dependency());
251            }
252        }
253        deps.push_set(PropertySetDependency {
254            id,
255            properties: Cow::Borrowed(set.get_properties()),
256        });
257    }
258}
259
260impl Reflect for PropertySetDependency {
261    fn reflect() -> UserDefinedType {
262        UserDefinedType {
263            name: PROPERTY_SET_DEP_TYPE_NAME.clone(),
264            size: 0,
265            members: vec![],
266            is_reference: false,
267            secondary_udts: vec![Property::reflect()],
268        }
269    }
270}
271
272impl InProcSerialize for PropertySetDependency {
273    const IN_PROC_SIZE: InProcSize = InProcSize::Dynamic;
274
275    fn get_value_size(&self) -> Option<u32> {
276        let header_size: u32 = std::mem::size_of::<u64>() as u32 + // id
277			std::mem::size_of::<u32>() as u32; // number of properties
278        let container_size: u32 =
279            self.properties.len() as u32 * std::mem::size_of::<Property>() as u32;
280        let size = header_size + container_size;
281        Some(size)
282    }
283
284    fn write_value(&self, buffer: &mut Vec<u8>) {
285        write_any(buffer, &self.id);
286        let nb_properties: u32 = self.properties.len() as u32;
287        write_any(buffer, &nb_properties);
288        for prop in self.properties.iter() {
289            write_any(buffer, prop);
290        }
291    }
292
293    unsafe fn read_value(mut _window: &[u8]) -> Self {
294        // dependencies don't need to be read in the instrumented process
295        unimplemented!();
296    }
297}