/* * Copyright (c) 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.sun.max.ins.debug.vmlog; import java.awt.*; import java.util.*; import java.util.List; import com.sun.max.ins.*; import com.sun.max.ins.gui.*; import com.sun.max.program.*; import com.sun.max.tele.debug.*; import com.sun.max.tele.object.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.log.*; import com.sun.max.vm.log.hosted.VMLogHosted.HostedLogRecord; abstract class VMLogElementsTableModel extends InspectorTableModel { private class ColumnRenderers { Component[] renderers = new Component[VMLogColumnKind.values().length]; } /** * Cache of the (logical) buffer of log records in the target VM. * Logical in the sense that per-thread log buffers are reconstituted into * a single, ordered, log in the Inspector. * * Although most VM implementations use a circular buffer and eventually * overwrite records we keep them all. (At some point may add a capability * to flush old records). * * Note that overwritten records may not be seen if the time between entries to the * Inspector is sufficiently long that the circular buffer wraps. * This could be addressed by a hidden breakpoint that was triggered * appropriately. * */ protected List<TeleHostedLogRecord> logRecordCache; /** * During {@link #refresh}, this holds the new value of the {@link VMLog} {@code nextID} field, * which is the id of the next record that will be written. */ private int nextId; /** * After {@link #refresh}, this is set equal to {@link #nextId}, allowing * optimization on a subsequent refresh where nothing changed. */ private int lastNextId; private final TeleVMLog teleVMLog; private int[] displayedRows; private ArrayList<ColumnRenderers> tableRenderers; protected VMLogElementsTableModel(Inspection inspection, TeleVMLog teleVMLog) { super(inspection); this.teleVMLog = teleVMLog; logRecordCache = new ArrayList<TeleHostedLogRecord>(teleVMLog.logEntries()); tableRenderers = new ArrayList<ColumnRenderers>(teleVMLog.logEntries()); } public int getColumnCount() { return VMLogColumnKind.values().length; } /** * The number of log records in the view. */ public int getRowCount() { return displayedRows == null ? logRecordCache.size() : displayedRows.length; } public void setDisplayedRows(int[] displayedRows) { this.displayedRows = displayedRows; this.fireTableDataChanged(); } private int displayed2ModelRow(int displayedRow) { return displayedRows == null ? displayedRow : displayedRows[displayedRow]; } TeleHostedLogRecord getRecord(int row) { return logRecordCache.get(displayed2ModelRow(row)); } private ColumnRenderers getColumnRenderers(int row) { row = displayed2ModelRow(row); int size = tableRenderers.size(); if (row >= size) { for (int r = size; r <= row; r++) { tableRenderers.add(new ColumnRenderers()); } } return tableRenderers.get(row); } Component getRenderer(int row, int column) { ColumnRenderers cr = getColumnRenderers(row); return cr.renderers[column]; } void setRenderer(int row, int column, Component renderer) { ColumnRenderers cr = getColumnRenderers(row); cr.renderers[column] = renderer; } /** * Get the value of the slot in the log buffer at the given logical row and column. */ public Object getValueAt(int row, int col) { TeleHostedLogRecord record = getRecord(row); if (record == null) { TeleError.unexpected("null log record in LogElementsTableModel.getValueAt"); } Object result = null; int argCount = VMLog.Record.getArgCount(record.getHeader()); switch (VMLogColumnKind.values()[col]) { case ID: result = record.getId(); break; case THREAD: result = VMLog.Record.getThreadId(record.getHeader()); break; case OPERATION: result = VMLog.Record.getOperation(record.getHeader()); break; default: // arguments final int argNum = col2argNum(col); if (argNum <= argCount) { result = record.getArg(argNum); } } return result; } private int col2argNum(int col) { return col - VMLogColumnKind.ARG1.ordinal() + 1; } private int argNum2col(int argNum) { return argNum + VMLogColumnKind.ARG1.ordinal() - 1; } @Override public void refresh() { nextId = teleVMLog.nextID(); if (nextId != lastNextId) { // Some new records. modelSpecificRefresh(); lastNextId = nextId; } super.refresh(); } /** * Is this header value well-formed? * @param header */ boolean wellFormedHeader(int header) { // there are brief periods when a record may not be well formed, // e.g., we have stopped in the Inspector after the log buffer id has been bumped // but before the data has been filled in. // specifically, the logger id may be bogus, which will cause a crash. if (VMLog.Record.isFree(header)) { return false; } int loggerId = VMLog.Record.getLoggerId(header); if (teleVMLog.getLogger(loggerId) == null) { return false; } return true; } /** * Responsible for any model-specific refresh before the main refresh happens. * E.g., collecting together the thread-specific records in {@link VMLogNativeElementsTableModel per-thread buffer model}. */ protected void modelSpecificRefresh() { int id = firstId(); while (id < nextId) { logRecordCache.add(getRecordFromVM(id)); id++; } } /** * Return the first id to start gathering new records from. * Default assumes contiguous id range, i.e. global, shared, buffer, handling case * where some records have been overwritten. */ protected int firstId() { if (nextId - lastNextId > teleVMLog.logEntries()) { // missed some records return nextId - teleVMLog.logEntries(); } else { // pick up where we left off return lastNextId; } } /** * Create a {@link HostedLogRecord} from the target VM record. * @param id the id of the record (N.B. may not be stored in target). */ protected abstract TeleHostedLogRecord getRecordFromVM(int id); /** * Refresh (create) the state from a list of records gathered offline. * @param records */ protected abstract void offLineRefresh(ArrayList<String> records); /** * Refreshes the display of every renderer in a column displaying a specified * log argument number. * * @param argNum the log argument number whose column is to be refreshed * @param force whether the refresh should override any caching. */ public void refreshColumnRenderers(int argNum, boolean force) { final int rowCount = getRowCount(); for (int row = 0; row < rowCount; row++) { final ColumnRenderers columnRenderers = getColumnRenderers(row); final Component component = columnRenderers.renderers[argNum2col(argNum)]; if (component instanceof Prober) { final Prober prober = (Prober) component; prober.refresh(force); } } } /** * Forces a redisplay of every render in a column displaying a specified log argument number. * * @param argNum the log argument number whose column is to be redisplayed. */ public void redisplayColumnRenderers(int argNum) { final int rowCount = getRowCount(); for (int row = 0; row < rowCount; row++) { final ColumnRenderers columnRenderers = getColumnRenderers(row); final Component component = columnRenderers.renderers[argNum2col(argNum)]; if (component instanceof Prober) { final Prober prober = (Prober) component; prober.redisplay(); } } } // Support for offline (file) log processing protected static Map<Integer, String> offLineThreadMap = new HashMap<Integer, String>(); /** * Gets the name of a thread gathered by {@link #offLineRefresh(ArrayList)} from the id. */ protected String offLineThreadName(int threadId) { return offLineThreadMap.get(threadId); } /** * Process embedded records defining thread name/ids. * @param records * @return array of {@code TeleHostedLogRecord} */ protected TeleHostedLogRecord[] processThreadIds(ArrayList<String> records) { int count = 0; for (String record : records) { if (record.startsWith(VMLog.RawDumpFlusher.THREAD_MARKER)) { continue; } count++; } TeleHostedLogRecord[] result = new TeleHostedLogRecord[count]; count = 0; for (String record : records) { if (record.startsWith(VMLog.RawDumpFlusher.THREAD_MARKER)) { // ...: name[id=N] int colonX = record.indexOf(':'); int bracketX = record.indexOf('['); int eqX = record.lastIndexOf('='); String name = record.substring(colonX + 1, bracketX); int id = Integer.parseInt(record.substring(eqX + 1, record.length() - 1)); offLineThreadMap.put(id, name); continue; } result[count++] = parseRecord(record); } return result; } protected TeleHostedLogRecord parseRecord(String record) { String[] parts = record.split(" "); int header = Integer.parseInt(parts[0]); int uuid = Integer.parseInt(parts[1]); int argc = Integer.parseInt(parts[2]); Word[] args = new Word[argc]; for (int i = 0; i < argc; i++) { args[i] = parseWord(parts[i + 3]); } return new TeleHostedLogRecord(uuid, header, args); } /** * Parse a hex word value in a log records file. * @param s a string in the file describing a hex word * @return the value of the string as a Word type */ protected static Word parseWord(String s) { int ix = 0; if (s.startsWith("0x")) { ix = 2; } long value = 0; for (int i = ix; i < s.length(); i++) { char ch = s.charAt(i); int chv = 0; if ('a' <= ch && ch <= 'f') { chv = (ch - 'a') + 10; } else if ('0' <= ch && ch <= '9') { chv = ch - '0'; } else { ProgramError.check(false, "malformed value in log entry"); } value = (value << 4) | (chv & 0xf); } return Address.fromLong(value); } }