Skip to content

Instantly share code, notes, and snippets.

@florianl
Created February 11, 2026 14:22
Show Gist options
  • Select an option

  • Save florianl/eeabde73d708182acbbc62d02b91dc6b to your computer and use it in GitHub Desktop.

Select an option

Save florianl/eeabde73d708182acbbc62d02b91dc6b to your computer and use it in GitHub Desktop.
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