initial project

This commit is contained in:
Sander Hautvast 2018-03-29 12:51:43 +02:00
commit e54907769d
7 changed files with 291 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
.idea/
perfix.iml
target/

76
pom.xml Normal file
View 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>

View 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();
}
}
}

View 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;
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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();
}
}