This library implements a Java agent and a couple of annotations to declare performance invariants in Java code.
The implementation is currently incomplete (and very primitive).
For more details on the ideas behind it see:
After you checkout the source code, all you have to do is:
~/performance>mvn package
You just need to add the agent and jar to the JVM:
java -javaagent:$HOME/performance/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar \
-Xbootclasspath/a:$HOME/performance/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar -cp [classpath] MainClass
Since it can impact performance, the agent is not automatically installed if it’s just included on the classpath, I might change this afterwards.
import performance.annotation.Expect; public class Test { static void bah(){} @Expect("bah < 10") static void foo() { for(int i = 0; i < 100; i++) { bah(); } } public static void main(String[] args) { foo(); } }
After compiling it, you can run it with:
java -javaagent:$HOME/performance/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar \
-Xbootclasspath/a:$HOME/performance/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar Test
It should produce output similar to the following:
Exception in thread “main” java.lang.AssertionError: Method ‘Test.foo’ did not fulfil: bah < 10
[#.bah=100]
at performance.runtime.PerformanceExpectation.validate(PerformanceExpectation.java:58)
at performance.runtime.ThreadHelper.endExpectation(ThreadHelper.java:49)
at performance.runtime.Helper.endExpectation(Helper.java:57)
at Test.foo(Test.java:20)
at Test.main(Test.java:25)
If running with Java 7 also add -XX:-UseSplitVerifier
The expression used to declare expectations consists of:
- logical operators:
&&
,||
- relational operators:
<
,>
,<=
,>=
- equality operators:
==
,!=
- arithmetic operators:
+
,-
,*
,/
- unary operators:
-
,!
- Method matchers
- Dynamic values
Simple identifiers are treated as method names. If they are qualified, the one to the left o the dot refers to a simple classname (as returned by Class.getSimpleClassName()
).
Expressions of the form ${a.b.c.d} refer to arguments, instance variables or static variables.
For example:
*${static.CONSTANT}
refers to a variable named CONSTANT in the current class.${this.instance}
refers to a variable named ‘instance’ in the current object (only valid for instance methods).${n}
refers to an argument named ‘n’ (this only works if the class has debug information)${3}
refers to the fourth argument from the left (zero based indexing)${list.size}
refers to an argument named ‘list’ with a method named ‘size’
All dynamic values MUST yield a numeric value, otherwise a failure will be reported.
Dynamic values are bound on method enter. For example, if you capture the size of list passed as an argument, the size used for evaluation will be the one that was on method enter. If during method execution the size changes, it will not be accounted for.
Members are resolved according to the following order:
Assume that we’re attempting to resolve ${x.size}
- it will first look for a field named ‘size’
- it will look for a method named ‘size’
- it will look for a method named ‘getSize’
- it will look for a method named ‘hasSize’
- it will look for a method named ‘isSize’
The code is layed out in several packages:
- annotation: Contains the annotations
- compiler: The expression interpreter (see SimpleGrammar)
- parser: a generic Pratt parser to build an AST from a simple grammar.
- runtime: runtime support classes, called from bytecode. (see Helper)
- transformer: the bytecode transformer (see PerformanceAgent)
- util: some generic utilities
The library works by instrumenting all methods as they are loaded. It inserts calls to performance.runtime.Helper
on method enter/exit. It adds additional calls for methods annotated with the @Expect
.
Parsing of expressions ocurs when the annotated method is entered. At that point, dynamic values are bound. During method execution, method calls are matched against the method matchersa in the expression.
On method exit (normal or otherwise), the expression is evaluated. If it yields a false value, an AssertionError will be thrown. Note that this might shadow exceptions on the underlying code.
<project> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine>-javaagent:${basedir}/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar</argLine> <argLine>-Xbootclasspath/p:${basedir}/target/performance-1.0-SNAPSHOT-jar-with-dependencies.jar</argLine> </configuration> </plugin> </plugins> </build> </project>
Licensed under the Apache License, Version 2.0 (the “License”);
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0