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}