From 07250d3adc94b93799c7f04259d54d3a5b8e0739 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 27 Jul 2021 15:42:02 -0700 Subject: [PATCH] Add JFR profile analyzer (#3678) * Add JFR profile analyzer * Spotless * Nullable --- benchmark-jfr-analyzer/build.gradle.kts | 9 + .../javaagent/benchmark/jfr/Analyzer.java | 183 ++++++++++++++++++ settings.gradle.kts | 1 + 3 files changed, 193 insertions(+) create mode 100644 benchmark-jfr-analyzer/build.gradle.kts create mode 100644 benchmark-jfr-analyzer/src/main/java/io/opentelemetry/javaagent/benchmark/jfr/Analyzer.java diff --git a/benchmark-jfr-analyzer/build.gradle.kts b/benchmark-jfr-analyzer/build.gradle.kts new file mode 100644 index 000000000000..07d0987696f3 --- /dev/null +++ b/benchmark-jfr-analyzer/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +tasks.withType().configureEach { + with(options) { + release.set(11) + } +} diff --git a/benchmark-jfr-analyzer/src/main/java/io/opentelemetry/javaagent/benchmark/jfr/Analyzer.java b/benchmark-jfr-analyzer/src/main/java/io/opentelemetry/javaagent/benchmark/jfr/Analyzer.java new file mode 100644 index 000000000000..92c9b611b6df --- /dev/null +++ b/benchmark-jfr-analyzer/src/main/java/io/opentelemetry/javaagent/benchmark/jfr/Analyzer.java @@ -0,0 +1,183 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.benchmark.jfr; + +import java.io.File; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.jfr.consumer.RecordingFile; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings("SystemOut") +public class Analyzer { + + private static final Node syntheticRootNode = new Node(""); + private static int totalSamples = 0; + + public static void main(String[] args) throws Exception { + File jfrFile = new File(args[0]); + List events = + RecordingFile.readAllEvents(jfrFile.toPath()).stream() + .filter(e -> e.getEventType().getName().equals("jdk.ExecutionSample")) + .collect(Collectors.toList()); + + Set agentCallers = getAgentCallers(events); + + for (RecordedEvent event : events) { + totalSamples++; + processStackTrace(event.getStackTrace(), agentCallers); + } + + int totalAgentSamples = 0; + for (Node rootNode : syntheticRootNode.getOrderedChildNodes()) { + totalAgentSamples += rootNode.count; + } + + System.out.println("Total samples: " + totalSamples); + System.out.print("Total agent samples: " + totalAgentSamples); + System.out.format(" (%.2f%%)%n", 100 * totalAgentSamples / (double) totalSamples); + System.out.println(); + for (Node rootNode : syntheticRootNode.getOrderedChildNodes()) { + printNode(rootNode, 0); + } + } + + // getting direct callers since those are likely the instrumented methods + private static Set getAgentCallers(List events) { + return events.stream() + .map(e -> getAgentCaller(e.getStackTrace())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Nullable + private static String getAgentCaller(RecordedStackTrace stackTrace) { + List frames = stackTrace.getFrames(); + for (int i = frames.size() - 1; i >= 0; i--) { + RecordedFrame frame = frames.get(i); + RecordedMethod method = frame.getMethod(); + if (isAgentMethod(method)) { + RecordedFrame callerFrame = frames.get(i + 1); + RecordedMethod callerMethod = callerFrame.getMethod(); + return getStackTraceElement(callerMethod, callerFrame); + } + } + return null; + } + + private static void printNode(Node node, int indent) { + for (int i = 0; i < indent; i++) { + System.out.print(" "); + } + System.out.format("%3d %s%n", node.count, node.frame); + for (Node childNode : node.getOrderedChildNodes()) { + printNode(childNode, indent + 1); + } + } + + private static void processStackTrace(RecordedStackTrace stackTrace, Set agentCallers) { + boolean analyze = false; + int analyzeFromIndex = 0; + List frames = stackTrace.getFrames(); + for (int i = frames.size() - 1; i >= 0; i--) { + RecordedFrame frame = frames.get(i); + RecordedMethod method = frame.getMethod(); + String stackTraceElement = getStackTraceElement(method, frame); + if (agentCallers.contains(stackTraceElement)) { + if (i == 0) { + analyze = true; + analyzeFromIndex = i; + break; + } + RecordedMethod nextMethod = frames.get(i - 1).getMethod(); + String nextClassName = nextMethod.getType().getName(); + // calls to java.* inside of the agent caller (likely an instrumented method) are + // potentially part of the injected agent code + if (nextClassName.startsWith("java.") || isAgentMethod(nextMethod)) { + analyze = true; + analyzeFromIndex = Math.min(i + 2, frames.size() - 1); + break; + } + } + if (isAgentMethod(method)) { + analyze = true; + analyzeFromIndex = Math.min(i + 1, frames.size() - 1); + break; + } + } + if (!analyze) { + return; + } + Node node = syntheticRootNode; + for (int i = analyzeFromIndex; i >= 0; i--) { + RecordedFrame frame = frames.get(i); + RecordedMethod method = frame.getMethod(); + String stackTraceElement = getStackTraceElement(method, frame); + node = node.recordChildSample(stackTraceElement); + } + } + + private static boolean isAgentMethod(RecordedMethod method) { + String className = method.getType().getName(); + String methodName = method.getName(); + return className.startsWith("io.opentelemetry.javaagent.") + && !className.startsWith("io.opentelemetry.javaagent.benchmark.") + // this shows up in stack traces because it's part of the filter chain + && !(className.equals( + "io.opentelemetry.javaagent.instrumentation.springwebmvc.HandlerMappingResourceNameFilter") + && methodName.equals("doFilter")); + } + + private static String getStackTraceElement(RecordedMethod method, RecordedFrame frame) { + return method.getType().getName() + + "." + + method.getName() + + "() line: " + + frame.getLineNumber(); + } + + private static class Node { + + private final String frame; + private final Map childNodes = new HashMap<>(); + private int count; + + private Node(String frame) { + this.frame = frame; + } + + private Node recordChildSample(String stackTraceElement) { + Node childNode = childNodes.get(stackTraceElement); + if (childNode == null) { + childNode = new Node(stackTraceElement); + childNodes.put(stackTraceElement, childNode); + } + childNode.count++; + return childNode; + } + + private List getOrderedChildNodes() { + return childNodes.values().stream() + .sorted(Comparator.comparingInt(Node::getCount).reversed()) + .collect(Collectors.toList()); + } + + private int getCount() { + return count; + } + } + + private Analyzer() {} +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7e6b4ce727d3..a2f7a973c5d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -331,3 +331,4 @@ include(":instrumentation:wicket-8.0:javaagent") include(":benchmark") include(":benchmark-e2e") include(":benchmark-overhead-jmh") +include(":benchmark-jfr-analyzer")