/* * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.oracle.max.vma.tools.log; import static com.oracle.max.vm.ext.vma.store.txt.VMATextStoreFormat.*; import java.io.*; import java.util.*; import com.oracle.max.vm.ext.vma.*; import com.oracle.max.vm.ext.vma.store.txt.*; import com.oracle.max.vm.ext.vma.store.*; import com.oracle.max.vma.tools.qa.*; import com.sun.max.program.*; /** * Rewrites a log file making various transformations: * <ul> * <li>-abstime convert relative times to absolute * <li>-reltime convert absolute times to relative * <li>-batch convert to per-thread batches of records (i.e., non-time-ordered) * <li>-unbatch convert unordered (i.e. per thread batches) to time-ordered * </ul> * */ public class ConvertLog { private static boolean toAbsTime; private static boolean toRelTime; private static boolean verbose; private static boolean timeChange; private static PrintStream out; public static void main(String[] args) throws Exception { String logFileIn = null; String logFileOut = null; Command command = new CloneCommand(); // Checkstyle: stop modified control variable check for (int i = 0; i < args.length; i++) { final String arg = args[i]; if (arg.equals("-f")) { logFileIn = args[++i]; } else if (arg.equals("-o")) { logFileOut = args[++i]; } else if (arg.equals("-v")) { verbose = true; } else if (arg.equals("-abstime")) { toAbsTime = true; timeChange = true; } else if (arg.equals("-reltime")) { toRelTime = true; timeChange = true; } else if (arg.equals("-batch")) { command = new BatchCommand(); } else if (arg.equals("-unbatch")) { command = new UnBatchCommand(); } else if (arg.equals("-ajtrace")) { command = new AJcommand(); } else if (arg.equals("-readable")) { command = new ReadableCommand(); } else if (arg.equals("-merge")) { command = new MergeCommand(); } else if (arg.equals("-textkey")) { command = new TextKeyCommand(); } else if (arg.equals("-stats")) { command = new StatsCommand(); } else { usage(); } } // Checkstyle: resume modified control variable check String logFileDir = null; if (logFileIn == null) { logFileIn = VMAStoreFile.DEFAULT_STOREFILE; logFileDir = VMAStoreFile.DEFAULT_STOREDIR; } else { File f = new File(logFileIn); if (f.isDirectory()) { logFileDir = logFileIn; logFileIn = new File(f, VMAStoreFile.GLOBAL_STORE).getPath(); } } File f = new File(logFileIn); if (f.exists()) { processLogFiles(new File[] {new File(logFileIn)}, logFileOut, command); } else { // maybe per-thread if (logFileDir != null) { File[] files = new File(logFileDir).listFiles(); if (command instanceof MergeCommand) { command.execute(files, logFileOut); } else { processLogFiles(files, logFileOut, command); } } else { usage(); } } } private static void usage() { System.err.println("usage: -f logfileIn [-o logFileOut] [-batch | -unbatch | -ajtrace] [-abstime] [-reltime]"); System.exit(1); } private static void processLogFiles(File[] inFiles, String outFile, Command command) throws IOException { try { out = outFile == null ? System.out : new PrintStream(new FileOutputStream(outFile)); command.startTiming(); for (File inFile : inFiles) { BufferedReader r = null; try { r = new BufferedReader(new FileReader(inFile)); boolean checked = false; while (true) { final String line = r.readLine(); if (line == null) { break; } if (line.length() == 0) { continue; } if (!checked) { command.checkStoreHeader(line); checked = true; } command.visitLine(line); } } finally { if (r != null) { try { r.close(); } catch (IOException ex) { } } } } command.finish(); } finally { if (outFile != null && out != null) { out.close(); } } } public static String[] split(boolean textKeyMode, String line) { return split(textKeyMode, line, false); } /** * Splits the line into space separated components, handling quoted thread names as a special case. * @param textKeyMode {@code true} iff file is in {@link VMATextStoreFormat#TEXT_KEY} mode * @param line the line to be split * @param insertThread iff {@code true} allocate an extra slot for an inserted thread field, * for converting per-thread files without a thread field */ private static String[] split(boolean textKeyMode, String line, boolean insertThread) { int count = insertThread ? 2 : 1; // 1 extra for the to-be-inserted thread field for (int i = 0; i < line.length(); i++) { if (line.charAt(i) == ' ') { count++; } } String[] result = new String[count]; int ix = line.indexOf(' '); int iy = 0; count = 0; boolean hasThread = false; while (ix > 0) { result[count] = line.substring(iy, ix); count++; if (count == 1) { VMATextStoreFormat.Key key = VMATextStoreFormat.getCommand(textKeyMode, result[0]); if (key == Key.THREAD_DEFINITION) { // handle quoted thread names with spaces iy = line.indexOf('"', 3); result[1] = line.substring(3, iy); result[2] = line.substring(iy + 2); return result; } else { hasThread = VMATextStoreFormat.hasTimeAndThread(key); } } else if (insertThread && hasThread && count == 2) { count++; } iy = ix + 1; ix = line.indexOf(' ', iy); } result[count] = line.substring(iy); return result; } private static String concat(String[] lineParts) { return concat(lineParts, null, 0); } private static String concat(String[] lineParts, String insert, int insertBefore) { final StringBuilder sb = new StringBuilder(lineParts[0]); for (int i = 1; i < lineParts.length; i++) { if (insert != null && insertBefore == i) { sb.append(' '); sb.append(insert); insert = null; } sb.append(' '); sb.append(lineParts[i]); } if (insert != null) { sb.append(' '); sb.append(insert); } return sb.toString(); } private static abstract class Command { boolean textKeyMode; protected long chunkStartTime; protected long processStartTime; int convertRecordCount; void checkStoreHeader(String line) { String[] recordParts = split(false, line); assert recordParts.length == 4; textKeyMode = (Integer.parseInt(recordParts[3]) & TEXT_KEY) != 0; assert VMATextStoreFormat.getCommand(textKeyMode, recordParts[0]) == Key.INITIALIZE_STORE; } public void startTiming() { chunkStartTime = System.currentTimeMillis(); processStartTime = chunkStartTime; } protected void logTiming() { convertRecordCount++; if (verbose && ((convertRecordCount % 100000) == 0)) { long endTime = System.currentTimeMillis(); System.out.printf("processed %d traces in %d ms (%d)%n", convertRecordCount, endTime - processStartTime, endTime - chunkStartTime); chunkStartTime = endTime; } } void execute(File[] files, String logFileOut) { } void visitLine(String line) { logTiming(); } void finish() { } } private static abstract class BasicCommand extends Command { boolean logUsesAbsTime; // constant once assigned long lineTime; // time field of last line visited long lineAbsTime; // absolute time of last line visited Key command; String[] lineParts; @Override void visitLine(String line) { super.visitLine(line); setCommand(line); if (VMATextStoreFormat.hasTime(command)) { lineTime = Long.parseLong(lineParts[1]); lineAbsTime = logUsesAbsTime ? lineTime : lineAbsTime + lineTime; } else if (command == Key.INITIALIZE_STORE || command == Key.THREAD_SWITCH || command == Key.FINALIZE_STORE) { checkTimeFormat(); } } void setCommand(String line) { lineParts = split(textKeyMode, line); command = VMATextStoreFormat.getCommand(textKeyMode, lineParts[0]); } void checkTimeFormat() { lineTime = Long.parseLong(lineParts[1]); lineAbsTime = lineTime; if (command == Key.INITIALIZE_STORE) { logUsesAbsTime = lineParts[2].equals("true"); } } } private static class TimedLine implements Comparable<TimedLine> { long time; String[] lineParts; TimedLine(long time, String[] lineParts) { this.time = time; this.lineParts = lineParts; } public int compareTo(TimedLine t) { if (time < t.time) { return -1; } else if (time > t.time) { return 1; } else { return 0; } } @Override public String toString() { return "time: " + time + " [" + concat(lineParts) + "]"; } } /** * Command to transform a batched (non-time-ordered) log into a time-ordered log. */ private static class UnBatchCommand extends BasicCommand { private boolean seenIL; private ArrayList<TimedLine> lines = new ArrayList<TimedLine>(); @Override void visitLine(String line) { super.visitLine(line); if (command == Key.THREAD_SWITCH) { // drop these records return; } else if (command == Key.INITIALIZE_STORE) { if (seenIL) { return; } else { seenIL = true; } } lines.add(new TimedLine(lineAbsTime, lineParts)); } @Override void finish() { TimedLine[] linesArray = lines.toArray(new TimedLine[lines.size()]); Arrays.sort(linesArray); long lastTime = linesArray[0].time; for (int i = 0; i < linesArray.length; i++) { String line; String[] lineParts = linesArray[i].lineParts; if (VMATextStoreFormat.hasTime(VMATextStoreFormat.getCommand(textKeyMode, lineParts[0]))) { line = fixupTime(linesArray[i], lastTime); lastTime = linesArray[i].time; } else { line = concat(linesArray[i].lineParts); } out.println(line); } } private String fixupTime(TimedLine timedLine, long absTime) { final StringBuilder sb = new StringBuilder(timedLine.lineParts[0]); sb.append(' '); if (logUsesAbsTime) { sb.append(absTime); } else { sb.append(timedLine.time - absTime); } for (int i = 2; i < timedLine.lineParts.length; i++) { sb.append(' '); sb.append(timedLine.lineParts[i]); } return sb.toString(); } } /** * * Command to transform into batch (per-thread) style. * Sanity checking for unbatch command. * */ private static class BatchCommand extends BasicCommand { private static class BatchData { ArrayList<String> lines = new ArrayList<String>(); long lastTime; BatchData(long startTime) { lastTime = startTime; lines.add(new StringBuilder().append(Key.THREAD_SWITCH).append(' ').append(startTime).toString()); } } Map<String, BatchData> batchMap = new HashMap<String, BatchData>(); String initialize; String finalize; BatchData currentBatch; BatchData initialBatch; @Override void visitLine(String line) { super.visitLine(line); if (VMATextStoreFormat.hasTime(command)) { if (VMATextStoreFormat.hasTimeAndThread(command)) { // thread is in lineParts[2] BatchData batch = batchMap.get(lineParts[2]); if (batch == null) { // new thread batch = new BatchData(lineAbsTime); batchMap.put(lineParts[2], batch); } currentBatch = batch; } currentBatch.lines.add(fixupTime(currentBatch, lineParts, lineAbsTime)); } else if (command == Key.INITIALIZE_STORE) { initialize = line; initialBatch = new BatchData(lineAbsTime); currentBatch = initialBatch; } else if (command == Key.THREAD_SWITCH) { // ignore existing resets } else if (command == Key.FINALIZE_STORE) { finalize = line; } else { // no time/thread component currentBatch.lines.add(line); } } @Override void finish() { out.println(initialize); // output batches for (String batchLine : initialBatch.lines) { out.println(batchLine); } for (BatchData batch : batchMap.values()) { for (String batchLine : batch.lines) { out.println(batchLine); } } out.println(finalize); } private String fixupTime(BatchData batch, String[] lineParts, long absTime) { final StringBuilder sb = new StringBuilder(lineParts[0]); sb.append(' '); if (logUsesAbsTime) { sb.append(absTime); } else { sb.append(absTime - batch.lastTime); batch.lastTime = absTime; } for (int i = 2; i < lineParts.length; i++) { sb.append(' '); sb.append(lineParts[i]); } return sb.toString(); } } /** * Merges per-thread files into a single file with a merge sort. */ public static class MergeCommand extends Command { private PushRecord pushRecord; public interface PushRecord { void pushRecord(String[] recordParts); } /** * Default constructor for file output mode. */ MergeCommand() { } /** * Constructor for push mode directly to registered callback. * @param push */ public MergeCommand(PushRecord pushRecord) { this.pushRecord = pushRecord; } void miscOut(String[] recordParts) { if (pushRecord != null) { pushRecord.pushRecord(recordParts); } else { out.println(ConvertLog.concat(recordParts)); if (verbose) { logTiming(); } } } private class FileInfo implements Comparable<FileInfo> { final File file; BufferedReader reader; boolean logUsesAbsTime; // constant once assigned long lastAbsTime; // absolute time of last line visited Record record; int lineNumber; String threadShortForm; String line; FileInfo(File file) throws IOException { this.file = file; this.reader = new BufferedReader(new FileReader(file)); line = reader.readLine(); checkStoreHeader(line); } @Override public String toString() { return file.getName() + ": " + lastAbsTime; } void readRecord() throws IOException { if (line == null) { line = reader.readLine(); } record = new Record(line); lineNumber++; line = null; } long outputRecordAndNext(long previousTime) throws IOException { if (VMATextStoreFormat.hasTime(record.command)) { record.adjustRelTime(previousTime); if (VMATextStoreFormat.hasTimeAndThread(record.command)) { // need to insert the thread at slot 2 assert record.timedLine.lineParts[2] == null; record.timedLine.lineParts[2] = threadShortForm; } } miscOut(record.timedLine.lineParts); previousTime = record.time(); readRecord(); return previousTime; } /** * Partially decoded record, contains command as a {@link Key}, absolute time, and record components. */ private class Record { final TimedLine timedLine; final Key command; Record(String line) { String[] parts = split(textKeyMode, line, true); command = VMATextStoreFormat.getCommand(textKeyMode, parts[0]); if (VMATextStoreFormat.hasTime(command)) { long thisTime = Long.parseLong(parts[1]); lastAbsTime = logUsesAbsTime ? thisTime : lastAbsTime + thisTime; } else if (command == Key.INITIALIZE_STORE || command == Key.FINALIZE_STORE) { long thisTime = Long.parseLong(parts[1]); lastAbsTime = thisTime; if (command == Key.INITIALIZE_STORE) { logUsesAbsTime = parts[2].equals("true"); } } else { // a definition; give it the same time as the last record if (command == Key.THREAD_DEFINITION) { threadShortForm = parts[2]; } } timedLine = new TimedLine(lastAbsTime, parts); } /** * Splits the record into space separated components and leaves an empty slot for the thread short form * to be inserted on output. */ @Override public String toString() { return command + ", " + timedLine.toString(); } long time() { return timedLine.time; } /** * If file uses relative time, adjust the time in the record to {@code baseTime}. * @param baseTime */ private void adjustRelTime(long baseTime) { if (!logUsesAbsTime) { long rel = timedLine.time - baseTime; if (rel < 0) { throw new IllegalArgumentException("negative relative time!"); } timedLine.lineParts[1] = Long.toString(rel); } } } @Override public int compareTo(FileInfo arg0) { return record.time() < arg0.record.time() ? -1 : (record.time() > arg0.record.time() ? 1 : 0); } } @Override public void execute(File[] files, String logFileOut) { ArrayList<File> fileList = new ArrayList<File>(); for (int i = 0; i < files.length; i++) { if (files[i].length() != 0) { fileList.add(files[i]); } } FileInfo[] fileInfos = new FileInfo[fileList.size()]; for (int i = 0; i < fileList.size(); i++) { File file = fileList.get(i); try { fileInfos[i] = new FileInfo(file); } catch (IOException ex) { System.err.println(ex); System.exit(1); } } // Ok, all files open, now read first record (INITIALIZE_STORE) try { // Read INITIALIZE_STORE and sort for (FileInfo fileInfo : fileInfos) { fileInfo.readRecord(); } Arrays.sort(fileInfos); if (pushRecord == null) { out = logFileOut == null ? System.out : new PrintStream(new FileOutputStream(logFileOut)); } // Earliest is the INITIALIZE_STORE for the merged file // The resulting merge file is not per thread, nor batched long previousTime = fileInfos[0].record.timedLine.time; miscOut(new String[] {VMATextStoreFormat.getString(textKeyMode, Key.INITIALIZE_STORE), Long.toString(previousTime), "false", textKeyMode ? "4" : "0"}); // new INITIALIZE_STORE // Read first real record and sort for (FileInfo fileInfo : fileInfos) { fileInfo.readRecord(); } Arrays.sort(fileInfos); LinkedList<FileInfo> fileInfoList = new LinkedList<FileInfo>(); for (FileInfo fileInfo : fileInfos) { fileInfoList.add(fileInfo); } if (verbose) { startTiming(); } // Starting with file containing earliest record, copy records to the output // until we reach one that is older than the earliest record in next youngest file. // Then sort file into correct place in the list and repeat. outer: while (fileInfoList.size() > 1) { FileInfo youngest = fileInfoList.get(0); FileInfo nextYoungest = fileInfoList.get(1); while (youngest.record.time() <= nextYoungest.record.time()) { if (youngest.record.command == Key.FINALIZE_STORE) { // end of this file if (verbose) { System.out.printf("finished %s%n", youngest.file); } fileInfoList.remove(); continue outer; } previousTime = youngest.outputRecordAndNext(previousTime); } // sort file if (verbose) { System.out.printf("sort %d %d: ", youngest.record.time(), nextYoungest.record.time()); } fileInfoList.remove(); boolean inserted = false; for (int i = 1; i < fileInfoList.size(); i++) { if (youngest.record.time() < fileInfoList.get(i).record.time()) { fileInfoList.add(i, youngest); if (verbose) { System.out.printf("inserted at %d%n", i); } inserted = true; break; } } if (!inserted) { if (verbose) { System.out.printf("inserted at end%n"); } fileInfoList.add(fileInfoList.size(), youngest); } } // copy remaining records in last file FileInfo last = fileInfoList.get(0); long lastRecordAbsTime = last.record.time(); while (last.record.command != Key.FINALIZE_STORE) { previousTime = last.outputRecordAndNext(previousTime); lastRecordAbsTime = last.record.time(); } miscOut(new String[] {VMATextStoreFormat.getString(textKeyMode, Key.FINALIZE_STORE), Long.toString(lastRecordAbsTime)}); } catch (IOException ex) { System.err.println(ex); System.exit(1); } finally { if (logFileOut != null && out != null) { out.close(); } if (pushRecord != null) { pushRecord.pushRecord(null); } } } } private static class StatsCommand extends BasicCommand { private long lineLengths; @Override void visitLine(String line) { super.visitLine(line); lineLengths += line.length(); } @Override void finish() { System.out.printf("Average line length %d%n", lineLengths / convertRecordCount); } } private static class TextKeyCommand extends BasicCommand { @Override void visitLine(String line) { super.visitLine(line); if (textKeyMode) { out.println(line); } else { lineParts[0] = VMATextStoreFormat.getString(true, command); final StringBuilder sb = new StringBuilder(lineParts[0]); for (int i = 1; i < lineParts.length; i++) { sb.append(' '); sb.append(lineParts[i]); } out.println(sb.toString()); } } } /** * Clones a log. Used to convert from rel/abs time and vice-versa. * */ private static class CloneCommand extends BasicCommand { @Override void visitLine(String line) { super.visitLine(line); if (!timeChange || (logUsesAbsTime && toAbsTime) || (!logUsesAbsTime && toRelTime)) { // no change out.println(line); } else { if (VMATextStoreFormat.hasTime(command)) { if (logUsesAbsTime) { // to relative ProgramError.unexpected("abs to rel not implemented"); } else { // to absolute final StringBuilder sb = new StringBuilder(lineParts[0]); sb.append(' '); sb.append(lineAbsTime); for (int i = 2; i < lineParts.length; i++) { sb.append(' '); sb.append(lineParts[i]); } line = sb.toString(); } } else if (command == Key.INITIALIZE_STORE) { line = lineParts[0] + " " + lineAbsTime + " " + !logUsesAbsTime + lineParts[2]; } out.println(line); } } } /** * Converts to the format expect by the AJTrace analyser tool for viewing call graph hierarchies. * This is based on {@code METHOD_ENTRY} and {@code RETURN} advice. The commented out code * also interprets {@code INVOKE} before/after, but does not properly handle the case where both are present. */ private static class AJcommand extends BasicCommand { /* private static EnumSet<Key> INVOKE_BEFORE_SET = EnumSet.of( Key.ADVISE_BEFORE_INVOKE_INTERFACE, Key.ADVISE_BEFORE_INVOKE_VIRTUAL, Key.ADVISE_BEFORE_INVOKE_SPECIAL, Key.ADVISE_BEFORE_INVOKE_STATIC); private static EnumSet<Key> INVOKE_AFTER_SET = EnumSet.of( Key.ADVISE_AFTER_INVOKE_INTERFACE, Key.ADVISE_AFTER_INVOKE_VIRTUAL, Key.ADVISE_AFTER_INVOKE_SPECIAL, Key.ADVISE_AFTER_INVOKE_STATIC); */ private long startTime; private Map<String, Integer> callDepth = new HashMap<String, Integer>(); private int fullMethodIndex; private Map<String, String> classShortForms = new HashMap<String, String>(); private Map<String, String> methodShortForms = new HashMap<String, String>(); private Map<String, String> classMethodDefs = new HashMap<String, String>(); private String[] entryMid = new String[1024]; private int entryMidIndex; @Override void visitLine(String line) { super.visitLine(line); StringBuilder sb = new StringBuilder(); if (command == Key.INITIALIZE_STORE) { sb.append("0 S S "); sb.append(lineAbsTime); startTime = lineAbsTime; } else if (command == Key.CLASS_DEFINITION) { classShortForms.put(lineParts[3], ClassRecord.getCanonicalName(lineParts[1])); return; } else if (command == Key.THREAD_DEFINITION) { sb.append("0 D T"); sb.append(lineParts[2]); sb.append(' '); sb.append(lineParts[1]); callDepth.put(lineParts[2], 1); } else if (command == Key.METHOD_DEFINITION) { methodShortForms.put(lineParts[3], lineParts[2]); return; } else if (/*INVOKE_BEFORE_SET.contains(command) || */command == Key.ADVISE_AFTER_METHOD_ENTRY) { String thread = lineParts[2]; String mid = checkMethodDef(); sb.append(callDepth.get(thread)); sb.append(" E"); sb.append(lineAbsTime - startTime); sb.append(" T"); sb.append(thread); sb.append(" M"); sb.append(mid); int tcd = callDepth.get(thread); callDepth.put(lineParts[2], tcd + 1); if (command == Key.ADVISE_AFTER_METHOD_ENTRY) { entryMid[entryMidIndex++] = mid; } } else if (/*INVOKE_AFTER_SET.contains(command) || */command == Key.ADVISE_BEFORE_RETURN) { String thread = lineParts[2]; int tcd = callDepth.get(thread) - 1; callDepth.put(lineParts[2], tcd); sb.append(callDepth.get(thread)); sb.append(" R"); sb.append(lineAbsTime - startTime); sb.append(" T"); sb.append(thread); sb.append(" M"); String mid = command == Key.ADVISE_BEFORE_RETURN ? entryMid[--entryMidIndex] : checkMethodDef(); sb.append(mid); } else { return; } out.println(sb.toString()); } private String checkMethodDef() { StringBuilder sb = new StringBuilder(); String fullName = classShortForms.get(lineParts[4]) + "." + methodShortForms.get(lineParts[5]); String id = classMethodDefs.get(fullName); if (id == null) { id = new String(Integer.toString(fullMethodIndex++)); sb.append("0 M M"); sb.append(id); sb.append(' '); sb.append(fullName); out.println(sb.toString()); classMethodDefs.put(fullName, id); } return id; } } /** * Converts to a readable format from the compressed form. */ private static class ReadableCommand extends BasicCommand { private Map<String, String> lastId = new HashMap<String, String>(); private boolean perThread; private String perThreadString; private String linePart(int slot) { if (slot < lineParts.length) { return lineParts[slot]; } else { return null; } } private String arg(int slot) { if (slot < 2) { return linePart(slot); } else { return perThread ? linePart(slot - 1) : linePart(slot); } } @Override void visitLine(String line) { super.visitLine(line); String arg1 = lineParts[1]; String arg2 = linePart(2); String bciArg = arg(3); String timeArg = arg1; String threadArg = perThread ? perThreadString : arg2; String objIdArg = "???"; if (VMATextStoreFormat.hasTime(command)) { String atTime = "@" + timeArg; out.printf("%-10s ", atTime); } else { out.printf("%-11c", ' '); } if (VMATextStoreFormat.hasId(command)) { if (arg(OBJ_ID_INDEX).charAt(0) == REPEAT_ID) { objIdArg = lastId.get(threadArg); } else { objIdArg = arg(OBJ_ID_INDEX); lastId.put(threadArg, objIdArg); } } out.printf("%s ", command); if (VMATextStoreFormat.hasBci(command)) { out.printf("bci: %s ", bciArg); } if (VMATextStoreFormat.hasTimeAndThread(command)) { printThreadId(threadArg); } if (VMATextStoreFormat.hasId(command)) { printObjId(objIdArg); } switch (command) { case INITIALIZE_STORE: int mode = Integer.parseInt(linePart(3)); perThread = (mode & PER_THREAD) != 0; out.printf("%s %s %s,%s,%s", timeArg, Boolean.parseBoolean(linePart(2)) ? "abs time" : "rel time", (mode & BATCHED) != 0 ? "Batched" : "Unbatched", (mode & PER_THREAD) != 0 ? "Per Thread" : "Shared", (mode & TEXT_KEY) != 0 ? "TextKey" : "CodeKey"); break; case THREAD_SWITCH: case FINALIZE_STORE: out.printf("%s", timeArg); break; case CLASS_DEFINITION: printClassId(lineParts[DEFINE_ARG_INDEX + 2]); out.printf(" %s", lineParts[DEFINE_ARG_INDEX]); printClId(lineParts[DEFINE_ARG_INDEX + 1]); break; case THREAD_DEFINITION: printThreadId(lineParts[DEFINE_ARG_INDEX + 1]); out.printf(" %s", lineParts[DEFINE_ARG_INDEX]); perThreadString = lineParts[DEFINE_ARG_INDEX + 1]; break; case METHOD_DEFINITION: printMethodId(lineParts[DEFINE_ARG_INDEX + 2]); printClassId(lineParts[DEFINE_ARG_INDEX]); out.printf(" %s", lineParts[DEFINE_ARG_INDEX + 1]); break; case FIELD_DEFINITION: printFieldId(lineParts[DEFINE_ARG_INDEX + 2]); printClassId(lineParts[DEFINE_ARG_INDEX]); out.printf(" %s", lineParts[DEFINE_ARG_INDEX + 1]); break; case ADVISE_AFTER_NEW: case ADVISE_AFTER_NEW_ARRAY: printClassId(arg(ID_CLASSNAME_INDEX)); if (command == Key.ADVISE_AFTER_NEW_ARRAY) { out.printf(" %s", arg(NEW_ARRAY_LENGTH_INDEX)); } break; case UNSEEN: printClassId(arg(ID_CLASSNAME_INDEX)); break; case ADVISE_BEFORE_CONST_LOAD: printValue(arg(CONST_LOAD_VALUE_INDEX), arg(CONST_LOAD_VALUE_INDEX + 1)); break; case ADVISE_BEFORE_LOAD: case ADVISE_BEFORE_STORE: out.printf(" %s", arg(LOADSTORE_DISP_INDEX)); if (command == Key.ADVISE_BEFORE_STORE) { printValue(arg(LOADSTORE_DISP_INDEX + 1), arg(LOADSTORE_DISP_INDEX + 2)); } break; case ADVISE_BEFORE_ARRAY_LOAD: case ADVISE_BEFORE_ARRAY_STORE: out.printf(" %s", threadArg, objIdArg, arg(ARRAY_INDEX_INDEX)); if (command == Key.ADVISE_BEFORE_ARRAY_STORE) { printValue(arg(ARRAY_INDEX_INDEX + 1), arg(ARRAY_INDEX_INDEX + 2)); } break; case ADVISE_BEFORE_ARRAY_LENGTH: out.printf(" %s", arg(ARRAY_LENGTH_INDEX)); break; case ADVISE_BEFORE_GET_STATIC: case ADVISE_BEFORE_PUT_STATIC: printFieldId(arg(STATIC_FIELDNAME_INDEX)); if (command == Key.ADVISE_BEFORE_PUT_STATIC) { printValue(arg(STATIC_FIELDNAME_INDEX + 1), arg(STATIC_FIELDNAME_INDEX + 2)); } break; case ADVISE_BEFORE_GET_FIELD: case ADVISE_BEFORE_PUT_FIELD: printFieldId(arg(ID_FIELDNAME_INDEX)); if (command == Key.ADVISE_BEFORE_PUT_FIELD) { printValue(arg(ID_FIELDNAME_INDEX + 1), arg(ID_FIELDNAME_INDEX + 2)); } break; case ADVISE_BEFORE_IF: printIfOpcode(arg(IF_OPCODE_INDEX)); if (arg(IF_OPCODE_INDEX + 1).equals("J")) { out.printf(" %s %s", arg(IF_OPCODE_INDEX + 2), arg(IF_OPCODE_INDEX + 3)); } else { printObjId(arg(IF_OPCODE_INDEX + 2)); printObjId(arg(IF_OPCODE_INDEX + 3)); } out.printf(" -> %s", arg(IF_OPCODE_INDEX + 4)); break; case ADVISE_BEFORE_OPERATION: printOperation(arg(OP_OPCODE_INDEX)); printValue(arg(OP_VALUES_INDEX), arg(OP_VALUES_INDEX + 1)); printValue(arg(OP_VALUES_INDEX), arg(OP_VALUES_INDEX + 2)); break; case ADVISE_BEFORE_INSTANCE_OF: case ADVISE_BEFORE_CHECK_CAST: printClassId(arg(ID_CLASSNAME_INDEX)); break; case ADVISE_BEFORE_CONVERSION: printOperation(arg(CONV_OPCODE_INDEX)); printValue(arg(CONV_OPCODE_INDEX + 1), arg(CONV_OPCODE_INDEX + 2)); break; case REMOVAL: printObjId(arg1); break; /* case ADVISE_AFTER_INVOKE_INTERFACE: case ADVISE_AFTER_INVOKE_STATIC: case ADVISE_AFTER_INVOKE_VIRTUAL: case ADVISE_AFTER_INVOKE_SPECIAL: */ case ADVISE_BEFORE_INVOKE_INTERFACE: case ADVISE_BEFORE_INVOKE_STATIC: case ADVISE_BEFORE_INVOKE_VIRTUAL: case ADVISE_BEFORE_INVOKE_SPECIAL: printMethodId(arg(ID_MEMBERNAME_INDEX)); break; case ADVISE_BEFORE_RETURN: if (arg(RETURN_VALUE_INDEX) != null) { printValue(arg(RETURN_VALUE_INDEX), arg(RETURN_VALUE_INDEX + 1)); } break; case ADVISE_BEFORE_STACK_ADJUST: out.printf(" %s", VMABytecodes.values()[Integer.parseInt(arg(STACK_ADJUST_INDEX))]); break; case ADVISE_AFTER_GC: case ADVISE_BEFORE_THROW: case ADVISE_BEFORE_MONITOR_ENTER: case ADVISE_BEFORE_MONITOR_EXIT: case ADVISE_BEFORE_GC: case ADVISE_BEFORE_THREAD_TERMINATING: case ADVISE_BEFORE_THREAD_STARTING: // nothing else break; case ADVISE_AFTER_MULTI_NEW_ARRAY: } out.println(); } private static void printThreadId(String thread) { out.printf("t:%s", thread); } private static void printObjId(String objId) { if (objId.equals("0")) { out.printf(" null"); } else { out.printf(" oid:%s", objId); } } private static void printClassId(String klass) { out.printf(" cid:%s", klass); } private static void printClId(String clId) { out.printf(" clid:%s", clId); } private static void printFieldId(String field) { out.printf(" fid:%s", field); } private static void printMethodId(String method) { out.printf(" mid:%s", method); } private static void printValue(String type, String value) { char typeCode = type.charAt(0); String rType = "???"; switch (typeCode) { case 'J': rType = "long"; break; case 'F': rType = "float"; break; case 'D': rType = "double"; break; case 'O': rType = "oid"; break; } out.printf(" %s:%s", rType, value); } private static void printIfOpcode(String opcode) { out.printf(" %s", VMABytecodes.values()[Integer.parseInt(opcode)]); } private static void printOperation(String opcode) { out.printf(" %s", VMABytecodes.values()[Integer.parseInt(opcode)]); } } }