From 3d3e4f2e3c98b7138292969c92165f4e328ba228 Mon Sep 17 00:00:00 2001 From: Wiktor-Sztajerowski Date: Sat, 23 Dec 2023 21:44:00 +0100 Subject: [PATCH] JFR delegate implementation --- .../javaagent/CurrentPidProvider.java | 29 +++++ .../javaagent/JFRProfilerDelegate.java | 110 +++++++----------- .../pyroscope/javaagent/PyroscopeAgent.java | 3 +- .../io/pyroscope/javaagent/config/Config.java | 17 +++ .../javaagent/config/ProfilerType.java | 5 + 5 files changed, 98 insertions(+), 66 deletions(-) create mode 100644 agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java create mode 100644 agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java diff --git a/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java new file mode 100644 index 0000000..869b929 --- /dev/null +++ b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java @@ -0,0 +1,29 @@ +package io.pyroscope.javaagent; + +import sun.management.VMManagement; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class CurrentPidProvider { + public static int getCurrentProcessId() { + + RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); + Field jvm = null; + try { + jvm = runtime.getClass().getDeclaredField("jvm"); + jvm.setAccessible(true); + + VMManagement management = (VMManagement) jvm.get(runtime); + Method method = management.getClass().getDeclaredMethod("getProcessId"); + method.setAccessible(true); + + 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 4233aa4..2c29630 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java +++ b/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java @@ -14,64 +14,74 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; - +import java.util.ArrayList; +import java.util.List; public final class JFRProfilerDelegate implements ProfilerDelegate { private Config config; - private EventType eventType; - private String alloc; - private String lock; - private Duration interval; - private Format format; private File tempJFRFile; - private final AsyncProfiler instance = PyroscopeAsyncProfiler.getAsyncProfiler(); - JFRProfilerDelegate(Config config) { setConfig(config); } public void setConfig(final Config config) { this.config = config; - this.alloc = config.profilingAlloc; - this.lock = config.profilingLock; - this.eventType = config.profilingEvent; - this.interval = config.profilingInterval; - this.format = config.format; - if (format == Format.JFR) { - try { - // flight recorder is built on top of a file descriptor, so we need a file. - tempJFRFile = File.createTempFile("pyroscope", ".jfr"); - tempJFRFile.deleteOnExit(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } else { - tempJFRFile = null; + try { + // flight recorder is built on top of a file descriptor, so we need a file. + tempJFRFile = File.createTempFile("pyroscope", ".jfr"); + tempJFRFile.deleteOnExit(); + } catch (IOException e) { + throw new IllegalStateException(e); } } /** - * Start async-profiler + * Start JFR profiler */ public synchronized void start() { - if (format == Format.JFR) { - try { - instance.execute(createJFRCommand()); - } catch (IOException e) { - throw new IllegalStateException(e); + try { + List commands = new ArrayList<>(); + commands.add("jcmd"); + commands.add(String.valueOf(CurrentPidProvider.getCurrentProcessId())); + commands.add("JFR.start"); + commands.add("name=Pyroscope"); + commands.add("filename="+tempJFRFile.getAbsolutePath()); + ProcessBuilder processBuilder = new ProcessBuilder(commands); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + if (exitCode != 0){ + throw new RuntimeException("Invalid exit code: " + exitCode); } - } else { - instance.start(eventType.id, interval.toNanos()); + } catch (IOException e) { + throw new IllegalStateException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); } } /** - * Stop async-profiler + * Stop JFR profiler */ public synchronized void stop() { - instance.stop(); + try { + List commands = new ArrayList<>(); + commands.add("jcmd"); + commands.add(String.valueOf(CurrentPidProvider.getCurrentProcessId())); + commands.add("JFR.stop"); + commands.add("name=Pyroscope"); + ProcessBuilder processBuilder = new ProcessBuilder(commands); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + if (exitCode != 0){ + throw new RuntimeException("Invalid exit code: " + exitCode); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } /** @@ -84,45 +94,15 @@ public synchronized Snapshot dumpProfile(Instant started, Instant ended) { return dumpImpl(started, ended); } - - - private String createJFRCommand() { - StringBuilder sb = new StringBuilder(); - sb.append("start,event=").append(eventType.id); - if (alloc != null && !alloc.isEmpty()) { - sb.append(",alloc=").append(alloc); - if (config.allocLive) { - sb.append(",live"); - } - } - if (lock != null && !lock.isEmpty()) { - sb.append(",lock=").append(lock); - } - sb.append(",interval=").append(interval.toNanos()) - .append(",file=").append(tempJFRFile.toString()); - if (config.APLogLevel != null) { - sb.append(",loglevel=").append(config.APLogLevel); - } - sb.append(",jstackdepth=").append(config.javaStackDepthMax); - if (config.APExtraArguments != null) { - sb.append(",").append(config.APExtraArguments); - } - return sb.toString(); - } - private Snapshot dumpImpl(Instant started, Instant ended) { if (config.gcBeforeDump) { System.gc(); } final byte[] data; - if (format == Format.JFR) { data = dumpJFR(); - } else { - data = instance.dumpCollapsed(Counter.SAMPLES).getBytes(StandardCharsets.UTF_8); - } return new Snapshot( - format, - eventType, + Format.JFR, + EventType.CPU, started, ended, data, diff --git a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java index a726dca..a25d307 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java +++ b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java @@ -30,7 +30,8 @@ public static void start() { } public static void start(Config config) { - start(new Options.Builder(config).build()); + start(new Options.Builder(config) + .build()); } public static void start(Options options) { 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 1e4f0bf..a875850 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/config/Config.java +++ b/agent/src/main/java/io/pyroscope/javaagent/config/Config.java @@ -28,6 +28,7 @@ public final class Config { private static final String PYROSCOPE_AGENT_ENABLED_CONFIG = "PYROSCOPE_AGENT_ENABLED"; private static final String PYROSCOPE_APPLICATION_NAME_CONFIG = "PYROSCOPE_APPLICATION_NAME"; private static final String PYROSCOPE_PROFILING_INTERVAL_CONFIG = "PYROSCOPE_PROFILING_INTERVAL"; + private static final String PYROSCOPE_PROFILER_TYPE_CONFIG = "PYROSCOPE_PROFILER_TYPE"; private static final String PYROSCOPE_PROFILER_EVENT_CONFIG = "PYROSCOPE_PROFILER_EVENT"; private static final String PYROSCOPE_PROFILER_ALLOC_CONFIG = "PYROSCOPE_PROFILER_ALLOC"; private static final String PYROSCOPE_PROFILER_LOCK_CONFIG = "PYROSCOPE_PROFILER_LOCK"; @@ -89,6 +90,7 @@ public final class Config { public final boolean agentEnabled; public final String applicationName; + private final ProfilerType profilerType; public final Duration profilingInterval; public final EventType profilingEvent; public final String profilingAlloc; @@ -123,6 +125,7 @@ public final class Config { Config(final boolean agentEnabled, final String applicationName, + final ProfilerType profilerType, final Duration profilingInterval, final EventType profilingEvent, final String profilingAlloc, @@ -150,6 +153,7 @@ public final class Config { String basicAuthPassword) { this.agentEnabled = agentEnabled; this.applicationName = applicationName; + this.profilerType = profilerType; this.profilingInterval = profilingInterval; this.profilingEvent = profilingEvent; this.profilingAlloc = profilingAlloc; @@ -207,6 +211,7 @@ public String toString() { return "Config{" + "agentEnabled=" + agentEnabled + ", applicationName='" + applicationName + '\'' + + ", profilerType=" + profilerType + ", profilingInterval=" + profilingInterval + ", profilingEvent=" + profilingEvent + ", profilingAlloc='" + profilingAlloc + '\'' + @@ -257,6 +262,7 @@ public static Config build(ConfigurationProvider cp) { return new Config( agentEnabled, applicationName(cp), + profilerType(cp), profilingInterval(cp), profilingEvent(cp), alloc, @@ -284,6 +290,14 @@ public static Config build(ConfigurationProvider cp) { cp.get(PYROSCOPE_BASIC_AUTH_PASSWORD_CONFIG)); } + private static ProfilerType profilerType(ConfigurationProvider configurationProvider) { + String profilerTypeName = configurationProvider.get(PYROSCOPE_PROFILER_TYPE_CONFIG); + if (profilerTypeName == null || profilerTypeName.isEmpty()) { + return ProfilerType.ASYNC; + } + return ProfilerType.valueOf(profilerTypeName); + } + private static String applicationName(ConfigurationProvider configurationProvider) { String applicationName = configurationProvider.get(PYROSCOPE_APPLICATION_NAME_CONFIG); if (applicationName == null || applicationName.isEmpty()) { @@ -640,6 +654,7 @@ private static Duration samplingDuration(ConfigurationProvider configurationProv public static class Builder { public boolean agentEnabled = DEFAULT_AGENT_ENABLED; public String applicationName = null; + public ProfilerType profilerType = ProfilerType.ASYNC; public Duration profilingInterval = DEFAULT_PROFILING_INTERVAL; public EventType profilingEvent = DEFAULT_PROFILER_EVENT; public String profilingAlloc = ""; @@ -673,6 +688,7 @@ public Builder() { public Builder(Config buildUpon) { agentEnabled = buildUpon.agentEnabled; applicationName = buildUpon.applicationName; + profilerType = buildUpon.profilerType; profilingInterval = buildUpon.profilingInterval; profilingEvent = buildUpon.profilingEvent; profilingAlloc = buildUpon.profilingAlloc; @@ -844,6 +860,7 @@ public Config build() { } return new Config(agentEnabled, applicationName, + profilerType, profilingInterval, profilingEvent, profilingAlloc, diff --git a/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java b/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java new file mode 100644 index 0000000..8a7c778 --- /dev/null +++ b/agent/src/main/java/io/pyroscope/javaagent/config/ProfilerType.java @@ -0,0 +1,5 @@ +package io.pyroscope.javaagent.config; + +public enum ProfilerType { + JFR, ASYNC; +}