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}