Skip to content

Commit

Permalink
Fixed #34. Added support for stack traces printing
Browse files Browse the repository at this point in the history
  • Loading branch information
kocherovms committed Mar 16, 2016
1 parent e55052e commit b9030a6
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 23 deletions.
5 changes: 4 additions & 1 deletion src/main/java/com/develorium/metracer/Main.java
Expand Up @@ -187,12 +187,15 @@ private void configureAgent() {
say(String.format("%d classes deinstrumented ok, %d failed", counters[0], counters[1]));
}
else {
int[] counters = agent.setPatterns(config.classMatchingPattern, config.methodMatchingPattern);
int[] counters = agent.setPatterns(config.classMatchingPattern, config.methodMatchingPattern, config.isWithStackTrace);
say(String.format("Class matching pattern set to \"%s\"", config.classMatchingPattern));

if(config.methodMatchingPattern != null)
say(String.format("Method matching pattern set to \"%s\"", config.methodMatchingPattern));

if(config.isWithStackTrace)
say("Stack traces enabled");

if(counters != null && counters.length == 2)
say(String.format("%d classes instrumented ok, %d failed", counters[0], counters[1]));
}
Expand Down
20 changes: 17 additions & 3 deletions src/main/java/com/develorium/metracer/Runtime.java
Expand Up @@ -18,6 +18,7 @@

import java.util.*;
import java.util.regex.*;
import java.io.*;

