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..5ce0040 100644 --- a/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java +++ b/lib/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java @@ -63,6 +63,14 @@ 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(); + try { + asprof.setTraceId(parseHex64(traceId, 0), parseHex64(traceId, 16)); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + asprof.setTraceId(0, 0); + } } @Override @@ -71,6 +79,7 @@ public void onEnd(ReadableSpan span) { return; } asprof.setTracingContext(0, 0); + asprof.setTraceId(0L, 0L); } public static long parseSpanId(String strProfileId) { @@ -84,6 +93,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(); 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..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,6 +52,11 @@ public void onStart(Context parentContext, ReadWriteSpan span) { span.setAttribute(ATTRIBUTE_KEY_PROFILE_ID, strProfileId); api.setTracingContext(spanId, spanName); + try { + api.setTraceId(span.getSpanContext().getTraceId()); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + api.clearTraceId(); + } } @Override @@ -59,7 +64,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) {