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