public class Runtime {
static public boolean isVerbose = false;
Expand Down Expand Up @@ -58,7 +59,7 @@ protected Integer initialValue() {
public static final TracingStateThreadLocal instance = new TracingStateThreadLocal();
}

public static void traceEntry(Class theClass, String theMethodName, String[] theArgumentNames, Object[] theArgumentValues) {
public static void traceEntry(Class theClass, String theMethodName, String[] theArgumentNames, Object[] theArgumentValues, boolean theIsWithStackTraces) {
StringBuilder arguments = new StringBuilder();

if(theArgumentValues != null) {
Expand All @@ -75,10 +76,23 @@ public static void traceEntry(Class theClass, String theMethodName, String[] the

Integer callDepth = TracingStateThreadLocal.instance.get() + 1;
TracingStateThreadLocal.instance.set(callDepth);
String message = String.format("[metracer.%s]%s +++ [%d] %s.%s(%s)", getFormattedThreadId(), getIndent(callDepth), callDepth, theClass.getName(), theMethodName, arguments.toString());
String messagePrefix = String.format("[metracer.%s]%s +++ [%d] ", getFormattedThreadId(), getIndent(callDepth), callDepth);
String message = String.format("%s%s.%s(%s)", messagePrefix, theClass.getName(), theMethodName, arguments.toString());

if(logger != null)
if(logger != null) {
logger.printMessage(theClass, theMethodName, message);

if(theIsWithStackTraces) {
StringWriter sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
String lines[] = sw.toString().split("\\r?\\n");

// skip first and last lines
for(int i = 1; i < lines.length - 1; ++i) {
logger.printMessage(theClass, theMethodName, messagePrefix + lines[i]);
}
}
}
}

public static void traceExit(Class theClass, String theMethodName, Object theReturnValue) {
Expand Down
Expand Up @@ -27,12 +27,14 @@ public class MetracerClassVisitor extends ClassVisitor {
private String className = null;
private Pattern classMatchingPattern = null;
private Pattern methodMatchingPattern = null;
private boolean isWithStackTraces = false;
private ClassNode parsedClass = null;

public MetracerClassVisitor(ClassVisitor theClassVisitor, Pattern theClassMatchingPattern, Pattern theMethodMatchingPattern, ClassNode theParsedClass) {
public MetracerClassVisitor(ClassVisitor theClassVisitor, Pattern theClassMatchingPattern, Pattern theMethodMatchingPattern, boolean theIsWithStackTraces, ClassNode theParsedClass) {
super(Opcodes.ASM5, theClassVisitor);
classMatchingPattern = theClassMatchingPattern;
methodMatchingPattern = theMethodMatchingPattern;
isWithStackTraces = theIsWithStackTraces;
parsedClass = theParsedClass;
}

Expand Down Expand Up @@ -76,7 +78,7 @@ else if(methodMatchingPattern != null && !com.develorium.metracer.Runtime.isMeth
}
}

methodVisitor = new PatternMatchedMethodMutator(className, method, api, methodVisitor, theAccess, theName, theDescription);
methodVisitor = new PatternMatchedMethodMutator(className, method, api, methodVisitor, theAccess, theName, theDescription, isWithStackTraces);
isChanged = true;
return methodVisitor;
}
Expand Down
Expand Up @@ -26,15 +26,18 @@ class PatternMatchedMethodMutator extends AdviceAdapter {
private MethodNode method = null;
private String methodName = null;
private boolean isStatic = false;
private boolean isWithStackTraces = false;
private Label startFinally = new Label();
private Label endFinally = new Label();

public PatternMatchedMethodMutator(String theClassName, MethodNode theMethod, int theApiVersion, MethodVisitor theDelegatingMethodVisitor, int theAccess, String theMethodName, String theMethodDescription) {
public PatternMatchedMethodMutator(String theClassName, MethodNode theMethod, int theApiVersion,
MethodVisitor theDelegatingMethodVisitor, int theAccess, String theMethodName, String theMethodDescription, boolean theIsWithStackTraces) {
super(theApiVersion, theDelegatingMethodVisitor, theAccess, theMethodName, theMethodDescription);
className = theClassName;
method = theMethod;
methodName = theMethodName;
isStatic = (theAccess & Opcodes.ACC_STATIC) != 0;
isWithStackTraces = theIsWithStackTraces;
}

@Override
Expand Down Expand Up @@ -64,8 +67,8 @@ protected void onMethodEnter() {
int runtimeClassVariableIndex = newLocal(Type.getType("Ljava/lang/Class;"));
mv.visitVarInsn(ASTORE, runtimeClassVariableIndex);

// Class<?>[] traceEntryArgumentTypes = { Class.class, String.class, String[].class, Object[].class };
mv.visitInsn(ICONST_4);
// Class<?>[] traceEntryArgumentTypes = { Class.class, String.class, String[].class, Object[].class, boolean.class };
mv.visitInsn(ICONST_5);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
Expand All @@ -83,6 +86,10 @@ protected void onMethodEnter() {
mv.visitInsn(ICONST_3);
mv.visitLdcInsn(Type.getType("[Ljava/lang/Object;"));
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_4);
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
mv.visitInsn(AASTORE);
int traceEntryArgumentTypesVariableIndex = newLocal(Type.getType("[Ljava/lang/Class;"));
mv.visitVarInsn(ASTORE, traceEntryArgumentTypesVariableIndex);

Expand Down Expand Up @@ -136,8 +143,8 @@ protected void onMethodEnter() {
mv.visitVarInsn(ASTORE, argumentValuesVariableIndex);
}

// Object[] traceEntryArgumentValues = { ?.class, "testMethod", argumentNames, argumentValues };
mv.visitInsn(ICONST_4);
// Object[] traceEntryArgumentValues = { ?.class, "testMethod", argumentNames, argumentValues, isWithStackTraces };
mv.visitInsn(ICONST_5);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
Expand All @@ -155,6 +162,11 @@ protected void onMethodEnter() {
mv.visitInsn(ICONST_3);
mv.visitVarInsn(ALOAD, argumentValuesVariableIndex);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_4);
mv.visitInsn(isWithStackTraces ? ICONST_1 : ICONST_0);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
mv.visitInsn(AASTORE);
int traceEntryArgumentValuesVariableIndex = newLocal(Type.getType("Ljava/lang/Object;"));
mv.visitVarInsn(ASTORE, traceEntryArgumentValuesVariableIndex);

Expand Down
30 changes: 22 additions & 8 deletions src/main/java/com/develorium/metracer/dynamic/Agent.java
Expand Up @@ -44,6 +44,7 @@ public class Agent extends NotificationBroadcasterSupport implements AgentMXBean
public static class Patterns {
public Pattern classMatchingPattern = null;
public Pattern methodMatchingPattern = null;
public boolean isWithStackTraces = false;

@Override
public boolean equals(Object theOther) {
Expand All @@ -53,11 +54,21 @@ else if(this == theOther)
return true;
else if(theOther instanceof Patterns) {
Patterns other = (Patterns)theOther;
return classMatchingPattern.equals(other.classMatchingPattern) && methodMatchingPattern.equals(other.methodMatchingPattern);
return
arePatternsEqual(classMatchingPattern, other.classMatchingPattern) &&
arePatternsEqual(methodMatchingPattern, other.methodMatchingPattern) &&
isWithStackTraces == other.isWithStackTraces;
}

return false;
}

private static boolean arePatternsEqual(Pattern theLeft, Pattern theRight) {
if(theLeft != null && theRight != null)
return theLeft.toString().equals(theRight.toString());
else
return (theLeft != null) == (theRight != null);
}
};
Patterns patterns = null;
List<Patterns> historyPatterns = new LinkedList<Patterns>();
Expand Down Expand Up @@ -96,22 +107,24 @@ public void setIsVerbose(boolean theIsVerbose) {
}

@Override
synchronized public int[] setPatterns(String theClassMatchingPattern, String theMethodMatchingPattern) {
synchronized public int[] setPatterns(String theClassMatchingPattern, String theMethodMatchingPattern, boolean theIsWithStackTraces) {
if(theClassMatchingPattern == null)
throw new NullPointerException("Class matching pattern is null");

Patterns newPatterns = createPatterns(theClassMatchingPattern, theMethodMatchingPattern);
Patterns newPatterns = createPatterns(theClassMatchingPattern, theMethodMatchingPattern, theIsWithStackTraces);

if(patterns != null && patterns.equals(newPatterns)) {
runtime.say(String.format("Patterns already set to: class matching pattern = %s%s",
runtime.say(String.format("Patterns already set to: class matching pattern = %s%s, stack traces = %s",
theClassMatchingPattern,
theMethodMatchingPattern != null ? String.format(", method matching pattern = %s", theMethodMatchingPattern) : ""));
theMethodMatchingPattern != null ? String.format(", method matching pattern = %s", theMethodMatchingPattern) : "",
"" + theIsWithStackTraces));
return null;
}

runtime.say(String.format("Setting patterns: class matching pattern = %s%s",
runtime.say(String.format("Setting patterns: class matching pattern = %s%s, stack traces = %s",
theClassMatchingPattern,
theMethodMatchingPattern != null ? String.format(", method matching pattern = %s", theMethodMatchingPattern) : ""));
theMethodMatchingPattern != null ? String.format(", method matching pattern = %s", theMethodMatchingPattern) : "",
"" + theIsWithStackTraces));

if(patterns != null)
historyPatterns.add(patterns);
Expand Down Expand Up @@ -203,10 +216,11 @@ private void unregisterMxBean() {
}
}

private static Patterns createPatterns(String theClassMatchingPattern, String theMethodMatchingPattern) {
static Patterns createPatterns(String theClassMatchingPattern, String theMethodMatchingPattern, boolean theIsWithStackTraces) {
Patterns rv = new Patterns();
rv.classMatchingPattern = createPattern(theClassMatchingPattern);
rv.methodMatchingPattern = createPattern(theMethodMatchingPattern);
rv.isWithStackTraces = theIsWithStackTraces;
return rv;
}

Expand Down
Expand Up @@ -18,6 +18,8 @@

public interface AgentMXBean {
public void setIsVerbose(boolean theIsVerbose);
public int[] setPatterns(String theClassMatchingPattern, String theMethodMatchingPattern); // return value is 2 elements array: 1 elem - number of instrument ok classes, number of instrument failed classes
public int[] removePatterns(); // return value meaning is the same as for setPatterns
// return value is 2 elements array: 1 elem - number of instrument ok classes, number of instrument failed classes
public int[] setPatterns(String theClassMatchingPattern, String theMethodMatchingPattern, boolean theIsWithStackTraces);
// return value meaning is the same as for setPatterns
public int[] removePatterns();
}
Expand Up @@ -70,7 +70,12 @@ private InstrumentClassResult instrumentClass(byte theBytecode[], ClassLoader th
reader.accept(parsedClass, 0);

MetracerClassWriter writer = new MetracerClassWriter(reader, theLoader);
MetracerClassVisitor visitor = new MetracerClassVisitor(writer, thePatterns.classMatchingPattern, thePatterns.methodMatchingPattern, parsedClass);
MetracerClassVisitor visitor = new MetracerClassVisitor(
writer,
thePatterns.classMatchingPattern,
thePatterns.methodMatchingPattern,
thePatterns.isWithStackTraces,
parsedClass);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);

InstrumentClassResult rv = new InstrumentClassResult();
Expand Down
Expand Up @@ -71,7 +71,7 @@ private InstrumentClassResult instrumentClass(byte theBytecode[], ClassLoader th
reader.accept(parsedClass, 0);

MetracerClassWriter writer = new MetracerClassWriter(reader, theLoader);
MetracerClassVisitor visitor = new MetracerClassVisitor(writer, pattern, null, parsedClass);
MetracerClassVisitor visitor = new MetracerClassVisitor(writer, pattern, null, false, parsedClass);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);

InstrumentClassResult rv = new InstrumentClassResult();
Expand Down
69 changes: 69 additions & 0 deletions src/test/java/TestAgentPatterns.java
@@ -0,0 +1,69 @@
/*
* Copyright 2015-2016 Michael Kocherov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.develorium.metracer.dynamic;

import java.util.*;
import java.util.regex.*;
import junit.framework.Assert;
import org.junit.Test;

public class TestAgentPatterns {
Agent.Patterns testNullFalsePattern = Agent.createPatterns("test", null, false);
Agent.Patterns testTestFalsePattern = Agent.createPatterns("test", "test", false);
Agent.Patterns testNullTruePattern = Agent.createPatterns("test", null, true);
Agent.Patterns testTestTruePattern = Agent.createPatterns("test", "test", true);
Agent.Patterns testNullFalsePattern2 = Agent.createPatterns("test", null, false);
Agent.Patterns testTestFalsePattern2 = Agent.createPatterns("test", "test", false);
Agent.Patterns testNullTruePattern2 = Agent.createPatterns("test", null, true);
Agent.Patterns testTestTruePattern2 = Agent.createPatterns("test", "test", true);

Agent.Patterns nullNullTruePattern = Agent.createPatterns(null, null, true);
Agent.Patterns nullNullFalsePattern = Agent.createPatterns(null, null, false);
Agent.Patterns nullTestTruePattern = Agent.createPatterns(null, "test", true);
Agent.Patterns nullTestFalsePattern = Agent.createPatterns(null, "test", false);
Agent.Patterns nullNullTruePattern2 = Agent.createPatterns(null, null, true);
Agent.Patterns nullNullFalsePattern2 = Agent.createPatterns(null, null, false);
Agent.Patterns nullTestTruePattern2 = Agent.createPatterns(null, "test", true);
Agent.Patterns nullTestFalsePattern2 = Agent.createPatterns(null, "test", false);

@Test
public void testEquality() {
Assert.assertEquals(testNullFalsePattern, testNullFalsePattern2);
Assert.assertEquals(testTestFalsePattern, testTestFalsePattern2);
Assert.assertEquals(testNullTruePattern, testNullTruePattern2);
Assert.assertEquals(testTestTruePattern, testTestTruePattern2);

Assert.assertEquals(nullNullTruePattern, nullNullTruePattern2);
Assert.assertEquals(nullNullFalsePattern, nullNullFalsePattern2);
Assert.assertEquals(nullTestTruePattern, nullTestTruePattern2);
Assert.assertEquals(nullTestFalsePattern, nullTestFalsePattern2);
}

@Test
public void testNonEquality() {
Assert.assertFalse(testNullFalsePattern.equals(testTestFalsePattern));
Assert.assertFalse(testNullFalsePattern.equals(testNullTruePattern));
Assert.assertFalse(testNullFalsePattern.equals(testTestTruePattern));
Assert.assertFalse(testNullFalsePattern.equals(testTestFalsePattern2));
Assert.assertFalse(testNullFalsePattern.equals(testNullTruePattern2));
Assert.assertFalse(testNullFalsePattern.equals(testTestTruePattern2));

Assert.assertFalse(nullNullTruePattern.equals(nullNullFalsePattern));
Assert.assertFalse(nullNullTruePattern.equals(nullTestTruePattern));
Assert.assertFalse(nullNullTruePattern.equals(nullTestFalsePattern));
}
}

0 comments on commit b9030a6

Please sign in to comment.