From e54907769daa2345a2f7f0b09ddbe93a6c4a09e2 Mon Sep 17 00:00:00 2001 From: Sander Hautvast Date: Thu, 29 Mar 2018 12:51:43 +0200 Subject: [PATCH] initial project --- .gitignore | 4 ++ pom.xml | 76 +++++++++++++++++++++++++ src/deploy/java/testperfix/Main.java | 31 ++++++++++ src/main/java/perfix/Agent.java | 84 ++++++++++++++++++++++++++++ src/main/java/perfix/Method.java | 29 ++++++++++ src/main/java/perfix/Registry.java | 51 +++++++++++++++++ src/test/java/TestMethod.java | 16 ++++++ 7 files changed, 291 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/deploy/java/testperfix/Main.java create mode 100644 src/main/java/perfix/Agent.java create mode 100644 src/main/java/perfix/Method.java create mode 100644 src/main/java/perfix/Registry.java create mode 100644 src/test/java/TestMethod.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f95e758 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.idea/ +perfix.iml +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4a86f09 --- /dev/null +++ b/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + perfix + agent + 0.1-SNAPSHOT + jar + + + + org.ow2.asm + asm + 4.1 + + + org.javassist + javassist + 3.21.0-GA + + + junit + junit + 4.12 + test + + + + + 1.8 + 1.8 + + + + + + maven-jar-plugin + + + + perfix.Agent + true + + + + + + + maven-shade-plugin + + + package + + shade + + + + + org.objectweb.asm + agent.org.objectweb.asm + + + org.javassist + agent.org.javassist + + + false + + + + + + + + diff --git a/src/deploy/java/testperfix/Main.java b/src/deploy/java/testperfix/Main.java new file mode 100644 index 0000000..5881828 --- /dev/null +++ b/src/deploy/java/testperfix/Main.java @@ -0,0 +1,31 @@ +package testperfix; + +import perfix.Registry; + +import java.util.concurrent.TimeUnit; + +public class Main { + public static void main(String[] args) { + System.out.print("start me with -javaagent:target/agent-0.1-SNAPSHOT.jar"); + System.out.println(" and preferrably: -Dperfix.excludes=com,java,sun,org"); + Runtime.getRuntime().addShutdownHook(new Thread(() -> Registry.report())); + run(); + } + + public static void run() { + someOtherMethod(); + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static void someOtherMethod() { + try { + TimeUnit.NANOSECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/perfix/Agent.java b/src/main/java/perfix/Agent.java new file mode 100644 index 0000000..0c7b6ea --- /dev/null +++ b/src/main/java/perfix/Agent.java @@ -0,0 +1,84 @@ +package perfix; + +import javassist.*; + +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Agent { + + public static void premain(String agentArgs, Instrumentation inst) { + List excludes = determineExcludes(); + + inst.addTransformer((classLoader, resource, aClass, protectionDomain, uninstrumentedByteCode) -> { + if (!shouldExclude(resource, excludes).get()) { + try { + return instrumentMethod(resource); + } catch (Exception ex) { + ex.printStackTrace(System.err); + } + } + return uninstrumentedByteCode; + }); + } + + private static byte[] instrumentMethod(String resource) throws NotFoundException, IOException, CannotCompileException { + ClassPool cp = ClassPool.getDefault(); + CtClass methodClass = cp.get("perfix.Method"); + + + CtClass cc = cp.get(resource.replaceAll("/", ".")); + Arrays.stream(cc.getDeclaredMethods()).forEach(m -> { + instrumentMethod(methodClass, m); + }); + byte[] byteCode = cc.toBytecode(); + cc.detach(); + return byteCode; + } + + private static void instrumentMethod(CtClass methodClass, CtMethod m) { + try { + m.addLocalVariable("perfixmethod", methodClass); + m.insertBefore("perfixmethod = perfix.Method.start(\"" + m.getLongName() + "\");"); + m.insertAfter("perfixmethod.stop();"); + } catch (CannotCompileException e) { + e.printStackTrace(System.err); + } + } + + private static List determineExcludes() { + List excludes = new ArrayList<>(Arrays.asList(System.getProperty("perfix.excludes").split(","))); + excludes.add("perfix"); + return excludes; + } + + private static BooleanWrapper shouldExclude(String resource, List excludes) { + BooleanWrapper excluded = new BooleanWrapper(false); + excludes.forEach(exclude -> { + if (resource.startsWith(exclude)) { + excluded.set(true); + } + }); + return excluded; + } + + static class BooleanWrapper { + boolean value; + + public BooleanWrapper(boolean value) { + this.value = value; + } + + void set(boolean value) { + this.value = value; + } + + boolean get() { + return value; + } + } +} + diff --git a/src/main/java/perfix/Method.java b/src/main/java/perfix/Method.java new file mode 100644 index 0000000..4c71152 --- /dev/null +++ b/src/main/java/perfix/Method.java @@ -0,0 +1,29 @@ +package perfix; + +public class Method { + private final long t0; + private final String name; + private long t1; + + public Method(String name) { + t0 = System.nanoTime(); + this.name = name; + } + + public static Method start(String name) { + return new Method(name); + } + + public void stop() { + t1 = System.nanoTime(); + Registry.add(this); + } + + public String getName() { + return name; + } + + long getDuration() { + return t1 - t0; + } +} diff --git a/src/main/java/perfix/Registry.java b/src/main/java/perfix/Registry.java new file mode 100644 index 0000000..918a8f3 --- /dev/null +++ b/src/main/java/perfix/Registry.java @@ -0,0 +1,51 @@ +package perfix; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.LongAdder; + +public class Registry { + + static final Map> methods = new ConcurrentHashMap<>(); + + static void add(Method method) { + methods.computeIfAbsent(method.getName(), key -> new ArrayList<>()).add(method); + } + + public static void report() { + System.out.println("Invoked methods, by duration desc:"); + sortedMethodsByDuration().forEach((k, report) -> { + System.out.println("method: " + report.name); + System.out.println("\tInvocations: " + report.invocations); + System.out.println("\tTotal duration: " + report.totalDuration + " nanosecs."); + System.out.println("\tAverage duration " + report.average() + " nanosecs."); + }); + } + + private static SortedMap sortedMethodsByDuration() { + SortedMap sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder()); + methods.forEach((name, results) -> { + LongAdder adder = new LongAdder(); + long totalDuration = results.stream().peek((r) -> adder.increment()).mapToLong(r -> r.getDuration()).sum(); + sortedByTotal.put(totalDuration, new Report(name, adder.longValue(), totalDuration)); + }); + return sortedByTotal; + } + + static class Report { + final String name; + final long invocations; + final long totalDuration; + + Report(String name, long invocations, long totalDuration) { + this.name = name; + this.invocations = invocations; + this.totalDuration = totalDuration; + } + + double average() { + return (double) totalDuration / invocations; + } + } +} diff --git a/src/test/java/TestMethod.java b/src/test/java/TestMethod.java new file mode 100644 index 0000000..0f7de7c --- /dev/null +++ b/src/test/java/TestMethod.java @@ -0,0 +1,16 @@ +import org.junit.Test; +import perfix.Method; +import perfix.Registry; + +public class TestMethod { + @Test + public void testAddMethodToRegistry() { + Method method = Method.start("somename"); + method.stop(); + Method method2 = Method.start("somename"); + method2.stop(); + + Registry.report(); + } + +}