diff --git a/.gitignore b/.gitignore
index 2f7896d..113bf58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
target/
+/.settings/
+/.classpath
+/.project
diff --git a/README.md b/README.md
index d38415d..ec920ac 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,22 @@
+This is a fork of https://github.com/papertrail/profiler/ version 1.0.3-SNAPSHOT
+
+* Requires Java 8 and therefore no dependency on joda-time anymore (since v1.1.0)
+* Now as OSGI bundle (since v1.1.1)
+
+It's meant to be a simple replacement using the same package name as the original (just with the Java 8 `Duration` type)
+
+Original sources Copyright 2015 Papertrail, Inc.
+
# JVM cpu profiler
A pure-java implementation of the [twitter/util](https://github.com/twitter/util) project's `CpuProfile` and related
-classes.
+classes.
Original Scala sources:
* [CpuProfile.scala](https://github.com/twitter/util/blob/develop/util-jvm/src/main/scala/com/twitter/jvm/CpuProfile.scala)
* [CpuProfileTest.scala](https://github.com/twitter/util/blob/develop/util-jvm/src/test/scala/com/twitter/jvm/CpuProfileTest.scala)
-
+
## Usage
The `CpuProfile.record` method will record samples of stacktrace elements and return a `CpuProfile` object. That object
@@ -27,4 +36,14 @@ threads. Here is an example of using `curl` to retrieve a profile and turn it in
```bash
curl http://localhost:8181/pprof/contention > prof
pprof --pdf prof > profile.pdf
-```
\ No newline at end of file
+```
+
+## Maven usage
+
+```xml
+
+ com.helger
+ profiler
+ 1.1.1
+
+```
diff --git a/pom.xml b/pom.xml
index f04854f..511a5d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,20 +1,25 @@
4.0.0
+
+ com.helger
+ parent-pom
+ 1.10.9
+
- com.papertrail
profiler
- 1.0.3-SNAPSHOT
- jar
-
+ 1.1.2-SNAPSHOT
+ bundle
${project.groupId}:${project.artifactId}
A pure-java implementation of the twitter/util project's `CpuProfile` and related classes.
- https://github.com/papertrail/profiler
+ https://github.com/phax/profiler
+
The Apache License, Version 2.0
http://www.apache.org/licenses/LICENSE-2.0.txt
+
Jared Harper
@@ -22,108 +27,50 @@
Papertrail, Inc.
http://www.papertrailapp.com
+
+ philip
+ Philip Helger
+ ph(at)helger.com
+ http://www.helger.com
+
+
- scm:git:git@github.com:papertrail/profiler.git
- scm:git:git@github.com:papertrail/profiler.git
- git@github.com:papertrail/profiler.git
+ scm:git:git@github.com:phax/profiler.git
+ scm:git:git@github.com:phax/profiler.git
+ git@github.com:phax/profiler.git
+ HEAD
-
- org.sonatype.oss
- oss-parent
- 7
-
-
-
- UTF-8
-
-
-
- release-sign-artifacts
-
-
- gpg.passphrase
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 2.4
-
-
- attach-sources
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.10.3
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.1
-
- 1.7
- 1.7
- UTF-8
-
-
-
-
-
- joda-time
- joda-time
- 2.9.1
-
javax.ws.rs
javax.ws.rs-api
- 2.0.1
+ 2.1.1
provided
+
junit
junit
- 4.11
test
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ com.papertrail.profiler
+ com.papertrail.profiler.*
+
+
+
+
+
diff --git a/src/main/java/com/papertrail/profiler/CpuProfile.java b/src/main/java/com/papertrail/profiler/CpuProfile.java
index e50b80e..fd4bdff 100644
--- a/src/main/java/com/papertrail/profiler/CpuProfile.java
+++ b/src/main/java/com/papertrail/profiler/CpuProfile.java
@@ -1,8 +1,5 @@
package com.papertrail.profiler;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-
import java.io.IOException;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
@@ -10,6 +7,8 @@
import java.lang.management.ThreadMXBean;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.time.Duration;
+import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -17,289 +16,337 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
-
/**
* A CPU profile.
*/
-public class CpuProfile {
- private final Map, Long> counts;
- public final Duration duration;
- public final long count;
- public final long missed;
-
- public CpuProfile(Map, Long> counts, Duration duration, long count, long missed) {
- this.counts = counts;
- this.duration = duration;
- this.count = count;
- this.missed = missed;
+public class CpuProfile
+{
+ private final Map , Long> m_counts;
+ public final Duration m_duration;
+ public final long m_count;
+ public final long m_missed;
+
+ public CpuProfile (final Map , Long> counts,
+ final Duration duration,
+ final long count,
+ final long missed)
+ {
+ this.m_counts = counts;
+ this.m_duration = duration;
+ this.m_count = count;
+ this.m_missed = missed;
+ }
+
+ private static class Word
+ {
+ final ByteBuffer m_buf;
+ final OutputStream m_os;
+
+ public Word (final OutputStream os)
+ {
+ this (createBuffer (), os);
}
- private static class Word {
- final ByteBuffer buf;
- final OutputStream os;
-
- public Word(OutputStream os) {
- this(createBuffer(), os);
- }
-
- private Word(ByteBuffer buf, OutputStream os) {
- this.buf = buf;
- this.os = os;
- }
-
- public void putWord(long n) throws IOException {
- buf.clear();
- buf.putLong(n);
- os.write(buf.array());
- }
-
- public void putString(String s) throws IOException {
- os.write(s.getBytes());
- }
-
- private static ByteBuffer createBuffer() {
- final ByteBuffer buf = ByteBuffer.allocate(8);
- buf.order(ByteOrder.LITTLE_ENDIAN);
- return buf;
- }
-
- public void flush() throws IOException {
- os.flush();
- }
+ private Word (final ByteBuffer buf, final OutputStream os)
+ {
+ this.m_buf = buf;
+ this.m_os = os;
}
- /**
- * Write a Google pprof-compatible profile to `out`. The format is
- * documented here:
- * http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile-fileformat.html
- *
- * @param out And OutputStream to which the pprof output will be written
- * @throws IOException if any operation on the OutputStream fails
- */
- public void writeGoogleProfile(OutputStream out) throws IOException {
- int next = 1;
- Map uniq = new HashMap<>();
- Word word = new Word(out);
- word.putString(String.format("--- symbol\nbinary=%s\n", mainClassName()));
- for (Map.Entry, Long> stack : counts.entrySet()) {
- for (StackTraceElement frame : stack.getKey()) {
- if (!uniq.containsKey(frame)) {
- word.putString(String.format("0x%016x %s\n", next, frame.toString()));
- uniq.put(frame, next);
- next += 1;
- }
- }
- }
- word.putString("---\n--- profile\n");
- for (int w : new int[]{0, 3, 0, 1, 0}) {
- word.putWord(w);
- }
- for (Map.Entry, Long> entry : counts.entrySet()) {
- List stack = entry.getKey();
- long n = entry.getValue();
- if (!stack.isEmpty()) {
- word.putWord(n);
- word.putWord(stack.size());
- }
- for (StackTraceElement frame : stack) {
- word.putWord(uniq.get(frame));
- }
- }
- word.putWord(0);
- word.putWord(1);
- word.putWord(0);
- word.flush();
+ public void putWord (final long n) throws IOException
+ {
+ m_buf.clear ();
+ m_buf.putLong (n);
+ m_os.write (m_buf.array ());
}
- static class StringPair {
- final String a;
- final String b;
-
- StringPair(String a, String b) {
- this.a = a;
- this.b = b;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- StringPair that = (StringPair) o;
- return Objects.equals(a, that.a) &&
- Objects.equals(b, that.b);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(a, b);
- }
+ public void putString (final String s) throws IOException
+ {
+ m_os.write (s.getBytes ());
}
- // (class name, method names) that say they are runnable, but are actually doing nothing.
- private static final Set idleClassAndMethod = new HashSet<>();
-
- static {
- idleClassAndMethod.add(new StringPair("sun.nio.ch.EPollArrayWrapper", "epollWait"));
- idleClassAndMethod.add(new StringPair("sun.nio.ch.KQueueArrayWrapper", "kevent0"));
- idleClassAndMethod.add(new StringPair("java.net.SocketInputStream", "socketRead0"));
- idleClassAndMethod.add(new StringPair("java.net.SocketOutputStream", "socketWrite0"));
- idleClassAndMethod.add(new StringPair("java.net.PlainSocketImpl", "socketAvailable"));
- idleClassAndMethod.add(new StringPair("java.net.PlainSocketImpl", "socketAccept"));
- idleClassAndMethod.add(new StringPair("sun.nio.ch.ServerSocketChannelImpl", "accept0"));
+ private static ByteBuffer createBuffer ()
+ {
+ final ByteBuffer buf = ByteBuffer.allocate (8);
+ buf.order (ByteOrder.LITTLE_ENDIAN);
+ return buf;
}
- /**
- * When looking for RUNNABLEs, the JVM's notion of runnable differs from the
- * from kernel's definition and for some well known cases, we can filter
- * out threads that are actually asleep.
- * See http://www.brendangregg.com/blog/2014-06-09/java-cpu-sampling-using-hprof.html
- *
- * @param elem StackTraceElement to check
- * @return true if it's not a known idle method
- */
- protected static boolean isRunnable(StackTraceElement elem) {
- return !idleClassAndMethod.contains(
- new StringPair(elem.getClassName(), elem.getMethodName()));
+ public void flush () throws IOException
+ {
+ m_os.flush ();
}
-
- /**
- * Profile CPU usage of threads in `state` for `howlong`, sampling
- * stacks at `frequency` Hz.
- * As an example, using Nyquist's sampling theorem, we see that
- * sampling at 100Hz will accurately represent components 50Hz or
- * less; ie. any stack that contributes 2% or more to the total CPU
- * time.
- * Note that the maximum sampling frequency is set to 1000Hz.
- * Anything greater than this is likely to consume considerable
- * amounts of CPU while sampling.
- * The profiler will discount its own stacks.
- * TODO:
- * - Should we synthesize GC frames? GC has significant runtime
- * impact, so it seems nonfaithful to exlude them.
- * - Limit stack depth?
- *
- * @param howlong Duration of profile
- * @param frequency polling interval
- * @param state Thread.State to match against
- * @return CpuProfile results
- */
- public static CpuProfile record(Duration howlong, int frequency, Thread.State state) {
- /*
- PLEASE NOTE: I modified this code to use millisecond precision as the original code that used microsecond
- precision was incorrect. Each time it looked at the clock or slept, it was using millis under the hood.
- */
- if (frequency > 1000) {
- throw new RuntimeException("frequency must be < 1000");
- }
-
- // TODO: it may make sense to write a custom hash function here
- // that needn't traverse the all stack trace elems. Usually, the
- // top handful of frames are distinguishing.
- Map, Long> counts = new HashMap<>();
- ThreadMXBean bean = ManagementFactory.getThreadMXBean();
- Instant start = Instant.now();
- Instant end = start.plus(howlong);
- int periodMillis = 1000 / frequency;
- long myId = Thread.currentThread().getId();
- Instant next = Instant.now();
-
- long n = 0;
- long nmissed = 0;
-
- while (Instant.now().isBefore(end)) {
- for (ThreadInfo thread : bean.dumpAllThreads(false, false)) {
- if (thread.getThreadState() == state && thread.getThreadId() != myId) {
- List s = Arrays.asList(thread.getStackTrace());
- if (!s.isEmpty()) {
- boolean include = state != Thread.State.RUNNABLE || isRunnable(s.get(0));
- if (include) {
- if (counts.get(s) == null) {
- counts.put(s, 1L);
- } else {
- long count = counts.get(s);
- counts.put(s, count + 1L);
- }
- }
- }
- }
- }
- n += 1;
- next = next.plus(periodMillis);
-
- while (next.isBefore(Instant.now()) && next.isBefore(end)) {
- nmissed += 1;
- next = next.plus(periodMillis);
- }
-
- long sleep = Math.max((next.getMillis() - Instant.now().getMillis()), 0);
- try {
- Thread.sleep(sleep);
- } catch (InterruptedException e) {
- System.out.print("CpuProfile interrupted.");
- Thread.currentThread().interrupt();
- return null;
- }
+ }
+
+ /**
+ * Write a Google pprof-compatible profile to `out`. The format is documented
+ * here:
+ * http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile-fileformat.html
+ *
+ * @param out
+ * And OutputStream to which the pprof output will be written
+ * @throws IOException
+ * if any operation on the OutputStream fails
+ */
+ public void writeGoogleProfile (final OutputStream out) throws IOException
+ {
+ int next = 1;
+ final Map uniq = new HashMap <> ();
+ final Word word = new Word (out);
+ word.putString (String.format ("--- symbol\nbinary=%s\n", mainClassName ()));
+ for (final Map.Entry , Long> stack : m_counts.entrySet ())
+ {
+ for (final StackTraceElement frame : stack.getKey ())
+ {
+ if (!uniq.containsKey (frame))
+ {
+ word.putString (String.format ("0x%016x %s\n", Integer.valueOf (next), frame.toString ()));
+ uniq.put (frame, Integer.valueOf (next));
+ next += 1;
}
-
- return new CpuProfile(counts, new Duration(start, Instant.now()), n, nmissed);
+ }
+ }
+ word.putString ("---\n--- profile\n");
+ for (final int w : new int [] { 0, 3, 0, 1, 0 })
+ {
+ word.putWord (w);
+ }
+ for (final Map.Entry , Long> entry : m_counts.entrySet ())
+ {
+ final List stack = entry.getKey ();
+ final long n = entry.getValue ().longValue ();
+ if (!stack.isEmpty ())
+ {
+ word.putWord (n);
+ word.putWord (stack.size ());
+ }
+ for (final StackTraceElement frame : stack)
+ {
+ word.putWord (uniq.get (frame).longValue ());
+ }
+ }
+ word.putWord (0);
+ word.putWord (1);
+ word.putWord (0);
+ word.flush ();
+ }
+
+ static class StringPair
+ {
+ final String m_a;
+ final String m_b;
+
+ StringPair (final String a, final String b)
+ {
+ this.m_a = a;
+ this.m_b = b;
}
- public CpuProfile record(Duration howlong, int frequency) {
- return record(howlong, frequency, Thread.State.RUNNABLE);
+ @Override
+ public boolean equals (final Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass () != o.getClass ())
+ return false;
+ final StringPair that = (StringPair) o;
+ return Objects.equals (m_a, that.m_a) && Objects.equals (m_b, that.m_b);
}
- /**
- * Call `record` in a thread with the given parameters, returning a
- * `Future` representing the completion of the profile.
- *
- * @param howlong Duration of profile
- * @param frequency polling interval
- * @param state Thread.State to match against
- * @return Future contiaining CpuProfile results
+ @Override
+ public int hashCode ()
+ {
+ return Objects.hash (m_a, m_b);
+ }
+ }
+
+ // (class name, method names) that say they are runnable, but are actually
+ // doing nothing.
+ private static final Set idleClassAndMethod = new HashSet <> ();
+
+ static
+ {
+ idleClassAndMethod.add (new StringPair ("sun.nio.ch.EPollArrayWrapper", "epollWait"));
+ idleClassAndMethod.add (new StringPair ("sun.nio.ch.KQueueArrayWrapper", "kevent0"));
+ idleClassAndMethod.add (new StringPair ("java.net.SocketInputStream", "socketRead0"));
+ idleClassAndMethod.add (new StringPair ("java.net.SocketOutputStream", "socketWrite0"));
+ idleClassAndMethod.add (new StringPair ("java.net.PlainSocketImpl", "socketAvailable"));
+ idleClassAndMethod.add (new StringPair ("java.net.PlainSocketImpl", "socketAccept"));
+ idleClassAndMethod.add (new StringPair ("sun.nio.ch.ServerSocketChannelImpl", "accept0"));
+ }
+
+ /**
+ * When looking for RUNNABLEs, the JVM's notion of runnable differs from the
+ * from kernel's definition and for some well known cases, we can filter out
+ * threads that are actually asleep. See
+ * http://www.brendangregg.com/blog/2014-06-09/java-cpu-sampling-using-hprof.html
+ *
+ * @param elem
+ * StackTraceElement to check
+ * @return true if it's not a known idle method
+ */
+ protected static boolean isRunnable (final StackTraceElement elem)
+ {
+ return !idleClassAndMethod.contains (new StringPair (elem.getClassName (), elem.getMethodName ()));
+ }
+
+ /**
+ * Profile CPU usage of threads in `state` for `howlong`, sampling stacks at
+ * `frequency` Hz. As an example, using Nyquist's sampling theorem, we see
+ * that sampling at 100Hz will accurately represent components 50Hz or less;
+ * ie. any stack that contributes 2% or more to the total CPU time. Note that
+ * the maximum sampling frequency is set to 1000Hz. Anything greater than this
+ * is likely to consume considerable amounts of CPU while sampling. The
+ * profiler will discount its own stacks. TODO: - Should we synthesize GC
+ * frames? GC has significant runtime impact, so it seems nonfaithful to
+ * exlude them. - Limit stack depth?
+ *
+ * @param howlong
+ * Duration of profile
+ * @param frequency
+ * polling interval
+ * @param state
+ * Thread.State to match against
+ * @return CpuProfile results
+ */
+ public static CpuProfile record (final Duration howlong, final int frequency, final Thread.State state)
+ {
+ /*
+ * PLEASE NOTE: I modified this code to use millisecond precision as the
+ * original code that used microsecond precision was incorrect. Each time it
+ * looked at the clock or slept, it was using millis under the hood.
*/
- public static Future recordInThread(final Duration howlong, final int frequency, final Thread.State state) {
- final FutureTask future = new FutureTask<>(new Callable() {
- @Override
- public CpuProfile call() throws Exception {
- return record(howlong, frequency, state);
- }
- });
- Thread t = new Thread(future, "CpuProfile");
- t.start();
- return future;
+ if (frequency > 1000)
+ {
+ throw new RuntimeException ("frequency must be < 1000");
}
- public static Future recordInThread(Duration howlong, int frequency) {
- return recordInThread(howlong, frequency, Thread.State.RUNNABLE);
+ // TODO: it may make sense to write a custom hash function here
+ // that needn't traverse the all stack trace elems. Usually, the
+ // top handful of frames are distinguishing.
+ final Map , Long> counts = new HashMap <> ();
+ final ThreadMXBean bean = ManagementFactory.getThreadMXBean ();
+ final Instant start = Instant.now ();
+ final Instant end = start.plus (howlong);
+ final int periodMillis = 1000 / frequency;
+ final long myId = Thread.currentThread ().getId ();
+ Instant next = Instant.now ();
+
+ long n = 0;
+ long nmissed = 0;
+
+ while (Instant.now ().isBefore (end))
+ {
+ for (final ThreadInfo thread : bean.dumpAllThreads (false, false))
+ {
+ if (thread.getThreadState () == state && thread.getThreadId () != myId)
+ {
+ final List s = Arrays.asList (thread.getStackTrace ());
+ if (!s.isEmpty ())
+ {
+ final boolean include = state != Thread.State.RUNNABLE || isRunnable (s.get (0));
+ if (include)
+ {
+ final Long count = counts.get (s);
+ if (count == null)
+ {
+ counts.put (s, Long.valueOf (1L));
+ }
+ else
+ {
+ counts.put (s, Long.valueOf (count.longValue () + 1));
+ }
+ }
+ }
+ }
+ }
+ n += 1;
+ next = next.plusMillis (periodMillis);
+
+ while (next.isBefore (Instant.now ()) && next.isBefore (end))
+ {
+ nmissed += 1;
+ next = next.plusMillis (periodMillis);
+ }
+
+ final long sleep = Math.max ((next.toEpochMilli () - Instant.now ().toEpochMilli ()), 0);
+ try
+ {
+ Thread.sleep (sleep);
+ }
+ catch (final InterruptedException e)
+ {
+ System.out.print ("CpuProfile interrupted.");
+ Thread.currentThread ().interrupt ();
+ return null;
+ }
}
- // Ripped and ported from Twitter's Jvm trait
-
- /**
- * Get the main class name for the currently running application.
- * Note that this works only by heuristic, and may not be accurate.
- * TODO: take into account the standard callstack around scala
- * invocations better.
- *
- * @return main class name
- */
- public static String mainClassName() {
- for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) {
- Thread t = entry.getKey();
- StackTraceElement[] elements = entry.getValue();
- if ("main".equals(t.getName())) {
- for (int i = elements.length - 1; i >= 0; i--) {
- StackTraceElement elem = elements[i];
- if (!elem.getClassName().startsWith("scala.tools.nsc.MainGenericRunner")) {
- return elem.getClassName();
- }
- }
- }
+ return new CpuProfile (counts, Duration.between (start, Instant.now ()), n, nmissed);
+ }
+
+ public CpuProfile record (final Duration howlong, final int frequency)
+ {
+ return record (howlong, frequency, Thread.State.RUNNABLE);
+ }
+
+ /**
+ * Call `record` in a thread with the given parameters, returning a `Future`
+ * representing the completion of the profile.
+ *
+ * @param howlong
+ * Duration of profile
+ * @param frequency
+ * polling interval
+ * @param state
+ * Thread.State to match against
+ * @return Future containing CpuProfile results
+ */
+ public static Future recordInThread (final Duration howlong,
+ final int frequency,
+ final Thread.State state)
+ {
+ final FutureTask future = new FutureTask <> ( () -> record (howlong, frequency, state));
+ final Thread t = new Thread (future, "CpuProfile");
+ t.start ();
+ return future;
+ }
+
+ public static Future recordInThread (final Duration howlong, final int frequency)
+ {
+ return recordInThread (howlong, frequency, Thread.State.RUNNABLE);
+ }
+
+ // Ripped and ported from Twitter's Jvm trait
+
+ /**
+ * Get the main class name for the currently running application. Note that
+ * this works only by heuristic, and may not be accurate. TODO: take into
+ * account the standard callstack around scala invocations better.
+ *
+ * @return main class name
+ */
+ public static String mainClassName ()
+ {
+ for (final Map.Entry entry : Thread.getAllStackTraces ().entrySet ())
+ {
+ final Thread t = entry.getKey ();
+ final StackTraceElement [] elements = entry.getValue ();
+ if ("main".equals (t.getName ()))
+ {
+ for (int i = elements.length - 1; i >= 0; i--)
+ {
+ final StackTraceElement elem = elements[i];
+ if (!elem.getClassName ().startsWith ("scala.tools.nsc.MainGenericRunner"))
+ {
+ return elem.getClassName ();
+ }
}
- return "unknown";
+ }
}
-}
\ No newline at end of file
+ return "unknown";
+ }
+}
diff --git a/src/main/java/com/papertrail/profiler/jaxrs/CpuProfileResource.java b/src/main/java/com/papertrail/profiler/jaxrs/CpuProfileResource.java
index 4158082..76324e6 100644
--- a/src/main/java/com/papertrail/profiler/jaxrs/CpuProfileResource.java
+++ b/src/main/java/com/papertrail/profiler/jaxrs/CpuProfileResource.java
@@ -1,54 +1,61 @@
package com.papertrail.profiler.jaxrs;
-import com.papertrail.profiler.CpuProfile;
-import org.joda.time.Duration;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-@Path("/pprof")
-public class CpuProfileResource {
- final Lock lock = new ReentrantLock();
+import com.papertrail.profiler.CpuProfile;
+
+@Path ("/pprof")
+public class CpuProfileResource
+{
+ private final Lock lock = new ReentrantLock ();
- @Produces("pprof/raw")
- @GET
- @Path("profile")
- public byte[] profile(
- @QueryParam("duration") @DefaultValue("10") Integer duration,
- @QueryParam("frequency") @DefaultValue("100") Integer frequency) throws IOException {
- return doProfile(duration, frequency, Thread.State.RUNNABLE);
- }
+ @Produces ("pprof/raw")
+ @GET
+ @Path ("profile")
+ public byte [] profile (@QueryParam ("duration") @DefaultValue ("10") final Integer duration,
+ @QueryParam ("frequency") @DefaultValue ("100") final Integer frequency) throws IOException
+ {
+ return doProfile (duration.intValue (), frequency.intValue (), Thread.State.RUNNABLE);
+ }
- @Produces("pprof/raw")
- @GET
- @Path("contention")
- public byte[] contention(
- @QueryParam("duration") @DefaultValue("10") Integer duration,
- @QueryParam("frequency") @DefaultValue("100") Integer frequency) throws IOException {
- return doProfile(duration, frequency, Thread.State.BLOCKED);
- }
+ @Produces ("pprof/raw")
+ @GET
+ @Path ("contention")
+ public byte [] contention (@QueryParam ("duration") @DefaultValue ("10") final Integer duration,
+ @QueryParam ("frequency") @DefaultValue ("100") final Integer frequency) throws IOException
+ {
+ return doProfile (duration.intValue (), frequency.intValue (), Thread.State.BLOCKED);
+ }
- protected byte[] doProfile(int duration, int frequency, Thread.State state) throws IOException {
- if (lock.tryLock()) {
- try {
- CpuProfile profile = CpuProfile.record(Duration.standardSeconds(duration), frequency, state);
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- if (profile == null) {
- throw new RuntimeException("could not create CpuProfile");
- }
- profile.writeGoogleProfile(stream);
- return stream.toByteArray();
- } finally {
- lock.unlock();
- }
+ protected byte [] doProfile (final int duration, final int frequency, final Thread.State state) throws IOException
+ {
+ if (lock.tryLock ())
+ {
+ try (final ByteArrayOutputStream stream = new ByteArrayOutputStream ())
+ {
+ final CpuProfile profile = CpuProfile.record (Duration.ofSeconds (duration), frequency, state);
+ if (profile == null)
+ {
+ throw new RuntimeException ("could not create CpuProfile");
}
- throw new RuntimeException("Only one profile request may be active at a time");
+ profile.writeGoogleProfile (stream);
+ return stream.toByteArray ();
+ }
+ finally
+ {
+ lock.unlock ();
+ }
}
-}
\ No newline at end of file
+ throw new RuntimeException ("Only one profile request may be active at a time");
+ }
+}
diff --git a/src/test/java/com/papertrail/profiler/CpuProfileTest.java b/src/test/java/com/papertrail/profiler/CpuProfileTest.java
index 9f60328..e7b9141 100644
--- a/src/test/java/com/papertrail/profiler/CpuProfileTest.java
+++ b/src/test/java/com/papertrail/profiler/CpuProfileTest.java
@@ -1,51 +1,62 @@
package com.papertrail.profiler;
-import org.joda.time.Duration;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
+import java.time.Duration;
-import static org.junit.Assert.*;
+import org.junit.Test;
/**
* Ported from scala
*/
-public class CpuProfileTest {
- @Test
- public void testRecord() throws Exception {
- Thread t = new Thread("CpuProfileTest") {
- @Override
- public void run() {
- try {
- Thread.sleep(10000);
- } catch (InterruptedException ignored) {
- }
- }
- };
- t.setDaemon(true);
- t.start();
+public class CpuProfileTest
+{
+ @Test
+ public void testRecord () throws Exception
+ {
+ final Thread t = new Thread ("CpuProfileTest")
+ {
+ @Override
+ public void run ()
+ {
+ try
+ {
+ Thread.sleep (10_000);
+ }
+ catch (final InterruptedException ignored)
+ {}
+ }
+ };
+ t.setDaemon (true);
+ t.start ();
- // Profile for 1000ms at 10 Hz => 100ms period; produces 10 samples.
- CpuProfile profile = CpuProfile.record(Duration.standardSeconds(1), 10, Thread.State.TIMED_WAITING);
- assertNotNull(profile);
- assertEquals(10, profile.count);
- assertEquals(0, profile.missed);
- OutputStream baos = new ByteArrayOutputStream();
- profile.writeGoogleProfile(baos);
- assertTrue(baos.toString().contains("CpuProfileTest"));
- assertTrue(baos.toString().contains("Thread.sleep"));
- }
+ // Profile for 1000ms at 10 Hz => 100ms period; produces 10 samples.
+ final CpuProfile profile = CpuProfile.record (Duration.ofSeconds (1), 10, Thread.State.TIMED_WAITING);
+ assertNotNull (profile);
+ assertEquals (10, profile.m_count);
+ assertEquals (0, profile.m_missed);
+ final OutputStream baos = new ByteArrayOutputStream ();
+ profile.writeGoogleProfile (baos);
+ assertTrue (baos.toString ().contains ("CpuProfileTest"));
+ assertTrue (baos.toString ().contains ("Thread.sleep"));
+ }
- @Test
- public void testisRunnable() {
- assertTrue(CpuProfile.isRunnable(newElem("foo", "bar")));
- assertFalse(CpuProfile.isRunnable(newElem("sun.nio.ch.EPollArrayWrapper", "epollWait")));
- assertFalse(CpuProfile.isRunnable(newElem("sun.nio.ch.KQueueArrayWrapper", "kevent0")));
- assertFalse(CpuProfile.isRunnable(newElem("sun.nio.ch.ServerSocketChannelImpl", "accept0")));
- }
+ @Test
+ public void testisRunnable ()
+ {
+ assertTrue (CpuProfile.isRunnable (newElem ("foo", "bar")));
+ assertFalse (CpuProfile.isRunnable (newElem ("sun.nio.ch.EPollArrayWrapper", "epollWait")));
+ assertFalse (CpuProfile.isRunnable (newElem ("sun.nio.ch.KQueueArrayWrapper", "kevent0")));
+ assertFalse (CpuProfile.isRunnable (newElem ("sun.nio.ch.ServerSocketChannelImpl", "accept0")));
+ }
- public static StackTraceElement newElem(String className, String methodName) {
- return new StackTraceElement(className, methodName, "SomeFile.java", 1);
- }
-}
\ No newline at end of file
+ public static StackTraceElement newElem (final String className, final String methodName)
+ {
+ return new StackTraceElement (className, methodName, "SomeFile.java", 1);
+ }
+}
diff --git a/src/test/java/com/papertrail/profiler/jaxrs/CpuProfileResourceTest.java b/src/test/java/com/papertrail/profiler/jaxrs/CpuProfileResourceTest.java
index 536b59d..2698c7e 100644
--- a/src/test/java/com/papertrail/profiler/jaxrs/CpuProfileResourceTest.java
+++ b/src/test/java/com/papertrail/profiler/jaxrs/CpuProfileResourceTest.java
@@ -1,37 +1,48 @@
package com.papertrail.profiler.jaxrs;
-import org.junit.Test;
+import static junit.framework.TestCase.assertEquals;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
-import static junit.framework.TestCase.assertEquals;
+import org.junit.Test;
-public class CpuProfileResourceTest {
- @Test
- public void testDoProfile() throws Exception {
- final CpuProfileResource resource = new CpuProfileResource();
- final CountDownLatch latch = new CountDownLatch(1);
- Thread t1 = new Thread() {
- @Override
- public void run() {
- latch.countDown();
- try {
- resource.doProfile(1, 100, State.BLOCKED);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- };
- t1.setDaemon(true);
- t1.start();
- latch.await();
- // Even though we await the latch to start the thread, we can still race, so mitigate with a short sleep
- Thread.sleep(10);
- try {
- resource.doProfile(1, 100, Thread.State.BLOCKED);
- } catch (RuntimeException ex) {
- assertEquals("Only one profile request may be active at a time", ex.getMessage());
+public class CpuProfileResourceTest
+{
+ @Test
+ public void testDoProfile () throws Exception
+ {
+ final CpuProfileResource resource = new CpuProfileResource ();
+ final CountDownLatch latch = new CountDownLatch (1);
+ final Thread t1 = new Thread ()
+ {
+ @Override
+ public void run ()
+ {
+ latch.countDown ();
+ try
+ {
+ resource.doProfile (1, 100, State.BLOCKED);
+ }
+ catch (final IOException e)
+ {
+ throw new RuntimeException (e);
}
+ }
+ };
+ t1.setDaemon (true);
+ t1.start ();
+ latch.await ();
+ // Even though we await the latch to start the thread, we can still race, so
+ // mitigate with a short sleep
+ Thread.sleep (10);
+ try
+ {
+ resource.doProfile (1, 100, Thread.State.BLOCKED);
+ }
+ catch (final RuntimeException ex)
+ {
+ assertEquals ("Only one profile request may be active at a time", ex.getMessage ());
}
-}
\ No newline at end of file
+ }
+}