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 is 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 is not available.
160    SourceType(Arc<str>),
161
162    /// Originated from a specific product, category, and/or service.
163    OriginMetadata {
164        /// Product.
165        product: u32,
166
167        /// Subproduct.
168        ///
169        /// Previously known as "category".
170        subproduct: u32,
171
172        /// Product detail.
173        ///
174        /// Previously known as "service".
175        product_detail: u32,
176    },
177}
178
179impl MetricOrigin {
180    /// Creates a `MetricsOrigin` for any metric ingested via DogStatsD.
181    pub fn dogstatsd() -> Self {
182        Self::OriginMetadata {
183            product: ORIGIN_PRODUCT_AGENT,
184            subproduct: ORIGIN_SUBPRODUCT_DOGSTATSD,
185            product_detail: ORIGIN_PRODUCT_DETAIL_NONE,
186        }
187    }
188
189    /// Creates a `MetricsOrigin` for any metric that originated via an JXM check integration.
190    pub fn jmx_check(check_name: &str) -> Self {
191        let product_detail = jmx_check_name_to_product_detail(check_name);
192
193        Self::OriginMetadata {
194            product: ORIGIN_PRODUCT_AGENT,
195            subproduct: ORIGIN_SUBPRODUCT_INTEGRATION,
196            product_detail,
197        }
198    }
199
200    /// Returns `true` if the origin of the metric is DogStatsD.
201    pub fn is_dogstatsd(&self) -> bool {
202        matches!(self, Self::OriginMetadata { subproduct, .. } if *subproduct == ORIGIN_SUBPRODUCT_DOGSTATSD)
203    }
204}
205
206impl fmt::Display for MetricOrigin {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        match self {
209            Self::SourceType(source_type) => write!(f, "source_type={}", source_type),
210            Self::OriginMetadata {
211                product,
212                subproduct,
213                product_detail,
214            } => write!(
215                f,
216                "product={} subproduct={} product_detail={}",
217                product_id_to_str(*product),
218                subproduct_id_to_str(*subproduct),
219                product_detail_id_to_str(*product_detail),
220            ),
221        }
222    }
223}
224
225fn jmx_check_name_to_product_detail(check_name: &str) -> u32 {
226    // Taken from Datadog Agent mappings:
227    // https://github.com/DataDog/datadog-agent/blob/fd3a119bda125462d578e0004f1370ee019ce2d5/pkg/serializer/internal/metrics/origin_mapping.go#L41
228    match check_name {
229        "jmx-custom-check" => 9,
230        "activemq" => 12,
231        "cassandra" => 28,
232        "confluent_platform" => 40,
233        "hazelcast" => 70,
234        "hive" => 73,
235        "hivemq" => 74,
236        "hudi" => 76,
237        "ignite" => 83,
238        "jboss_wildfly" => 87,
239        "kafka" => 90,
240        "presto" => 130,
241        "solr" => 147,
242        "sonarqube" => 148,
243        "tomcat" => 163,
244        "weblogic" => 172,
245        _ => 0,
246    }
247}
248
249fn product_id_to_str(product_id: u32) -> &'static str {
250    match product_id {
251        ORIGIN_PRODUCT_AGENT => "agent",
252        _ => "unknown_product",
253    }
254}
255
256fn subproduct_id_to_str(subproduct_id: u32) -> &'static str {
257    match subproduct_id {
258        ORIGIN_SUBPRODUCT_DOGSTATSD => "dogstatsd",
259        ORIGIN_SUBPRODUCT_INTEGRATION => "integration",
260        _ => "unknown_subproduct",
261    }
262}
263
264fn product_detail_id_to_str(product_detail_id: u32) -> &'static str {
265    match product_detail_id {
266        // TODO: Map the JMX check integration product detail IDs to their respective names.
267        ORIGIN_PRODUCT_DETAIL_NONE => "none",
268        _ => "unknown_product_detail",
269    }
270}