package org.ntlab.trace; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Stack; public class TraceJSON extends Trace { private HashMap<String, ClassInfo> classes = new HashMap<>(); private TraceJSON() { } /** * 指定したJSONのトレースファイルを解読して Trace オブジェクトを生成する * @param file トレースファイル */ public TraceJSON(BufferedReader file) { try { readJSON(file); file.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 指定したJSONのトレースファイルを解読して Trace オブジェクトを生成する * @param traceFile トレースファイルのパス */ public TraceJSON(String traceFile) { BufferedReader file; try { file = new BufferedReader(new FileReader(traceFile)); readJSON(file); file.close(); } catch (IOException e) { e.printStackTrace(); } } private void readJSON(BufferedReader file) throws IOException { // トレースファイル読み込み String line = null; String[] type; String[] classNameData; String[] pathData; String[] signature; String[] receiver; String[] arguments; String[] lineData; String[] threadId; String[] thisObj; String[] containerObj; String[] valueObj; String[] returnValue; String[] arrayObj; String[] thisData; String[] containerData; String[] valueData; String[] returnData; String[] fieldData; String[] arrayData; String[] blockIdData; String[] incomingsData; String[] dimensionData; String[] indexData; String className; String classPath; String loaderPath; String time; String thisObjectId; String thisClassName; String containerObjectId; String containerClassName; String valueObjectId; String valueClassName; String returnClassName; String returnObjectId; String arrayObjectId; String arrayClassName; String shortSignature; boolean isConstractor = false; boolean isCollectionType = false; boolean isStatic = false; int dimension; int index; int blockId; int incomings; int lineNum; long timeStamp = 0L; ThreadInstance thread = null; HashMap<String, Stack<String>> stacks = new HashMap<String, Stack<String>>(); while ((line = file.readLine()) != null) { // トレースファイルの解析 if (line.startsWith("{\"type\":\"classDef\"")) { // クラス定義 type = line.split(",\"name\":\""); classNameData = type[1].split("\",\"path\":\""); className = classNameData[0]; pathData = classNameData[1].split("\",\"loaderPath\":\""); classPath = pathData[0].substring(1); // 先頭の / を取り除く loaderPath = pathData[1].substring(1, pathData[1].length() - 3); // 先頭の / と、末尾の "}, を取り除く initializeClass(className, classPath, loaderPath); } else if (line.startsWith("{\"type\":\"methodCall\"")) { // メソッド呼び出しの呼び出し側 type = line.split(",\"callerSideSignature\":\""); signature = type[1].split("\",\"threadId\":"); threadId = signature[1].split(",\"lineNum\":"); lineNum = Integer.parseInt(threadId[1].substring(0, threadId[1].length() - 2)); // 末尾の }, を取り除く thread = threads.get(threadId[0]); thread.preCallMethod(signature[0], lineNum); } else if (line.startsWith("{\"type\":\"methodEntry\"")) { // メソッド呼び出し type = line.split("\"signature\":\""); signature = type[1].split("\",\"receiver\":"); receiver = signature[1].split(",\"args\":"); arguments = receiver[1].split(",\"threadId\":"); threadId = arguments[1].split(",\"time\":"); thisData = parseClassNameAndObjectId(receiver[0]); thisClassName = thisData[0]; thisObjectId = thisData[1]; isConstractor = false; isStatic = false; if (signature[0].contains("static ")) { isStatic = true; } thread = threads.get(threadId[0]); time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); Stack<String> stack; if (thread == null) { thread = new ThreadInstance(threadId[0]); threads.put(threadId[0], thread); stack = new Stack<String>(); stacks.put(threadId[0], stack); } else { stack = stacks.get(threadId[0]); } stack.push(signature[0]); // メソッド呼び出しの設定 thread.callMethod(signature[0], null, thisClassName, thisObjectId, isConstractor, isStatic, timeStamp); // 引数の設定 thread.setArgments(parseArguments(arguments)); } else if (line.startsWith("{\"type\":\"constructorEntry\"")) { // コンストラクタ呼び出し type = line.split("\"signature\":\""); signature = type[1].split("\",\"class\":\""); receiver = signature[1].split("\",\"args\":"); arguments = receiver[1].split(",\"threadId\":"); threadId = arguments[1].split(",\"time\":"); thisClassName = receiver[0]; thisObjectId = "0"; isConstractor = true; isStatic = false; thread = threads.get(threadId[0]); time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); Stack<String> stack; if (thread == null) { thread = new ThreadInstance(threadId[0]); threads.put(threadId[0], thread); stack = new Stack<String>(); stacks.put(threadId[0], stack); } else { stack = stacks.get(threadId[0]); } stack.push(signature[0]); // メソッド呼び出しの設定 thread.callMethod(signature[0], null, thisClassName, thisObjectId, isConstractor, isStatic, timeStamp); // 引数の設定 thread.setArgments(parseArguments(arguments)); } else if (line.startsWith("{\"type\":\"methodExit\"")) { // メソッドからの復帰 type = line.split(",\"shortSignature\":\""); signature = type[1].split("\",\"receiver\":"); receiver = signature[1].split(",\"returnValue\":"); returnValue = receiver[1].split(",\"threadId\":"); threadId = returnValue[1].split(",\"time\":"); thisData = parseClassNameAndObjectId(receiver[0]); thisClassName = thisData[0]; thisObjectId = thisData[1]; returnData = parseClassNameAndObjectId(returnValue[0]); returnClassName = returnData[0]; returnObjectId = returnData[1]; shortSignature = signature[0]; time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); Stack<String> stack = stacks.get(threadId[0]); if (!stack.isEmpty()) { String line2 = stack.peek(); if (line2.endsWith(shortSignature)) { stack.pop(); } else { do { stack.pop(); thread.terminateMethod(); line2 = stack.peek(); } while (!stack.isEmpty() && !line2.endsWith(shortSignature)); if (!stack.isEmpty()) stack.pop(); } thread = threads.get(threadId[0]); ObjectReference returnVal = new ObjectReference(returnObjectId, returnClassName); isCollectionType = false; if(thisClassName.contains("java.util.List") || thisClassName.contains("java.util.Vector") || thisClassName.contains("java.util.Iterator") || thisClassName.contains("java.util.ListIterator") || thisClassName.contains("java.util.ArrayList") || thisClassName.contains("java.util.Stack") || thisClassName.contains("java.util.Hash") || thisClassName.contains("java.util.Map") || thisClassName.contains("java.util.Set") || thisClassName.contains("java.util.Linked") || thisClassName.contains("java.lang.Thread")) { isCollectionType = true; } // メソッドからの復帰の設定 thread.returnMethod(returnVal, thisObjectId, isCollectionType, timeStamp); } } else if (line.startsWith("{\"type\":\"constructorExit\"")) { // コンストラクタからの復帰 type = line.split(",\"shortSignature\":\""); signature = type[1].split("\",\"returnValue\":"); returnValue = signature[1].split(",\"threadId\":"); threadId = returnValue[1].split(",\"time\":"); returnData = parseClassNameAndObjectId(returnValue[0]); thisClassName = returnClassName = returnData[0]; thisObjectId = returnObjectId = returnData[1]; time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); Stack<String> stack = stacks.get(threadId[0]); shortSignature = signature[0]; if (!stack.isEmpty()) { String line2 = stack.peek(); if (line2.endsWith(shortSignature)) { stack.pop(); } else { do { stack.pop(); thread.terminateMethod(); line2 = stack.peek(); } while (!stack.isEmpty() && !line2.endsWith(shortSignature)); if (!stack.isEmpty()) stack.pop(); } thread = threads.get(threadId[0]); ObjectReference returnVal = new ObjectReference(returnObjectId, returnClassName); isCollectionType = false; if(thisClassName.contains("java.util.List") || thisClassName.contains("java.util.Vector") || thisClassName.contains("java.util.Iterator") || thisClassName.contains("java.util.ListIterator") || thisClassName.contains("java.util.ArrayList") || thisClassName.contains("java.util.Stack") || thisClassName.contains("java.util.Hash") || thisClassName.contains("java.util.Map") || thisClassName.contains("java.util.Set") || thisClassName.contains("java.util.Linked") || thisClassName.contains("java.lang.Thread")) { isCollectionType = true; } // メソッドからの復帰の設定 thread.returnMethod(returnVal, thisObjectId, isCollectionType, timeStamp); } } else if (line.startsWith("{\"type\":\"fieldGet\"")) { // フィールドアクセス type = line.split(",\"fieldName\":\""); fieldData = type[1].split("\",\"this\":"); thisObj = fieldData[1].split(",\"container\":"); containerObj = thisObj[1].split(",\"value\":"); valueObj = containerObj[1].split(",\"threadId\":"); threadId = valueObj[1].split(",\"lineNum\":"); lineData = threadId[1].split(",\"time\":"); thisData = parseClassNameAndObjectId(thisObj[0]); thisClassName = thisData[0]; thisObjectId = thisData[1]; containerData = parseClassNameAndObjectId(containerObj[0]); containerClassName = containerData[0]; containerObjectId = containerData[1]; valueData = parseClassNameAndObjectId(valueObj[0]); valueClassName = valueData[0]; valueObjectId = valueData[1]; thread = threads.get(threadId[0]); lineNum = Integer.parseInt(lineData[0]); time = lineData[1].substring(0, lineData[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); // フィールドアクセスの設定 if (thread != null) thread.fieldAccess(fieldData[0], valueClassName, valueObjectId, containerClassName, containerObjectId, thisClassName, thisObjectId, lineNum, timeStamp); } else if (line.startsWith("{\"type\":\"fieldSet\"")) { // フィールド更新 type = line.split(",\"fieldName\":\""); fieldData = type[1].split("\",\"container\":"); containerObj = fieldData[1].split(",\"value\":"); valueObj = containerObj[1].split(",\"threadId\":"); threadId = valueObj[1].split(",\"lineNum\":"); lineData = threadId[1].split(",\"time\":"); containerData = parseClassNameAndObjectId(containerObj[0]); containerClassName = containerData[0]; containerObjectId = containerData[1]; valueData = parseClassNameAndObjectId(valueObj[0]); valueClassName = valueData[0]; valueObjectId = valueData[1]; thread = threads.get(threadId[0]); lineNum = Integer.parseInt(lineData[0]); time = lineData[1].substring(0, lineData[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); // フィールド更新の設定 if (thread != null) thread.fieldUpdate(fieldData[0], valueClassName, valueObjectId, containerClassName, containerObjectId, lineNum, timeStamp); } else if (line.startsWith("{\"type\":\"arrayCreate\"")) { // 配列生成 type = line.split(",\"array\":"); arrayObj = type[1].split(",\"dimension\":"); arrayData = parseClassNameAndObjectId(arrayObj[0]); arrayClassName = arrayData[0]; arrayObjectId = arrayData[1]; dimensionData = arrayObj[1].split(",\"threadId\":"); dimension = Integer.parseInt(dimensionData[0]); threadId = dimensionData[1].split(",\"lineNum\":"); thread = threads.get(threadId[0]); lineData = threadId[1].split(",\"time\":"); lineNum = Integer.parseInt(lineData[0]); time = lineData[1].substring(0, lineData[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); if (thread != null) thread.arrayCreate(arrayClassName, arrayObjectId, dimension, lineNum, timeStamp); } else if (line.startsWith("{\"type\":\"arraySet\"")) { // 配列要素への代入 type = line.split(",\"array\":"); arrayObj = type[1].split(",\"index\":"); arrayData = parseClassNameAndObjectId(arrayObj[0]); arrayClassName = arrayData[0]; arrayObjectId = arrayData[1]; indexData = arrayObj[1].split(",\"value\":"); index = Integer.parseInt(indexData[0]); valueObj = indexData[1].split(",\"threadId\":"); valueData = parseClassNameAndObjectId(valueObj[0]); valueClassName = valueData[0]; valueObjectId = valueData[1]; threadId = valueObj[1].split(",\"time\":"); thread = threads.get(threadId[0]); time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); if (thread != null) thread.arraySet(arrayClassName, arrayObjectId, index, valueClassName, valueObjectId, 0, timeStamp); } else if (line.startsWith("{\"type\":\"arrayGet\"")) { // 配列要素の参照 type = line.split(",\"array\":"); arrayObj = type[1].split(",\"index\":"); arrayData = parseClassNameAndObjectId(arrayObj[0]); arrayClassName = arrayData[0]; arrayObjectId = arrayData[1]; indexData = arrayObj[1].split(",\"value\":"); index = Integer.parseInt(indexData[0]); valueObj = indexData[1].split(",\"threadId\":"); valueData = parseClassNameAndObjectId(valueObj[0]); valueClassName = valueData[0]; valueObjectId = valueData[1]; threadId = valueObj[1].split(",\"time\":"); thread = threads.get(threadId[0]); time = threadId[1].substring(0, threadId[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); if (thread != null) thread.arrayGet(arrayClassName, arrayObjectId, index, valueClassName, valueObjectId, 0, timeStamp); } else if (line.startsWith("{\"type\":\"blockEntry\"")) { // ブロックの開始 type = line.split(",\"methodSignature\":\""); signature = type[1].split("\",\"blockId\":"); blockIdData = signature[1].split(",\"incomings\":"); blockId = Integer.parseInt(blockIdData[0]); incomingsData = blockIdData[1].split(",\"threadId\":"); incomings = Integer.parseInt(incomingsData[0]); threadId = incomingsData[1].split(",\"lineNum\":"); thread = threads.get(threadId[0]); lineData = threadId[1].split(",\"time\":"); lineNum = Integer.parseInt(lineData[0]); time = lineData[1].substring(0, lineData[1].length() - 2); // 末尾の }, を取り除く timeStamp = Long.parseLong(time); if (thread != null) thread.blockEnter(blockId, incomings, lineNum, timeStamp); } } } /** * クラス名とオブジェクトIDを表すJSONオブジェクトを解読する * @param classNameAndObjectIdJSON トレースファイル内のJSONオブジェクト * @return */ protected String[] parseClassNameAndObjectId(String classNameAndObjectIdJSON) { // 先頭の {"class":" の10文字と末尾の } を取り除いて分離 return classNameAndObjectIdJSON.substring(10, classNameAndObjectIdJSON.length() - 1).split("\",\"id\":"); } /** * 引数を表すJSON配列を解読する * @param arguments * @return */ protected ArrayList<ObjectReference> parseArguments(String[] arguments) { String[] argData; argData = arguments[0].substring(1, arguments[0].length() - 1).split(","); // 先頭の [ と末尾の ] を取り除く ArrayList<ObjectReference> argumentsData = new ArrayList<ObjectReference>(); for (int k = 0; k < argData.length - 1; k += 2) { argumentsData.add(new ObjectReference(argData[k+1].substring(5, argData[k+1].length() - 1), argData[k].substring(10, argData[k].length() - 1))); } return argumentsData; } /** * オンライン解析用シングルトンの取得 * @return オンライン解析用トレース */ public static TraceJSON getInstance() { if (theTrace == null) { theTrace = new TraceJSON(); } return (TraceJSON)theTrace; } /** * スレッドIDを指定してスレッドインスタンスを取得する(オンライン解析用) * @param threadId * @return スレッドインスタンス */ public static ThreadInstance getThreadInstance(String threadId) { return getInstance().threads.get(threadId); } /** * 指定したスレッド上で現在実行中のメソッド実行を取得する * @param thread 対象スレッド * @return thread 上で現在実行中のメソッド実行 */ public static MethodExecution getCurrentMethodExecution(Thread thread) { ThreadInstance t = getInstance().threads.get(String.valueOf(thread.getId())); return t.getCurrentMethodExecution(); } /** * 指定したスレッド上で現在実行中のトレースポイントを取得する * @param thread 対象スレッド * @return thread 上で現在実行中の実行文のトレースポイント */ public static TracePoint getCurrentTracePoint(Thread thread) { ThreadInstance t = getInstance().threads.get(String.valueOf(thread.getId())); return t.getCurrentTracePoint(); } public void initializeClass(String name, String path, String loaderPath) { classes.put(name, new ClassInfo(name, path, loaderPath)); } public ClassInfo getClassInfo(String className) { return classes.get(className); } public TracePoint getArraySetTracePoint(final Reference ref, TracePoint before) { final TracePoint start = before.duplicate(); before = traverseStatementsInTraceBackward(new IStatementVisitor() { @Override public boolean preVisitStatement(Statement statement) { if (statement instanceof ArrayUpdate) { ArrayUpdate arraySet = (ArrayUpdate)start.getStatement(); String srcObjId = ref.getSrcObjectId(); String dstObjId = ref.getDstObjectId(); String srcClassName = ref.getSrcClassName(); String dstClassName = ref.getDstClassName(); if ((srcObjId != null && srcObjId.equals(arraySet.getArrayObjectId())) || (srcObjId == null || isNull(srcObjId)) && srcClassName.equals(arraySet.getArrayClassName())) { if ((dstObjId != null && dstObjId.equals(arraySet.getValueObjectId())) || ((dstObjId == null || isNull(dstObjId)) && dstClassName.equals(arraySet.getValueClassName()))) { if (srcObjId == null) { ref.setSrcObjectId(arraySet.getArrayObjectId()); } else if (srcClassName == null) { ref.setSrcClassName(arraySet.getArrayClassName()); } if (dstObjId == null) { ref.setDstObjectId(arraySet.getValueObjectId()); } else if (dstClassName == null) { ref.setDstClassName(arraySet.getValueClassName()); } return true; } } } return false; } @Override public boolean postVisitStatement(Statement statement) { return false; } }, start); if (before != null) { return before; } return null; } /** * 実行された全ブロックを取得する * @return 全ブロック(メソッド名:ブロックID) */ public HashSet<String> getAllBlocks() { final HashSet<String> blocks = new HashSet<String>(); Iterator<String> threadsIterator = threads.keySet().iterator(); for (; threadsIterator.hasNext();) { ThreadInstance thread = threads.get(threadsIterator.next()); thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() { @Override public boolean preVisitThread(ThreadInstance thread) { return false; } @Override public boolean postVisitThread(ThreadInstance thread) { return false; } @Override public boolean preVisitMethodExecution(MethodExecution methodExecution) { for (Statement s: methodExecution.getStatements()) { if (s instanceof BlockEnter) { blocks.add(methodExecution.getSignature() + ":" + ((BlockEnter)s).getBlockId()); } } return false; } @Override public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) { return false; } }); } return blocks; } /** * マーク内で実行が開始されたブロックを取得する * @param markStart マークの開始時刻 * @param markEnd マークの終了時刻 * @return 該当するブロック(メソッド名:ブロックID) */ public HashSet<String> getMarkedBlocks(final long markStart, final long markEnd) { final HashSet<String> blocks = new HashSet<String>(); Iterator<String> threadsIterator = threads.keySet().iterator(); for (; threadsIterator.hasNext();) { ThreadInstance thread = threads.get(threadsIterator.next()); thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() { @Override public boolean preVisitThread(ThreadInstance thread) { return false; } @Override public boolean postVisitThread(ThreadInstance thread) { return false; } @Override public boolean preVisitMethodExecution(MethodExecution methodExecution) { if (!methodExecution.isTerminated() && methodExecution.getExitTime() < markStart) return true; // 探索終了 if (methodExecution.getEntryTime() > markEnd) return false; for (Statement s: methodExecution.getStatements()) { if (s instanceof BlockEnter) { long entryTime = ((BlockEnter)s).getTimeStamp(); if (entryTime >= markStart && entryTime <= markEnd) { blocks.add(methodExecution.getSignature() + ":" + ((BlockEnter)s).getBlockId()); } } } return false; } @Override public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) { return false; } }); } return blocks; } /** * 実行された全フローを取得する * @return 全フロー(メソッド名:フロー元ブロックID:フロー先ブロックID) */ public HashSet<String> getAllFlows() { final HashSet<String> flows = new HashSet<String>(); Iterator<String> threadsIterator = threads.keySet().iterator(); for (; threadsIterator.hasNext();) { ThreadInstance thread = threads.get(threadsIterator.next()); thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() { @Override public boolean preVisitThread(ThreadInstance thread) { return false; } @Override public boolean postVisitThread(ThreadInstance thread) { return false; } @Override public boolean preVisitMethodExecution(MethodExecution methodExecution) { int prevBlockId = -1; for (Statement s: methodExecution.getStatements()) { if (s instanceof BlockEnter) { int curBlockID = ((BlockEnter)s).getBlockId(); if (prevBlockId != -1) { flows.add(methodExecution.getSignature() + ":" + prevBlockId + ":" + curBlockID); } else { flows.add(methodExecution.getSignature() + ":" + curBlockID); } prevBlockId = curBlockID; } } return false; } @Override public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) { return false; } }); } return flows; } /** * マーク内で実行されたフローを取得する * @param markStart マークの開始時刻 * @param markEnd マークの終了時刻 * @return 該当するフロー(メソッド名:フロー元ブロックID:フロー先ブロックID) */ public HashSet<String> getMarkedFlows(final long markStart, final long markEnd) { final HashSet<String> flows = new HashSet<String>(); Iterator<String> threadsIterator = threads.keySet().iterator(); for (; threadsIterator.hasNext();) { ThreadInstance thread = threads.get(threadsIterator.next()); thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() { @Override public boolean preVisitThread(ThreadInstance thread) { return false; } @Override public boolean postVisitThread(ThreadInstance thread) { return false; } @Override public boolean preVisitMethodExecution(MethodExecution methodExecution) { if (!methodExecution.isTerminated() && methodExecution.getExitTime() < markStart) return true; // 探索終了 if (methodExecution.getEntryTime() > markEnd) return false; int prevBlockId = -1; for (Statement s: methodExecution.getStatements()) { if (s instanceof BlockEnter) { long entryTime = ((BlockEnter)s).getTimeStamp(); int curBlockID = ((BlockEnter)s).getBlockId(); if (entryTime >= markStart && entryTime <= markEnd) { if (prevBlockId != -1) { flows.add(methodExecution.getSignature() + ":" + prevBlockId + ":" + curBlockID); } else { flows.add(methodExecution.getSignature() + ":" + curBlockID); } } prevBlockId = curBlockID; } } return false; } @Override public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) { return false; } }); } return flows; } /** * トレース内の全スレッドを同期させながら全実行文を逆方向に探索する * * @param visitor 実行文のビジター * @return 中断したトレースポイント */ public TracePoint traverseStatementsInTraceBackward(IStatementVisitor visitor) { HashMap<String, ArrayList<MethodExecution>> threadRoots = new HashMap<String, ArrayList<MethodExecution>>(); HashMap<String, TracePoint> threadLastPoints = new HashMap<String, TracePoint>(); // 各スレッドにおいて一番最後に開始したメソッド実行を探す long traceLastTime = 0; String traceLastThread = null; long traceLastTime2 = 0; String traceLastThread2 = null; for (String threadId: threads.keySet()) { ThreadInstance thread = threads.get(threadId); ArrayList<MethodExecution> roots = (ArrayList<MethodExecution>)thread.getRoot().clone(); threadRoots.put(threadId, roots); TracePoint threadLastTp; do { MethodExecution threadLastExecution = roots.remove(roots.size() - 1); threadLastTp = threadLastExecution.getExitPoint(); } while (!threadLastTp.isValid() && roots.size() > 0); if (threadLastTp.isValid()) { threadLastPoints.put(threadId, threadLastTp); long threadLastTime; if (threadLastTp.getStatement() instanceof MethodInvocation) { threadLastTime = ((MethodInvocation) threadLastTp.getStatement()).getCalledMethodExecution().getExitTime(); } else { threadLastTime = threadLastTp.getStatement().getTimeStamp(); } if (traceLastTime < threadLastTime) { traceLastTime2 = traceLastTime; traceLastThread2 = traceLastThread; traceLastTime = threadLastTime; traceLastThread = threadId; } } else { threadLastPoints.put(threadId, null); } } return traverseStatementsInTraceBackwardSub(visitor, threadRoots, threadLastPoints, traceLastThread, traceLastThread2, traceLastTime2); } /** * トレース内の全スレッドを同期させながら指定したトレースポイントから逆方向に探索する * * @param visitor 実行文のビジター * @param before 探索開始トレースポイント * @return 中断したトレースポイント */ public TracePoint traverseStatementsInTraceBackward(IStatementVisitor visitor, TracePoint before) { if (before == null) { return traverseStatementsInTraceBackward(visitor); } // 全てのスレッドのトレースポイントを before の前まで巻き戻す HashMap<String, ArrayList<MethodExecution>> threadRoots = new HashMap<String, ArrayList<MethodExecution>>(); HashMap<String, TracePoint> threadLastPoints = new HashMap<String, TracePoint>(); String traceLastThread = null; long traceLastTime2 = 0; String traceLastThread2 = null; ThreadInstance thread = threads.get(before.getStatement().getThreadNo()); for (String threadId: threads.keySet()) { ThreadInstance t = threads.get(threadId); ArrayList<MethodExecution> rootExecutions = (ArrayList<MethodExecution>)t.getRoot().clone(); threadRoots.put(threadId, rootExecutions); if (t == thread) { traceLastThread = threadId; threadLastPoints.put(threadId, before); for (int n = rootExecutions.size() - 1; n >= 0; n--) { MethodExecution root = rootExecutions.get(n); if (root.getEntryTime() > before.getStatement().getTimeStamp()) { rootExecutions.remove(n); } else { break; } } if (rootExecutions.size() > 0) { rootExecutions.remove(rootExecutions.size() - 1); } } else { TracePoint threadBeforeTp; do { MethodExecution threadLastExecution = rootExecutions.remove(rootExecutions.size() - 1); threadBeforeTp = threadLastExecution.getExitPoint(); } while (!threadBeforeTp.isValid() && rootExecutions.size() > 0); if (threadBeforeTp.isValid()) { TracePoint[] tp = new TracePoint[] {threadBeforeTp}; long threadLastTime; if (before.getStatement() instanceof MethodInvocation) { threadLastTime = ((MethodInvocation) before.getStatement()).getCalledMethodExecution().getExitTime(); } else { threadLastTime = before.getStatement().getTimeStamp(); } getLastStatementInThread(rootExecutions, tp, threadLastTime, new IStatementVisitor() { @Override public boolean preVisitStatement(Statement statement) { return false; } @Override public boolean postVisitStatement(Statement statement) { return false; } }); threadLastPoints.put(threadId, tp[0]); if (tp[0] != null) { if (tp[0].getStatement() instanceof MethodInvocation) { threadLastTime = ((MethodInvocation) tp[0].getStatement()).getCalledMethodExecution().getExitTime(); // ※Exception が発生した場合は考えなくてよい? } else { threadLastTime = tp[0].getStatement().getTimeStamp(); } if (traceLastTime2 < threadLastTime) { traceLastTime2 = threadLastTime; traceLastThread2 = threadId; } } } else { threadLastPoints.put(threadId, null); } } } return traverseStatementsInTraceBackwardSub(visitor, threadRoots, threadLastPoints, traceLastThread, traceLastThread2, traceLastTime2); } /** * トレース内の全スレッドを同期させながら指定したトレースポイントから逆方向に探索する * * @param visitor 実行文のビジター * @param before 探索開始時間 * @return 中断したトレースポイント */ public TracePoint traverseStatementsInTraceBackward(IStatementVisitor visitor, long before) { if (before <= 0L) { return traverseStatementsInTraceBackward(visitor); } // 全てのスレッドのトレースポイントを before の前まで巻き戻す HashMap<String, ArrayList<MethodExecution>> threadRoots = new HashMap<String, ArrayList<MethodExecution>>(); HashMap<String, TracePoint> threadLastPoints = new HashMap<String, TracePoint>(); long traceLastTime = 0; String traceLastThread = null; long traceLastTime2 = 0; String traceLastThread2 = null; for (String threadId: threads.keySet()) { ThreadInstance t = threads.get(threadId); ArrayList<MethodExecution> rootExecutions = (ArrayList<MethodExecution>)t.getRoot().clone(); threadRoots.put(threadId, rootExecutions); TracePoint threadBeforeTp; do { MethodExecution threadLastExecution = rootExecutions.remove(rootExecutions.size() - 1); threadBeforeTp = threadLastExecution.getExitPoint(); } while (!threadBeforeTp.isValid() && rootExecutions.size() > 0); if (threadBeforeTp.isValid()) { TracePoint[] tp = new TracePoint[] {threadBeforeTp}; getLastStatementInThread(rootExecutions, tp, before, new IStatementVisitor() { @Override public boolean preVisitStatement(Statement statement) { return false; } @Override public boolean postVisitStatement(Statement statement) { return false; } }); threadLastPoints.put(threadId, tp[0]); if (tp[0] != null) { long threadLastTime; if (tp[0].getStatement() instanceof MethodInvocation) { threadLastTime = ((MethodInvocation) tp[0].getStatement()).getCalledMethodExecution().getExitTime(); // ※Exception が発生した場合は考えなくてよい? } else { threadLastTime = tp[0].getStatement().getTimeStamp(); } if (traceLastTime < threadLastTime) { traceLastTime2 = traceLastTime; traceLastThread2 = traceLastThread; traceLastTime = threadLastTime; traceLastThread = threadId; } } } else { threadLastPoints.put(threadId, null); } } return traverseStatementsInTraceBackwardSub(visitor, threadRoots, threadLastPoints, traceLastThread, traceLastThread2, traceLastTime2); } private TracePoint traverseStatementsInTraceBackwardSub(IStatementVisitor visitor, HashMap<String, ArrayList<MethodExecution>> threadRoots, HashMap<String, TracePoint> threadLastPoints, String traceLastThread, String traceLastThread2, long traceLastTime2) { // 全スレッドの同期をとりながら逆向きに実行文を探索する for (;;) { // 探索対象のスレッド内の逆向き探索 TracePoint lastTp = threadLastPoints.get(traceLastThread); while (lastTp != null) { Statement statement = lastTp.getStatement(); if (visitor.preVisitStatement(statement)) return lastTp; if (statement instanceof MethodInvocation) { // 呼び出し先がある場合、呼び出し先に潜る lastTp.stepBackNoReturn(); if (lastTp.isValid()) { // 普通に呼び出し先に移った場合 MethodExecution methodExecution = ((MethodInvocation) statement).getCalledMethodExecution(); if (!methodExecution.isTerminated() && methodExecution.getExitTime() < traceLastTime2) { break; } continue; } // 空のメソッド実行の場合 } else { if (visitor.postVisitStatement(statement)) return lastTp; } if (lastTp.isValid() && lastTp.getStatement().getTimeStamp() < traceLastTime2) break; // 1ステップ巻き戻す while (!lastTp.stepBackOver() && lastTp.isValid()) { // 呼び出し元に戻った場合(メソッド呼び出し文に復帰) statement = lastTp.getStatement(); if (visitor.postVisitStatement(statement)) return lastTp; } if (!lastTp.isValid()) { // 呼び出し木の開始時点まで探索し終えた場合 ArrayList<MethodExecution> roots = threadRoots.get(traceLastThread); while (!lastTp.isValid() && roots.size() > 0) { // 次の呼び出し木を探す MethodExecution lastExecution = roots.remove(roots.size() - 1); lastTp = lastExecution.getExitPoint(); } if (lastTp.isValid()) { // 次の呼び出し木があればそれを最後から探索 threadLastPoints.put(traceLastThread, lastTp); if (lastTp.getStatement().getTimeStamp() < traceLastTime2) break; } else { // そのスレッドの探索がすべて終了した場合 threadLastPoints.put(traceLastThread, null); break; } } } traceLastThread = traceLastThread2; // 次の次に探索すべきスレッド(未探索の領域が一番最後まで残っているスレッド)を決定する traceLastTime2 = 0; traceLastThread2 = null; boolean continueTraverse = false; for (String threadId: threadLastPoints.keySet()) { if (!threadId.equals(traceLastThread)) { TracePoint threadLastTp = threadLastPoints.get(threadId); if (threadLastTp != null) { continueTraverse = true; long threadLastTime; if (threadLastTp.getStatement() instanceof MethodInvocation) { threadLastTime = ((MethodInvocation) threadLastTp.getStatement()).getCalledMethodExecution().getExitTime(); } else { threadLastTime = threadLastTp.getStatement().getTimeStamp(); } if (traceLastTime2 < threadLastTime) { traceLastTime2 = threadLastTime; traceLastThread2 = threadId; } } } } if (traceLastThread == null && traceLastThread2 == null) break; if (!continueTraverse && threadLastPoints.get(traceLastThread) == null) break; } return null; } }