diff --git a/README.md b/README.md
index 21d08e7e..8949dd0e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,33 @@
+
java-callgraph: Java Call Graph Utilities
-=========================================
+
+# 项目背景
+
+笔者试图阅读一些开源项目的源码,发现几个问题:
+
+- 代码量过于庞大,不知从何看起。
+- 一行行阅读代码无法看到整体结构。
+- 下断点一行行跟踪代码费事费力。
+
+此项目解决源码阅读问题,让您快速掌握软件的整体结构,帮助理解软件运行机制。
+
+# 使用方法
+
+先把代码拉下来,然后执行编译:
+
+
+mvn package -DskipTests
+
+
+这时产生一个文件`javacg-0.1-SNAPSHOT-dycg-agent.jar`,用于做JavaAgent。
+
+然后运行需要研究的项目,加上如下参数:
+
+-javaagent:javacg-dycg-agent.jar="incl=mylib.*,mylib2.*,java.nio.*;excl=java.nio.charset.*"
+
+运行完成后将会得到一个Xmind文件,用Xmind打开,就可以看到运行的整个过程。
+
+# 原始文档
A suite of programs for generating static and dynamic call graphs in Java.
@@ -12,7 +40,7 @@ A suite of programs for generating static and dynamic call graphs in Java.
#### Compile
-The java-callgraph package is build with maven. Install maven and do:
+The java-callgraph package is build with maven. Install maven and do:
mvn install
diff --git a/assembly-dyn.xml b/assembly-dyn.xml
index 5c342dcf..5094d853 100644
--- a/assembly-dyn.xml
+++ b/assembly-dyn.xml
@@ -14,6 +14,7 @@
true
javassist:javassist
+ com.alibaba:fastjson
provided
diff --git a/assembly-test.xml b/assembly-test.xml
new file mode 100644
index 00000000..95cbba51
--- /dev/null
+++ b/assembly-test.xml
@@ -0,0 +1,21 @@
+
+
+ test
+
+ jar
+
+ false
+
+
+
+
+ gr/gousiosg/javacg/test/*.class
+
+ target/classes
+ /
+
+
+
diff --git a/pom.xml b/pom.xml
index 21046a41..62808177 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,12 @@
3.12.1.GA
provided
+
+ com.alibaba
+ fastjson
+ 1.2.8
+ provided
+
@@ -37,6 +43,7 @@
assembly-dyn.xml
assembly-st.xml
+ assembly-test.xml
diff --git a/process_trace.rb b/process/process_trace.rb
similarity index 100%
rename from process_trace.rb
rename to process/process_trace.rb
diff --git a/process/to_xmind.py b/process/to_xmind.py
new file mode 100644
index 00000000..68fcd9fe
--- /dev/null
+++ b/process/to_xmind.py
@@ -0,0 +1,121 @@
+import sys
+import json
+
+file = sys.argv[1]
+
+
+def main():
+ # read traces
+ traces = list(read_file())
+
+ # separate threads
+ thread_traces = separate_threads(traces)
+
+ # make tree for every thread
+ root = TraceNode('')
+ for thread_id, traces in thread_traces:
+ t = make_tree(str(thread_id), traces)
+ root.add(t)
+
+ # output as xmind
+ print(xmind(root))
+
+
+def xmind(node):
+ return '\n'.join(xmind_lines(node))
+
+
+def xmind_lines(node):
+ lines = []
+ lines.append(node.text)
+ for e in node.children:
+ for line in xmind_lines(e):
+ lines.append('\t' + line)
+ return lines
+
+
+def make_tree(name, traces):
+ root = TraceNode(name)
+ if not traces: return root
+ target_depth = traces[0].depth
+ next_level = []
+ for e in traces:
+ if e.direction == 'in': continue
+ if e.depth != target_depth:
+ next_level.append(e)
+ continue
+ if e.depth == target_depth:
+ name = simple_method_name(e.clazz, e.method)
+ node = make_tree(name, next_level)
+ root.add(node)
+ next_level = []
+ continue
+ return root
+
+
+def simple_method_name(full_class, method_name):
+ dot = full_class.rindex('.')
+ simple_class = full_class[dot + 1:]
+ if '$' not in simple_class: return simple_class + '.' + method_name
+ dollar = simple_class.rindex('$')
+ nest_class = simple_class[dollar + 1:]
+ return nest_class + '.' + method_name
+
+
+def separate_threads(traces):
+ # all threads
+ threads = []
+ for e in traces:
+ if e.thread not in threads:
+ threads.append(e.thread)
+
+ # every thread:
+ result = []
+ for e in threads:
+ thread_traces = []
+ for e2 in traces:
+ if e2.thread == e:
+ thread_traces.append(e2)
+ result.append((e, thread_traces))
+ return result
+
+
+def read_file():
+ with open(file) as f:
+ for e in f:
+ e = e.strip()
+ if not e: continue
+ yield parse_trace(e)
+
+
+def parse_trace(trace):
+ trace = json.loads(trace)
+ result = Trace()
+ result.time = trace['@timestamp']
+ result.method = trace['method']
+ result.clazz = trace['class']
+ result.depth = trace['depth']
+ result.thread = trace['thread']
+ result.direction = trace['direction']
+ return result
+
+
+class Trace:
+ direction = None
+ thread = None
+ depth = None
+ clazz = None
+ method = None
+ time = None
+
+
+class TraceNode:
+ def __init__(self, text):
+ self.text = text
+ self.children = []
+
+ def add(self, node):
+ self.children.append(node)
+
+
+main()
diff --git a/src/main/java/gr/gousiosg/javacg/dyn/CallTrace.java b/src/main/java/gr/gousiosg/javacg/dyn/CallTrace.java
new file mode 100644
index 00000000..a3cbfbe2
--- /dev/null
+++ b/src/main/java/gr/gousiosg/javacg/dyn/CallTrace.java
@@ -0,0 +1,47 @@
+package gr.gousiosg.javacg.dyn;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.Stack;
+
+/**
+ * Created by caipeichao on 16/12/10.
+ */
+public class CallTrace {
+
+ private static ThreadLocal threadLocal = new ThreadLocal() {
+ @Override
+ public CallTrace initialValue() {
+ return new CallTrace();
+ }
+ };
+
+ private Stack stack = new Stack();
+
+ public static void push(String className, String method) {
+ threadLocal.get().localPush(className, method);
+ }
+
+ public static void pop() {
+ threadLocal.get().localPop();
+ }
+
+ private void localPush(String className, String method) {
+ JSONObject json = new JSONObject();
+ json.put("@timestamp", System.currentTimeMillis());
+ json.put("thread", Thread.currentThread().getId());
+ json.put("class", className);
+ json.put("method", method);
+ json.put("direction", "in");
+ json.put("depth", stack.size());
+ stack.push(json);
+ LogFile.log(json);
+ }
+
+ private void localPop() {
+ JSONObject json = stack.pop();
+ json.put("@timestamp", System.currentTimeMillis());
+ json.put("direction", "out");
+ LogFile.log(json);
+ }
+}
diff --git a/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java b/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java
index 3aee29b8..ce4fa192 100644
--- a/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java
+++ b/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java
@@ -43,6 +43,8 @@
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
+import javassist.expr.ExprEditor;
+import javassist.expr.MethodCall;
public class Instrumenter implements ClassFileTransformer {
@@ -102,7 +104,7 @@ public static void premain(String argument, Instrumentation instrumentation) {
}
public byte[] transform(ClassLoader loader, String className, Class clazz,
- java.security.ProtectionDomain domain, byte[] bytes) {
+ java.security.ProtectionDomain domain, byte[] bytes) {
boolean enhanceClass = false;
String name = className.replace("/", ".");
@@ -169,10 +171,8 @@ private void enhanceMethod(CtBehavior method, String className)
if (method.getName().equals(name))
methodName = "";
-
- method.insertBefore("gr.gousiosg.javacg.dyn.MethodStack.push(\"" + className
- + ":" + methodName + "\");");
- method.insertAfter("gr.gousiosg.javacg.dyn.MethodStack.pop();");
+ method.insertBefore(String.format("gr.gousiosg.javacg.dyn.CallTrace.push(\"%s\",\"%s\");", className, methodName));
+ method.insertAfter("gr.gousiosg.javacg.dyn.CallTrace.pop();", true);
}
private static void err(String msg) {
diff --git a/src/main/java/gr/gousiosg/javacg/dyn/LogFile.java b/src/main/java/gr/gousiosg/javacg/dyn/LogFile.java
new file mode 100644
index 00000000..a1db5966
--- /dev/null
+++ b/src/main/java/gr/gousiosg/javacg/dyn/LogFile.java
@@ -0,0 +1,61 @@
+package gr.gousiosg.javacg.dyn;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Created by caipeichao on 16/12/10.
+ */
+public class LogFile {
+ private static final LogFile INSTANCE = new LogFile();
+
+ public static void log(JSONObject json) {
+ INSTANCE.internalLog(json);
+ }
+
+ private final BufferedWriter writer;
+
+ public LogFile() {
+ // 初始化日志文件
+ FileOutputStream fout = null;
+ try {
+ fout = new FileOutputStream("calltrace.json");
+ } catch (FileNotFoundException e) {
+ }
+ this.writer = new BufferedWriter(new OutputStreamWriter(fout, Charset.forName("utf8")));
+
+ // 退出时关闭日志文件
+ this.closeWhenExit();
+ }
+
+ public void internalLog(JSONObject json) {
+ try {
+ String x = json.toJSONString();
+ this.writer.write(x);
+ this.writer.newLine();
+ } catch (IOException e) {
+ }
+ }
+
+ public void close() {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ }
+ }
+
+ private void closeWhenExit() {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ close();
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java b/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java
deleted file mode 100644
index 90cc7a0f..00000000
--- a/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (c) 2011 - Georgios Gousios
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package gr.gousiosg.javacg.dyn;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
-
-public class MethodStack {
-
- private static Stack stack = new Stack();
- private static Map, Integer> callgraph = new HashMap, Integer>();
- static FileWriter fw;
- static StringBuffer sb;
- static long threadid = -1L;
-
- static {
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- try {
- fw.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- //Sort by number of calls
- List> keys = new ArrayList>();
- keys.addAll(callgraph.keySet());
- Collections.sort(keys, new Comparator