From 2bf10e02671fddccc639e117c26542cdb1efacae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Pa=C5=82ka?= Date: Fri, 29 Dec 2023 12:13:59 +0100 Subject: [PATCH] added JFR configuration which enables only supported JFR event types --- .../javaagent/AsyncProfilerDelegate.java | 2 +- .../javaagent/CurrentPidProvider.java | 11 ++- .../javaagent/JFRProfilerDelegate.java | 74 ++++++++++--------- .../pyroscope/javaagent/ProfilerDelegate.java | 11 +++ .../pyroscope/javaagent/PyroscopeAgent.java | 6 +- .../io/pyroscope/javaagent/config/Config.java | 7 +- .../javaagent/config/ProfilerType.java | 23 +++++- agent/src/main/resources/pyroscope.jfc | 44 +++++++++++ 8 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 agent/src/main/resources/pyroscope.jfc diff --git a/agent/src/main/java/io/pyroscope/javaagent/AsyncProfilerDelegate.java b/agent/src/main/java/io/pyroscope/javaagent/AsyncProfilerDelegate.java index d5bdb84..438659e 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/AsyncProfilerDelegate.java +++ b/agent/src/main/java/io/pyroscope/javaagent/AsyncProfilerDelegate.java @@ -27,7 +27,7 @@ public final class AsyncProfilerDelegate implements ProfilerDelegate { private final AsyncProfiler instance = PyroscopeAsyncProfiler.getAsyncProfiler(); - AsyncProfilerDelegate(Config config) { + public AsyncProfilerDelegate(Config config) { setConfig(config); } diff --git a/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java index 869b929..089651e 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java +++ b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java @@ -10,18 +10,17 @@ public class CurrentPidProvider { public static int getCurrentProcessId() { - RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); Field jvm = null; try { jvm = runtime.getClass().getDeclaredField("jvm"); - jvm.setAccessible(true); + jvm.setAccessible(true); - VMManagement management = (VMManagement) jvm.get(runtime); - Method method = management.getClass().getDeclaredMethod("getProcessId"); - method.setAccessible(true); + VMManagement management = (VMManagement) jvm.get(runtime); + Method method = management.getClass().getDeclaredMethod("getProcessId"); + method.setAccessible(true); - return (Integer) method.invoke(management); + return (Integer) method.invoke(management); } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { throw new RuntimeException(e); } diff --git a/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java b/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java index 2c29630..aefc735 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java +++ b/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java @@ -3,31 +3,29 @@ import io.pyroscope.http.Format; import io.pyroscope.javaagent.config.Config; import io.pyroscope.labels.Pyroscope; -import io.pyroscope.labels.io.pyroscope.PyroscopeAsyncProfiler; -import one.profiler.AsyncProfiler; -import one.profiler.Counter; -import java.io.DataInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; import java.util.ArrayList; import java.util.List; public final class JFRProfilerDelegate implements ProfilerDelegate { + private static final String RECORDING_NAME = "pyroscope"; private Config config; private File tempJFRFile; + private Path jcmdBin; - JFRProfilerDelegate(Config config) { + public JFRProfilerDelegate(Config config) { setConfig(config); } public void setConfig(final Config config) { this.config = config; - + jcmdBin = findJcmdBin(); try { // flight recorder is built on top of a file descriptor, so we need a file. tempJFRFile = File.createTempFile("pyroscope", ".jfr"); @@ -43,15 +41,16 @@ public void setConfig(final Config config) { public synchronized void start() { try { List commands = new ArrayList<>(); - commands.add("jcmd"); + commands.add(jcmdBin.toString()); commands.add(String.valueOf(CurrentPidProvider.getCurrentProcessId())); commands.add("JFR.start"); - commands.add("name=Pyroscope"); - commands.add("filename="+tempJFRFile.getAbsolutePath()); + commands.add("name=" + RECORDING_NAME); + commands.add("filename=" + tempJFRFile.getAbsolutePath()); + commands.add("settings=pyroscope"); ProcessBuilder processBuilder = new ProcessBuilder(commands); Process process = processBuilder.start(); int exitCode = process.waitFor(); - if (exitCode != 0){ + if (exitCode != 0) { throw new RuntimeException("Invalid exit code: " + exitCode); } } catch (IOException e) { @@ -67,14 +66,14 @@ public synchronized void start() { public synchronized void stop() { try { List commands = new ArrayList<>(); - commands.add("jcmd"); + commands.add(jcmdBin.toString()); commands.add(String.valueOf(CurrentPidProvider.getCurrentProcessId())); commands.add("JFR.stop"); - commands.add("name=Pyroscope"); + commands.add("name=" + RECORDING_NAME); ProcessBuilder processBuilder = new ProcessBuilder(commands); Process process = processBuilder.start(); int exitCode = process.waitFor(); - if (exitCode != 0){ + if (exitCode != 0) { throw new RuntimeException("Invalid exit code: " + exitCode); } } catch (IOException e) { @@ -85,9 +84,8 @@ public synchronized void stop() { } /** - * * @param started - time when profiling has been started - * @param ended - time when profiling has ended + * @param ended - time when profiling has ended * @return Profiling data and dynamic labels as {@link Snapshot} */ public synchronized Snapshot dumpProfile(Instant started, Instant ended) { @@ -98,27 +96,31 @@ private Snapshot dumpImpl(Instant started, Instant ended) { if (config.gcBeforeDump) { System.gc(); } - final byte[] data; - data = dumpJFR(); - return new Snapshot( - Format.JFR, - EventType.CPU, - started, - ended, - data, - Pyroscope.LabelsWrapper.dump() - ); - } - - private byte[] dumpJFR() { try { - byte[] bytes = new byte[(int) tempJFRFile.length()]; - try (DataInputStream ds = new DataInputStream(new FileInputStream(tempJFRFile))) { - ds.readFully(bytes); - } - return bytes; + byte[] data = Files.readAllBytes(tempJFRFile.toPath()); + return new Snapshot( + Format.JFR, + EventType.CPU, + started, + ended, + data, + Pyroscope.LabelsWrapper.dump() + ); } catch (IOException e) { throw new IllegalStateException(e); } } + + private static Path findJcmdBin() { + Path javaHome = Paths.get(System.getProperty("java.home")); + //find jcmd binary + Path jcmdBin = javaHome.resolve("bin/jcmd"); + if (!Files.isExecutable(jcmdBin)) { + jcmdBin = javaHome.getParent().resolve("bin/jcmd"); + if (!Files.isExecutable(jcmdBin)) { + throw new RuntimeException("cannot find executable jcmd in Java home"); + } + } + return jcmdBin; + } } diff --git a/agent/src/main/java/io/pyroscope/javaagent/ProfilerDelegate.java b/agent/src/main/java/io/pyroscope/javaagent/ProfilerDelegate.java index 0801687..313d0d1 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/ProfilerDelegate.java +++ b/agent/src/main/java/io/pyroscope/javaagent/ProfilerDelegate.java @@ -1,10 +1,21 @@ package io.pyroscope.javaagent; import io.pyroscope.javaagent.config.Config; +import io.pyroscope.javaagent.config.ProfilerType; import java.time.Instant; public interface ProfilerDelegate { + /** + * Creates profiler delegate instance based on configuration. + * + * @param config + * @return + */ + static ProfilerDelegate create(Config config) { + return config.profilerType.create(config); + } + void start(); void stop(); diff --git a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java index a25d307..02e977d 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java +++ b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java @@ -76,14 +76,13 @@ private Options(Builder b) { public static class Builder { final Config config; - final ProfilerDelegate profiler; + ProfilerDelegate profiler; Exporter exporter; ProfilingScheduler scheduler; Logger logger; public Builder(Config config) { this.config = config; - this.profiler = new AsyncProfilerDelegate(config); } public Builder setExporter(Exporter exporter) { @@ -115,6 +114,9 @@ public Options build() { scheduler = new SamplingProfilingScheduler(config, exporter, logger); } } + if (profiler == null) { + profiler = ProfilerDelegate.create(config); + } return new Options(this); } } diff --git a/agent/src/main/java/io/pyroscope/javaagent/config/Config.java b/agent/src/main/java/io/pyroscope/javaagent/config/Config.java index a875850..2a8309f 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/config/Config.java +++ b/agent/src/main/java/io/pyroscope/javaagent/config/Config.java @@ -90,7 +90,7 @@ public final class Config { public final boolean agentEnabled; public final String applicationName; - private final ProfilerType profilerType; + public final ProfilerType profilerType; public final Duration profilingInterval; public final EventType profilingEvent; public final String profilingAlloc; @@ -854,6 +854,11 @@ public Builder setBasicAuthPassword(String basicAuthPassword) { return this; } + public Builder setProfilerType(ProfilerType profilerType) { + this.profilerType = profilerType; + return this; + } + public Config build() { if (applicationName == null || applicationName.isEmpty()) { applicationName = generateApplicationName(); diff --git a/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java b/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java index 8a7c778..783b146 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java +++ b/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java @@ -1,5 +1,26 @@ package io.pyroscope.javaagent.config; +import io.pyroscope.javaagent.AsyncProfilerDelegate; +import io.pyroscope.javaagent.JFRProfilerDelegate; +import io.pyroscope.javaagent.ProfilerDelegate; + +import java.lang.reflect.InvocationTargetException; + public enum ProfilerType { - JFR, ASYNC; + JFR(JFRProfilerDelegate.class), ASYNC(AsyncProfilerDelegate.class); + + private final Class profilerDelegateClass; + + ProfilerType(Class profilerDelegateClass) { + this.profilerDelegateClass = profilerDelegateClass; + } + + public ProfilerDelegate create(Config config) { + try { + return profilerDelegateClass.getConstructor(Config.class).newInstance(config); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } } diff --git a/agent/src/main/resources/pyroscope.jfc b/agent/src/main/resources/pyroscope.jfc new file mode 100644 index 0000000..51ef780 --- /dev/null +++ b/agent/src/main/resources/pyroscope.jfc @@ -0,0 +1,44 @@ + + + + + + true + 1 ms + + + + true + true + 10 ms + + + + true + true + + + + true + true + + + + true + true + 10 ms + + + + true + true + 10 ms + + + + true + + +