/* * Copyright (c) 2004, 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.qa; import static com.oracle.max.vm.ext.vma.store.txt.VMATextStoreFormat.*; import static com.oracle.max.vma.tools.qa.TransientVMAdviceHandlerTypes.*; import static com.oracle.max.vma.tools.qa.TransientVMAdviceHandlerTypes.RecordType.*; import java.io.*; import java.util.*; import com.oracle.max.vm.ext.vma.*; import com.oracle.max.vm.ext.vma.store.*; import com.oracle.max.vm.ext.vma.store.txt.*; import com.oracle.max.vma.tools.log.*; import com.oracle.max.vma.tools.log.ConvertLog.MergeCommand.PushRecord; import com.sun.max.program.*; /** * Reads a log file in the format generated by {@link CSFVMATextStore}. * * Object identifiers in the log may be reused owing to garbage collection. Object identifiers in the {@link #objects} * map are unique and always qualified by the allocation (gc) epoch, {@link #allocationEpoch). I.e., an id X in the log is * stored as "X:allocationEpoch", e.g. "X:0, X:3, ..." * * Note that although the map may contain many instances of X:n, at any point in time there can only be one active * instance, which is the value of "n" given by the {@link #maxAllocationEpoch} map. * * The log must be time ordered for the main body of the code to work properly. * An unordered log is detected automatically and converted using {@link ConvertLog}. * * */ public class ProcessLog { static class TraceException extends Exception { static final long serialVersionUID = 0; TraceException(String s) { super(s); } } public static abstract class RecordReader { public abstract String[] readLine() throws IOException; public abstract void close() throws IOException; } private static class BufferedRecordReader extends RecordReader { private BufferedReader reader; BufferedRecordReader(BufferedReader reader) { this.reader = reader; } @Override public String[] readLine() throws IOException { String line = reader.readLine(); if (line == null) { return null; } return ConvertLog.split(textKeyMode, line); } @Override public void close() throws IOException { reader.close(); } } private static class PushReader extends RecordReader implements PushRecord { private static final int LENGTH = 1024; private String[][]lineParts = new String[LENGTH][]; private int count; private int writeIndex; private int readIndex; @Override public synchronized String[] readLine() throws IOException { while (count == 0) { try { wait(); } catch (InterruptedException ex) { } } count--; notify(); String[] result = lineParts[readIndex]; readIndex = (readIndex + 1) % LENGTH; return result; } @Override public void close() throws IOException { } @Override public synchronized void pushRecord(String[] lineParts) { while (count >= LENGTH) { try { wait(); } catch (InterruptedException ex) { } } this.lineParts[writeIndex] = lineParts; writeIndex = (writeIndex + 1) % LENGTH; count++; notify(); } } private static class PushReaderThread extends Thread { File[] files; PushReader pushReader; PushReaderThread(File[] files, PushReader pushReader) { this.pushReader = pushReader; this.files = files; setName("PushReader"); } @Override public void run() { ConvertLog.MergeCommand command = new ConvertLog.MergeCommand(pushReader); command.execute(files, null); } } /** * Uniquely identifies a class by its name and classloader id. */ static class ClassNameId implements Comparable { String className; String clId; ClassNameId(String name, String id) { this.className = name; this.clId = id; } public int compareTo(Object other) { ClassNameId otherClassName = (ClassNameId) other; if (otherClassName.className.equals(className)) { return clId.compareTo(otherClassName.clId); } else { return className.compareTo(otherClassName.className); } } @Override public int hashCode() { return className.hashCode() ^ clId.hashCode(); } @Override public boolean equals(Object other) { return compareTo(other) == 0; } @Override public String toString() { return className + "(cl:" + clId + ")"; } } private boolean verbose = false; private int maxLines; /** * Experimentally determined, using {@code cv -stats}, used to set the size of the {@link #adviceRecordList}. */ private static final int AVG_LINE_LENGTH = 12; // private Map<String, ObjectRecord> objects = new HashMap<String, ObjectRecord>(1024 * 1024); private SortedMap<ClassNameId, ClassRecord> classMap = new TreeMap<ClassNameId, ClassRecord>(); private Map<String, ThreadRecord> threadMap = new HashMap<String, ThreadRecord>(); private Map<String, FieldRecord> fieldMap = new HashMap<String, FieldRecord>(); private Map<String, MethodRecord> methodMap = new HashMap<String, MethodRecord>(); private ArrayList<AllocationEpoch> allocationEpochs = new ArrayList<AllocationEpoch>(); private long objectCount = 0; private long arrayCount = 0; private int missingConstructorCount = 0; private long startTime; // absolute value in trace private long lastTime; // absolute value computed from startTime + increments in the trace records private boolean absTime; // if log uses absolute time /** * bytecode index of associated bytecode instruction. */ private short bci; private AllocationEpoch allocationEpoch; private AllocationEpoch prevAllocationEpoch; /** * Key is an unqualified id and the value is the last allocation epoch that id was defined (allocated) in. * I.e., to get a qualified id, look up the value mapped from the unqualified id and append it. */ private Map<String, Integer> maxAllocationEpoch = new HashMap<String, Integer>(); private int startRemoval = -1; private int endRemoval; private Map<String, String> lastId = new HashMap<String, String>(); // Following fields hold the values for the current trace (if appropriate) private ObjectRecord objectRecord; private FieldRecord fieldRecord; private MethodRecord methodRecord; private ThreadRecord threadRecord; private ClassRecord classRecord; private Map<String, ObjectRecord> missingConstructors = new HashMap<String, ObjectRecord>(); /* These maps hold forward references that can occur in logs that were generated by * the per-thread batching generator. Even when time ordered, one thread may use * a short form that is defined later in another thread. This is because there is * no relationship between the time the thread buffer is flushed and the time the * short form is created. */ /** * A map from the class short form to the full name and class loader id. */ private Map<String, ClassNameId> classShortFormsToFull = new HashMap<String, ClassNameId>(); /** * A map from the thread short form to the full name. */ private Map<String, String> threadShortFormsToFull = new HashMap<String, String>(); /** * Records the forward references to class short forms, allowing a placeholder * {@link ClassRecord} to be created using a fake {@link ClassNameId} which * can be fixed up later when the definition is encountered. */ private Map<String, ClassNameId> classForwardRefs = new HashMap<String, ClassNameId>(); private static final String FORWARD_PREFIX = "Forward:"; /* * Fields/methods share common behavior so we have a generic handler class. * */ private interface Factory<T> { T create(ClassRecord cr, String name); } private static class FieldRecordFactory implements Factory<FieldRecord> { @Override public FieldRecord create(ClassRecord cr, String name) { return new FieldRecord(cr, name); } } private static class MethodRecordFactory implements Factory<MethodRecord> { @Override public MethodRecord create(ClassRecord cr, String name) { return new MethodRecord(cr, name); } } private static class QualName { ClassRecord cr; String name; QualName(ClassRecord cr, String name) { this.cr = cr; this.name = name; } } private class ShortFormHandler<T extends MemberRecord> { /** * The key is the short form and the value (when defined) is the actual name. */ private Map<String, QualName> shortFormToFull = new HashMap<String, QualName>(); /** * Set of forward references. */ private Set<String> forwardRefs = new HashSet<String>(); /** * Key is short name (which is unique, unlike the member name); value is an instance of T, * but {@code classRecord} field may be a forward ref also if the class has not yet been defined. */ private Map<String, T> kindMap = new HashMap<String, T>(); /** * factory for creating instances of {@link T}. */ private final Factory<T> factory; ShortFormHandler(String kind, Factory<T> factory) { this.factory = factory; } /** * Gets an instance of T for short form arguments. * If these short forms have already been defined then the result * will not need fixing up later. * @param shortName */ T getRecord(String shortName) { QualName qualName = shortFormToFull.get(shortName); if (qualName == null) { // forward reference if (!forwardRefs.contains(shortName)) { // new forward reference forwardRefs.add(shortName); } } T kindRecord = kindMap.get(shortName); if (kindRecord == null) { kindRecord = factory.create(qualName == null ? null : qualName.cr, qualName == null ? null : qualName.name); kindMap.put(shortName, kindRecord); } return kindRecord; } /** * Definition of a member (field/method). * N.B. The definition uses a class short form which may not yet be defined. */ void define() { String classShortForm = recordParts[DEFINE_ARG_INDEX]; ClassRecord cr = getClassRecord(classShortForm); // may be forward String definition = recordParts[DEFINE_ARG_INDEX + 1]; String shortName = recordParts[DEFINE_ARG_INDEX + 2]; shortFormToFull.put(shortName, new QualName(cr, definition)); T t; if (forwardRefs.contains(shortName)) { t = kindMap.remove(shortName); assert t != null; t.setName(definition); t.setClassRecord(cr); } else { // no forward refs to this member t = factory.create(cr, definition); } kindMap.put(shortName, t); } } private ShortFormHandler<FieldRecord> fieldShortFormHandler = new ShortFormHandler<FieldRecord>("Field", new FieldRecordFactory()); private ShortFormHandler<MethodRecord> methodShortFormHandler = new ShortFormHandler<MethodRecord>("Method", new MethodRecordFactory()); /** * List of all the records in the trace using the same format as {@link TransientVMAdviceHandlerTypes}. */ private ArrayList<AdviceRecord> adviceRecordList; /** * The parts of a log record split at space boundaries. */ private String[] recordParts; private int lineNumber; private static boolean textKeyMode; private ProcessLog(boolean verbose, int maxLines) throws IOException { this.verbose = verbose; this.maxLines = maxLines; } public static TraceRun processTrace(String dataDir, boolean verbose, int maxLines) throws IOException { ProcessLog pt = new ProcessLog(verbose, maxLines); return pt.doProcessTrace(dataDir); } private int estimateRecordCount(File[] files) { int length = 0; for (File file : files) { length += file.length(); } return length / AVG_LINE_LENGTH; } private TraceRun doProcessTrace(String dataDirName) throws IOException { long chunkStartTime = System.currentTimeMillis(); long processStartTime = chunkStartTime; File dataDir = new File(dataDirName); File dataFile = null; if (dataDir.isDirectory()) { dataFile = new File(dataDir, VMAStoreFile.GLOBAL_STORE); } else { dataFile = dataDir; } int adviceRecordListCountEstimate = 0; RecordReader reader = null; if (dataFile.exists()) { adviceRecordListCountEstimate = estimateRecordCount(new File[] {dataFile}); reader = checkTimeOrdered(dataFile); } else { // either a per-thread store or an error if (dataDir.isDirectory()) { adviceRecordListCountEstimate = estimateRecordCount(dataDir.listFiles()); PushReader pushReader = new PushReader(); reader = pushReader; new PushReaderThread(dataDir.listFiles(), pushReader).start(); } else { throw new FileNotFoundException(dataDirName); } } if (verbose) { System.out.println("processing trace file " + dataDirName + " starting"); } adviceRecordList = new ArrayList<AdviceRecord>(adviceRecordListCountEstimate); lineNumber = 1; boolean checked = false; try { while (true) { recordParts = reader.readLine(); if (recordParts == null) { break; } if (recordParts.length == 0 || recordParts[0].charAt(0) == '#') { continue; } if (!checked) { checked = true; } try { processTraceRecord(); } catch (TraceException e) { System.err.println("line " + lineNumber + ": " + e); } lineNumber++; if (lineNumber >= maxLines) { System.out.println("reached max line count - terminating processing"); break; } if (verbose && ((lineNumber % 100000) == 0)) { long endTime = System.currentTimeMillis(); System.out.printf("processed %d traces in %d ms (%d)%n", lineNumber, endTime - processStartTime, endTime - chunkStartTime); chunkStartTime = endTime; } } } finally { reader.close(); } if (verbose) { System.out.println("processing trace file " + dataDirName + " complete"); } checkSorted(); // create a map from classloaders to classes loaded by them Map<String, SortedMap<String, ClassRecord>> classLoaders = new HashMap<String, SortedMap<String, ClassRecord>>(); for (ClassRecord cr : classMap.values()) { SortedMap<String, ClassRecord> clMap = classLoaders.get(cr.getClassLoaderId()); if (clMap == null) { clMap = new TreeMap<String, ClassRecord>(); classLoaders.put(cr.getClassLoaderId(), clMap); } clMap.put(cr.getName(), cr); } fixupEndCreationRecords(); TraceRun result = new TraceRun(dataDirName, adviceRecordList, objects, threadMap, classLoaders, missingConstructors, objectCount, arrayCount, missingConstructorCount, allocationEpochs, startTime, lastTime); return result; } private void checkSorted() { AdviceRecord last = null; for (int i = 0; i < adviceRecordList.size(); i++) { AdviceRecord ar = adviceRecordList.get(i); if (last != null) { if (last.time > ar.time) { System.err.println("advice record list is not sorted by time, at record " + i); System.exit(1); } } last = ar; } } /** * Check that the header line is not corrupt and set the key mode. */ private void checkStoreHeader() { assert recordParts.length == 4; textKeyMode = (Integer.parseInt(recordParts[3]) & TEXT_KEY) != 0; assert VMATextStoreFormat.getCommand(textKeyMode, recordParts[0]) == Key.INITIALIZE_STORE; } private RecordReader checkTimeOrdered(File file) throws IOException { BufferedRecordReader reader = new BufferedRecordReader(new BufferedReader(new FileReader(file))); recordParts = reader.readLine(); checkStoreHeader(); reader.close(); int mode = Integer.parseInt(recordParts[3]); if ((mode & BATCHED) != 0) { // not time ordered, run the converter to a temp file if (verbose) { System.out.println("creating time ordered log from per-thread batched log"); } File tempFile = File.createTempFile("vma", null); try { ConvertLog.main(new String[] {"-f", file.getAbsolutePath(), "-o", tempFile.getAbsolutePath(), "-unbatch"}); } catch (IOException ex) { throw ex; } catch (Exception ex) { ProgramError.unexpected("failed to convert: " + file.getAbsolutePath(), ex); } file = tempFile; } return new BufferedRecordReader(new BufferedReader(new FileReader(file))); } /** * Fixup the end creation record for object records. * If the trace has INVOKESPECIAL/AFTER records, this just means fixing up * the case where the AFTER <init> was a forward reference. * * Otherwise, where we only have METHOD_ENTRY/RETURN pairs it means locating * the matching RETURN for the <init> after the NEW. */ private void fixupEndCreationRecords() { for (ObjectRecord objectRecord : objects.values()) { if (objectRecord.endCreationRecord == null) { /* TODO depends on INVOKE AFTER advice // we scan backwards to find the last <init> return for (int i = adviceRecordList.size() - 1; i > 0; i--) { AdviceRecord ar = adviceRecordList.get(i); RecordType rt = ar.getRecordType(); if (rt == InvokeSpecial && ar.getAdviceMode() == AdviceMode.AFTER.ordinal()) { ObjectRecord invokeObject = AdviceRecordHelper.getObjectRecord(ar); if (objectRecord.id.equals(invokeObject.id)) { MethodRecord mr = AdviceRecordHelper.getMethod(ar); if (mr.name.equals("<init>")) { objectRecord.setEndCreationRecord(ar); } } } } */ int index = getRecordListIndex(objectRecord.beginCreationRecord); assert index >= 0 : "failed to find creation record index"; int mIndex = getInitMethodEntry(objectRecord, index + 1); // We define end creation to be the RETURN that matches this constructor invocation. // There may be an arbitrary number of other method invocations in between AdviceRecord endCreationRecord = null; if (mIndex > 0) { int depth = 0; for (int i = mIndex + 1; i < adviceRecordList.size(); i++) { AdviceRecord ar = adviceRecordList.get(i); RecordType art = ar.getRecordType(); if (art == MethodEntry) { depth++; } else if (art == Return || art == ReturnDouble || art == ReturnFloat || art == ReturnLong || art == ReturnObject || art == ReturnByThrow) { if (depth == 0) { endCreationRecord = ar; break; } else { if (art == ReturnByThrow) { int pop = ar.getPackedValue(); depth -= pop; } else { depth--; } } } } } else { endCreationRecord = adviceRecordList.get(index + 1); } if (endCreationRecord == null) { System.err.printf("failed to find end creation record for %s%n", objectRecord); endCreationRecord = adviceRecordList.get(index + 1); } objectRecord.setEndCreationRecord(endCreationRecord); } } } /** * Attempt to locate the constructor METHOD_ENTRY for {@code objectRecord} starting at {@code index}. * There may not be one if the class was not instrumented. * @param objectRecord * @param index * @return index of METHOD_ENTRY revcord or -1 of not found */ private int getInitMethodEntry(ObjectRecord objectRecord, int index) { for (int i = index; i < adviceRecordList.size(); i++) { AdviceRecord ar = adviceRecordList.get(i); if (ar.getRecordType() == MethodEntry) { ObjectRecord methodEntryObject = AdviceRecordHelper.getObjectRecord(ar); if (methodEntryObject != null && objectRecord.id.equals(methodEntryObject.id)) { MethodRecord mr = AdviceRecordHelper.getMethod(ar); if (mr.name.equals("<init>")) { return i; } } } } return -1; } private int getRecordListIndex(AdviceRecord ar) { return AdviceRecordHelper.getRecordListIndex(adviceRecordList, ar); } private void objectsPut(String id, ObjectRecord td) { final ObjectRecord old = objects.put(td.getId(), td); assert old == null; // remember max epoch for resolving unqualified ids maxAllocationEpoch.put(id, allocationEpoch.epoch); } /** * Gets the {@link ObjectRecord} associated with an unqualified id. The qualified id is found using the * {@link #maxEpochMap}. * * @param id * @throws TraceException */ private ObjectRecord getTraceRecord(String id) throws TraceException { if (id.equals("0")) { return null; } final Integer thisMaxAllocationEpoch = maxAllocationEpoch.get(id); assert thisMaxAllocationEpoch != null; final String uid = ObjectRecord.getMapId(id, thisMaxAllocationEpoch); ObjectRecord td = objects.get(uid); if (td == null) { throw new TraceException("no creation record for id = " + uid + ", at line " + lineNumber); } td.traceOccurrences++; return td; } /** * Sets (and returns) {@link #classRecord}, handling forward references. * In the latter case, the name field in the ClassRecord will be based on {@value CLASS_FORWARD_PREFIX} * and will be fixed up later. * @param shortClassName in the trace * @return a {@link ClassRecord} */ private ClassRecord getClassRecord(String shortClassName) { ClassNameId classNameId = classShortFormsToFull.get(shortClassName); if (classNameId == null) { // forward reference classNameId = classForwardRefs.get(shortClassName); if (classNameId == null) { // new forward reference String forwardName = FORWARD_PREFIX + shortClassName; // the class definition will contain the classloader id; // at this stage we create a don't care value, as it isn't used classNameId = new ClassNameId(forwardName, ObjectRecord.getMapId("0", allocationEpoch.epoch)); classForwardRefs.put(shortClassName, classNameId); } } // Have we created a ClassRecord for this class? classRecord = classMap.get(classNameId); if (classRecord == null) { classRecord = new ClassRecord(classNameId.className, classNameId.clId); classMap.put(classNameId, classRecord); } return classRecord; } /* */ /** * Sets (and returns) {@link #fieldRecord} handling forward references. * @param shortFieldName */ private FieldRecord getFieldRecord(String shortFieldName) { fieldRecord = fieldShortFormHandler.getRecord(shortFieldName); return fieldRecord; } /** * Sets (and returns) {@link #methodRecord} handling forward references. * @param shortMethodName */ private MethodRecord getMethodRecord(String shortMethodName) { methodRecord = methodShortFormHandler.getRecord(shortMethodName); return methodRecord; } /** * Class definition, may resolve a previous forward reference. */ private void defineClass(String name, long classLoaderId, String shortForm) { // class definition ClassNameId classNameId = new ClassNameId(name, getClassLoaderIdAsString(classLoaderId)); classShortFormsToFull.put(shortForm, classNameId); // fix up forward reference ClassNameId forwardName = classForwardRefs.get(shortForm); if (forwardName != null) { // find class record in the classMap under the forward name, remove it, // patch name/id and add back under new name. ClassRecord cr = classMap.remove(forwardName); assert cr != null; cr.setName(name, getClassLoaderIdAsString(classLoaderId)); classMap.put(classNameId, cr); } } private String getClassLoaderIdAsString(long clId) { String clIdString = Long.toString(clId); Integer epoch = maxAllocationEpoch.get(clIdString); if (epoch == null) { // This is a special case for the self referring bootstrap class loader epoch = allocationEpoch.epoch; } return ObjectRecord.getMapId(Long.toString(clId), epoch); } private void defineField() { fieldShortFormHandler.define(); } private void defineMethod() { methodShortFormHandler.define(); } private long getTime() throws TraceException { long timeValue = Long.parseLong(recordParts[TIME_INDEX]); if (absTime) { lastTime = timeValue; } else { lastTime += timeValue; } return lastTime; } private long expectNumber(String arg) throws TraceException { if (!(arg.charAt(0) >= '0' || arg.charAt(0) <= '9')) { throw new TraceException("number expected at line " + lineNumber); } else { return Long.parseLong(arg); } } private Key expectValidTraceStart() throws TraceException { Key result = VMATextStoreFormat.getCommand(textKeyMode, recordParts[KEY_INDEX]); if (result == null) { throw new TraceException("unknown trace command at line " + lineNumber); } else { return result; } } private void getTimeAndThread() throws TraceException { getTime(); // N.B. there can be no forward references to threads String threadName = threadShortFormsToFull.get(recordParts[THREAD_INDEX]); assert threadName != null; threadRecord = threadMap.get(threadName); } private String arg(int slot) { if (slot < recordParts.length) { return recordParts[slot]; } else { return null; } } private void processTraceRecord() throws TraceException { String arg1 = arg(1); String arg2 = arg(2); String bciArg = arg(3); String threadArg = arg2; String objIdArg = "???"; int adviceModeInt = -1; Key key = VMATextStoreFormat.getCommand(textKeyMode, recordParts[0]); if (VMATextStoreFormat.hasTimeAndThread(key)) { getTimeAndThread(); } else if (VMATextStoreFormat.hasTime(key)) { getTime(); } if (VMATextStoreFormat.hasBci(key)) { bci = (short) Integer.parseInt(bciArg); } if (VMATextStoreFormat.hasId(key)) { if (arg(OBJ_ID_INDEX).charAt(0) == REPEAT_ID) { objIdArg = lastId.get(threadArg); } else { objIdArg = arg(OBJ_ID_INDEX); lastId.put(threadArg, objIdArg); } } AdviceRecord adviceRecord = null; switch (key) { case INITIALIZE_STORE: startTime = Long.parseLong(arg1); lastTime = startTime; allocationEpoch = new AllocationEpoch(startTime); allocationEpochs.add(allocationEpoch); absTime = Boolean.parseBoolean(arg2); return; case FINALIZE_STORE: { long t = Long.parseLong(arg1); lastTime = absTime ? t : lastTime + t; allocationEpoch.endTime = lastTime; return; } case THREAD_SWITCH: throw new TraceException("batched log is not supported - use ConvertLog -unbatch"); case CLASS_DEFINITION: { defineClass(ClassRecord.getCanonicalName(recordParts[DEFINE_ARG_INDEX]), expectNumber(recordParts[DEFINE_ARG_INDEX + 1]), recordParts[DEFINE_ARG_INDEX + 2]); return; } case FIELD_DEFINITION: { defineField(); return; } case METHOD_DEFINITION: { defineMethod(); return; } case THREAD_DEFINITION: { ThreadRecord tr = new ThreadRecord(recordParts[DEFINE_ARG_INDEX]); threadMap.put(tr.name, tr); threadShortFormsToFull.put(recordParts[DEFINE_ARG_INDEX + 1], tr.name); return; } case ADVISE_BEFORE_THROW: case ADVISE_BEFORE_MONITOR_ENTER: case ADVISE_BEFORE_MONITOR_EXIT: { objectRecord = getTraceRecord(objIdArg); ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); objectAdviceRecord.value = objectRecord; objectRecord.addTraceElement(objectAdviceRecord); adviceRecord = objectAdviceRecord; break; } case UNSEEN: case ADVISE_AFTER_NEW_ARRAY: case ADVISE_AFTER_NEW: { ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.AFTER, bci); getClassRecord(recordParts[NEW_CLASSNAME_INDEX]); objectRecord = new ObjectRecord(objIdArg, allocationEpoch.epoch, classRecord, threadRecord, objectAdviceRecord); classRecord.addObject(objectRecord); objectsPut(objIdArg, objectRecord); objectAdviceRecord.value = objectRecord; if (key == Key.UNSEEN) { // We don't know how or when this was constructed, but give it an end creation time of now. objectRecord.setEndCreationRecord(objectAdviceRecord); missingConstructors.put(objectRecord.getId(), objectRecord); missingConstructorCount++; } else if (key == Key.ADVISE_AFTER_NEW_ARRAY) { objectAdviceRecord.setPackedValue(Integer.parseInt(arg(NEW_ARRAY_LENGTH_INDEX))); // array length objectRecord.setEndCreationRecord(objectAdviceRecord); } if (classRecord.isArray()) { arrayCount++; } else { objectCount++; } adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_CONST_LOAD: { adviceRecord = createAdviceRecordAndSetTimeThreadValue("ConstLoad", AdviceMode.BEFORE, arg(CONST_LOAD_VALUE_INDEX), arg(CONST_LOAD_VALUE_INDEX + 1)); break; } case ADVISE_BEFORE_LOAD: { adviceRecord = createAdviceRecordAndSetTimeAndThread(Load, AdviceMode.BEFORE, bci); adviceRecord.setPackedValue(Integer.parseInt(arg(LOADSTORE_DISP_INDEX))); break; } case ADVISE_AFTER_LOAD: case ADVISE_BEFORE_STORE: { adviceRecord = createAdviceRecordAndSetTimeThreadValue(key == Key.ADVISE_AFTER_LOAD ? "Load" : "Store", AdviceMode.BEFORE, arg(LOADSTORE_DISP_INDEX + 1), arg(LOADSTORE_DISP_INDEX + 2)); if (adviceRecord.getRecordType() == StoreObject || adviceRecord.getRecordType() == LoadObject) { ObjectRecord or = AdviceRecordHelper.getObjectRecord(adviceRecord); if (or != null) { or.addTraceElement(adviceRecord); } } adviceRecord.setPackedValue(Integer.parseInt(arg(LOADSTORE_DISP_INDEX))); break; } case ADVISE_BEFORE_ARRAY_LOAD: { objectRecord = getTraceRecord(objIdArg); int arrayIndex = (int) expectNumber(arg(ARRAY_INDEX_INDEX)); ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); objectAdviceRecord.value = objectRecord; objectAdviceRecord.setPackedValue(arrayIndex); objectRecord.addTraceElement(objectAdviceRecord); adviceRecord = objectAdviceRecord; break; } case ADVISE_AFTER_ARRAY_LOAD: case ADVISE_BEFORE_ARRAY_STORE: { objectRecord = getTraceRecord(objIdArg); int arrayIndex = (int) expectNumber(arg(ARRAY_INDEX_INDEX)); ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeThreadValue( key == Key.ADVISE_BEFORE_ARRAY_STORE ? "ArrayStore" : "ArrayLoad", AdviceMode.BEFORE, arg(ARRAY_INDEX_INDEX + 1), arg(ARRAY_INDEX_INDEX + 2)); objectAdviceRecord.value = objectRecord; objectAdviceRecord.setPackedValue(arrayIndex); objectRecord.addTraceElement(objectAdviceRecord); if (objectAdviceRecord.getRecordType() == ArrayStoreObject || objectAdviceRecord.getRecordType() == ArrayLoadObject) { ObjectRecord object1 = getTraceRecord(arg(ARRAY_INDEX_INDEX + 2)); if (object1 != null) { object1.addTraceElement(objectAdviceRecord); } } adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_ARRAY_LENGTH: { objectRecord = getTraceRecord(objIdArg); ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(ArrayLength, AdviceMode.BEFORE, bci); objectAdviceRecord.value = objectRecord; objectAdviceRecord.setPackedValue(Integer.parseInt(arg(ARRAY_LENGTH_INDEX))); objectRecord.addTraceElement(objectAdviceRecord); adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_GET_STATIC: { getFieldRecord(arg(STATIC_FIELDNAME_INDEX)); ObjectFieldAdviceRecord objectFieldAdviceRecord = (ObjectFieldAdviceRecord) createAdviceRecordAndSetTimeAndThread(GetStatic, AdviceMode.BEFORE, bci); objectFieldAdviceRecord.value = classRecord; objectFieldAdviceRecord.field = fieldRecord; classRecord.addTraceElement(objectFieldAdviceRecord); adviceRecord = objectFieldAdviceRecord; break; } case ADVISE_BEFORE_PUT_STATIC: { getFieldRecord(arg(STATIC_FIELDNAME_INDEX)); ObjectFieldAdviceRecord objectFieldAdviceRecord = (ObjectFieldAdviceRecord) createAdviceRecordAndSetTimeThreadValue("PutStatic", AdviceMode.BEFORE, arg(STATIC_FIELDNAME_INDEX + 1), arg(STATIC_FIELDNAME_INDEX + 2)); objectFieldAdviceRecord.value = classRecord; objectFieldAdviceRecord.field = fieldRecord; classRecord.addTraceElement(objectFieldAdviceRecord); adviceRecord = objectFieldAdviceRecord; break; } case ADVISE_BEFORE_GET_FIELD: { objectRecord = getTraceRecord(objIdArg); getFieldRecord(arg(ID_FIELDNAME_INDEX)); ObjectFieldAdviceRecord objectFieldAdviceRecord = (ObjectFieldAdviceRecord) createAdviceRecordAndSetTimeAndThread(GetField, AdviceMode.BEFORE, bci); objectFieldAdviceRecord.value = objectRecord; objectFieldAdviceRecord.field = fieldRecord; objectRecord.addTraceElement(objectFieldAdviceRecord); adviceRecord = objectFieldAdviceRecord; break; } case ADVISE_BEFORE_PUT_FIELD: { objectRecord = getTraceRecord(objIdArg); getFieldRecord(arg(ID_FIELDNAME_INDEX)); ObjectFieldAdviceRecord objectFieldAdviceRecord = (ObjectFieldAdviceRecord) createAdviceRecordAndSetTimeThreadValue("PutField", AdviceMode.BEFORE, arg(ID_FIELDNAME_INDEX + 1), arg(ID_FIELDNAME_INDEX + 2)); objectFieldAdviceRecord.value = objectRecord; objectFieldAdviceRecord.field = fieldRecord; objectRecord.addTraceElement(objectFieldAdviceRecord); adviceRecord = objectFieldAdviceRecord; break; } case ADVISE_BEFORE_IF: { if (arg(IF_OPCODE_INDEX + 1).equals("J")) { LongLongTBciAdviceRecord longLongAdviceRecord = (LongLongTBciAdviceRecord) createAdviceRecordAndSetTimeAndThread(IfInt, AdviceMode.BEFORE, bci); longLongAdviceRecord.value = Long.parseLong(arg(IF_OPCODE_INDEX + 2)); longLongAdviceRecord.value2 = Long.parseLong(arg(IF_OPCODE_INDEX + 3)); longLongAdviceRecord.targetBci = Short.parseShort(arg(IF_OPCODE_INDEX + 4)); adviceRecord = longLongAdviceRecord; } else { ObjectObjectTBciAdviceRecord objectObjectAdviceRecord = (ObjectObjectTBciAdviceRecord) createAdviceRecordAndSetTimeAndThread(IfObject, AdviceMode.BEFORE, bci); ObjectRecord object1 = getTraceRecord(arg(IF_OPCODE_INDEX + 2)); ObjectRecord object2 = getTraceRecord(arg(IF_OPCODE_INDEX + 3)); objectObjectAdviceRecord.value = object1; objectObjectAdviceRecord.value2 = object2; objectObjectAdviceRecord.targetBci = Short.parseShort(arg(IF_OPCODE_INDEX + 4)); if (object1 != null) { object1.addTraceElement(objectObjectAdviceRecord); } if (object2 != null) { object2.addTraceElement(objectObjectAdviceRecord); } adviceRecord = objectObjectAdviceRecord; } adviceRecord.setPackedValue(Integer.parseInt(arg(IF_OPCODE_INDEX))); break; } case ADVISE_BEFORE_OPERATION: { adviceRecord = createAdviceRecordAndSetTimeThreadValue("Operation", AdviceMode.BEFORE, arg(OP_VALUES_INDEX), arg(OP_VALUES_INDEX + 1)); adviceRecord.setPackedValue(Integer.parseInt(arg(OP_OPCODE_INDEX))); switch (arg(OP_VALUES_INDEX).charAt(0)) { case LONG_VALUE: ((LongLongAdviceRecord) adviceRecord).value2 = Long.parseLong(arg(OP_VALUES_INDEX + 2)); break; case FLOAT_VALUE: ((FloatFloatAdviceRecord) adviceRecord).value2 = Float.parseFloat(arg(OP_VALUES_INDEX + 2)); break; case DOUBLE_VALUE: ((DoubleDoubleAdviceRecord) adviceRecord).value2 = Double.parseDouble(arg(OP_VALUES_INDEX + 2)); break; default: throw new IllegalArgumentException("bad type " + arg(OP_VALUES_INDEX).charAt(0) + " in value"); } break; } case ADVISE_BEFORE_INSTANCE_OF: case ADVISE_BEFORE_CHECK_CAST: { objectRecord = getTraceRecord(objIdArg); getClassRecord(arg(ID_CLASSNAME_INDEX)); ObjectObjectAdviceRecord objectObjectAdviceRecord = (ObjectObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); objectObjectAdviceRecord.value = objectRecord; objectObjectAdviceRecord.value2 = classRecord; if (objectRecord != null) { objectRecord.addTraceElement(objectObjectAdviceRecord); } adviceRecord = objectObjectAdviceRecord; break; } case ADVISE_BEFORE_CONVERSION: { adviceRecord = createAdviceRecordAndSetTimeThreadValue("Conversion", AdviceMode.BEFORE, arg(CONV_OPCODE_INDEX + 1), arg(CONV_OPCODE_INDEX + 2)); adviceRecord.setPackedValue(Integer.parseInt(arg(CONV_OPCODE_INDEX))); break; } case ADVISE_BEFORE_GC: { allocationEpoch.setEndTime(lastTime); adviceRecord = createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); break; } case ADVISE_AFTER_GC: { prevAllocationEpoch = allocationEpoch; allocationEpoch = new AllocationEpoch(lastTime); allocationEpochs.add(allocationEpoch); adviceRecord = createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.AFTER, bci); break; } case REMOVAL: { if (startRemoval < 0) { startRemoval = adviceRecordList.size(); } endRemoval = adviceRecordList.size(); objIdArg = arg1; // This object may have been created in some earlier epoch, so can't use current. // It must be the death of id recorded by maxAllocationEpoch objectRecord = objects.get(ObjectRecord.getMapId(objIdArg, maxAllocationEpoch.get(objIdArg))); ObjectAdviceRecord objectAdviceRecord = (ObjectAdviceRecord) createAdviceRecordAndSetTimeAndThread(Removal, AdviceMode.AFTER, bci); objectAdviceRecord.value = objectRecord; objectRecord.setRemovalRecord(objectAdviceRecord); adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_INVOKE_INTERFACE: case ADVISE_BEFORE_INVOKE_STATIC: case ADVISE_BEFORE_INVOKE_VIRTUAL: case ADVISE_BEFORE_INVOKE_SPECIAL: adviceModeInt = AdviceMode.BEFORE.ordinal(); // Checkstyle: stop /* case ADVISE_AFTER_INVOKE_INTERFACE: case ADVISE_AFTER_INVOKE_STATIC: case ADVISE_AFTER_INVOKE_VIRTUAL: case ADVISE_AFTER_INVOKE_SPECIAL: */ case ADVISE_AFTER_METHOD_ENTRY: { // Checkstyle: resume if (adviceModeInt == -1) { adviceModeInt = AdviceMode.AFTER.ordinal(); } objectRecord = getTraceRecord(objIdArg); getMethodRecord(arg(ID_MEMBERNAME_INDEX)); ObjectMethodAdviceRecord objectAdviceRecord = (ObjectMethodAdviceRecord) createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.values()[adviceModeInt], bci); if (key == Key.ADVISE_BEFORE_INVOKE_STATIC /*|| key == Key.ADVISE_AFTER_INVOKE_STATIC*/) { objectAdviceRecord.value = classRecord; } else { objectAdviceRecord.value = objectRecord; if (objectRecord != null) { objectRecord.addTraceElement(objectAdviceRecord); } } objectAdviceRecord.value2 = methodRecord; /* if (key == Key.ADVISE_AFTER_INVOKE_SPECIAL) { if (methodRecord.name.equals("<init>")) { objectRecord.setEndCreationRecord(objectAdviceRecord); } } */ adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_RETURN: { if (arg(RETURN_VALUE_INDEX) != null) { adviceRecord = createAdviceRecordAndSetTimeThreadValue("Return", AdviceMode.BEFORE, arg(RETURN_VALUE_INDEX), arg(RETURN_VALUE_INDEX + 1)); if (adviceRecord.getRecordType() == ReturnObject) { ObjectRecord or = AdviceRecordHelper.getObjectRecord(adviceRecord); if (or != null) { or.addTraceElement(adviceRecord); } } } else { adviceRecord = createAdviceRecordAndSetTimeAndThread(Return, AdviceMode.BEFORE, bci); } break; } case ADVISE_BEFORE_RETURN_BY_THROW: { objectRecord = getTraceRecord(objIdArg); ObjectLongAdviceRecord objectAdviceRecord = (ObjectLongAdviceRecord) createAdviceRecordAndSetTimeAndThread(ReturnByThrow, AdviceMode.BEFORE, bci); objectAdviceRecord.setPackedValue(Integer.parseInt(arg(RETURN_THROW_POP_INDEX))); objectAdviceRecord.value = objectRecord; adviceRecord = objectAdviceRecord; break; } case ADVISE_BEFORE_STACK_ADJUST: { adviceRecord = createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); adviceRecord.setPackedValue(Integer.parseInt(arg(STACK_ADJUST_INDEX))); break; } case ADVISE_BEFORE_THREAD_TERMINATING: case ADVISE_BEFORE_THREAD_STARTING: adviceRecord = createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); if (key == Key.ADVISE_BEFORE_THREAD_STARTING) { threadRecord.startTime = lastTime; } else { threadRecord.endTime = lastTime; } break; case ADVISE_AFTER_MULTI_NEW_ARRAY: assert false : key + " unexpected"; break; case ADVISE_BEFORE_GOTO: adviceRecord = createAdviceRecordAndSetTimeAndThread(keyToRecordType(key), AdviceMode.BEFORE, bci); adviceRecord.setPackedValue(Integer.parseInt(arg(GOTO_TARGET_INDEX))); break; default: throw new TraceException("unimplemented key: " + key); } if (key != Key.REMOVAL && startRemoval >= 0) { prevAllocationEpoch.setRemovalRange(startRemoval, endRemoval); startRemoval = -1; } assert adviceRecord != null; adviceRecordList.add(adviceRecord); } private AdviceRecord createAdviceRecordAndSetTimeThreadValue(String rtPrefix, AdviceMode adviceMode, String valueKey, String value) throws TraceException { RecordType rt = getRecordTypeForValue(rtPrefix, valueKey); AdviceRecord adviceRecord = createAdviceRecordAndSetTimeAndThread(rt, adviceMode, bci); switch (valueKey.charAt(0)) { case OBJ_VALUE: { ObjectRecord or = getTraceRecord(value); switch (rt) { case PutFieldObject: case PutStaticObject: ((ObjectFieldObjectAdviceRecord) adviceRecord).value = or; break; case ConstLoadObject: case StoreObject: case LoadObject: case ReturnObject: ((ObjectAdviceRecord) adviceRecord).value = or; break; default: ((ObjectObjectAdviceRecord) adviceRecord).value2 = or; } break; } case LONG_VALUE: { long v = Long.parseLong(value); switch (rt) { case PutFieldLong: case PutStaticLong: ((ObjectFieldLongAdviceRecord) adviceRecord).value2 = v; break; case ConstLoadLong: case StoreLong: case ConversionLong: case ReturnLong: ((LongAdviceRecord) adviceRecord).value = v; break; case OperationLong: ((LongLongAdviceRecord) adviceRecord).value = v; break; default: ((ObjectLongAdviceRecord) adviceRecord).value2 = v; } break; } case FLOAT_VALUE: { float v = Float.parseFloat(value); switch (rt) { case PutFieldFloat: case PutStaticFloat: ((ObjectFieldFloatAdviceRecord) adviceRecord).value2 = v; break; case ConstLoadFloat: case StoreFloat: case ConversionFloat: case ReturnFloat: ((FloatAdviceRecord) adviceRecord).value = v; break; case OperationFloat: ((FloatFloatAdviceRecord) adviceRecord).value = v; break; default: ((ObjectFloatAdviceRecord) adviceRecord).value2 = v; } break; } case DOUBLE_VALUE: { double v = Double.parseDouble(value); switch (rt) { case PutFieldDouble: case PutStaticDouble: ((ObjectFieldDoubleAdviceRecord) adviceRecord).value2 = v; break; case ConstLoadDouble: case StoreDouble: case ConversionDouble: case ReturnDouble: ((DoubleAdviceRecord) adviceRecord).value = v; break; case OperationDouble: ((DoubleDoubleAdviceRecord) adviceRecord).value = v; break; default: ((ObjectDoubleAdviceRecord) adviceRecord).value2 = v; } break; } default: assert false; } return adviceRecord; } private AdviceRecord createAdviceRecordAndSetTimeAndThread(RecordType rt, AdviceMode adviceMode, short bci) { AdviceRecord adviceRecord; // Handle the change of record type for PUT/GET/FIELD/STATIC and REMOVAL types switch (rt) { case PutFieldLong: case PutStaticLong: adviceRecord = new ObjectFieldLongAdviceRecord(); break; case PutFieldFloat: case PutStaticFloat: adviceRecord = new ObjectFieldFloatAdviceRecord(); break; case PutFieldDouble: case PutStaticDouble: adviceRecord = new ObjectFieldDoubleAdviceRecord(); break; case PutFieldObject: case PutStaticObject: adviceRecord = new ObjectFieldObjectAdviceRecord(); break; case GetField: case GetStatic: adviceRecord = new ObjectFieldAdviceRecord(); break; case Removal: adviceRecord = new ObjectAdviceRecord(); break; default: adviceRecord = rt.newAdviceRecord(); } adviceRecord.setCodeModeBci(rt, adviceMode, bci); adviceRecord.time = lastTime; adviceRecord.thread = threadRecord; return adviceRecord; } private static RecordType getRecordTypeForValue(String rtPrefix, String valueKey) { String suffix; switch (valueKey.charAt(0)) { case OBJ_VALUE: suffix = "Object"; break; case LONG_VALUE: suffix = "Long"; break; case FLOAT_VALUE: suffix = "Float"; break; case DOUBLE_VALUE: suffix = "Double"; break; default: throw new IllegalArgumentException("bad type " + valueKey.charAt(0) + " in value"); } return RecordType.valueOf(rtPrefix + suffix); } private static RecordType keyToRecordType(Key key) throws TraceException { switch (key) { case ADVISE_BEFORE_THROW: return Throw; case ADVISE_BEFORE_INSTANCE_OF: return InstanceOf; case ADVISE_BEFORE_ARRAY_LENGTH: return ArrayLength; case ADVISE_BEFORE_CHECK_CAST: return CheckCast; case ADVISE_BEFORE_GET_STATIC: return GetStatic; case ADVISE_BEFORE_GET_FIELD: return GetField; case ADVISE_BEFORE_MONITOR_ENTER: return MonitorEnter; case ADVISE_BEFORE_MONITOR_EXIT: return MonitorExit; /*case ADVISE_AFTER_INVOKE_INTERFACE:*/ case ADVISE_BEFORE_INVOKE_INTERFACE: return InvokeInterface; /*case ADVISE_AFTER_INVOKE_STATIC:*/ case ADVISE_BEFORE_INVOKE_STATIC: return InvokeStatic; /*case ADVISE_AFTER_INVOKE_SPECIAL:*/ case ADVISE_BEFORE_INVOKE_SPECIAL: return InvokeSpecial; case ADVISE_BEFORE_INVOKE_VIRTUAL: /*case ADVISE_AFTER_INVOKE_VIRTUAL:*/ return InvokeVirtual; case ADVISE_AFTER_METHOD_ENTRY: return MethodEntry; case ADVISE_BEFORE_STACK_ADJUST: return StackAdjust; case ADVISE_AFTER_GC: case ADVISE_BEFORE_GC: return GC; case ADVISE_BEFORE_THREAD_TERMINATING: return ThreadTerminating; case ADVISE_BEFORE_THREAD_STARTING: return ThreadStarting; case ADVISE_BEFORE_ARRAY_LOAD: return ArrayLoad; case ADVISE_AFTER_NEW: return New; case ADVISE_AFTER_NEW_ARRAY: return NewArray; case REMOVAL: return Removal; case UNSEEN: return Unseen; case ADVISE_BEFORE_RETURN: return Return; case ADVISE_BEFORE_GOTO: return Goto; case ADVISE_AFTER_MULTI_NEW_ARRAY: // These are all value based so do not have a simple static mapping case ADVISE_BEFORE_CONVERSION: case ADVISE_BEFORE_PUT_FIELD: case ADVISE_BEFORE_PUT_STATIC: case ADVISE_BEFORE_ARRAY_STORE: case ADVISE_AFTER_ARRAY_LOAD: case ADVISE_BEFORE_IF: case ADVISE_BEFORE_OPERATION: case ADVISE_BEFORE_LOAD: case ADVISE_AFTER_LOAD: case ADVISE_BEFORE_STORE: case ADVISE_BEFORE_CONST_LOAD: } throw new TraceException("unimplemented case"); } public static void main(String[] args) { QueryAnalysis.main(args); } }