1use std::collections::{btree_map::Entry, BTreeMap, BTreeSet};
6use std::sync::{Arc, Mutex};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use datadog_protos::metrics::{metric_payload::MetricSeries, MetricPayload, SketchPayload};
10use serde::{Deserialize, Serialize};
11use stele::{Metric, MetricValue};
12use tracing::warn;
13
14const SELF_TELEMETRY_PREFIX: &str = "datadog.";
15
16#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub(crate) enum Target {
19 Agent,
20 Adp,
21}
22
23impl Target {
24 #[must_use]
25 pub(crate) fn parse(value: &str) -> Option<Self> {
26 match value {
27 "agent" => Some(Self::Agent),
28 "adp" => Some(Self::Adp),
29 _ => None,
30 }
31 }
32
33 pub(crate) fn as_str(self) -> &'static str {
34 match self {
35 Self::Agent => "agent",
36 Self::Adp => "adp",
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub(crate) enum MetricKind {
45 Count,
46 Rate,
47 Gauge,
48 Sketch,
49}
50
51impl MetricKind {
52 fn of(metric: &Metric) -> Option<Self> {
53 metric.values().first().map(|(_, value)| match value {
54 MetricValue::Count { .. } => Self::Count,
55 MetricValue::Rate { .. } => Self::Rate,
56 MetricValue::Gauge { .. } => Self::Gauge,
57 MetricValue::Sketch { .. } => Self::Sketch,
58 })
59 }
60}
61
62#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
63pub(crate) struct EpochSeconds(i64);
64
65impl EpochSeconds {
66 pub(crate) const fn from_epoch_secs(secs: i64) -> Self {
67 Self(secs)
68 }
69
70 pub(crate) fn now() -> Option<Self> {
73 let secs = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
74 i64::try_from(secs).ok().map(Self)
75 }
76}
77
78#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
80pub(crate) struct Context {
81 pub(crate) name: String,
82 pub(crate) tagset: BTreeSet<String>,
83 pub(crate) kind: MetricKind,
84}
85
86#[derive(Clone, Debug, Deserialize, Serialize)]
88pub(crate) struct ContextAt {
89 #[serde(flatten)]
90 pub(crate) context: Context,
91 pub(crate) first_seen: EpochSeconds,
92}
93
94#[derive(Clone, Debug, Deserialize, Serialize)]
96pub(crate) struct LaneView {
97 pub(crate) now: EpochSeconds,
98 pub(crate) contexts: Vec<ContextAt>,
99}
100
101#[derive(Debug, Default)]
102struct Lanes {
103 seen: BTreeMap<(Target, Context), EpochSeconds>,
104}
105
106impl Lanes {
107 fn record(&mut self, target: Target, contexts: &[Context], now: EpochSeconds) -> usize {
108 let mut added = 0;
109 for context in contexts {
110 if context.name.starts_with(SELF_TELEMETRY_PREFIX) {
111 continue;
112 }
113 if let Entry::Vacant(slot) = self.seen.entry((target, context.clone())) {
114 slot.insert(now);
115 added += 1;
116 }
117 }
118 added
119 }
120
121 fn contexts(&self, target: Target) -> Vec<ContextAt> {
122 self.seen
123 .iter()
124 .filter(|((lane, _), _)| *lane == target)
125 .map(|((_, context), &first_seen)| ContextAt {
126 context: context.clone(),
127 first_seen,
128 })
129 .collect()
130 }
131}
132
133#[derive(Clone, Debug, Default)]
136pub struct State {
137 lanes: Arc<Mutex<Lanes>>,
138}
139
140impl State {
141 #[must_use]
143 pub fn new() -> Self {
144 Self::default()
145 }
146
147 pub(crate) fn record_series_v2(&self, target: Target, payload: MetricPayload, now: EpochSeconds) -> usize {
148 let contexts = observe_series(target, payload);
149 self.with_lanes(|lanes| lanes.record(target, &contexts, now))
150 }
151
152 pub(crate) fn record_sketches(&self, target: Target, payload: SketchPayload, now: EpochSeconds) -> usize {
153 let contexts = observe_sketches(target, payload);
154 self.with_lanes(|lanes| lanes.record(target, &contexts, now))
155 }
156
157 pub(crate) fn contexts(&self, target: Target) -> Vec<ContextAt> {
158 self.with_lanes(|lanes| lanes.contexts(target))
159 }
160
161 fn with_lanes<T>(&self, f: impl FnOnce(&mut Lanes) -> T) -> T {
162 f(&mut self.lanes.lock().expect("capture lock poisoned"))
163 }
164}
165
166const MAX_METRIC_NAME_LEN: usize = 350;
168const MAX_TAG_COUNT: usize = 100;
170const MAX_RESOURCE_COUNT: usize = 500;
172
173pub(crate) fn series_kept_by_intake(series: &MetricSeries) -> bool {
178 let name = series.metric.as_str();
179 let name_ok =
180 !name.is_empty() && name.len() <= MAX_METRIC_NAME_LEN && name.bytes().any(|b| b.is_ascii_alphabetic());
181 name_ok && series.tags.len() <= MAX_TAG_COUNT && series.resources.len() <= MAX_RESOURCE_COUNT
182}
183
184fn observe_series(target: Target, payload: MetricPayload) -> Vec<Context> {
186 let mut contexts = Vec::new();
187 for series in payload.series {
188 if !series_kept_by_intake(&series) {
189 continue;
190 }
191 let mut single = MetricPayload::new();
192 single.series.push(series);
193 match Metric::try_from_series_v2(single) {
194 Ok(metrics) => contexts.extend(metrics.iter().filter_map(context_of)),
195 Err(error) => {
196 warn!(target = target.as_str(), %error, "skipped a series that did not convert to a stele metric");
197 }
198 }
199 }
200 contexts
201}
202
203fn observe_sketches(target: Target, payload: SketchPayload) -> Vec<Context> {
205 let mut contexts = Vec::new();
206 for sketch in payload.sketches {
207 let mut single = SketchPayload::new();
208 single.sketches.push(sketch);
209 match Metric::try_from_sketch(single) {
210 Ok(metrics) => contexts.extend(metrics.iter().filter_map(context_of)),
211 Err(error) => {
212 warn!(target = target.as_str(), %error, "skipped a sketch that did not convert to a stele metric");
213 }
214 }
215 }
216 contexts
217}
218
219fn context_of(metric: &Metric) -> Option<Context> {
220 let kind = MetricKind::of(metric)?;
221 Some(Context {
222 name: metric.context().name().to_string(),
223 tagset: metric.context().tags().iter().cloned().collect(),
224 kind,
225 })
226}
227
228#[cfg(test)]
229mod tests;