Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
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
)

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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}