From df947b4cd6459ed319dc9a14a52862c6eb9e2edf Mon Sep 17 00:00:00 2001 From: Tolya Korniltsev Date: Sat, 28 May 2022 04:50:36 +0700 Subject: [PATCH] Update async-profiler to 2.8, change alloc,lock params for jfr (#38) * Update async-profiler to 2.8, change alloc,lock params for jfr * fix copypaste mistake * rm debug logging * Temporarly switch to a pyroscope fork of async-profiler to include a fix of a crash --- agent/build.gradle | 6 +- .../java/io/pyroscope/javaagent/Profiler.java | 101 ++++++++++-------- .../pyroscope/javaagent/PyroscopeAgent.java | 2 +- .../io/pyroscope/javaagent/config/Config.java | 4 +- 4 files changed, 63 insertions(+), 50 deletions(-) diff --git a/agent/build.gradle b/agent/build.gradle index d67f3ef..4dec18a 100644 --- a/agent/build.gradle +++ b/agent/build.gradle @@ -19,7 +19,7 @@ repositories { mavenCentral() } -def asyncProfilerVersion = "2.7" +def asyncProfilerVersion = "2.8.0.1" dependencies { implementation files("$buildDir/async-profiler/async-profiler.jar") implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1' @@ -82,8 +82,10 @@ task asyncProfilerLib { doLast { suffixes.forEach { suffix, ext -> +// def repo = "https://github.com/jvm-profiling-tools/async-profiler" + def repo = "https://github.com/pyroscope-io/async-profiler" download { - src "https://github.com/jvm-profiling-tools/async-profiler/releases/download/v${asyncProfilerVersion}/async-profiler-${asyncProfilerVersion}-${suffix}.${ext}" + src "$repo/releases/download/v${asyncProfilerVersion}/async-profiler-${asyncProfilerVersion}-${suffix}.${ext}" dest new File(asyncProfilerDir, "async-profiler-${asyncProfilerVersion}-${suffix}.${ext}") overwrite true } diff --git a/agent/src/main/java/io/pyroscope/javaagent/Profiler.java b/agent/src/main/java/io/pyroscope/javaagent/Profiler.java index 98eeabd..512c08d 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/Profiler.java +++ b/agent/src/main/java/io/pyroscope/javaagent/Profiler.java @@ -25,6 +25,7 @@ class Profiler { private final Format format; private static String libraryPath; + static { try { deployLibrary(); @@ -45,7 +46,7 @@ private static void deployLibrary() throws IOException { targetDir.mkdirs(); try (final InputStream is = Objects.requireNonNull( - Profiler.class.getResourceAsStream("/" + fileName))) { + Profiler.class.getResourceAsStream("/" + fileName))) { final Path target = targetDir.toPath().resolve(targetLibraryFileName(fileName)).toAbsolutePath(); Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); libraryPath = target.toString(); @@ -94,7 +95,7 @@ private static String libraryFileName() { *

Adds the checksum to the library file name.

* *

E.g. {@code libasyncProfiler-linux-x64.so} -> - * {@code libasyncProfiler-linux-x64-7b43b7cc6c864dd729cc7dcdb6e3db8f5ee5b4a4.so}

+ * {@code libasyncProfiler-linux-x64-7b43b7cc6c864dd729cc7dcdb6e3db8f5ee5b4a4.so}

*/ private static String targetLibraryFileName(final String libraryFileName) throws IOException { if (!libraryFileName.endsWith(".so")) { @@ -104,22 +105,18 @@ private static String targetLibraryFileName(final String libraryFileName) throws final String checksumFileName = libraryFileName + ".sha1"; String checksum; try (final InputStream is = Objects.requireNonNull( - Profiler.class.getResourceAsStream("/" + checksumFileName))) { + Profiler.class.getResourceAsStream("/" + checksumFileName))) { checksum = InputStreamUtils.readToString(is); } return libraryFileName.substring(0, libraryFileName.length() - 3) + "-" + checksum + ".so"; } - public static void init() { - // Do nothing, rely in the static initialization in the static block. - } - private final AsyncProfiler instance = AsyncProfiler.getInstance(libraryPath); // TODO this is actually start of snapshot, not profiling as a whole private Instant profilingStarted = null; - private File tempFile; + private final File tempJFRFile; Profiler(final Logger logger, final EventType eventType, final String alloc, final String lock, final Duration interval, final Format format) { this.logger = logger; @@ -128,17 +125,45 @@ public static void init() { this.eventType = eventType; this.interval = interval; this.format = 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; + } } - // TODO new method for starting new snapshot/batch final synchronized void start() { if (format == Format.JFR) { - startJFR(); + try { + instance.execute(createJFRCommand()); + } catch (IOException e) { + throw new IllegalStateException(e); + } } else { instance.start(eventType.id, interval.toNanos()); } profilingStarted = Instant.now(); - logger.info("Profiling started"); + } + + 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 (lock != null && !lock.isEmpty()) { + sb.append(",lock=").append(lock); + } + sb.append(",interval=").append(interval.toNanos()) + .append(",file=").append(tempJFRFile.toString()); + return sb.toString(); } final synchronized Snapshot dump() { @@ -146,48 +171,34 @@ final synchronized Snapshot dump() { throw new IllegalStateException("Profiling is not started"); } - final Snapshot result = new Snapshot( - eventType, - profilingStarted, - Instant.now(), - format == Format.JFR ? dumpJFR() : instance.dumpCollapsed(Counter.SAMPLES).getBytes(StandardCharsets.UTF_8) - ); + instance.stop(); - // TODO use `this.start()` or analogue - profilingStarted = Instant.now(); - if (format == Format.JFR) { - restartJFR(); - } else { - instance.stop(); - instance.start(eventType.id, interval.toNanos()); - } - return result; - } + Snapshot result = dumpImpl(); - final private void startJFR() { - try { - // flight recorder is built on top of a file descriptor, so we need a file. - tempFile = File.createTempFile("pyroscope", ".jfr"); - tempFile.deleteOnExit(); - instance.execute(String.format("start,event=%s,alloc=%s,lock=%s,interval=%s,file=%s", eventType.id, alloc, lock, interval.toNanos(), tempFile.toString())); - } catch (IOException e) { - throw new IllegalStateException(e); - } + start(); + + return result; } - final private void restartJFR() { - try { - instance.execute(String.format("start,event=%s,alloc=%s,lock=%s,interval=%s,file=%s", eventType.id, alloc, lock, interval.toNanos(), tempFile.toString())); - } catch (IOException e) { - throw new IllegalStateException(e); + private Snapshot dumpImpl() { + final byte[] data; + if (format == Format.JFR) { + data = dumpJFR(); + } else { + data = instance.dumpCollapsed(Counter.SAMPLES).getBytes(StandardCharsets.UTF_8); } + return new Snapshot( + eventType, + profilingStarted, + Instant.now(), + data + ); } - final private byte[] dumpJFR() { + private byte[] dumpJFR() { try { - instance.stop(); - byte[] bytes = new byte[(int) tempFile.length()]; - try (DataInputStream ds = new DataInputStream(new FileInputStream(tempFile))) { + byte[] bytes = new byte[(int) tempJFRFile.length()]; + try (DataInputStream ds = new DataInputStream(new FileInputStream(tempJFRFile))) { ds.readFully(bytes); } return bytes; diff --git a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java index 63f78b5..291740b 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java +++ b/agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java @@ -20,7 +20,6 @@ public static void premain(final String agentArgs, final Config config; final Logger logger; try { - Profiler.init(); config = Config.build(); logger = new SimpleLogger( @@ -56,6 +55,7 @@ public Thread newThread(Runnable r) { } }); profiler.start(); + logger.info("Profiling started"); final Runnable dumpProfile = () -> { try { 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 6e90641..d8fd99a 100644 --- a/agent/src/main/java/io/pyroscope/javaagent/config/Config.java +++ b/agent/src/main/java/io/pyroscope/javaagent/config/Config.java @@ -28,8 +28,8 @@ public final class Config { private static final String DEFAULT_SPY_NAME = "javaspy"; private static final Duration DEFAULT_PROFILING_INTERVAL = Duration.ofMillis(10); private static final EventType DEFAULT_PROFILER_EVENT = EventType.ITIMER; - private static final String DEFAULT_PROFILER_ALLOC = "0"; - private static final String DEFAULT_PROFILER_LOCK = "0"; + private static final String DEFAULT_PROFILER_ALLOC = ""; + private static final String DEFAULT_PROFILER_LOCK = ""; private static final Duration DEFAULT_UPLOAD_INTERVAL = Duration.ofSeconds(10); private static final String DEFAULT_SERVER_ADDRESS = "http://localhost:4040"; private static final Format DEFAULT_FORMAT = Format.COLLAPSED;