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
@@ -0,0 +1,105 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// envelope_decode_test.go pins the wire-format invariants the
// patched DDSketch processor's decoder relies on. Background:
// post-#262, the SDK's metricdata.DDSketchEncodingProtoDelta path
// landed but the OTLP encoding-string switch was incomplete and
// the SDK's DDSketch aggregator (internal/aggregate/ddsketch.go)
// still serializes via DataDog `sketchpb.DDSketch` — a different
// proto schema than the sketchlib-go `SketchEnvelope{DDSketchState}`
// the agent decoder expects. The structural mismatch surfaces as
// `DDSketchState.alpha: invalid wire type: LengthDelimited (expected
// SixtyFourBit)` on the consuming side. These tests pin the
// decoder's expected-input contract so the next time someone touches
// the encoder we catch a re-introduction of the same drift.

package ddsketchprocessor

import (
"testing"

commonpb "github.com/ProjectASAP/sketchlib-go/proto/common"
ddpb "github.com/ProjectASAP/sketchlib-go/proto/ddsketch"
envpb "github.com/ProjectASAP/sketchlib-go/proto/sketch_envelope"
ddsketch "github.com/ProjectASAP/sketchlib-go/sketches/DDSketch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)

// TestDecodeDDSketchEnvelope_RoundTripFull confirms a happy-path
// SerializePortable → proto.Marshal → decodeDDSketchEnvelope round
// trip succeeds and the recovered sketch matches the input. This is
// the wire-format contract the EXPORTER_SDK_AGG=dd-full path
// requires once the SDK encoder is migrated off DataDog's
// sketchpb.DDSketch onto sketchlib-go's portable shape.
func TestDecodeDDSketchEnvelope_RoundTripFull(t *testing.T) {
src := ddsketch.NewDDSketch(0.01)
for _, v := range []float64{1.5, 2.5, 3.5, 100.0, 99.0} {
src.Update(v)
}
env, err := src.SerializePortable()
require.NoError(t, err)
bytes, err := proto.Marshal(env)
require.NoError(t, err)

recovered, err := decodeDDSketchEnvelope(bytes)
require.NoError(t, err)
require.NotNil(t, recovered)
assert.Equal(t, src.GetCount(), recovered.GetCount())
}

// TestDecodeDDSketchEnvelope_BareStateRejected confirms that a bare
// DDSketchState (no envelope wrapper) is rejected. proto.Unmarshal
// trips on the field-number / wire-type clash between
// SketchEnvelope.format_version (varint, field 1) and
// DDSketchState.alpha (fixed64, field 1), surfacing as a generic
// `cannot parse invalid wire-format data` error. The decoder
// returns this as-is without reaching the GetDdsketch sentinel —
// the runtime's dispatcher then falls through to bare-state and
// delta decode paths, which is the documented contract.
func TestDecodeDDSketchEnvelope_BareStateRejected(t *testing.T) {
bare := &ddpb.DDSketchState{
Alpha: 0.01,
StoreCounts: []uint64{1, 2, 3},
StoreOffset: 0,
Count: 6,
Sum: 42,
Min: 1.5,
Max: 99.0,
}
bytes, err := proto.Marshal(bare)
require.NoError(t, err)

got, err := decodeDDSketchEnvelope(bytes)
require.Error(t, err)
assert.Nil(t, got)
}

// TestDecodeDDSketchEnvelope_EmptyEnvelopeRejected pins that an
// envelope with no oneof variant set (e.g. an envelope built without
// a sketch_state field, which is exactly the wire-shape the runtime
// produces when `proto.Unmarshal` is fed bytes from a different proto
// schema entirely — like DataDog's sketchpb.DDSketch) trips the same
// "did not carry DDSketchState" error path. This is the class of
// failure the sweep agent's `EXPORTER_SDK_AGG=dd-full` cell hit; the
// fix lives in the SDK encoder (out of this PR's scope) and is
// tracked as sweep-blocker-1 follow-up.
func TestDecodeDDSketchEnvelope_EmptyEnvelopeRejected(t *testing.T) {
env := &envpb.SketchEnvelope{
FormatVersion: 1,
Producer: &commonpb.ProducerInfo{
Library: "test",
Version: "0",
},
// No sketch_state oneof set.
}
bytes, err := proto.Marshal(env)
require.NoError(t, err)

got, err := decodeDDSketchEnvelope(bytes)
require.Error(t, err)
assert.Nil(t, got)
assert.Contains(t, err.Error(), "did not carry DDSketchState")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Tests for the *EncodingValue helpers that map metricdata-typed
// encoding strings to the OTLP proto enum. Splits out from the
// auto-generated metricdata_test.go (which is regenerated from
// internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl)
// so the new cases survive a re-template.
//
// Background: PR #262 introduced metricdata.DDSketchEncodingProtoDelta
// alongside the pre-existing CountSketch / CountMinSketch / HLLSketch
// delta encodings, but DDSketchEncodingValue's switch was not extended
// to cover it. The OTLP exporter then rejected EXPORTER_SDK_AGG=dd-delta
// with `unknown ddsketch encoding: ddsketch_proto_delta`. These tests
// pin the post-#262 absorption.

package transform

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/sdk/metric/metricdata"
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
)

func TestDDSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.DDSketchEncoding
want mpb.DDSketchEncoding
}{
{"proto_full", metricdata.DDSketchEncodingProto, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO},
{"proto_delta", metricdata.DDSketchEncodingProtoDelta, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := DDSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestDDSketchEncodingValue_Unknown(t *testing.T) {
got, err := DDSketchEncodingValue(metricdata.DDSketchEncoding("not-a-real-encoding"))
require.Error(t, err)
assert.Equal(t, mpb.DDSketchEncoding_DDSKETCH_ENCODING_UNSPECIFIED, got)
}

// Sibling sketches: confirm Delta + Proto map cleanly today so we
// catch regressions the next time someone touches the switch.

func TestKLLSketchEncodingValue_Proto(t *testing.T) {
got, err := KLLSketchEncodingValue(metricdata.KLLSketchEncodingProto)
require.NoError(t, err)
assert.Equal(t, mpb.KLLSketchEncoding_KLL_SKETCH_ENCODING_PROTO, got)
}

func TestCountSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.CountSketchEncoding
want mpb.CountSketchEncoding
}{
{"proto", metricdata.CountSketchEncodingProto, mpb.CountSketchEncoding_COUNT_SKETCH_ENCODING_PROTO},
{"delta", metricdata.CountSketchEncodingDelta, mpb.CountSketchEncoding_COUNT_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CountSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestCountMinSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.CountMinSketchEncoding
want mpb.CountMinSketchEncoding
}{
{"proto", metricdata.CountMinSketchEncodingProto, mpb.CountMinSketchEncoding_COUNT_MIN_SKETCH_ENCODING_PROTO},
{"delta", metricdata.CountMinSketchEncodingDelta, mpb.CountMinSketchEncoding_COUNT_MIN_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CountMinSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestHLLSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.HLLSketchEncoding
want mpb.HLLSketchEncoding
}{
{"proto", metricdata.HLLSketchEncodingProto, mpb.HLLSketchEncoding_HLL_SKETCH_ENCODING_PROTO},
{"delta", metricdata.HLLSketchEncodingDelta, mpb.HLLSketchEncoding_HLL_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := HLLSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

// TestDDSketchDataPointsCarriesProtoDeltaEncoding round-trips a
// dd-delta-encoded SDK data point through DDSketchDataPoints and
// asserts the OTLP enum lands at PROTO_DELTA. This is the path
// EXPORTER_SDK_AGG=dd-delta exercises end-to-end.
func TestDDSketchDataPointsCarriesProtoDeltaEncoding(t *testing.T) {
in := []metricdata.DDSketchDataPoint[float64]{
{
Sketch: []byte{0x01, 0x02, 0x03},
Encoding: metricdata.DDSketchEncodingProtoDelta,
},
}
out, err := DDSketchDataPoints(in)
require.NoError(t, err)
require.Len(t, out, 1)
assert.Equal(t, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO_DELTA, out[0].Encoding)
assert.Equal(t, []byte{0x01, 0x02, 0x03}, out[0].Sketch)
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Tests for the *EncodingValue helpers that map metricdata-typed
// encoding strings to the OTLP proto enum. Splits out from the
// auto-generated metricdata_test.go (which is regenerated from
// internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl)
// so the new cases survive a re-template.
//
// Background: PR #262 introduced metricdata.DDSketchEncodingProtoDelta
// alongside the pre-existing CountSketch / CountMinSketch / HLLSketch
// delta encodings, but DDSketchEncodingValue's switch was not extended
// to cover it. The OTLP exporter then rejected EXPORTER_SDK_AGG=dd-delta
// with `unknown ddsketch encoding: ddsketch_proto_delta`. These tests
// pin the post-#262 absorption.

package transform

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/sdk/metric/metricdata"
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
)

func TestDDSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.DDSketchEncoding
want mpb.DDSketchEncoding
}{
{"proto_full", metricdata.DDSketchEncodingProto, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO},
{"proto_delta", metricdata.DDSketchEncodingProtoDelta, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := DDSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestDDSketchEncodingValue_Unknown(t *testing.T) {
got, err := DDSketchEncodingValue(metricdata.DDSketchEncoding("not-a-real-encoding"))
require.Error(t, err)
assert.Equal(t, mpb.DDSketchEncoding_DDSKETCH_ENCODING_UNSPECIFIED, got)
}

// Sibling sketches: confirm Delta + Proto map cleanly today so we
// catch regressions the next time someone touches the switch.

func TestKLLSketchEncodingValue_Proto(t *testing.T) {
got, err := KLLSketchEncodingValue(metricdata.KLLSketchEncodingProto)
require.NoError(t, err)
assert.Equal(t, mpb.KLLSketchEncoding_KLL_SKETCH_ENCODING_PROTO, got)
}

func TestCountSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.CountSketchEncoding
want mpb.CountSketchEncoding
}{
{"proto", metricdata.CountSketchEncodingProto, mpb.CountSketchEncoding_COUNT_SKETCH_ENCODING_PROTO},
{"delta", metricdata.CountSketchEncodingDelta, mpb.CountSketchEncoding_COUNT_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CountSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestCountMinSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.CountMinSketchEncoding
want mpb.CountMinSketchEncoding
}{
{"proto", metricdata.CountMinSketchEncodingProto, mpb.CountMinSketchEncoding_COUNT_MIN_SKETCH_ENCODING_PROTO},
{"delta", metricdata.CountMinSketchEncodingDelta, mpb.CountMinSketchEncoding_COUNT_MIN_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CountMinSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

func TestHLLSketchEncodingValue(t *testing.T) {
tests := []struct {
name string
in metricdata.HLLSketchEncoding
want mpb.HLLSketchEncoding
}{
{"proto", metricdata.HLLSketchEncodingProto, mpb.HLLSketchEncoding_HLL_SKETCH_ENCODING_PROTO},
{"delta", metricdata.HLLSketchEncodingDelta, mpb.HLLSketchEncoding_HLL_SKETCH_ENCODING_DELTA},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := HLLSketchEncodingValue(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}

// TestDDSketchDataPointsCarriesProtoDeltaEncoding round-trips a
// dd-delta-encoded SDK data point through DDSketchDataPoints and
// asserts the OTLP enum lands at PROTO_DELTA. This is the path
// EXPORTER_SDK_AGG=dd-delta exercises end-to-end.
func TestDDSketchDataPointsCarriesProtoDeltaEncoding(t *testing.T) {
in := []metricdata.DDSketchDataPoint[float64]{
{
Sketch: []byte{0x01, 0x02, 0x03},
Encoding: metricdata.DDSketchEncodingProtoDelta,
},
}
out, err := DDSketchDataPoints(in)
require.NoError(t, err)
require.Len(t, out, 1)
assert.Equal(t, mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO_DELTA, out[0].Encoding)
assert.Equal(t, []byte{0x01, 0x02, 0x03}, out[0].Sketch)
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ func DDSketchEncodingValue(enc metricdata.DDSketchEncoding) (mpb.DDSketchEncodin
switch enc {
case metricdata.DDSketchEncodingProto:
return mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO, nil
case metricdata.DDSketchEncodingProtoDelta:
return mpb.DDSketchEncoding_DDSKETCH_ENCODING_PROTO_DELTA, nil
default:
return mpb.DDSketchEncoding_DDSKETCH_ENCODING_UNSPECIFIED, fmt.Errorf("%w: %s", errUnknownDDSketchEncoding, enc)
}
Expand Down