diff --git a/Cargo.lock b/Cargo.lock index 92ba50ee05a77..067d0cdd26e90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2864,6 +2864,7 @@ dependencies = [ "ordered-float 5.3.0", "parquet", "pin-project", + "proptest", "prost 0.12.6", "prost-reflect 0.14.7", "rand 0.10.1", diff --git a/lib/codecs/Cargo.toml b/lib/codecs/Cargo.toml index c2c3bcdce42ec..6726d1aa2b922 100644 --- a/lib/codecs/Cargo.toml +++ b/lib/codecs/Cargo.toml @@ -64,6 +64,7 @@ toml = { version = "0.9.8", optional = true } criterion.workspace = true futures.workspace = true indoc.workspace = true +proptest.workspace = true tokio = { workspace = true, features = ["test-util"] } toml.workspace = true similar-asserts = "2.0.0" diff --git a/lib/codecs/tests/data/native_encoding/README.md b/lib/codecs/tests/data/native_encoding/README.md index 2cf2aaba974be..61700837cee5e 100644 --- a/lib/codecs/tests/data/native_encoding/README.md +++ b/lib/codecs/tests/data/native_encoding/README.md @@ -5,24 +5,6 @@ codecs. These fixtures were generated when the feature was first implemented, and we test that all the examples can be successfully parsed, parse the same across both formats, and match the current serialized format. -In order to avoid small inherent serialization differences between JSON and -protobuf (e.g. float handling), the `generate-fixtures` feature flag in -`vector-core` activates a stricter `Arbitrary` implementation for `Event` that -produces simpler, round-trip-safe f64 values and non-empty field names. These -changes are intentionally scoped to fixture generation and not used in regular -property testing. - -## Re-generating fixtures - -Both this repo and the VRL repo have a `generate-fixtures` feature flag that -activates fixture-stable `Arbitrary` implementations. The vector-core -`generate-fixtures` feature automatically enables `vrl/generate-fixtures`. - -### Run the generator - -```bash -cargo run -p vector-core --features generate-fixtures --bin generate-fixtures -``` - -The binary writes files directly into this directory's `json/` and `proto/` -subdirectories, replacing the existing fixtures. +These snapshots are intentionally frozen rather than regenerated. New coverage +should use focused historical wire literals or property tests instead of adding +more generated fixture files. diff --git a/lib/codecs/tests/native.rs b/lib/codecs/tests/native.rs index 0fb71ee02b932..e3590ec7c35ad 100644 --- a/lib/codecs/tests/native.rs +++ b/lib/codecs/tests/native.rs @@ -3,16 +3,359 @@ use std::{ fs::{self, File}, io::{Read, Write}, + num::NonZeroU32, path::{Path, PathBuf}, + sync::Arc, }; use bytes::{Bytes, BytesMut}; +use chrono::{DateTime, Utc}; use codecs::{ NativeDeserializerConfig, NativeJsonDeserializerConfig, NativeJsonSerializerConfig, NativeSerializerConfig, decoding::format::Deserializer, encoding::format::Serializer, }; +use proptest::{ + collection::{btree_map, btree_set}, + prelude::*, + test_runner::Config as ProptestConfig, +}; use similar_asserts::assert_eq; -use vector_core::{config::LogNamespace, event::Event}; +use tokio_util::codec::Encoder; +use uuid::Uuid; +use vector_core::{ + config::{ComponentKey, LogNamespace, OutputId}, + event::{ + DatadogMetricOriginMetadata, Event, EventMetadata, LogEvent, Metric, MetricKind, + MetricTags, MetricValue, ObjectMap, TraceEvent, Value, + metric::{Bucket, Quantile, TagValue}, + }, +}; +use vrl::event_path; + +const PROPERTY_TESTS: u32 = 1_000; + +fn bounded_string() -> BoxedStrategy { + proptest::collection::vec(any::(), 0..16) + .prop_map(|characters| characters.into_iter().collect()) + .boxed() +} + +fn nonempty_bounded_string() -> BoxedStrategy { + proptest::collection::vec(any::(), 1..16) + .prop_map(|characters| characters.into_iter().collect()) + .boxed() +} + +fn json_safe_leaf() -> BoxedStrategy { + prop_oneof![ + bounded_string().prop_map(Value::from), + any::().prop_map(Value::from), + (-1_000_000.0_f64..=1_000_000.0).prop_map(|value| { + let rounded = (value * 10_000.0).round() / 10_000.0; + Value::from(if rounded == -0.0 { 0.0 } else { rounded }) + }), + any::().prop_map(Value::from), + Just(Value::Null), + ] + .boxed() +} + +fn value_strategy(leaf: BoxedStrategy) -> BoxedStrategy { + leaf.prop_recursive(3, 32, 4, |inner| { + prop_oneof![ + proptest::collection::vec(inner.clone(), 0..4).prop_map(Value::Array), + btree_map(bounded_string(), inner, 0..4).prop_map(|entries| { + Value::Object( + entries + .into_iter() + .map(|(key, value)| (key.into(), value)) + .collect(), + ) + }), + ] + }) + .boxed() +} + +fn json_safe_value() -> BoxedStrategy { + value_strategy(json_safe_leaf()) +} + +fn datetime() -> BoxedStrategy> { + (-32_000_i64..=32_000, 0_u32..1_000_000_000) + .prop_map(|(seconds, nanoseconds)| DateTime::from_timestamp(seconds, nanoseconds).unwrap()) + .boxed() +} + +fn proto_value() -> BoxedStrategy { + value_strategy( + prop_oneof![ + 5 => json_safe_leaf(), + 1 => datetime().prop_map(Value::Timestamp), + ] + .boxed(), + ) +} + +fn object_map(value: BoxedStrategy) -> BoxedStrategy { + btree_map(bounded_string(), value, 0..4) + .prop_map(|entries| { + entries + .into_iter() + .map(|(key, value)| (key.into(), value)) + .collect() + }) + .boxed() +} + +fn metric_float() -> BoxedStrategy { + (proptest::num::f64::POSITIVE | proptest::num::f64::NEGATIVE | proptest::num::f64::ZERO).boxed() +} + +fn quantile_value() -> BoxedStrategy { + (0_u32..=10_000) + .prop_map(|value| f64::from(value) / 10_000.0) + .boxed() +} + +fn metric_value() -> BoxedStrategy { + prop_oneof![ + 7 => any::(), + 1 => btree_set(bounded_string(), 0..4) + .prop_map(|values| MetricValue::Set { values }), + 1 => ( + proptest::collection::vec( + (metric_float(), any::()) + .prop_map(|(upper_limit, count)| Bucket { upper_limit, count }), + 0..8, + ), + any::(), + metric_float(), + ).prop_map(|(buckets, count, sum)| MetricValue::AggregatedHistogram { + buckets, + count, + sum, + }), + 1 => ( + proptest::collection::vec( + (quantile_value(), metric_float()) + .prop_map(|(quantile, value)| Quantile { quantile, value }), + 0..8, + ), + any::(), + metric_float(), + ).prop_map(|(quantiles, count, sum)| MetricValue::AggregatedSummary { + quantiles, + count, + sum, + }), + ] + .boxed() +} + +fn metric_tags() -> BoxedStrategy> { + let tag_value = prop_oneof![ + Just(TagValue::Bare), + bounded_string().prop_map(TagValue::Value), + ]; + + proptest::option::of(btree_map( + bounded_string(), + proptest::collection::vec(tag_value, 0..4), + 1..4, + )) + .prop_map(|entries| { + entries.map(|entries| { + let mut tags = MetricTags::default(); + for (name, values) in entries { + tags.set_multi_value(name, values); + } + tags + }) + }) + .boxed() +} + +fn event_metadata(value: BoxedStrategy) -> BoxedStrategy { + ( + value, + proptest::option::of(bounded_string()), + proptest::option::of(bounded_string()), + proptest::option::of((bounded_string(), proptest::option::of(bounded_string()))), + btree_map(bounded_string(), bounded_string(), 0..4), + proptest::option::of(( + proptest::option::of(any::()), + proptest::option::of(any::()), + proptest::option::of(any::()), + )), + proptest::option::of(any::<[u8; 16]>().prop_map(Uuid::from_bytes)), + ) + .prop_map( + |(value, source_id, source_type, upstream_id, secrets, origin, source_event_id)| { + let mut metadata = + EventMetadata::default_with_value(value).with_source_event_id(source_event_id); + if let Some(source_id) = source_id { + metadata.set_source_id(Arc::new(ComponentKey::from(source_id))); + } + if let Some(source_type) = source_type { + metadata.set_source_type(source_type); + } + if let Some((component, port)) = upstream_id { + metadata.set_upstream_id(Arc::new(OutputId::from((component, port)))); + } + for (key, value) in secrets { + metadata.secrets_mut().insert(key, value); + } + if let Some((product, category, service)) = origin { + metadata = metadata.with_origin_metadata(DatadogMetricOriginMetadata::new( + product, category, service, + )); + } + metadata + }, + ) + .boxed() +} + +fn timestamp() -> BoxedStrategy>> { + proptest::option::of(datetime()).boxed() +} + +fn interval() -> BoxedStrategy> { + proptest::option::of((1_u32..=u32::MAX).prop_map(|value| NonZeroU32::new(value).unwrap())) + .boxed() +} + +fn event_strategy(value: BoxedStrategy) -> BoxedStrategy { + let metadata = event_metadata(value.clone()); + let log = (object_map(value.clone()), metadata.clone()) + .prop_map(|(fields, metadata)| Event::Log(LogEvent::from_map(fields, metadata))); + let trace = (object_map(value), metadata.clone()) + .prop_map(|(fields, metadata)| Event::Trace(TraceEvent::from_parts(fields, metadata))); + let metric = ( + bounded_string(), + prop_oneof![Just(MetricKind::Absolute), Just(MetricKind::Incremental)], + metric_value(), + metric_tags(), + proptest::option::of(nonempty_bounded_string()), + timestamp(), + interval(), + metadata, + ) + .prop_map( + |(name, kind, value, tags, namespace, timestamp, interval, metadata)| { + Event::Metric( + Metric::new_with_metadata(name, kind, value, metadata) + .with_tags(tags) + .with_namespace(namespace) + .with_timestamp(timestamp) + .with_interval_ms(interval), + ) + }, + ); + + prop_oneof![log, metric, trace].boxed() +} + +fn without_metadata(mut event: Event) -> Event { + *event.metadata_mut() = EventMetadata::default(); + event +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(PROPERTY_TESTS))] + + #[test] + fn native_proto_is_canonical_for_arbitrary_events(event in event_strategy(proto_value())) { + let expected = event.clone(); + let serializer = &mut NativeSerializerConfig.build(); + let mut encoded = BytesMut::new(); + serializer.encode(event, &mut encoded).unwrap(); + + let mut decoded = NativeDeserializerConfig + .build() + .parse(encoded.clone().freeze(), LogNamespace::Legacy) + .unwrap(); + prop_assert_eq!(decoded.len(), 1); + let decoded = decoded.pop().unwrap(); + prop_assert_eq!( + decoded.metadata().source_event_id(), + expected.metadata().source_event_id() + ); + prop_assert_eq!(&decoded, &expected); + + let mut reencoded = BytesMut::new(); + serializer.encode(decoded, &mut reencoded).unwrap(); + + prop_assert_eq!(encoded, reencoded); + } + + #[test] + fn native_json_is_canonical_for_arbitrary_events(event in event_strategy(json_safe_value())) { + let expected = without_metadata(event.clone()); + let serializer = &mut NativeJsonSerializerConfig.build(); + let mut encoded = BytesMut::new(); + serializer.encode(event, &mut encoded).unwrap(); + + let mut decoded = NativeJsonDeserializerConfig::default() + .build() + .parse(encoded.clone().freeze(), LogNamespace::Legacy) + .unwrap(); + prop_assert_eq!(decoded.len(), 1); + let decoded = decoded.pop().unwrap(); + prop_assert_eq!(&decoded, &expected); + + let mut reencoded = BytesMut::new(); + serializer.encode(decoded, &mut reencoded).unwrap(); + + prop_assert_eq!(encoded, reencoded); + } +} + +#[test] +fn native_json_decodes_legacy_u32_metric_counts() { + let input = Bytes::from_static( + br#"{"metric":{"name":"requests","kind":"absolute","aggregated_histogram":{"buckets":[{"upper_limit":1.0,"count":4294967295}],"count":4294967295,"sum":2.0}}}"#, + ); + + let mut events = NativeJsonDeserializerConfig::default() + .build() + .parse(input, LogNamespace::Legacy) + .unwrap(); + let metric = events.pop().unwrap().into_metric(); + + assert_eq!( + metric.value(), + &MetricValue::AggregatedHistogram { + buckets: vec![vector_core::event::metric::Bucket { + upper_limit: 1.0, + count: u64::from(u32::MAX), + }], + count: u64::from(u32::MAX), + sum: 2.0, + } + ); +} + +#[test] +fn native_json_decodes_events_without_metadata() { + let input = Bytes::from_static(br#"{"log":{"message":"legacy"}}"#); + + let mut events = NativeJsonDeserializerConfig::default() + .build() + .parse(input, LogNamespace::Legacy) + .unwrap(); + let log = events.pop().unwrap().into_log(); + + assert_eq!( + log.get(event_path!("message")), + Some(&vector_core::event::Value::from("legacy")) + ); + assert_eq!( + log.metadata().value(), + &vector_core::event::Value::Object(Default::default()) + ); +} #[test] fn pre_v24_fixtures_match() { diff --git a/lib/vector-core/Cargo.toml b/lib/vector-core/Cargo.toml index 5d2159240d28f..82c7c8aee4ae8 100644 --- a/lib/vector-core/Cargo.toml +++ b/lib/vector-core/Cargo.toml @@ -66,7 +66,6 @@ vector-config = { path = "../vector-config" } vector-config-common = { path = "../vector-config-common" } vrl.workspace = true cfg-if.workspace = true -quickcheck = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] security-framework = "3.6.0" @@ -101,12 +100,6 @@ vector-common = { path = "../vector-common", default-features = false, features default = [] lua = ["dep:mlua", "dep:tokio-stream", "vrl/lua"] test = ["vector-common/test", "proptest"] -generate-fixtures = ["vrl/generate-fixtures", "dep:quickcheck"] - -[[bin]] -name = "generate-fixtures" -path = "src/bin/generate_fixtures.rs" -required-features = ["generate-fixtures"] [[bench]] name = "event" diff --git a/lib/vector-core/src/bin/generate_fixtures.rs b/lib/vector-core/src/bin/generate_fixtures.rs deleted file mode 100644 index 9ab133757bbf8..0000000000000 --- a/lib/vector-core/src/bin/generate_fixtures.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::{fs::File, io::Write, path::PathBuf}; - -use bytes::BytesMut; -use prost::Message; -use quickcheck::{Arbitrary as _, Gen}; -use vector_core::event::{Event, EventArray, proto}; - -const SEED: u64 = 0; -const GEN_SIZE: usize = 128; - -fn main() { - let fixture_dir = - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../codecs/tests/data/native_encoding"); - let json_dir = fixture_dir.join("json"); - let proto_dir = fixture_dir.join("proto"); - std::fs::create_dir_all(&json_dir).unwrap(); - std::fs::create_dir_all(&proto_dir).unwrap(); - - let mut rng = Gen::from_size_and_seed(GEN_SIZE, SEED); - for n in 0..1024_usize { - let event = Event::arbitrary(&mut rng); - - let mut json_out = File::create(json_dir.join(format!("{n:04}.json"))).unwrap(); - serde_json::to_writer(&mut json_out, &event).unwrap(); - - let mut proto_out = File::create(proto_dir.join(format!("{n:04}.pb"))).unwrap(); - let mut buf = BytesMut::new(); - proto::EventArray::from(EventArray::from(event)) - .encode(&mut buf) - .unwrap(); - proto_out.write_all(&buf).unwrap(); - } - - #[allow(clippy::print_stdout)] - { - println!("Written 1024 fixtures to {}", fixture_dir.display()); - } -} diff --git a/lib/vector-core/src/event/arbitrary_impl.rs b/lib/vector-core/src/event/arbitrary_impl.rs index fe02b846aa9b2..85c9a4f763828 100644 --- a/lib/vector-core/src/event/arbitrary_impl.rs +++ b/lib/vector-core/src/event/arbitrary_impl.rs @@ -21,25 +21,8 @@ const ALPHABET: [&str; 27] = [ "t", "u", "v", "w", "x", "y", "z", "_", ]; -// When generating fixtures we need f64 values that survive a JSON round-trip -// without any loss of precision or serialization ambiguity (NaN, -0.0). -// Under the `generate-fixtures` feature the helper produces clean values; -// otherwise it falls back to the standard quickcheck approach. fn f64_for_arbitrary(g: &mut Gen) -> f64 { - #[cfg(feature = "generate-fixtures")] - { - let mut value = f64::arbitrary(g) % MAX_F64_SIZE; - while value.is_nan() || value == -0.0 { - value = f64::arbitrary(g) % MAX_F64_SIZE; - } - let rounded = (value * 10_000.0).round() / 10_000.0; - // Rounding can produce -0.0 from small negatives; normalize to +0.0. - if rounded == -0.0_f64 { 0.0 } else { rounded } - } - #[cfg(not(feature = "generate-fixtures"))] - { - f64::arbitrary(g) % MAX_F64_SIZE - } + f64::arbitrary(g) % MAX_F64_SIZE } #[derive(Debug, Clone)] @@ -50,9 +33,6 @@ pub struct Name { impl Arbitrary for Name { fn arbitrary(g: &mut Gen) -> Self { let mut name = String::with_capacity(MAX_STR_SIZE); - #[cfg(feature = "generate-fixtures")] - let len = usize::max(1, g.size() % MAX_STR_SIZE); - #[cfg(not(feature = "generate-fixtures"))] let len = g.size() % MAX_STR_SIZE; for _ in 0..len { let idx: usize = usize::arbitrary(g) % ALPHABET.len(); @@ -101,9 +81,6 @@ impl Arbitrary for Event { impl Arbitrary for LogEvent { fn arbitrary(g: &mut Gen) -> Self { - #[cfg(feature = "generate-fixtures")] - let mut generator = Gen::from_size_and_seed(MAX_MAP_SIZE, u64::arbitrary(g)); - #[cfg(not(feature = "generate-fixtures"))] let mut generator = Gen::new(MAX_MAP_SIZE); let map: ObjectMap = ObjectMap::arbitrary(&mut generator); let metadata: EventMetadata = EventMetadata::arbitrary(g); @@ -242,8 +219,6 @@ impl Arbitrary for MetricValue { let mut sketch = AgentDDSketch::with_agent_defaults(); sketch.insert_many(&samples); - #[cfg(feature = "generate-fixtures")] - sketch.set_sum_avg(f64_for_arbitrary(g), f64_for_arbitrary(g)); MetricValue::Sketch { sketch: MetricSketch::AgentDDSketch(sketch), diff --git a/lib/vector-core/src/event/metric/tags.rs b/lib/vector-core/src/event/metric/tags.rs index eba5b2bd3587e..cb312fb9aaf9b 100644 --- a/lib/vector-core/src/event/metric/tags.rs +++ b/lib/vector-core/src/event/metric/tags.rs @@ -420,12 +420,15 @@ impl<'de> Deserialize<'de> for TagValueSet { enum Variants { // Backwards compatibility for existing data String(String), + // A single bare tag is serialized as null. + Null(()), // This is the new form of tag values Array(Vec), } Variants::deserialize(de).map(|v| match v { Variants::String(s) => Self::from([s]), + Variants::Null(()) => Self::from([TagValue::Bare]), Variants::Array(a) => Self::from(a), }) } @@ -614,7 +617,7 @@ impl ByteSizeOf for MetricTags { } } -#[cfg(any(test, feature = "generate-fixtures"))] +#[cfg(test)] mod test_support { use std::collections::HashSet; @@ -678,6 +681,17 @@ mod tests { assert!(tags.contains_key("a")); } + #[test] + fn single_bare_tag_value_set_json_roundtrip() { + let value = TagValueSet::from([TagValue::Bare]); + + let encoded = serde_json::to_string(&value).unwrap(); + let decoded: TagValueSet = serde_json::from_str(&encoded).unwrap(); + + assert_eq!(encoded, "null"); + assert_eq!(decoded, value); + } + proptest! { #[test] fn reduces_set_to_simple(mut values: TagValueSet) { diff --git a/lib/vector-core/src/event/mod.rs b/lib/vector-core/src/event/mod.rs index 2348c787f2069..f5a5a4c7c0c70 100644 --- a/lib/vector-core/src/event/mod.rs +++ b/lib/vector-core/src/event/mod.rs @@ -24,7 +24,7 @@ pub use vrl_target::{TargetEvents, VrlTarget}; use crate::config::{LogNamespace, OutputId}; -#[cfg(any(test, feature = "generate-fixtures"))] +#[cfg(test)] pub(crate) mod arbitrary_impl; pub mod array; pub mod discriminant; diff --git a/lib/vector-core/src/event/proto.rs b/lib/vector-core/src/event/proto.rs index 73878c9a68c5c..efb7fd9590cac 100644 --- a/lib/vector-core/src/event/proto.rs +++ b/lib/vector-core/src/event/proto.rs @@ -241,9 +241,13 @@ impl From for super::Metric { .collect(), ); // The current Vector encoding includes copies of the "single" values of tags in `tags_v2` - // above. This `extend` will re-add those values, forcing them to become the last added in - // the value set. - tags.extend(metric.tags_v1); + // above. Only re-add a v1 value when it disagrees with v2; inserting an already-selected + // value would reorder an otherwise canonical enhanced tag set. + for (tag, value) in metric.tags_v1 { + if tags.get(&tag) != Some(value.as_str()) { + tags.insert(tag, value); + } + } let tags = (!tags.is_empty()).then_some(tags); let value = super::MetricValue::from(metric.value.unwrap()); @@ -777,3 +781,149 @@ fn encode_array(items: Vec) -> ValueArray { items: items.into_iter().map(encode_value).collect(), } } + +#[cfg(test)] +mod tests { + use prost::Message as _; + + use super::*; + use crate::event::{MetricValue as EventMetricValue, metric}; + + // Frozen payloads emitted by the historical protobuf schema. These must not be regenerated + // from the current Rust types: their purpose is to pin the legacy field numbers on the wire. + const PRE_V24_METRICS: &[u8] = &[ + 18, 170, 1, 10, 38, 10, 10, 104, 105, 115, 116, 111, 103, 114, 97, 109, 49, 74, 24, 10, 8, + 0, 0, 0, 0, 0, 0, 248, 63, 18, 1, 2, 24, 2, 33, 0, 0, 0, 0, 0, 0, 8, 64, 10, 38, 10, 10, + 104, 105, 115, 116, 111, 103, 114, 97, 109, 50, 106, 24, 10, 11, 9, 0, 0, 0, 0, 0, 0, 248, + 63, 16, 2, 16, 2, 25, 0, 0, 0, 0, 0, 0, 8, 64, 10, 43, 10, 8, 115, 117, 109, 109, 97, 114, + 121, 49, 82, 31, 10, 8, 0, 0, 0, 0, 0, 0, 224, 63, 18, 8, 0, 0, 0, 0, 0, 0, 248, 63, 24, 2, + 33, 0, 0, 0, 0, 0, 0, 8, 64, 10, 43, 10, 8, 115, 117, 109, 109, 97, 114, 121, 50, 114, 31, + 10, 18, 9, 0, 0, 0, 0, 0, 0, 224, 63, 17, 0, 0, 0, 0, 0, 0, 248, 63, 16, 2, 25, 0, 0, 0, 0, + 0, 0, 8, 64, + ]; + const PRE_V27_TAGS: &[u8] = &[ + 10, 8, 114, 101, 113, 117, 101, 115, 116, 115, 26, 14, 10, 7, 115, 101, 114, 118, 105, 99, + 101, 18, 3, 97, 112, 105, 42, 9, 9, 0, 0, 0, 0, 0, 0, 240, 63, + ]; + const PRE_V34_LOG_METADATA: &[u8] = &[ + 26, 17, 10, 15, 108, 101, 103, 97, 99, 121, 32, 109, 101, 116, 97, 100, 97, 116, 97, + ]; + const PRE_V34_TRACE_METADATA: &[u8] = &[ + 18, 17, 10, 15, 108, 101, 103, 97, 99, 121, 32, 109, 101, 116, 97, 100, 97, 116, 97, + ]; + const PRE_V34_METRIC_METADATA: &[u8] = &[ + 10, 8, 114, 101, 113, 117, 101, 115, 116, 115, 42, 9, 9, 0, 0, 0, 0, 0, 0, 240, 63, 154, 1, + 17, 10, 15, 108, 101, 103, 97, 99, 121, 32, 109, 101, 116, 97, 100, 97, 116, 97, + ]; + const PRE_V41_METADATA: &[u8] = &[34, 6, 108, 101, 103, 97, 99, 121]; + + #[test] + fn decodes_pre_v24_histogram_and_summary_variants() { + let expected = [ + EventMetricValue::AggregatedHistogram { + buckets: vec![metric::Bucket { + upper_limit: 1.5, + count: 2, + }], + count: 2, + sum: 3.0, + }, + EventMetricValue::AggregatedHistogram { + buckets: vec![metric::Bucket { + upper_limit: 1.5, + count: 2, + }], + count: 2, + sum: 3.0, + }, + EventMetricValue::AggregatedSummary { + quantiles: vec![metric::Quantile { + quantile: 0.5, + value: 1.5, + }], + count: 2, + sum: 3.0, + }, + EventMetricValue::AggregatedSummary { + quantiles: vec![metric::Quantile { + quantile: 0.5, + value: 1.5, + }], + count: 2, + sum: 3.0, + }, + ]; + + let encoded = EventArray::decode(PRE_V24_METRICS).unwrap(); + let Some(event_array::Events::Metrics(metrics)) = encoded.events else { + panic!("legacy payload did not contain metrics"); + }; + let decoded = metrics + .metrics + .into_iter() + .map(crate::event::Metric::from) + .map(|metric| metric.value().clone()) + .collect::>(); + + assert_eq!(decoded, expected); + } + + #[test] + fn decodes_pre_v27_single_valued_metric_tags() { + let encoded = Metric::decode(PRE_V27_TAGS).unwrap(); + + let decoded = crate::event::Metric::from(encoded); + + assert_eq!(decoded.tag_value("service").as_deref(), Some("api")); + } + + #[test] + fn current_metric_tags_preserve_enhanced_value_order() { + let mut tags = metric::MetricTags::default(); + tags.set_multi_value( + "service".to_owned(), + [ + metric::TagValue::Value(String::new()), + metric::TagValue::Bare, + ], + ); + let event = crate::event::Metric::new( + "requests", + crate::event::MetricKind::Absolute, + EventMetricValue::Counter { value: 1.0 }, + ) + .with_tags(Some(tags)); + + let decoded = crate::event::Metric::from(Metric::from(event)); + let values = decoded + .tags() + .unwrap() + .iter_all() + .map(|(_, value)| value.map(str::to_owned)) + .collect::>(); + + assert_eq!(values, [Some(String::new()), None]); + } + + #[test] + #[allow(deprecated)] + fn decodes_pre_v34_metadata_for_all_event_types() { + let expected = VrlValue::from("legacy metadata"); + + let log = crate::event::LogEvent::from(Log::decode(PRE_V34_LOG_METADATA).unwrap()); + let trace = crate::event::TraceEvent::from(Trace::decode(PRE_V34_TRACE_METADATA).unwrap()); + let metric = crate::event::Metric::from(Metric::decode(PRE_V34_METRIC_METADATA).unwrap()); + + assert_eq!(log.metadata().value(), &expected); + assert_eq!(trace.metadata().value(), &expected); + assert_eq!(metric.metadata().value(), &expected); + } + + #[test] + fn decodes_pre_v41_metadata_without_source_event_id() { + let decoded = EventMetadata::from(Metadata::decode(PRE_V41_METADATA).unwrap()); + + assert_eq!(decoded.source_event_id(), None); + assert_eq!(decoded.source_type(), Some("legacy")); + } +} diff --git a/lib/vector-core/src/metrics/ddsketch.rs b/lib/vector-core/src/metrics/ddsketch.rs index d30ef5977ccb6..10c1a54f51169 100644 --- a/lib/vector-core/src/metrics/ddsketch.rs +++ b/lib/vector-core/src/metrics/ddsketch.rs @@ -284,17 +284,6 @@ impl AgentDDSketch { }) } - /// Overrides `sum` and `avg` with arbitrary values. - /// - /// Only available under the `generate-fixtures` feature, where we need to - /// produce sketches with independently-randomized summary statistics to - /// exercise round-trip serialization of those fields. - #[cfg(feature = "generate-fixtures")] - pub fn set_sum_avg(&mut self, sum: f64, avg: f64) { - self.sum = sum; - self.avg = avg; - } - pub fn gamma(&self) -> f64 { self.config.gamma_v }