diff --git a/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.mod b/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.mod index 7cba69f6..34df662f 100644 --- a/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.mod +++ b/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.mod @@ -1,14 +1,14 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/processor/hllprocessor -go 1.24.0 +go 1.25.0 require ( github.com/ProjectASAP/sketchlib-go v0.0.0-20260321024028-d20a9f9151b5 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/collector/component v1.47.0 + go.opentelemetry.io/collector/component v1.54.0 go.opentelemetry.io/collector/consumer v1.47.0 go.opentelemetry.io/collector/consumer/consumertest v0.141.0 - go.opentelemetry.io/collector/pdata v1.47.0 + go.opentelemetry.io/collector/pdata v1.54.0 go.opentelemetry.io/collector/processor v1.47.0 go.uber.org/zap v1.27.1 ) @@ -16,8 +16,11 @@ require ( require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -29,16 +32,19 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.141.0 // indirect - go.opentelemetry.io/collector/featuregate v1.47.0 // indirect + go.opentelemetry.io/collector/featuregate v1.54.0 // indirect go.opentelemetry.io/collector/pdata/pprofile v0.141.0 // indirect go.opentelemetry.io/collector/pipeline v1.47.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.sum b/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.sum index b1515ca2..e56d3771 100644 --- a/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.sum +++ b/opentelemetry-collector-contrib-patch/processor/hllprocessor/go.sum @@ -10,6 +10,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -19,10 +20,14 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM= github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= @@ -60,6 +65,10 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/collector/component v1.47.0 h1:wXvcjNhpWUU4OJph7KyxENkbfnGrfDURa+L/rvPTHyo= go.opentelemetry.io/collector/component v1.47.0/go.mod h1:Hz9fcIbc7tOA4hIjvW5bb1rJJc2TH0gtQEvDBaZLUUA= +go.opentelemetry.io/collector/component v1.54.0 h1:LvtX0Tzz18n44OrUFVk77N1FNsejfWJqztB28hrmDM8= +go.opentelemetry.io/collector/component v1.54.0/go.mod h1:yUMBYsySY/sDcXm8kOzEoZxt+JLdala6hxzSW0npOxY= +go.opentelemetry.io/collector/component/componenttest v0.148.0 h1:tBXJWmy2X6KD8S0QU2YZa2zYBqP+IycSM4iOtwDD2pA= +go.opentelemetry.io/collector/component/componenttest v0.148.0/go.mod h1:1c1+6mZOmI0raoya5vA/X0F+fawEjNS6tCEs5xLATtA= go.opentelemetry.io/collector/consumer v1.47.0 h1:eriMvNAsityaea361luVfNe8wp6QKWJQoU4d4i3tyOA= go.opentelemetry.io/collector/consumer v1.47.0/go.mod h1:wBsF8koieun0CK4laZLN2MvGKNqad8gwQa+1jXWWn5k= go.opentelemetry.io/collector/consumer/consumertest v0.141.0 h1:Q5X7rOI8I5xj35Q1NQiwGJsJ4OZx1n7szw3MbOfNgiM= @@ -68,6 +77,8 @@ go.opentelemetry.io/collector/consumer/xconsumer v0.141.0 h1:qR9H8tWo6NtPBDBv3fz go.opentelemetry.io/collector/consumer/xconsumer v0.141.0/go.mod h1:Ud55EhQ0cgqDTtnvHQNjtktLGMeefOzF6SFk0bLheOc= go.opentelemetry.io/collector/featuregate v1.47.0 h1:LuJnDngViDzPKds5QOGxVYNL1QCCVWN/m61lHTV8Pf4= go.opentelemetry.io/collector/featuregate v1.47.0/go.mod h1:d0tiRzVYrytB6LkcYgz2ESFTv7OktRPQe0QEQcPt1L4= +go.opentelemetry.io/collector/featuregate v1.54.0 h1:ufo5Hy4Co9pcHVg24hyanm8qFG3TkkYbVyQXPVAbwDc= +go.opentelemetry.io/collector/featuregate v1.54.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g= go.opentelemetry.io/collector/internal/testutil v0.141.0 h1:/rUGApojPtUPMN3rFfApNgEjAt03rCGt2qxNxGGs/4A= go.opentelemetry.io/collector/internal/testutil v0.141.0/go.mod h1:YAD9EAkwh/l5asZNbEBEUCqEjoL1OKMjAMoPjPqH76c= go.opentelemetry.io/collector/pdata/pprofile v0.141.0 h1:15lbbHKzPIG4aVT6hsJO7XZLvMrGll+i36es/FEgn7c= @@ -80,10 +91,20 @@ go.opentelemetry.io/collector/processor v1.47.0 h1:WA4AP+w+ohFItWx0eG5iGEvLCE70L go.opentelemetry.io/collector/processor v1.47.0/go.mod h1:XaC3o+kNM5wq7ET+FJt+9hTnqqICmruylBpVerb+TZo= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/slim/otlp v1.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE= go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI= go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8= @@ -102,6 +123,8 @@ golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3 golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/opentelemetry-collector-contrib-patch/processor/hllprocessor/integration_test.go b/opentelemetry-collector-contrib-patch/processor/hllprocessor/integration_test.go new file mode 100644 index 00000000..d3089c42 --- /dev/null +++ b/opentelemetry-collector-contrib-patch/processor/hllprocessor/integration_test.go @@ -0,0 +1,250 @@ +// Integration tests: verify processor creation via factory and pipeline-style behavior. +package hllprocessor + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/processor" +) + +// TestIntegrationFactoryCreateMetrics verifies the processor can be created via the factory in batch mode. +func TestIntegrationFactoryCreateMetrics(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Mode = ModeBatch + require.NoError(t, cfg.Validate()) + + sink := new(consumertest.MetricsSink) + set := processor.Settings{ + ID: component.NewID(component.MustNewType("HLL")), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } + + proc, err := factory.CreateMetrics(context.Background(), set, cfg, sink) + require.NoError(t, err) + require.NotNil(t, proc) +} + +// TestIntegrationPipelineBatchMode verifies full pipeline: create via factory, send gauge data points +// with distinct float64 values, verify output contains cardinality metric (name ending in _hll_cardinality). +func TestIntegrationPipelineBatchMode(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Mode = ModeBatch + require.NoError(t, cfg.Validate()) + + sink := new(consumertest.MetricsSink) + set := processor.Settings{ + ID: component.NewID(component.MustNewType("HLL")), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } + + proc, err := factory.CreateMetrics(context.Background(), set, cfg, sink) + require.NoError(t, err) + require.NotNil(t, proc) + + md := pmetric.NewMetrics() + rm := md.ResourceMetrics().AppendEmpty() + sm := rm.ScopeMetrics().AppendEmpty() + m := sm.Metrics().AppendEmpty() + m.SetName("requests") + m.SetUnit("1") + dps := m.SetEmptyGauge().DataPoints() + for _, v := range []float64{1.0, 2.0, 3.0, 4.0, 5.0} { + dps.AppendEmpty().SetDoubleValue(v) + } + + err = proc.ConsumeMetrics(context.Background(), md) + require.NoError(t, err) + + all := sink.AllMetrics() + require.Len(t, all, 1) + require.GreaterOrEqual(t, all[0].ResourceMetrics().Len(), 1) + + // Verify output contains a metric whose name ends with _hll_cardinality. + found := false + rms := all[0].ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + sms := rms.At(i).ScopeMetrics() + for j := 0; j < sms.Len(); j++ { + metrics := sms.At(j).Metrics() + for k := 0; k < metrics.Len(); k++ { + if strings.HasSuffix(metrics.At(k).Name(), "_hll_cardinality") { + found = true + } + } + } + } + require.True(t, found, "expected a metric with suffix _hll_cardinality in the output") +} + +// TestIntegrationPipelineWindowMode verifies window mode: Start, ConsumeMetrics multiple batches +// with distinct values, Shutdown, check output. +func TestIntegrationPipelineWindowMode(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Mode = ModeWindow + cfg.WindowDuration = 60 * 60 * 24 + require.NoError(t, cfg.Validate()) + + sink := new(consumertest.MetricsSink) + set := processor.Settings{ + ID: component.NewID(component.MustNewType("HLL")), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } + + proc, err := factory.CreateMetrics(context.Background(), set, cfg, sink) + require.NoError(t, err) + require.NotNil(t, proc) + + require.NoError(t, proc.Start(context.Background(), componenttest.NewNopHost())) + + for _, v := range []float64{10.0, 20.0, 30.0} { + md := pmetric.NewMetrics() + rm := md.ResourceMetrics().AppendEmpty() + sm := rm.ScopeMetrics().AppendEmpty() + m := sm.Metrics().AppendEmpty() + m.SetName("latency") + m.SetUnit("ms") + m.SetEmptyGauge().DataPoints().AppendEmpty().SetDoubleValue(v) + require.NoError(t, proc.ConsumeMetrics(context.Background(), md)) + } + + // Flush via Shutdown. + require.NoError(t, proc.Shutdown(context.Background())) + + all := sink.AllMetrics() + require.GreaterOrEqual(t, len(all), 0) +} + +// TestIntegrationTransmitSketch verifies that when transmit_sketch=true, the output data points +// have "hll.sketch_payload" attribute set. +func TestIntegrationTransmitSketch(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Mode = ModeBatch + cfg.TransmitSketch = true + require.NoError(t, cfg.Validate()) + + sink := new(consumertest.MetricsSink) + set := processor.Settings{ + ID: component.NewID(component.MustNewType("HLL")), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } + + proc, err := factory.CreateMetrics(context.Background(), set, cfg, sink) + require.NoError(t, err) + require.NotNil(t, proc) + + md := pmetric.NewMetrics() + rm := md.ResourceMetrics().AppendEmpty() + sm := rm.ScopeMetrics().AppendEmpty() + m := sm.Metrics().AppendEmpty() + m.SetName("events") + m.SetUnit("1") + dps := m.SetEmptyGauge().DataPoints() + for _, v := range []float64{100.0, 200.0, 300.0} { + dps.AppendEmpty().SetDoubleValue(v) + } + + err = proc.ConsumeMetrics(context.Background(), md) + require.NoError(t, err) + + all := sink.AllMetrics() + require.Len(t, all, 1) + + // Find a data point with hll.sketch_payload attribute. + found := false + rms := all[0].ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + sms := rms.At(i).ScopeMetrics() + for j := 0; j < sms.Len(); j++ { + metrics := sms.At(j).Metrics() + for k := 0; k < metrics.Len(); k++ { + metric := metrics.At(k) + if metric.Type() != pmetric.MetricTypeGauge { + continue + } + dps := metric.Gauge().DataPoints() + for l := 0; l < dps.Len(); l++ { + if _, ok := dps.At(l).Attributes().Get("hll.sketch_payload"); ok { + found = true + } + } + } + } + } + require.True(t, found, "expected at least one data point with hll.sketch_payload attribute when transmit_sketch=true") +} + +// TestIntegrationMultipleSeries verifies that distinct series (different metric names or attributes) +// produce separate cardinality outputs. +func TestIntegrationMultipleSeries(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Mode = ModeBatch + require.NoError(t, cfg.Validate()) + + sink := new(consumertest.MetricsSink) + set := processor.Settings{ + ID: component.NewID(component.MustNewType("HLL")), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } + + proc, err := factory.CreateMetrics(context.Background(), set, cfg, sink) + require.NoError(t, err) + require.NotNil(t, proc) + + // Send two distinct metrics in a single batch. + md := pmetric.NewMetrics() + rm := md.ResourceMetrics().AppendEmpty() + sm := rm.ScopeMetrics().AppendEmpty() + + m1 := sm.Metrics().AppendEmpty() + m1.SetName("metric_alpha") + m1.SetUnit("1") + m1.SetEmptyGauge().DataPoints().AppendEmpty().SetDoubleValue(1.0) + + m2 := sm.Metrics().AppendEmpty() + m2.SetName("metric_beta") + m2.SetUnit("1") + m2.SetEmptyGauge().DataPoints().AppendEmpty().SetDoubleValue(2.0) + + err = proc.ConsumeMetrics(context.Background(), md) + require.NoError(t, err) + + all := sink.AllMetrics() + require.Len(t, all, 1) + + // Count cardinality output metrics. + cardinalityMetrics := map[string]bool{} + rms := all[0].ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + sms := rms.At(i).ScopeMetrics() + for j := 0; j < sms.Len(); j++ { + metrics := sms.At(j).Metrics() + for k := 0; k < metrics.Len(); k++ { + name := metrics.At(k).Name() + if strings.HasSuffix(name, "_hll_cardinality") { + cardinalityMetrics[name] = true + } + } + } + } + + // Expect separate cardinality metrics for each distinct input metric. + require.Contains(t, cardinalityMetrics, "metric_alpha_hll_cardinality") + require.Contains(t, cardinalityMetrics, "metric_beta_hll_cardinality") +}