From 3dd9cbce3a7139a53f64e270cbcf1f6380f458fa Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Mon, 8 Jun 2026 10:27:55 +0200 Subject: [PATCH 1/3] feat: add trace_id pprof label --- README.md | 4 +--- gradle.properties | 2 +- .../java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java | 4 ++++ .../java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java | 5 ++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73b5d06..420e486 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ link traces with the profiling data, and find specific lines of code related to * Because of how sampling profilers work, spans shorter than the sample interval may not be captured. By default pyroscope CPU profiler probes stack traces 100 times per second, meaning that spans shorter than 10ms may not be captured. -Java code can be easily instrumented with otel-profiling-java package - -a `OpenTelemetry` implementation, that annotates profiling data with span IDs which makes it possible to filter -out profile of a particular trace span in Pyroscope. +Java code can be easily instrumented with the otel-profiling-java package, an `OpenTelemetry` implementation that annotates profiling data with `trace_id` and `span_id` labels, which makes it possible to filter the profile of a particular trace (or trace span) in Pyroscope. Visit [docs](https://grafana.com/docs/pyroscope/latest/configure-client/trace-span-profiles/java-span-profiles/) page for usage and configuration documentation. diff --git a/gradle.properties b/gradle.properties index b8f49d4..0d4ad8b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -pyroscope_version=2.5.1 +pyroscope_version=2.6.0 # x-release-please-start-version otel_profiling_version=2.0.6 # x-release-please-end diff --git a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java index c29d0ca..090c68b 100644 --- a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -63,6 +63,9 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(ATTRIBUTE_KEY_PROFILE_ID, strProfileId); asprof.setTracingContext(spanId, spanName); + String traceId = span.getSpanContext().getTraceId(); + asprof.setTraceId(Long.parseUnsignedLong(traceId.substring(0, 16), 16), + Long.parseUnsignedLong(traceId.substring(16, 32), 16)); } @Override @@ -71,6 +74,7 @@ public void onEnd(ReadableSpan span) { return; } asprof.setTracingContext(0, 0); + asprof.setTraceId(0L, 0L); } public static long parseSpanId(String strProfileId) { diff --git a/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java b/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java index 54e476e..3b13e69 100644 --- a/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -52,6 +52,7 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(ATTRIBUTE_KEY_PROFILE_ID, strProfileId); api.setTracingContext(spanId, spanName); + api.setTraceId(span.getSpanContext().getTraceId()); } @Override @@ -59,7 +60,9 @@ public void onEnd(ReadableSpan span) { if (configuration.rootSpanOnly && !isRootSpan(span)) { return; } - getProfiler().setTracingContext(0, 0); + ProfilerApi api = getProfiler(); + api.setTracingContext(0, 0); + api.clearTraceId(); } public static long parseSpanId(String strProfileId) { From b2327dc81e4adddaf3b9045c86f139ce05461a2f Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Tue, 9 Jun 2026 16:38:43 +0200 Subject: [PATCH 2/3] perf: parse trace_id hex without allocating substrings --- .../pyroscope/PyroscopeOtelSpanProcessor.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java index 090c68b..fa60ac7 100644 --- a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -63,9 +63,10 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(ATTRIBUTE_KEY_PROFILE_ID, strProfileId); asprof.setTracingContext(spanId, spanName); + // W3C trace ID is 32 hex chars (128 bits). Parse directly into two longs + // to avoid the String#substring allocations on this hot path. String traceId = span.getSpanContext().getTraceId(); - asprof.setTraceId(Long.parseUnsignedLong(traceId.substring(0, 16), 16), - Long.parseUnsignedLong(traceId.substring(16, 32), 16)); + asprof.setTraceId(parseHex64(traceId, 0), parseHex64(traceId, 16)); } @Override @@ -88,6 +89,25 @@ public static long parseSpanId(String strProfileId) { } } + static long parseHex64(String s, int offset) { + long result = 0L; + for (int i = 0; i < 16; i++) { + int c = s.charAt(offset + i); + int nibble; + if (c >= '0' && c <= '9') { + nibble = c - '0'; + } else if (c >= 'a' && c <= 'f') { + nibble = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + nibble = c - 'A' + 10; + } else { + throw new NumberFormatException("invalid hex char in trace_id at index " + (offset + i)); + } + result = (result << 4) | nibble; + } + return result; + } + public static boolean isRootSpan(ReadableSpan span) { SpanContext parent = span.getParentSpanContext(); boolean noParent = parent == SpanContext.getInvalid(); From edd20e96d8ac2a8de62988a89b9f9877914b52eb Mon Sep 17 00:00:00 2001 From: Marc Sanmiquel Date: Tue, 23 Jun 2026 15:57:09 +0200 Subject: [PATCH 3/3] fix: guard setTraceId against malformed trace IDs --- .../java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java | 6 +++++- .../java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java index fa60ac7..5ce0040 100644 --- a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -66,7 +66,11 @@ public void onStart(Context parentContext, ReadWriteSpan span) { // W3C trace ID is 32 hex chars (128 bits). Parse directly into two longs // to avoid the String#substring allocations on this hot path. String traceId = span.getSpanContext().getTraceId(); - asprof.setTraceId(parseHex64(traceId, 0), parseHex64(traceId, 16)); + try { + asprof.setTraceId(parseHex64(traceId, 0), parseHex64(traceId, 16)); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + asprof.setTraceId(0, 0); + } } @Override diff --git a/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java b/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java index 3b13e69..33734d8 100644 --- a/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/otel-extension/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -52,7 +52,11 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(ATTRIBUTE_KEY_PROFILE_ID, strProfileId); api.setTracingContext(spanId, spanName); - api.setTraceId(span.getSpanContext().getTraceId()); + try { + api.setTraceId(span.getSpanContext().getTraceId()); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + api.clearTraceId(); + } } @Override