Skip to content

Commit

Permalink
JFR delegate implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
wsztajerowski committed Dec 23, 2023
1 parent 8757844 commit 3d3e4f2
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 66 deletions.
29 changes: 29 additions & 0 deletions agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
110 changes: 45 additions & 65 deletions agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<String> 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);
}
}

/**
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 17 additions & 0 deletions agent/src/main/java/io/pyroscope/javaagent/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -207,6 +211,7 @@ public String toString() {
return "Config{" +
"agentEnabled=" + agentEnabled +
", applicationName='" + applicationName + '\'' +
", profilerType=" + profilerType +
", profilingInterval=" + profilingInterval +
", profilingEvent=" + profilingEvent +
", profilingAlloc='" + profilingAlloc + '\'' +
Expand Down Expand Up @@ -257,6 +262,7 @@ public static Config build(ConfigurationProvider cp) {
return new Config(
agentEnabled,
applicationName(cp),
profilerType(cp),
profilingInterval(cp),
profilingEvent(cp),
alloc,
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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 = "";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -844,6 +860,7 @@ public Config build() {
}
return new Config(agentEnabled,
applicationName,
profilerType,
profilingInterval,
profilingEvent,
profilingAlloc,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.pyroscope.javaagent.config;

public enum ProfilerType {
JFR, ASYNC;
}

0 comments on commit 3d3e4f2

Please sign in to comment.