Agent and annotations to enforce performance invariants in Java
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
.gitignore
README.textile
pom.xml

README.textile

Performance Invariants

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:

Compiling

After you checkout the source code, all you have to do is:

~/performance>mvn package

Running the agent

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.

Annotating Methods

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

Expectation Syntax

The expression used to declare expectations consists of:

  • logical operators: &&, ||
  • relational operators: <, >, <=, >=
  • equality operators: ==, !=
  • arithmetic operators: +, -, *, /
  • unary operators: -, !
  • Method matchers
  • Dynamic values

Method matchers

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()).

Dynamic values

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.

Member resolution

Members are resolved according to the following order:

Assume that we’re attempting to resolve ${x.size}

  1. it will first look for a field named ‘size’
  2. it will look for a method named ‘size’
  3. it will look for a method named ‘getSize’
  4. it will look for a method named ‘hasSize’
  5. it will look for a method named ‘isSize’

Code structure

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.

Integrating with Maven

<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>

License

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