-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jfr profiler support #136
base: main
Are you sure you want to change the base?
Jfr profiler support #136
Conversation
Thanks for the PR. Look forward to merge it. I will take a look soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good and simple.
I have some minor comments.
I will get a windows VM for manual testing.
It would be nice to have some tests running on windows in github actions. However I realize the project does not have much tests right now so I can not request you to write them. It would be nice to have them though.
import java.lang.reflect.Method; | ||
|
||
public class CurrentPidProvider { | ||
public static int getCurrentProcessId() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use long pid = ProcessHandle.current().pid();
on Java>=9 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got this on correto 17.0.9 win x64
java.lang.reflect.InaccessibleObjectException: Unable to make field private final sun.management.VMManagement sun.management.RuntimeImpl.jvm accessible: module java.management does not "opens sun.management" to unnamed module @4bb4de6a
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at io.pyroscope.javaagent.CurrentPidProvider.getCurrentProcessId(CurrentPidProvider.java:17)
at io.pyroscope.javaagent.JFRProfilerDelegate.start(JFRProfilerDelegate.java:45)
at io.pyroscope.javaagent.impl.ContinuousProfilingScheduler.startFirst(ContinuousProfilingScheduler.java:96)
at io.pyroscope.javaagent.impl.ContinuousProfilingScheduler.start(ContinuousProfilingScheduler.java:44)
at io.pyroscope.javaagent.PyroscopeAgent.start(PyroscopeAgent.java:51)
at App.main(App.java:21)
```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets add a unit test for this function and run it in gh actions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessHandle.current().pid() is available > Java 9, are ok with bumping the jdk version to 9, or we consider using multi release JAR? with two implementations of pid discovery?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we do it at runtime? or maybe also with reflection?
I dont like the idea to have multiple jars.
I dont realy mind how it works, it should just work and currently it throws exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multirelease is a single jar, which holds different versions of classes for different JDK versions, https://openjdk.org/jeps/238, so it is single jar :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multirelease is a single jar, which holds different versions of classes for different JDK versions, https://openjdk.org/jeps/238, so it is single jar :)
Nice. Did not know about it. This should do the trick.
@@ -114,6 +114,9 @@ public Options build() { | |||
scheduler = new SamplingProfilingScheduler(config, exporter, logger); | |||
} | |||
} | |||
if (profiler == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add a setter for profiler field in the builder? Otherwise it looks like profiler is always null here
agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
Outdated
Show resolved
Hide resolved
I also noticed LabelsWrapper does not work. I think we should make it not crash and maybe print a warning once. With some little changes (mostly for debugging purposes) to workaround issues I encounted the profiling seem to start. diff --git a/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java
index 089651e..60ba240 100644
--- a/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java
+++ b/agent/src/main/java/io/pyroscope/javaagent/CurrentPidProvider.java
@@ -10,19 +10,6 @@ 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);
- }
+ return (int) ProcessHandle.current().pid();
}
}
diff --git a/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java b/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
index aefc735..2557b4d 100644
--- a/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
+++ b/agent/src/main/java/io/pyroscope/javaagent/JFRProfilerDelegate.java
@@ -30,6 +30,7 @@ public final class JFRProfilerDelegate implements ProfilerDelegate {
// flight recorder is built on top of a file descriptor, so we need a file.
tempJFRFile = File.createTempFile("pyroscope", ".jfr");
tempJFRFile.deleteOnExit();
+ System.out.println(tempJFRFile);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -46,9 +47,11 @@ public final class JFRProfilerDelegate implements ProfilerDelegate {
commands.add("JFR.start");
commands.add("name=" + RECORDING_NAME);
commands.add("filename=" + tempJFRFile.getAbsolutePath());
- commands.add("settings=pyroscope");
+ commands.add("settings=C:\\Users\\huihu\\pyro\\pyroscope-java\\agent\\build\\resources\\main\\pyroscope.jfc");
ProcessBuilder processBuilder = new ProcessBuilder(commands);
Process process = processBuilder.start();
+ System.out.println(new String(process.getInputStream().readAllBytes()));
+ System.out.println(new String(process.getErrorStream().readAllBytes()));
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Invalid exit code: " + exitCode);
@@ -72,6 +75,8 @@ public final class JFRProfilerDelegate implements ProfilerDelegate {
commands.add("name=" + RECORDING_NAME);
ProcessBuilder processBuilder = new ProcessBuilder(commands);
Process process = processBuilder.start();
+ System.out.println(new String(process.getInputStream().readAllBytes()));
+ System.out.println(new String(process.getErrorStream().readAllBytes()));
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Invalid exit code: " + exitCode);
@@ -114,7 +119,7 @@ public final class JFRProfilerDelegate implements ProfilerDelegate {
private static Path findJcmdBin() {
Path javaHome = Paths.get(System.getProperty("java.home"));
//find jcmd binary
- Path jcmdBin = javaHome.resolve("bin/jcmd");
+ Path jcmdBin = javaHome.resolve("bin/jcmd.exe");
if (!Files.isExecutable(jcmdBin)) {
jcmdBin = javaHome.getParent().resolve("bin/jcmd");
if (!Files.isExecutable(jcmdBin)) {
diff --git a/agent/src/main/java/io/pyroscope/javaagent/impl/PyroscopeExporter.java b/agent/src/main/java/io/pyroscope/javaagent/impl/PyroscopeExporter.java
index 850ab4d..71f9ef6 100644
--- a/agent/src/main/java/io/pyroscope/javaagent/impl/PyroscopeExporter.java
+++ b/agent/src/main/java/io/pyroscope/javaagent/impl/PyroscopeExporter.java
@@ -9,6 +9,7 @@ import io.pyroscope.javaagent.util.zip.GzipSink;
import io.pyroscope.labels.Pyroscope;
import okhttp3.*;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
@@ -43,7 +44,16 @@ public class PyroscopeExporter implements Exporter {
}
}
+ int counter;
private void uploadSnapshot(final Snapshot snapshot) throws InterruptedException {
+ String fname = String.format("dump_%d.jfr", counter++);
+ try {
+ FileOutputStream fos = new FileOutputStream(fname);
+ fos.write(snapshot.data);
+ fos.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
final HttpUrl url = urlForSnapshot(snapshot);
final ExponentialBackoff exponentialBackoff = new ExponentialBackoff(1_000, 30_000, new Random());
boolean retry = true;
diff --git a/demo/src/main/java/App.java b/demo/src/main/java/App.java
index cf9480e..2295e34 100644
--- a/demo/src/main/java/App.java
+++ b/demo/src/main/java/App.java
@@ -4,6 +4,7 @@ import io.pyroscope.javaagent.Snapshot;
import io.pyroscope.javaagent.api.Exporter;
import io.pyroscope.javaagent.api.Logger;
import io.pyroscope.javaagent.config.Config;
+import io.pyroscope.javaagent.config.ProfilerType;
import io.pyroscope.javaagent.impl.DefaultConfigurationProvider;
import io.pyroscope.labels.Pyroscope;
import io.pyroscope.labels.LabelsSet;
@@ -20,9 +21,10 @@ public class App {
PyroscopeAgent.start(
new PyroscopeAgent.Options.Builder(
new Config.Builder()
- .setApplicationName("demo.app{qweqwe=asdasd}")
- .setServerAddress("http://localhost:4040")
+ .setApplicationName("windows.app")
+ .setServerAddress("http://192.168.0.13:4040")
.setFormat(Format.JFR)
+ .setProfilerType(ProfilerType.JFR)
.setLogLevel(Logger.Level.DEBUG)
.setLabels(mapOf("user", "tolyan"))
.build())
@@ -38,7 +40,7 @@ public class App {
ExecutorService executors = Executors.newFixedThreadPool(N_THREADS);
for (int i = 0; i < N_THREADS; i++) {
executors.submit(() -> {
- Pyroscope.LabelsWrapper.run(new LabelsSet("thread_name", Thread.currentThread().getName()), () -> {
+// Pyroscope.LabelsWrapper.run(new LabelsSet("thread_name", Thread.currentThread().getName()), () -> {
while (true) {
try {
fib(32L);
@@ -47,8 +49,8 @@ public class App {
break;
}
}
- }
- );
+// }
+// );
});
}
}
@@ -68,7 +70,6 @@ public class App {
if (n == 1L) {
return 1L;
}
- Thread.sleep(100);
return fib(n - 1) + fib(n - 2);
}
It started profiling on my VM. However it failed to ingest
we will need to update jfr-parser I guess |
2bf10e0
to
304bd08
Compare
just FYI I've made some changes to our jfr-parser |
ProcessBuilder processBuilder = new ProcessBuilder(commands); | ||
Process process = processBuilder.start(); | ||
Process process = processBuilder.inheritIO().start(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
am i understanding correctly that this will redirect the commands stdout to the java process stdout?
If so, I suggest we read it manualy and log it with our logger interface so users can disable it
please let me know when the PR is ready for second round review. look forward to merge it. |
42ac936
to
8928419
Compare
8928419
to
2b7069c
Compare
Hey, thanks for updating. I'll try to look next week. |
do we really need to execute did you investigate works with based on implementation of they need to access to |
This change adds support for JFR recordings, it allows to run agents on platforms that don't support async profiler (like Windows).
We have introduced ProfilerDelegate abstraction, allowing us to use different implementations to collect profiler recordings.
This requires running JFR commands with specific settings, which only collect events supported by the JFR parser on the pyroscope server side. This configuration can be found pyroscope.jfc file.
Any changes/suggestions are more than welcomed.