Skip to content

Commit

Permalink
added JFR configuration which enables only supported JFR event types
Browse files Browse the repository at this point in the history
  • Loading branch information
kcrimson committed Dec 29, 2023
1 parent 3d3e4f2 commit 2bf10e0
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
74 changes: 38 additions & 36 deletions agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -43,15 +41,16 @@ public void setConfig(final Config config) {
public synchronized void start() {
try {
List<String> 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) {
Expand All @@ -67,14 +66,14 @@ public synchronized void start() {
public synchronized void stop() {
try {
List<String> 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) {
Expand All @@ -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) {
Expand All @@ -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;
}
}
11 changes: 11 additions & 0 deletions agent/src/main/java/io/pyroscope/javaagent/ProfilerDelegate.java
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -115,6 +114,9 @@ public Options build() {
scheduler = new SamplingProfilingScheduler(config, exporter, logger);
}
}
if (profiler == null) {
profiler = ProfilerDelegate.create(config);
}
return new Options(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends ProfilerDelegate> profilerDelegateClass;

ProfilerType(Class<? extends ProfilerDelegate> 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);
}
}
}
44 changes: 44 additions & 0 deletions agent/src/main/resources/pyroscope.jfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>

<configuration version="2.0" label="Pyroscope"
description="Low overhead configuration safe for continuous use in production environments, typically less than 1 % overhead."
provider="Pyroscope">

<event name="jdk.ExecutionSample">
<setting name="enabled">true</setting>
<setting name="period">1 ms</setting>
</event>

<event name="jdk.ThreadPark">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
<setting name="threshold">10 ms</setting>
</event>

<event name="jdk.ObjectAllocationInNewTLAB">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>

<event name="jdk.ObjectAllocationOutsideTLAB">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>

<event name="jdk.JavaMonitorEnter">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
<setting name="threshold">10 ms</setting>
</event>

<event name="jdk.ThreadPark">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
<setting name="threshold">10 ms</setting>
</event>

<event name="jdk.ActiveSetting">
<setting name="enabled">true</setting>
</event>

</configuration>

0 comments on commit 2bf10e0

Please sign in to comment.