|
| 1 | +//! Construct [`tracing_core::Metadata`] objects in the way required by that ecosystem. |
| 2 | +//! |
| 3 | +//! Dynamic call site stuff based on <https://github.com/slowli/tracing-toolbox/> |
| 4 | +
|
| 5 | +use crate::util::*; |
| 6 | +use std::{ |
| 7 | + borrow::Cow, |
| 8 | + collections::HashMap, |
| 9 | + sync::{LazyLock, OnceLock, RwLock}, |
| 10 | +}; |
| 11 | +use tracing_core::{field::FieldSet, metadata::Level, Dispatch, Kind, Metadata}; |
| 12 | + |
| 13 | +/// Registry of dynamic tracing metadata generated from Flecs |
| 14 | +pub(crate) static REGISTRY: LazyLock<RwLock<Registry>> = LazyLock::new(Default::default); |
| 15 | + |
| 16 | +/// Registry of dynamic tracing metadata generated from Flecs |
| 17 | +pub(crate) struct Registry { |
| 18 | + pub(crate) metadata: |
| 19 | + HashMap<MetadataRequest<'static>, &'static Metadata<'static>, fxhash::FxBuildHasher>, |
| 20 | +} |
| 21 | + |
| 22 | +impl Default for Registry { |
| 23 | + fn default() -> Self { |
| 24 | + Self { |
| 25 | + // Capacity was chosen arbitrarily |
| 26 | + metadata: HashMap::with_capacity_and_hasher(256, Default::default()), |
| 27 | + } |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +pub(crate) fn init() { |
| 32 | + LazyLock::force(®ISTRY); |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Default)] |
| 36 | +pub(crate) struct DynamicCallsite { |
| 37 | + /// Due to the opaque [`tracing_core::callsite::Identifier`], |
| 38 | + /// which borrows [`tracing_core::Callsite`] for `'static`, have to use interior mutability. |
| 39 | + /// |
| 40 | + /// See [`MetadataRequest::register`] for details |
| 41 | + pub(crate) metadata: OnceLock<Metadata<'static>>, |
| 42 | +} |
| 43 | + |
| 44 | +impl tracing_core::Callsite for DynamicCallsite { |
| 45 | + fn set_interest(&self, _interest: tracing_core::Interest) { |
| 46 | + // No-op |
| 47 | + } |
| 48 | + |
| 49 | + fn metadata(&self) -> &Metadata<'_> { |
| 50 | + self.metadata.get().unwrap() |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| 55 | +pub(crate) enum MetadataKind { |
| 56 | + // hash/eq skip filename/line since they may differ between start & stop |
| 57 | + // only used when perf_trace is enabled |
| 58 | + #[cfg_attr(not(feature = "perf_trace"), allow(unused))] |
| 59 | + Span, |
| 60 | + Event, |
| 61 | +} |
| 62 | + |
| 63 | +#[derive(Clone, Debug)] |
| 64 | +pub(crate) struct MetadataRequest<'a> { |
| 65 | + pub(crate) kind: MetadataKind, |
| 66 | + pub(crate) level: Level, |
| 67 | + pub(crate) name: Cow<'a, str>, |
| 68 | + pub(crate) filename: Option<Cow<'a, str>>, |
| 69 | + pub(crate) line: Option<u32>, |
| 70 | +} |
| 71 | + |
| 72 | +impl<'a> PartialEq for MetadataRequest<'a> { |
| 73 | + fn eq(&self, other: &Self) -> bool { |
| 74 | + self.kind == other.kind |
| 75 | + && self.name == other.name |
| 76 | + && self.level == other.level |
| 77 | + && match self.kind { |
| 78 | + MetadataKind::Span => true, |
| 79 | + MetadataKind::Event => self.filename == other.filename && self.line == other.line, |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +impl<'a> Eq for MetadataRequest<'a> {} |
| 85 | + |
| 86 | +impl<'a> std::hash::Hash for MetadataRequest<'a> { |
| 87 | + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 88 | + self.kind.hash(state); |
| 89 | + self.name.hash(state); |
| 90 | + self.level.hash(state); |
| 91 | + match self.kind { |
| 92 | + MetadataKind::Span => {} |
| 93 | + MetadataKind::Event => { |
| 94 | + self.filename.hash(state); |
| 95 | + self.line.hash(state); |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +impl<'a> MetadataRequest<'a> { |
| 102 | + /// Construct a new [`Metadata`]. |
| 103 | + /// |
| 104 | + /// Does not interact with [`crate::REGISTRY`] by design to avoid having to deal with locking; |
| 105 | + /// the caller should handle memoization if necessary. |
| 106 | + pub(crate) fn register( |
| 107 | + self, |
| 108 | + dispatch: &Dispatch, |
| 109 | + ) -> (&'static Metadata<'static>, MetadataRequest<'static>) { |
| 110 | + // `Dispatch::register_callsite` demands `&'static Metadata`, this is the simplest way to get that |
| 111 | + let callsite = Box::leak(Box::new(DynamicCallsite::default())); |
| 112 | + |
| 113 | + // This macro is the only 'public' way of constructing [`Identifier`] |
| 114 | + let id = tracing_core::identify_callsite!(callsite); |
| 115 | + |
| 116 | + let name = leak_cowstr(self.name); |
| 117 | + let filename: Option<&'static str> = self.filename.map(leak_cowstr); |
| 118 | + |
| 119 | + let metadata = Metadata::new( |
| 120 | + name, |
| 121 | + "flecs", |
| 122 | + self.level, |
| 123 | + filename, |
| 124 | + self.line, |
| 125 | + Some("flecs_ecs_tracing"), |
| 126 | + match self.kind { |
| 127 | + MetadataKind::Span { .. } => FieldSet::new(&[], id), |
| 128 | + MetadataKind::Event { .. } => FieldSet::new(&["message"], id), |
| 129 | + }, |
| 130 | + match self.kind { |
| 131 | + MetadataKind::Span { .. } => Kind::SPAN, |
| 132 | + MetadataKind::Event { .. } => Kind::EVENT, |
| 133 | + }, |
| 134 | + ); |
| 135 | + |
| 136 | + // Store the new Metadata |
| 137 | + callsite.metadata.set(metadata).unwrap(); |
| 138 | + |
| 139 | + // Since the `DynamicCallsite` is alive forever we can also use it to get |
| 140 | + // a `&'static` to the metadata, without extra allocations |
| 141 | + let metadata: &'static Metadata = callsite.metadata.get().unwrap(); |
| 142 | + |
| 143 | + // Tell `tracing` subscribers about the new callsite |
| 144 | + dispatch.register_callsite(metadata); |
| 145 | + |
| 146 | + ( |
| 147 | + metadata, |
| 148 | + MetadataRequest { |
| 149 | + name: Cow::Borrowed(name), |
| 150 | + kind: self.kind, |
| 151 | + filename: filename.map(Cow::Borrowed), |
| 152 | + line: self.line, |
| 153 | + level: self.level, |
| 154 | + }, |
| 155 | + ) |
| 156 | + } |
| 157 | +} |
0 commit comments