Skip to content

Commit

Permalink
added JavaAPIUsageTracer
Browse files Browse the repository at this point in the history
  • Loading branch information
chrstphlbr committed Mar 14, 2018
1 parent cbfddaf commit 06bd5c7
Show file tree
Hide file tree
Showing 8 changed files with 508 additions and 0 deletions.
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# An AspectJ-based API-usage tracer

The primary and only goal is to log how often the methods in the public API of a specific project are called at runtime.
Calls are written directly to a CSV file. Calls need to be counted afterwards.
The tracer can be integrated (relatively) easily
into an existing Maven or Gradle build.

## Installing

Run `mvn clean install -DskipTests` in this directory. This installs the tracer to your local
m2 repo, and it should be usable from then on.

## Tracing a project

### For Maven

Enabling the tracing for an existing project requires some modifications to the `pom.xml` build file and
project structure.

- Step 1: add an aop.xml file to `src/test/resources/META-INF`. The content can be like below. Replace the
log4j stuff in the example with a package name pattern that makes sense for the lib you want to trace.

```xml
<aspectj>
<!-- <weaver options="-verbose -showWeaveInfo -debug"> -->
<weaver>
<include within="org.apache.logging.log4j..**"/>
<include within="ch.uzh.ifi.seal.dynamicanalyzer.LogAspect"/>
<!--<exclude within="org.apache.logging.log4j.osgi..**" />-->
</weaver>
<aspects>
<aspect name="ch.uzh.ifi.seal.dynamicanalyzer.LogAspect"/>
</aspects>
</aspectj>
```

- Step 2: Add some dependencies to `pom.xml`. You will need to add the following dependencies:

```xml
<dependency>
<groupId>hopper</groupId>
<artifactId>aspectj-tracer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
```

- Step 3: Enable LTW. The following assumes you want to trace unit test execution. In that case add
the following to your surefire configuration:

```xml
<configuration>
<argLine>
-javaagent:$HOME/.m2/repository/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar -Dtracer.file=output.csv -Dtracer.projectname=testproject -Dtracer.libname=log4j2
</argLine>
</configuration>
```

(replace $HOME with your home directory, and the other parameters as you want, see below)

### For Gradle

For Gradle the process is *almost* the same.

- Step 1: the same as for Mvn (see above).

- Step 2: add repository mavenLocal if not used:
```gradle
repositories {
mavenLocal()
}
```

- Step 3: Add some dependencies to `build.gradle`. You will need to add the following dependencies:

```gradle
testCompile("org.aspectj:aspectjrt:1.8.10")
testCompile("org.aspectj:aspectjweaver:1.8.10")
testCompile("hopper:aspectj-tracer:0.0.1-SNAPSHOT")
```

- Step 4: Enable LTW. The following assumes you want to trace unit test execution. In that case add
the following to your Gradle test plugin configuration:

```gradle
jvmArgs '-javaagent:$HOME/.m2/repository/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar'
systemProperty 'tracer.file', 'output.csv'
systemProperty 'tracer.projectname', 'reveno'
systemProperty 'tracer.libname', 'protostuff'
```

## Parameters

The tracer accepts 3 parameters as environment variables. You can either set them via
an argline in the `pom.xml` as above, but you could also export them before launching the app, or give them
as parameter to your Maven or Gradle build (`-Dtracer.file=output.csv`)

- "tracer.file" is the name of the output file that the results will be written to
- "tracer.projectname" is the project name to use in the CSV file
- "tracer.libname" is the library name to use in the CSV file


## Other things of import

The way the tracer is currently implemented is *slow*. Do not be puzzled if it *significantly* slows down
unit test execution. Some builds might even break because of that.
61 changes: 61 additions & 0 deletions aggregate_traces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# import pandas
import sys

# usage: python aggregate_traces logfile outfile

# Version 1 - with Pandas

# df = pandas.read_csv(sys.argv[0])
# outfile = sys.argv[1]
# lib = df.LIB[0]
# project = df.PROJECT[0]
#
# with open(outfile, 'w') as f:
# f.write("LIB;PROJECT;METHOD;COUNT\n")
# for method in df.METHOD.unique():
# count = len(df[df.METHOD == method])
# f.write("%s;%s;%s;%d\n" % (lib, project, method, count))

# Version 2 - linear time

def parse_file(file):
with open(file, 'r') as infile:
methods = {}
lib = ''
project = ''
for line in infile:
try:
lib, project, method = line.strip().split(';')
if lib == 'LIB':
continue # ignore header
if method in methods:
methods[method] += 1
else:
methods[method] = 1
except:
print('Ignoring exception while parsing\n')
return (lib, project, methods)

def append_to_results(results, lib, project, methods):
if lib not in results:
results[lib] = {}
results[lib][project] = methods
return results



outfile = sys.argv[1]
total_results = {}
for f in sys.argv[2:]:
print("Parsing %s" % f)
lib, project, methods = parse_file(f)
print("Finished reading %s" % f)
append_to_results(total_results, lib, project, methods)
print("Finished reading all")
with open(outfile, 'w') as outfile:
outfile.write('LIB;PROJECT;METHOD;COUNT\n')
for lib in total_results:
for project in total_results[lib]:
for method, count in total_results[lib][project].items():
outfile.write("%s;%s;%s;%d\n" % (lib, project, method, count))
print('Finished writing\n')
59 changes: 59 additions & 0 deletions calculate_ranking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# import pandas
import sys

def parse_file(file):
parsed = {}
with open(file, 'r') as infile:
for line in infile:
try:
lib, project, method, count = line.strip().split(';')
if lib == 'LIB':
continue # ignore header
if lib not in parsed:
parsed[lib] = {}
if project not in parsed[lib]:
parsed[lib][project] = {}
parsed[lib][project][method] = int(count)
except:
print('Ignoring exception while parsing\n')
return parsed

def get_all_methods(ranking):
methods = []
for project in ranking:
for method in ranking[project]:
if method not in methods:
methods += [method]
return methods

def weights_for_project(methods):
result = {}
_sum = sum(methods.values())
for method in methods:
result[method] = methods[method] / float(_sum)
return result

def select_top_n(ranking, n):
aggregated_weights = {}
methods = get_all_methods(ranking)
for method in methods:
agg = 0
for project in ranking:
if method in ranking[project]:
agg += ranking[project][method]
aggregated_weights[method] = agg
sorted_list = sorted(aggregated_weights.items(), key=lambda x: -1 * x[1])
return sorted_list[:n+1]

infile = sys.argv[1]
top_n = int(sys.argv[2])
data = parse_file(infile)

for lib in data:
ranking = {}
for project in data[lib]:
ranking[project] = weights_for_project(data[lib][project])
top = select_top_n(ranking, top_n)
print("Ranking of methods for %s:" % lib)
for t in top:
print("%s (weight %0.5f)" % (t[0], t[1]))
91 changes: 91 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hopper</groupId>
<artifactId>aspectj-tracer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<complianceLevel>1.8</complianceLevel>
<verbose>true</verbose>
<weaveWithAspectsInMainSourceFolder>true</weaveWithAspectsInMainSourceFolder>
</configuration>
<executions>
<execution>
<configuration>
<XnoInline>true</XnoInline>
</configuration>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<configuration>
<argLine>
-javaagent:/Users/philipp/.m2/repository/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar -Dtracer.file=output.csv -Dtracer.projectname=testproject -Dtracer.libname=log4j2
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>java</executable>
<arguments>
<argument>-javaagent:/Users/philipp/.m2/repository/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar</argument>
<argument>-classpath</argument>
<classpath />
<argument>ch.uzh.ifi.seal.dynamicanalyzer.Testclass</argument>
</arguments>
</configuration>
</plugin>
-->
</plugins>
</build>
</project>
54 changes: 54 additions & 0 deletions src/main/java/ch/uzh/ifi/seal/dynamicanalyzer/CsvFileAppender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ch.uzh.ifi.seal.dynamicanalyzer;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Formatter;

public class CsvFileAppender {

private String filename = System.getProperty("tracer.file");
private String libname = System.getProperty("tracer.libname");
private String projectname = System.getProperty("tracer.projectname");
private BufferedWriter appender = null;

private static CsvFileAppender instance = null;

private CsvFileAppender() throws IOException {
if(!new File(this.filename).exists()) {
appender = new BufferedWriter(new FileWriter(filename));
writeFileHeader(appender);
} else {
appender = new BufferedWriter(new FileWriter(filename, true));
}
}

public synchronized static CsvFileAppender getInstance() throws IOException {
if(instance == null)
instance = new CsvFileAppender();
return instance;
}

public synchronized void addInvocationEntry(String fullMethodName) throws IOException {

writeEntry(appender, libname, projectname, fullMethodName);

}

private void writeEntry(BufferedWriter writer, String lib, String project,
String method) throws IOException {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
formatter.format("%s;%s;%s\n", lib, project, method);
writer.write(sb.toString());
formatter.close();
}

private void writeFileHeader(BufferedWriter writer) throws IOException {
String header = "LIB;PROJECT;METHOD\n";
writer.write(header);
writer.flush();
}

}
Loading

0 comments on commit 06bd5c7

Please sign in to comment.