Skip to content

Commit 4545ef2

Browse files
authored
mutate/VRL: allow setting .mtype for simple metrics (#113)
Fixes BIT-8327 Signed-off-by: Matt Klein <mklein@bitdrift.io>
1 parent c4f5a48 commit 4545ef2

4 files changed

Lines changed: 184 additions & 3 deletions

File tree

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
uses: actions/checkout@v4
1818

1919
- name: Cache
20-
uses: Swatinem/rust-cache@v2.7.3
20+
uses: Swatinem/rust-cache@v2.9.1
2121

2222
- name: Rust Toolchain
2323
uses: actions-rs/toolchain@v1
@@ -71,7 +71,7 @@ jobs:
7171
profile: minimal
7272

7373
- name: Cache
74-
uses: Swatinem/rust-cache@v2.7.3
74+
uses: Swatinem/rust-cache@v2.9.1
7575

7676
- name: Build
7777
env:

pulse-metrics/src/pipeline/processor/mutate/mod_test.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,122 @@ async fn flatten_summary() {
244244
.await;
245245
}
246246

247+
#[tokio::test]
248+
async fn change_mtype_counter_to_gauge() {
249+
let mut helper = Helper::new(
250+
r#"
251+
.mtype = "gauge"
252+
"#,
253+
);
254+
255+
helper
256+
.expect_send_and_receive(
257+
make_abs_counter("foo", &[("tag", "value")], 0, 1.0),
258+
vec![make_gauge("foo", &[("tag", "value")], 0, 1.0)],
259+
)
260+
.await;
261+
}
262+
263+
#[tokio::test]
264+
async fn change_mtype_gauge_to_delta_gauge() {
265+
let mut helper = Helper::new(
266+
r#"
267+
.mtype = "delta_gauge"
268+
"#,
269+
);
270+
271+
helper
272+
.expect_send_and_receive(
273+
make_gauge("foo", &[("tag", "value")], 0, 1.0),
274+
vec![make_metric_ex(
275+
"foo",
276+
&[("tag", "value")],
277+
0,
278+
Some(MetricType::DeltaGauge),
279+
None,
280+
MetricValue::Simple(1.0),
281+
MetricSource::PromRemoteWrite,
282+
DownstreamId::LocalOrigin,
283+
None,
284+
)],
285+
)
286+
.await;
287+
}
288+
289+
#[tokio::test]
290+
async fn reject_histogram_mtype_change() {
291+
let helper = Helper::new(
292+
r#"
293+
.mtype = "counter"
294+
"#,
295+
);
296+
297+
helper
298+
.processor
299+
.clone()
300+
.recv_samples(vec![make_metric_ex(
301+
"foo",
302+
&[],
303+
0,
304+
Some(MetricType::Histogram),
305+
None,
306+
MetricValue::Histogram(HistogramData {
307+
buckets: vec![HistogramBucket {
308+
le: 3.0,
309+
count: 1.0,
310+
}],
311+
sample_count: 1.0,
312+
sample_sum: 2.0,
313+
}),
314+
MetricSource::PromRemoteWrite,
315+
DownstreamId::LocalOrigin,
316+
None,
317+
)])
318+
.await;
319+
320+
helper
321+
.helper
322+
.stats_helper
323+
.assert_counter_eq(1, "processor:drop_error", &labels! {});
324+
}
325+
326+
#[tokio::test]
327+
async fn reject_summary_mtype_change() {
328+
let helper = Helper::new(
329+
r#"
330+
.mtype = "counter"
331+
"#,
332+
);
333+
334+
helper
335+
.processor
336+
.clone()
337+
.recv_samples(vec![make_metric_ex(
338+
"foo",
339+
&[],
340+
0,
341+
Some(MetricType::Summary),
342+
None,
343+
MetricValue::Summary(SummaryData {
344+
quantiles: vec![SummaryBucket {
345+
quantile: 3.0,
346+
value: 1.0,
347+
}],
348+
sample_count: 1.0,
349+
sample_sum: 2.0,
350+
}),
351+
MetricSource::PromRemoteWrite,
352+
DownstreamId::LocalOrigin,
353+
None,
354+
)])
355+
.await;
356+
357+
helper
358+
.helper
359+
.stats_helper
360+
.assert_counter_eq(1, "processor:drop_error", &labels! {});
361+
}
362+
247363
#[tokio::test]
248364
async fn modify_all_tags() {
249365
let mut helper = Helper::new(
@@ -768,6 +884,19 @@ fn editable_parsed_metric() {
768884
assert_matches!(metric.cached_metric(), CachedMetric::NotInitialized);
769885
}
770886

887+
{
888+
let mut metric = make_abs_counter("prod:some_service:test:name", &[], 0, 1.0);
889+
metric.initialize_cache(&metric_cache);
890+
let mut editable_metric = EditableParsedMetric::new(&mut metric);
891+
editable_metric.change_mtype(b"gauge").unwrap();
892+
drop(editable_metric);
893+
assert_eq!(
894+
metric,
895+
make_gauge("prod:some_service:test:name", &[], 0, 1.0)
896+
);
897+
assert_matches!(metric.cached_metric(), CachedMetric::NotInitialized);
898+
}
899+
771900
{
772901
let mut metric = make_abs_counter(
773902
"prod:some_service:test:name",

pulse-metrics/src/protos/metric.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ pub struct EditableParsedMetric<'a> {
618618
metric: &'a mut ParsedMetric,
619619
tag_insertion_index: Option<usize>,
620620
name_changed: bool,
621+
mtype_changed: bool,
621622
deleted_tags: Option<Vec<bool>>,
622623
}
623624

@@ -627,6 +628,7 @@ impl<'a> EditableParsedMetric<'a> {
627628
metric,
628629
tag_insertion_index: None,
629630
name_changed: false,
631+
mtype_changed: false,
630632
deleted_tags: None,
631633
}
632634
}
@@ -736,6 +738,28 @@ impl<'a> EditableParsedMetric<'a> {
736738
self.name_changed = true;
737739
}
738740

741+
pub fn change_mtype(&mut self, mtype: &[u8]) -> Result<(), &'static str> {
742+
if !matches!(self.metric.metric.value, MetricValue::Simple(_)) {
743+
return Err("assigning to mtype is only supported for simple metrics");
744+
}
745+
746+
let mtype = match mtype {
747+
b"counter" => MetricType::Counter(CounterType::Absolute),
748+
b"gauge" => MetricType::Gauge,
749+
b"delta_gauge" => MetricType::DeltaGauge,
750+
b"direct_gauge" => MetricType::DirectGauge,
751+
b"timer" => MetricType::Timer,
752+
_ => return Err("assigning to mtype requires a supported metric type string"),
753+
};
754+
755+
if self.metric.metric.id.mtype() != Some(mtype) {
756+
self.metric.metric.id.set_mtype(mtype);
757+
self.mtype_changed = true;
758+
}
759+
760+
Ok(())
761+
}
762+
739763
#[must_use]
740764
pub fn metric(&self) -> &ParsedMetric {
741765
self.metric
@@ -765,7 +789,11 @@ impl Drop for EditableParsedMetric<'_> {
765789
self.metric.metric.id.tags.sort_unstable();
766790
}
767791

768-
if self.tag_insertion_index.is_some() || self.name_changed || self.deleted_tags.is_some() {
792+
if self.tag_insertion_index.is_some()
793+
|| self.name_changed
794+
|| self.mtype_changed
795+
|| self.deleted_tags.is_some()
796+
{
769797
self.metric.cached_metric = CachedMetric::NotInitialized;
770798
}
771799
}

pulse-metrics/src/vrl/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use vrl::compiler::state::{ExternalEnv, RuntimeState};
1818
use vrl::compiler::{
1919
CompileConfig,
2020
Context,
21+
ExpressionError,
2122
OwnedValueOrRef,
2223
Program,
2324
Resolved,
@@ -43,13 +44,15 @@ mod pulse_log;
4344
struct EditableMetricVrlTarget<'a> {
4445
metric: EditableParsedMetric<'a>,
4546
flatten_prom_histogram_and_summary: bool,
47+
runtime_error: Option<&'static str>,
4648
}
4749

4850
impl<'a> EditableMetricVrlTarget<'a> {
4951
const fn new(metric: EditableParsedMetric<'a>) -> Self {
5052
Self {
5153
metric,
5254
flatten_prom_histogram_and_summary: false,
55+
runtime_error: None,
5356
}
5457
}
5558
}
@@ -90,6 +93,18 @@ impl Target for EditableMetricVrlTarget<'_> {
9093
.clone(),
9194
);
9295
},
96+
[mtype] if mtype == "mtype" => {
97+
let Some(mtype) = value.as_bytes() else {
98+
self
99+
.runtime_error
100+
.get_or_insert("assigning to mtype requires a string");
101+
return Ok(());
102+
};
103+
104+
if let Err(error) = self.metric.change_mtype(mtype) {
105+
self.runtime_error.get_or_insert(error);
106+
}
107+
},
93108
[tags, tag_name] if tags == "tags" => {
94109
self.metric.add_or_change_tag(TagValue {
95110
tag: tag_name.to_bytes(),
@@ -368,10 +383,19 @@ impl ProgramWrapper {
368383
Context::new(&mut target, &mut state, &timezone).with_dynamic_state(&mut dynamic_data);
369384
let resolved = self.program.resolve(&mut ctx);
370385
let flatten_prom_histogram_and_summary = target.flatten_prom_histogram_and_summary;
386+
let runtime_error = target.runtime_error;
371387

372388
// Drop target to release mutable borrow of sample before applying downstream ID suffix.
373389
drop(target);
374390

391+
let resolved = match (resolved, runtime_error) {
392+
(
393+
Ok(_) | Err(ExpressionError::Return { .. } | ExpressionError::Abort { .. }),
394+
Some(error),
395+
) => Err(ExpressionError::from(error)),
396+
(resolved, _) => resolved,
397+
};
398+
375399
// Apply downstream ID suffix if set by VRL program.
376400
if let Some(suffix) = dynamic_data.downstream_id_suffix {
377401
sample.append_to_downstream_id(&suffix);

0 commit comments

Comments
 (0)