Skip to main content

saluki_core/data_model/event/metric/
metadata.rs

1use std::{fmt, sync::Arc};
2
3use stringtheory::MetaString;
4
5const ORIGIN_PRODUCT_AGENT: u32 = 10;
6const ORIGIN_SUBPRODUCT_DOGSTATSD: u32 = 10;
7const ORIGIN_SUBPRODUCT_INTEGRATION: u32 = 11;
8const ORIGIN_PRODUCT_DETAIL_NONE: u32 = 0;
9
10/// Metric metadata.
11///
12/// Metadata includes all information that's not specifically related to the context or value of the metric itself,
13/// such as sample rate and timestamp.
14#[must_use]
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
16pub struct MetricMetadata {
17    /// The hostname where the metric originated from.
18    // TODO: We made this `Arc<str>` because it's 16 bytes vs 24 bytes for `MetaString`, but one problem is that it means that
19    // we have to allocate a new `Arc<str>` for every hostname override (or empty hostname to disable the host tag) which is
20    // suboptimal.
21    //
22    // A main part of the problem is that we want to determine if a hostname was set at all -- whether empty or not -- so that
23    // when we get to host enrichment, we can determine if we should be overriding the hostname or not. This means we can't
24    // simply drop the `Option` and check if the string is empty or not.
25    pub hostname: Option<Arc<str>>,
26
27    /// The metric origin.
28    // TODO: only optional so we can default? seems like we always have one
29    pub origin: Option<MetricOrigin>,
30
31    /// The unit of the metric values, if known.
32    ///
33    /// This is set for DogStatsD timing metrics (`ms` type), which carry an implicit unit of `"millisecond"`. For all
34    /// other metric types the unit is empty.
35    pub unit: MetaString,
36}
37
38impl MetricMetadata {
39    /// Returns the hostname.
40    pub fn hostname(&self) -> Option<&str> {
41        self.hostname.as_deref()
42    }
43
44    /// Returns the metric origin.
45    pub fn origin(&self) -> Option<&MetricOrigin> {
46        self.origin.as_ref()
47    }
48
49    /// Set the hostname where the metric originated from.
50    ///
51    /// This could be specified as part of a metric payload that was received from a client, or set internally to the
52    /// hostname where this process is running.
53    ///
54    /// This variant is specifically for use in builder-style APIs.
55    pub fn with_hostname(mut self, hostname: impl Into<Option<Arc<str>>>) -> Self {
56        self.hostname = hostname.into();
57        self
58    }
59
60    /// Set the hostname where the metric originated from.
61    ///
62    /// This could be specified as part of a metric payload that was received from a client, or set internally to the
63    /// hostname where this process is running.
64    pub fn set_hostname(&mut self, hostname: impl Into<Option<Arc<str>>>) {
65        self.hostname = hostname.into();
66    }
67
68    /// Set the metric origin to the given source type.
69    ///
70    /// Indicates the source of the metric, such as the product or service that emitted it, or the source component
71    /// itself that emitted it.
72    ///
73    /// This variant is specifically for use in builder-style APIs.
74    pub fn with_source_type(mut self, source_type: impl Into<Option<Arc<str>>>) -> Self {
75        self.origin = source_type.into().map(MetricOrigin::SourceType);
76        self
77    }
78
79    /// Set the metric origin to the given source type.
80    ///
81    /// Indicates the source of the metric, such as the product or service that emitted it, or the source component
82    /// itself that emitted it.
83    pub fn set_source_type(&mut self, source_type: impl Into<Option<Arc<str>>>) {
84        self.origin = source_type.into().map(MetricOrigin::SourceType);
85    }
86
87    /// Set the metric origin to the given origin.
88    ///
89    /// Indicates the source of the metric, such as the product or service that emitted it, or the source component
90    /// itself that emitted it.
91    ///
92    /// This variant is specifically for use in builder-style APIs.
93    pub fn with_origin(mut self, origin: impl Into<Option<MetricOrigin>>) -> Self {
94        self.origin = origin.into();
95        self
96    }
97
98    /// Set the metric origin to the given origin.
99    ///
100    /// Indicates the source of the metric, such as the product or service that emitted it, or the source component
101    /// itself that emitted it.
102    pub fn set_origin(&mut self, origin: impl Into<Option<MetricOrigin>>) {
103        self.origin = origin.into();
104    }
105
106    /// Returns the unit of the metric values, if set.
107    pub fn unit(&self) -> Option<&str> {
108        if self.unit.is_empty() {
109            None
110        } else {
111            Some(&self.unit)
112        }
113    }
114
115    /// Set the unit of the metric values.
116    ///
117    /// This variant is specifically for use in builder-style APIs.
118    pub fn with_unit(mut self, unit: impl Into<MetaString>) -> Self {
119        self.unit = unit.into();
120        self
121    }
122
123    /// Set the unit of the metric values.
124    pub fn set_unit(&mut self, unit: impl Into<MetaString>) {
125        self.unit = unit.into();
126    }
127}
128
129impl fmt::Display for MetricMetadata {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        if let Some(origin) = &self.origin {
132            write!(f, " origin={}", origin)?;
133        }
134
135        if !self.unit.is_empty() {
136            write!(f, " unit={}", self.unit)?;
137        }
138
139        Ok(())
140    }
141}
142
143// TODO: This is not technically right.
144//
145// In practice, the Datadog Agent _does_ ship metrics with both source type name and origin metadata, although perhaps
146// luckily, that is only the case for check metrics, which we don't deal with in ADP (yet).
147//
148// Eventually, we likely will have to consider exposing both of these fields.
149
150/// Categorical origin of a metric.
151///
152/// This is used to describe, in high-level terms, where a metric originated from, such as the specific software package
153/// or library that emitted. This is distinct from the `OriginEntity`, which describes the specific sender of the metric.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub enum MetricOrigin {
156    /// Originated from a generic source.
157    ///
158    /// This is used to set the origin of a metric as the source component type itself, such as `dogstatsd` or `otel`,
159    /// when richer origin metadata isn't available.
160    SourceType(Arc<str>),
161
162    /// Originated from a specific product/subproduct, with product-specific detail.
163    OriginMetadata {
164        /// Product.
165        product: u32,
166
167        /// Subproduct.
168        subproduct: u32,
169
170        /// Product detail.
171        product_detail: u32,
172    },
173}
174
175impl MetricOrigin {
176    /// Creates a `MetricsOrigin` for any metric ingested via DogStatsD.
177    pub fn dogstatsd() -> Self {
178        Self::OriginMetadata {
179            product: ORIGIN_PRODUCT_AGENT,
180            subproduct: ORIGIN_SUBPRODUCT_DOGSTATSD,
181            product_detail: ORIGIN_PRODUCT_DETAIL_NONE,
182        }
183    }
184
185    /// Creates a `MetricsOrigin` for any metric that originated via an JXM check integration.
186    pub fn jmx_check(check_name: &str) -> Self {
187        let product_detail = jmx_check_name_to_product_detail(check_name);
188
189        Self::OriginMetadata {
190            product: ORIGIN_PRODUCT_AGENT,
191            subproduct: ORIGIN_SUBPRODUCT_INTEGRATION,
192            product_detail,
193        }
194    }
195
196    /// Returns `true` if the origin of the metric is DogStatsD.
197    pub fn is_dogstatsd(&self) -> bool {
198        matches!(self, Self::OriginMetadata { subproduct, .. } if *subproduct == ORIGIN_SUBPRODUCT_DOGSTATSD)
199    }
200}
201
202impl fmt::Display for MetricOrigin {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        match self {
205            Self::SourceType(source_type) => write!(f, "source_type={}", source_type),
206            Self::OriginMetadata {
207                product,
208                subproduct,
209                product_detail,
210            } => write!(
211                f,
212                "product={} subproduct={} product_detail={}",
213                product_id_to_str(*product),
214                subproduct_id_to_str(*subproduct),
215                product_detail_id_to_str(*product_detail),
216            ),
217        }
218    }
219}
220
221fn jmx_check_name_to_product_detail(check_name: &str) -> u32 {
222    // Taken from Datadog Agent mappings:
223    // https://github.com/DataDog/datadog-agent/blob/fd3a119bda125462d578e0004f1370ee019ce2d5/pkg/serializer/internal/metrics/origin_mapping.go#L41
224    match check_name {
225        "jmx-custom-check" => 9,
226        "activemq" => 12,
227        "cassandra" => 28,
228        "confluent_platform" => 40,
229        "hazelcast" => 70,
230        "hive" => 73,
231        "hivemq" => 74,
232        "hudi" => 76,
233        "ignite" => 83,
234        "jboss_wildfly" => 87,
235        "kafka" => 90,
236        "presto" => 130,
237        "solr" => 147,
238        "sonarqube" => 148,
239        "tomcat" => 163,
240        "weblogic" => 172,
241        _ => 0,
242    }
243}
244
245fn product_id_to_str(product_id: u32) -> &'static str {
246    match product_id {
247        ORIGIN_PRODUCT_AGENT => "agent",
248        _ => "unknown_product",
249    }
250}
251
252fn subproduct_id_to_str(subproduct_id: u32) -> &'static str {
253    match subproduct_id {
254        ORIGIN_SUBPRODUCT_DOGSTATSD => "dogstatsd",
255        ORIGIN_SUBPRODUCT_INTEGRATION => "integration",
256        _ => "unknown_subproduct",
257    }
258}
259
260fn product_detail_id_to_str(product_detail_id: u32) -> &'static str {
261    match product_detail_id {
262        // TODO: Map the JMX check integration product detail IDs to their respective names.
263        ORIGIN_PRODUCT_DETAIL_NONE => "none",
264        _ => "unknown_product_detail",
265    }
266}