Created
February 11, 2026 14:22
-
-
Save florianl/eeabde73d708182acbbc62d02b91dc6b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From d1238d4e93d7da728fe06e7d107d23d8e00aa1d2 Mon Sep 17 00:00:00 2001 | |
| From: Florian Lehner <florian.lehner@elastic.co> | |
| Date: Wed, 11 Feb 2026 14:47:28 +0100 | |
| Subject: [PATCH] implement benchmark | |
| Signed-off-by: Florian Lehner <florian.lehner@elastic.co> | |
| --- | |
| pdata/pprofile/go.mod | 2 + | |
| pdata/pprofile/go.sum | 14 +-- | |
| pdata/pprofile/pb_bench_test.go | 186 ++++++++++++++++++++++++++++++++ | |
| pdata/pprofile/pb_test.go | 3 + | |
| 4 files changed, 199 insertions(+), 6 deletions(-) | |
| create mode 100644 pdata/pprofile/pb_bench_test.go | |
| diff --git a/pdata/pprofile/go.mod b/pdata/pprofile/go.mod | |
| index e0f7ce147..47cd08aa5 100644 | |
| --- a/pdata/pprofile/go.mod | |
| +++ b/pdata/pprofile/go.mod | |
| @@ -7,6 +7,7 @@ require ( | |
| go.opentelemetry.io/collector/featuregate v1.51.0 | |
| go.opentelemetry.io/collector/internal/testutil v0.145.0 | |
| go.opentelemetry.io/collector/pdata v1.51.0 | |
| + go.opentelemetry.io/otel v1.40.0 | |
| go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 | |
| go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.2.0 | |
| go.uber.org/goleak v1.3.0 | |
| @@ -15,6 +16,7 @@ require ( | |
| ) | |
| require ( | |
| + github.com/cespare/xxhash/v2 v2.3.0 // indirect | |
| github.com/davecgh/go-spew v1.1.1 // indirect | |
| github.com/hashicorp/go-version v1.8.0 // indirect | |
| github.com/json-iterator/go v1.1.12 // indirect | |
| diff --git a/pdata/pprofile/go.sum b/pdata/pprofile/go.sum | |
| index cd05b79bd..e753f9c46 100644 | |
| --- a/pdata/pprofile/go.sum | |
| +++ b/pdata/pprofile/go.sum | |
| @@ -1,3 +1,5 @@ | |
| +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | |
| +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | |
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
| @@ -36,16 +38,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu | |
| github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | |
| go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= | |
| go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= | |
| -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/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= | |
| -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= | |
| +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= | |
| +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= | |
| +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= | |
| +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= | |
| go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= | |
| go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= | |
| go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= | |
| go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= | |
| -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.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= | |
| +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= | |
| 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= | |
| diff --git a/pdata/pprofile/pb_bench_test.go b/pdata/pprofile/pb_bench_test.go | |
| new file mode 100644 | |
| index 000000000..b38852dd3 | |
| --- /dev/null | |
| +++ b/pdata/pprofile/pb_bench_test.go | |
| @@ -0,0 +1,186 @@ | |
| +// Copyright The OpenTelemetry Authors | |
| +// SPDX-License-Identifier: Apache-2.0 | |
| + | |
| +package pprofile | |
| + | |
| +import ( | |
| + "fmt" | |
| + "testing" | |
| + | |
| + semconv "go.opentelemetry.io/otel/semconv/v1.39.0" | |
| +) | |
| + | |
| +// generateProfiles creates a Profiles object with the specified number of resources, scopes, profiles, and samples. | |
| +func generateProfiles(b *testing.B, resourceCount, scopeCount, profileCount, sampleCount int) Profiles { | |
| + b.Helper() | |
| + | |
| + profiles := NewProfiles() | |
| + dict := profiles.Dictionary() | |
| + | |
| + // Pre-populate dictionary with common strings | |
| + dict.StringTable().Append("") // Index 0 is always empty string | |
| + dict.StringTable().Append("cpu") | |
| + dict.StringTable().Append("nanoseconds") | |
| + dict.StringTable().Append("samples") | |
| + dict.StringTable().Append("count") | |
| + | |
| + // Generate resource profiles | |
| + for r := 0; r < resourceCount; r++ { | |
| + rp := profiles.ResourceProfiles().AppendEmpty() | |
| + rp.SetSchemaUrl(semconv.SchemaURL) | |
| + resource := rp.Resource() | |
| + | |
| + // Add resource attributes | |
| + attrs := resource.Attributes() | |
| + attrs.PutStr(string(semconv.ServiceNameKey), fmt.Sprintf("service-%d", r)) | |
| + attrs.PutStr(string(semconv.ServiceVersionKey), fmt.Sprintf("version-%d", r)) | |
| + attrs.PutStr(string(semconv.ProcessPIDKey), fmt.Sprintf("%d", 1000+r)) | |
| + attrs.PutStr(string(semconv.K8SPodNameKey), fmt.Sprintf("pod-%d", r%10)) | |
| + attrs.PutStr(string(semconv.K8SNamespaceNameKey), "default") | |
| + attrs.PutStr(string(semconv.TelemetrySDKNameKey), "opentelemetry") | |
| + | |
| + // Generate scope profiles | |
| + for s := 0; s < scopeCount; s++ { | |
| + sp := rp.ScopeProfiles().AppendEmpty() | |
| + sp.SetSchemaUrl(semconv.SchemaURL) | |
| + scope := sp.Scope() | |
| + scope.SetName(fmt.Sprintf("profiler-scope-%d", s)) | |
| + scope.SetVersion("1.0.0") | |
| + | |
| + // Generate profiles | |
| + for p := 0; p < profileCount; p++ { | |
| + profile := sp.Profiles().AppendEmpty() | |
| + | |
| + // Add sample types | |
| + sampleType := profile.SampleType() | |
| + sampleType.SetTypeStrindex(1) // "cpu" | |
| + sampleType.SetUnitStrindex(2) // "nanoseconds" | |
| + | |
| + // Add period type | |
| + periodType := profile.PeriodType() | |
| + periodType.SetTypeStrindex(1) // "cpu" | |
| + periodType.SetUnitStrindex(2) // "nanoseconds" | |
| + profile.SetPeriod(1000000) | |
| + | |
| + // Generate samples | |
| + samples := profile.Samples() | |
| + for i := 0; i < sampleCount; i++ { | |
| + sample := samples.AppendEmpty() | |
| + sample.SetStackIndex(int32(i % 100)) | |
| + | |
| + // Add attribute indices for samples | |
| + sample.AttributeIndices().Append(int32(i % 10)) | |
| + } | |
| + } | |
| + } | |
| + } | |
| + | |
| + return profiles | |
| +} | |
| + | |
| +func BenchmarkUnmarshalProfiles(b *testing.B) { | |
| + testCases := []struct { | |
| + name string | |
| + resourceCount int | |
| + scopeCount int | |
| + profileCount int | |
| + sampleCount int | |
| + }{ | |
| + { | |
| + name: "small", | |
| + resourceCount: 1, | |
| + scopeCount: 1, | |
| + profileCount: 1, | |
| + sampleCount: 100, | |
| + }, | |
| + { | |
| + name: "medium", | |
| + resourceCount: 5, | |
| + scopeCount: 2, | |
| + profileCount: 2, | |
| + sampleCount: 500, | |
| + }, | |
| + { | |
| + name: "large", | |
| + resourceCount: 20, | |
| + scopeCount: 3, | |
| + profileCount: 5, | |
| + sampleCount: 1000, | |
| + }, | |
| + } | |
| + | |
| + for _, tc := range testCases { | |
| + b.Run(tc.name, func(b *testing.B) { | |
| + // Generate profile data and marshal it | |
| + profiles := generateProfiles(b, tc.resourceCount, tc.scopeCount, tc.profileCount, tc.sampleCount) | |
| + marshaler := &ProtoMarshaler{} | |
| + data, err := marshaler.MarshalProfiles(profiles) | |
| + if err != nil { | |
| + b.Fatalf("failed to marshal profiles: %v", err) | |
| + } | |
| + | |
| + unmarshaler := &ProtoUnmarshaler{} | |
| + b.ResetTimer() | |
| + b.ReportAllocs() | |
| + | |
| + for i := 0; i < b.N; i++ { | |
| + profiles, err := unmarshaler.UnmarshalProfiles(data) | |
| + if err != nil { | |
| + b.Fatalf("failed to unmarshal: %v", err) | |
| + } | |
| + _ = profiles | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +func BenchmarkMarshalProfiles(b *testing.B) { | |
| + testCases := []struct { | |
| + name string | |
| + resourceCount int | |
| + scopeCount int | |
| + profileCount int | |
| + sampleCount int | |
| + }{ | |
| + { | |
| + name: "small", | |
| + resourceCount: 1, | |
| + scopeCount: 1, | |
| + profileCount: 1, | |
| + sampleCount: 100, | |
| + }, | |
| + { | |
| + name: "medium", | |
| + resourceCount: 5, | |
| + scopeCount: 2, | |
| + profileCount: 2, | |
| + sampleCount: 500, | |
| + }, | |
| + { | |
| + name: "large", | |
| + resourceCount: 20, | |
| + scopeCount: 3, | |
| + profileCount: 5, | |
| + sampleCount: 1000, | |
| + }, | |
| + } | |
| + | |
| + for _, tc := range testCases { | |
| + b.Run(tc.name, func(b *testing.B) { | |
| + // Generate profile data once | |
| + profiles := generateProfiles(b, tc.resourceCount, tc.scopeCount, tc.profileCount, tc.sampleCount) | |
| + | |
| + marshaler := &ProtoMarshaler{} | |
| + b.ResetTimer() | |
| + b.ReportAllocs() | |
| + | |
| + for i := 0; i < b.N; i++ { | |
| + buf, err := marshaler.MarshalProfiles(profiles) | |
| + if err != nil { | |
| + b.Fatalf("failed to marshal: %v", err) | |
| + } | |
| + _ = buf | |
| + } | |
| + }) | |
| + } | |
| +} | |
| diff --git a/pdata/pprofile/pb_test.go b/pdata/pprofile/pb_test.go | |
| index 7dc3ee110..12f093a67 100644 | |
| --- a/pdata/pprofile/pb_test.go | |
| +++ b/pdata/pprofile/pb_test.go | |
| @@ -86,6 +86,8 @@ func BenchmarkProfilesToProto(b *testing.B) { | |
| marshaler := &ProtoMarshaler{} | |
| profiles := generateBenchmarkProfiles(128) | |
| + b.ResetTimer() | |
| + b.ReportAllocs() | |
| for b.Loop() { | |
| buf, err := marshaler.MarshalProfiles(profiles) | |
| require.NoError(b, err) | |
| @@ -101,6 +103,7 @@ func BenchmarkProfilesFromProto(b *testing.B) { | |
| require.NoError(b, err) | |
| assert.NotEmpty(b, buf) | |
| + b.ResetTimer() | |
| b.ReportAllocs() | |
| for b.Loop() { | |
| profiles, err := unmarshaler.UnmarshalProfiles(buf) | |
| -- | |
| 2.51.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment