Skip to content

Commit

Permalink
skip too many field reads
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarv committed Jan 28, 2018
1 parent e22e4f1 commit 01af006
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 6 deletions.
6 changes: 4 additions & 2 deletions examples/falo.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ groupBy: ENTRY

# store dot files and coverage matrix
# writeTo: DOT
# writeTo: DOT,TRACE,COVERAGE,DD_DOT,LINES,COMBINED_DOT
writeTo: DOT,TRACE,COVERAGE,LINES
writeTo: DOT,TRACE,COVERAGE,DD_DOT,LINES,COMBINED_DOT
# writeTo: DOT,TRACE,COVERAGE,LINES
# writeTo: DOT,DD_DOT,LINES

format: {class}#{line}

fastDD: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* MIT License
* <p>
* Copyright (c) 2017 David Krebs
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.dkarv.jdcallgraph;

import com.dkarv.jdcallgraph.callgraph.CallGraph;
import com.dkarv.jdcallgraph.data.DataDependenceGraph;
import com.dkarv.jdcallgraph.util.StackItem;
import com.dkarv.jdcallgraph.util.StackItemCache;
import com.dkarv.jdcallgraph.util.config.Config;
import com.dkarv.jdcallgraph.util.log.Logger;
import com.dkarv.jdcallgraph.util.options.Target;
import java.io.IOException;

/**
* Improved version of the fast field access recorder tuned for performance. The downsides are:
* - It might not always be right
* - No logging available
*/
public class FastFieldAccessRecorder {
private static final Logger LOG = new Logger(FastFieldAccessRecorder.class);

/**
* Collect the call graph per thread.
*/
static DataDependenceGraph GRAPH;
private static final boolean needCombined;
private static final int SIZE = 10;
private static final int[] HASH_CACHE = new int[SIZE];
private static final String[] CACHE = new String[SIZE];
private static int POS = SIZE - 1;

static {
boolean combined = false;
for (Target t : Config.getInst().writeTo()) {
if (t == Target.COMBINED_DOT) {
combined = true;
}
}
needCombined = combined;
try {
GRAPH = new DataDependenceGraph();
} catch (IOException e) {
throw new IllegalStateException("Error setting up data dependence graph", e);
}
}

public static void write(String fromClass, String fromMethod, int lineNumber, String fieldClass,
String fieldName) {
int hash = 31 * fieldClass.hashCode() + fieldName.hashCode();
for (int i = 0; i < SIZE; i++) {
if (HASH_CACHE[i] == hash) {
HASH_CACHE[i] = -1;
CACHE[i] = null;
}
}

try {
StackItem item = StackItemCache.get(fromClass, fromMethod, lineNumber, false);
GRAPH.addWrite(item, fieldClass + "::" + fieldName);
} catch (Exception e) {
LOG.error("Error in write", e);
}
}

public static void read(String fromClass, String fromMethod, int lineNumber, String fieldClass,
String fieldName) {
int hash = 31 * fieldClass.hashCode() + fieldName.hashCode();
for (int j = 0, i = POS; j < SIZE; j++, i = (i + 1) % SIZE) {
if (HASH_CACHE[i] == hash) {
String s =
fromClass + "%" + fromMethod + "%" + lineNumber + "%" + fieldClass + "%" + fieldName;
if (s.equals(CACHE[i])) {
// we already saw this read recently
return;
}
}
}

// store in cache
POS = (POS - 1 + SIZE) % SIZE;
HASH_CACHE[POS] = hash;
CACHE[POS] =
fromClass + "%" + fromMethod + "%" + lineNumber + "%" + fieldClass + "%" + fieldName;

try {
CallGraph callGraph;
if (needCombined) {
callGraph = CallRecorder.GRAPHS.get(Thread.currentThread().getId());
} else {
callGraph = null;
}
StackItem item = StackItemCache.get(fromClass, fromMethod, lineNumber, false);
GRAPH.addRead(item, fieldClass + "::" + fieldName, callGraph);
} catch (Exception e) {
LOG.error("Error in read", e);
}
}

public static void log(Object o) {
LOG.info("log: {}", o);
}

public static void shutdown() {
try {
GRAPH.finish();
} catch (IOException e) {
LOG.error("Error finishing call graph.", e);
}
}
}
13 changes: 9 additions & 4 deletions jdcallgraph/src/main/java/com/dkarv/jdcallgraph/FieldTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package com.dkarv.jdcallgraph;

import com.dkarv.jdcallgraph.util.config.Config;
import com.dkarv.jdcallgraph.util.log.Logger;
import javassist.CannotCompileException;
import javassist.CtBehavior;
Expand Down Expand Up @@ -83,6 +84,10 @@ public class FieldTracer extends ExprEditor {
*/
private static final Pattern IGNORE_VAL = Pattern.compile("^val\\$[a-zA-Z_$][a-zA-Z_$0-9]*$");

private static final String TARGET =
Config.getInst().fastDD() ? FastFieldAccessRecorder.class.getCanonicalName() :
FieldAccessRecorder.class.getCanonicalName();

public final void edit(FieldAccess f) throws CannotCompileException {
CtBehavior method = f.where();
String className = f.getEnclosingClass().getName();
Expand All @@ -101,10 +106,10 @@ public final void edit(FieldAccess f) throws CannotCompileException {
String field = ",\"" + f.getClassName() + "\",\"" + f.getFieldName() + "\"";
if (isWrite) {
f.replace("{ $proceed($$); " +
"com.dkarv.jdcallgraph.FieldAccessRecorder.write(" + from + field + "); }");
TARGET + ".write(" + from + field + "); }");
} else {
f.replace("{ $_ = $proceed($$); " +
"com.dkarv.jdcallgraph.FieldAccessRecorder.read(" + from + field + "); }");
TARGET + ".read(" + from + field + "); }");
}
} else {
String field = ",\"" + f.getClassName() + "@\"" +
Expand All @@ -117,11 +122,11 @@ public final void edit(FieldAccess f) throws CannotCompileException {
LOG.debug("Ignore write to val {}:{} from {}", f.getClassName(), f.getFieldName(), from);
} else {
f.replace("{ $_ = $proceed($$); " +
"com.dkarv.jdcallgraph.FieldAccessRecorder.write(" + from + field + "); }");
TARGET + ".write(" + from + field + "); }");
}
} else {
f.replace("{ $_ = $proceed($$); " +
"com.dkarv.jdcallgraph.FieldAccessRecorder.read(" + from + field + "); }");
TARGET + ".read(" + from + field + "); }");
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class CallGraph {
private static final String FOLDER = "cg/";
private final long threadId;
final Stack<StackItem> calls = new Stack<>();
final Stack<Integer> reads = new Stack<>();

final List<GraphWriter> writers = new ArrayList<>();

Expand Down Expand Up @@ -90,6 +91,7 @@ String checkStartCondition(StackItem method) {
}

public void called(StackItem method) throws IOException {
//reads.push(0);
if (calls.isEmpty()) {
// First node
String identifier = checkStartCondition(method);
Expand All @@ -112,6 +114,12 @@ public void called(StackItem method) throws IOException {
}

public void returned(StackItem method) throws IOException {
//if (!reads.isEmpty()) {
// int r = reads.pop();
// if (r != 0) {
// LOG.info("{} did reads: {}", method, r);
// }
//}
Stack<StackItem> trace = new Stack<>();
int removed = 0;
boolean found = false;
Expand All @@ -138,6 +146,9 @@ public void returned(StackItem method) throws IOException {
}

public void dataEdge(StackItem from, StackItem to) throws IOException {
//if (!reads.isEmpty()) {
// reads.push(reads.pop() + 1);
//}
if (calls.isEmpty()) {
LOG.info("Ignore dd egde {} -> {}", from, to);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ static void reset() {
@Option
private String format = "{class}::{method}#{line}";

@Option
private boolean fastDD = false;

private boolean dataDependency = false;

void set(Field f, String value) throws IllegalAccessException {
Expand Down Expand Up @@ -144,4 +147,8 @@ public boolean dataDependency() {
public String format() {
return format;
}

public boolean fastDD() {
return fastDD;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.dkarv.jdcallgraph;

import com.dkarv.jdcallgraph.callgraph.CallGraph;
import com.dkarv.jdcallgraph.data.DataDependenceGraph;
import com.dkarv.jdcallgraph.util.StackItem;
import com.dkarv.jdcallgraph.util.config.Config;
import com.dkarv.jdcallgraph.util.config.ConfigUtils;
import java.io.IOException;
import java.util.Random;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

public class FastFieldAccessRecorderTest {
private static Random RND = new Random(123987);

private DataDependenceGraph graph;
private String[][] methods = new String[20][];
private int[] lines = new int[methods.length];
private String[][] fields = new String[methods.length][];

@Before
public void setUp() {
Config c = new Config();
ConfigUtils.inject(c);

graph = Mockito.mock(DataDependenceGraph.class);
FastFieldAccessRecorder.GRAPH = graph;

for (int i = 0; i < methods.length; i++) {
methods[i] = new String[]{"class_" + RND.nextInt(), "method_" + RND.nextInt()};
lines[i] = RND.nextInt();
fields[i] = new String[]{"class-" + RND.nextInt(), "field-" + RND.nextInt()};
}
}

@Test
public void testWrite() throws IOException {
write(0);
verifyWrite(0);

write(1);
verifyWrite(1);
}

@Test
public void testRead() throws IOException {
read(0);
verifyRead(0);
}

@Test
public void testReadCache() throws IOException {
read(0);
verifyRead(0);

// should ignore further reads
read(0);
read(1);
verifyRead(1);

read(1);
for (int i = 0; i < 10; i++) {
read(0);
}
}

@Test
public void testCacheReset() throws IOException {
read(0);
verifyRead(0);

write(0);
verifyWrite(0);
read(0);
verifyRead(0, 2);
}

@After
public void after() {
Mockito.verifyNoMoreInteractions(graph);
}

private void write(int i) {
FastFieldAccessRecorder
.write(methods[i][0], methods[i][1], lines[i], fields[i][0], fields[i][1]);
}

private void read(int i) {
FastFieldAccessRecorder
.read(methods[i][0], methods[i][1], lines[i], fields[i][0], fields[i][1]);
}

private void verifyWrite(int i) throws IOException {
verifyWrite(i, 1);
}

private void verifyWrite(int i, int n) throws IOException {
Mockito.verify(graph, Mockito.times(n))
.addWrite(Mockito.eq(new StackItem(methods[i][0], methods[i][1], lines[i])),
Mockito.eq(fields[i][0] + "::" + fields[i][1]));
}

private void verifyRead(int i) throws IOException {
verifyRead(i, 1);
}

private void verifyRead(int i, int n) throws IOException {
Mockito.verify(graph, Mockito.times(n))
.addRead(Mockito.eq(new StackItem(methods[i][0], methods[i][1], lines[i])),
Mockito.eq(fields[i][0] + "::" + fields[i][1]), Mockito.<CallGraph>isNull());
}
}

0 comments on commit 01af006

Please sign in to comment.