Files
Arpit SrivastavaandGitHub 7f5772c2b9 [exporter/debug] output buckets for exponential histograms (#14656)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description
the `writeexponentialhistogramdatapoints` function in the debug
exporter’s normal verbosity mode previously had todo placeholder and did
not print any bucket data. this change implements the missing bucket
display.

each bucket is now printed as `leX=Y`, where `X` is the computed upper
bound and `Y` is the bucket count. for positive buckets, the upper bound
is calculated as `exp((index+1) * factor)`, and for negative buckets as
`-exp(index * factor)`, where `factor = math.Ldexp(math.Ln2, -scale)`.
negative buckets are listed from most-negative to least-negative, and
the zero bucket is printed as `zero=N` when its count is non-zero.
<!-- Issue number if applicable -->
#### Link to tracking issue
Fixes #10463

<!--Describe what testing was performed and which tests were added.-->
#### Testing

<!--Describe the documentation added.-->
#### Documentation
No documentation changes required.
<!--Please delete paragraphs that you did not use before submitting.-->

---------

Signed-off-by: arpit529srivastava <[email protected]>
2026-03-01 17:14:53 +00:00

212 lines
7.7 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func TestMarshalMetrics(t *testing.T) {
tests := []struct {
name string
input pmetric.Metrics
expected string
}{
{
name: "empty metrics",
input: pmetric.NewMetrics(),
expected: "",
},
{
name: "sum data point",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("system.cpu.time")
dataPoint := metric.SetEmptySum().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(123.456)
dataPoint.Attributes().PutStr("state", "user")
dataPoint.Attributes().PutStr("cpu", "0")
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
system.cpu.time{state=user,cpu=0} 123.456
`,
},
{
name: "data point with resource and scope attributes",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
resourceMetrics.SetSchemaUrl("https://opentelemetry.io/resource-schema-url")
resourceMetrics.Resource().Attributes().PutStr("resourceKey1", "resourceValue1")
resourceMetrics.Resource().Attributes().PutBool("resourceKey2", false)
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
scopeMetrics.SetSchemaUrl("http://opentelemetry.io/scope-schema-url")
scopeMetrics.Scope().SetName("scope-name")
scopeMetrics.Scope().SetVersion("1.2.3")
scopeMetrics.Scope().Attributes().PutStr("scopeKey1", "scopeValue1")
scopeMetrics.Scope().Attributes().PutBool("scopeKey2", true)
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("system.cpu.time")
dataPoint := metric.SetEmptySum().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(123.456)
dataPoint.Attributes().PutStr("state", "user")
dataPoint.Attributes().PutStr("cpu", "0")
return metrics
}(),
expected: `ResourceMetrics #0 [https://opentelemetry.io/resource-schema-url] resourceKey1=resourceValue1 resourceKey2=false
ScopeMetrics #0 [email protected] [http://opentelemetry.io/scope-schema-url] scopeKey1=scopeValue1 scopeKey2=true
system.cpu.time{state=user,cpu=0} 123.456
`,
},
{
name: "gauge data point",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("system.cpu.utilization")
dataPoint := metric.SetEmptyGauge().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(78.901234567)
dataPoint.Attributes().PutStr("state", "free")
dataPoint.Attributes().PutStr("cpu", "8")
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
system.cpu.utilization{state=free,cpu=8} 78.901234567
`,
},
{
name: "histogram",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.ExplicitBounds().FromRaw([]float64{0.125, 0.5, 1, 3})
dataPoint.BucketCounts().FromRaw([]uint64{1324, 13, 0, 2, 1})
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
http.server.request.duration{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573 min=0.017 max=8.13 le0.125=1324 le0.5=13 le1=0 le3=2 1
`,
},
{
name: "exponential histogram without buckets",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
http.server.request.duration{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573 min=0.017 max=8.13
`,
},
{
name: "exponential histogram with buckets",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
dataPoint.SetScale(3)
dataPoint.SetZeroCount(3)
dataPoint.Negative().SetOffset(-2)
dataPoint.Negative().BucketCounts().FromRaw([]uint64{10, 20})
dataPoint.Positive().SetOffset(1)
dataPoint.Positive().BucketCounts().FromRaw([]uint64{40, 50, 60})
return metrics
}(),
expected: func() string {
f := math.Ldexp(math.Ln2, -3)
return fmt.Sprintf("ResourceMetrics #0\nScopeMetrics #0\n"+
"http.server.request.duration{http.response.status_code=200,http.request.method=GET}"+
" count=1340 sum=99.573 min=0.017 max=8.13"+
" le%v=20 le%v=10 zero=3 le%v=40 le%v=50 le%v=60\n",
-math.Exp(-1*f), -math.Exp(-2*f),
math.Exp(2*f), math.Exp(3*f), math.Exp(4*f))
}(),
},
{
name: "exponential histogram with positive buckets only",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("latency")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.SetCount(2)
dataPoint.SetScale(1)
dataPoint.Positive().SetOffset(1)
dataPoint.Positive().BucketCounts().FromRaw([]uint64{1, 1})
return metrics
}(),
expected: func() string {
f := math.Ldexp(math.Ln2, -1)
return fmt.Sprintf("ResourceMetrics #0\nScopeMetrics #0\n"+
"latency{} count=2 le%v=1 le%v=1\n",
math.Exp(2*f), math.Exp(3*f))
}(),
},
{
name: "summary",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("summary")
dataPoint := metric.SetEmptySummary().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
quantile := dataPoint.QuantileValues().AppendEmpty()
quantile.SetQuantile(0.01)
quantile.SetValue(15)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
summary{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573000 q0.01=15
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalMetricsMarshaler().MarshalMetrics(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}