initial project
This commit is contained in:
commit
e54907769d
7 changed files with 291 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
.idea/
|
||||
perfix.iml
|
||||
target/
|
||||
76
pom.xml
Normal file
76
pom.xml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>perfix</groupId>
|
||||
<artifactId>agent</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.21.0-GA</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Premain-Class>perfix.Agent</Premain-Class>
|
||||
<Can-Retransform-Classes>true</Can-Retransform-Classes>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.objectweb.asm</pattern>
|
||||
<shadedPattern>agent.org.objectweb.asm</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.javassist</pattern>
|
||||
<shadedPattern>agent.org.javassist</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
31
src/deploy/java/testperfix/Main.java
Normal file
31
src/deploy/java/testperfix/Main.java
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main/java/perfix/Agent.java
Normal file
84
src/main/java/perfix/Agent.java
Normal file
|
|
@ -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<String> 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<String> determineExcludes() {
|
||||
List<String> excludes = new ArrayList<>(Arrays.asList(System.getProperty("perfix.excludes").split(",")));
|
||||
excludes.add("perfix");
|
||||
return excludes;
|
||||
}
|
||||
|
||||
private static BooleanWrapper shouldExclude(String resource, List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/main/java/perfix/Method.java
Normal file
29
src/main/java/perfix/Method.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
51
src/main/java/perfix/Registry.java
Normal file
51
src/main/java/perfix/Registry.java
Normal file
|
|
@ -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<String, List<Method>> 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<Long, Report> sortedMethodsByDuration() {
|
||||
SortedMap<Long, Report> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/test/java/TestMethod.java
Normal file
16
src/test/java/TestMethod.java
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue