/* * 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.awt.event.*; import java.io.*; import java.lang.reflect.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; import com.sun.max.ins.*; import com.sun.max.ins.debug.*; import com.sun.max.ins.gui.*; import com.sun.max.ins.gui.TableColumnVisibilityPreferences.TableColumnViewPreferenceListener; import com.sun.max.ins.value.*; import com.sun.max.ins.view.*; import com.sun.max.ins.view.InspectionViews.ViewKind; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.object.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.log.*; import com.sun.max.vm.reference.*; import com.sun.max.vm.thread.*; /** * A custom singleton viewer for VM log records. * Essentially a hybrid array/object viewer. * * This code supports the several implementations of {@link VMLog}. * The main distinctions are: * <ul> * <ol>Java implementation, Record objects stored in an array. * <ol>Native implementation, C-like records stored in a shared (global) native buffer. * <ol>Native implementation, C-like records stored in a per-thread native buffer, accessed via {@link VmThreadLocal}. * </ul> * Variant 3 requires the global view to be reconstituted from the various threads in the target. */ @SuppressWarnings("unused") public class VMLogView extends AbstractView<VMLogView> implements TableColumnViewPreferenceListener { private static final ViewKind VIEW_KIND = ViewKind.VMLOG; private static final String SHORT_NAME = "VM Log"; private static final String LONG_NAME = "VM Log View"; private static final String GEOMETRY_SETTINGS_KEY = "vmLogViewGeometry"; public static final class VMLogViewManager extends AbstractSingletonViewManager<VMLogView> { protected VMLogViewManager(Inspection inspection) { super(inspection, VIEW_KIND, SHORT_NAME, LONG_NAME); } @Override protected VMLogView createView(Inspection inspection) { return new VMLogView(inspection); } } private void readLogFile(File file) { final ArrayList<String> records = new ArrayList<String>(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); String logClassMarker = null; while (true) { String line = reader.readLine(); if (line == null) { break; } if (line.length() == 0) { continue; } if (line.startsWith(VMLog.RawDumpFlusher.LOGCLASS_MARKER)) { if (logClassMarker == null) { logClassMarker = line; } continue; } records.add(line); } // Check that logClassName matches that discovered in the image. checkLogClassMatch(logClassMarker); tableModel.offLineRefresh(records); table.refresh(true); } catch (IOException ex) { System.err.println("failed to read log file: " + file + ": " + ex); System.exit(1); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { } } } } private void checkLogClassMatch(String logClassMarker) { String logClassName = logClassMarker.substring(VMLog.RawDumpFlusher.LOGCLASS_MARKER.length()); String imageLogClassName = tableModel.getClass().getSimpleName(); if (!imageLogClassName.startsWith(logClassName)) { System.err.println("VMLog class in boot image: " + imageLogClassName + " does not match log file: " + logClassName); System.exit(1); } } // Will be non-null before any instances created. private static VMLogViewManager viewManager = null; public static VMLogViewManager makeViewManager(Inspection inspection) { if (viewManager == null) { viewManager = new VMLogViewManager(inspection); } return viewManager; } private InspectorPanel contentPane; private final LogViewPreferences viewPreferences; private VMLogElementsTable table; private VMLogElementsTableModel tableModel; private TableRowFilterToolBar filterToolBar = null; private JCheckBoxMenuItem showFilterCheckboxMenuItem; private int[] filterMatchingRows = null; private static Component emptyStringRenderer; private final TeleVMLog vmLog; @SuppressWarnings("unchecked") VMLogView(Inspection inspection) { super(inspection, VIEW_KIND, GEOMETRY_SETTINGS_KEY); vmLog = inspection.vm().vmLog(); emptyStringRenderer = new PlainLabel(inspection, ""); viewPreferences = LogViewPreferences.globalPreferences(inspection()); viewPreferences.addListener(this); showFilterCheckboxMenuItem = new InspectorCheckBox(inspection, "Filter view", "Show Filter Field", false); showFilterCheckboxMenuItem.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { final JCheckBoxMenuItem checkBoxMenuItem = (JCheckBoxMenuItem) e.getSource(); if (checkBoxMenuItem.isSelected()) { openFilter(); } else { closeFilter(); } } }); createFrame(true); } private final RowMatchListener rowMatchListener = new RowMatchListener() { public void setSearchResult(int[] result) { filterMatchingRows = result; table.setDisplayedRows(filterMatchingRows); //System.out.println("Match=" + Arrays.toString(filterMatchingRows)); } public void closeRequested() { closeFilter(); showFilterCheckboxMenuItem.setState(false); } }; private void openFilter() { if (filterToolBar == null) { filterToolBar = new TableRowFilterToolBar(inspection(), rowMatchListener, table); contentPane.add(filterToolBar, BorderLayout.NORTH); validate(); filterToolBar.getFocus(); } } private void closeFilter() { if (filterToolBar != null) { contentPane.remove(filterToolBar); table.setDisplayedRows(null); validate(); filterToolBar = null; filterMatchingRows = null; } } @Override public String getTextForTitle() { return viewManager.shortName(); } @Override protected void createViewContent() { table = new VMLogElementsTable(inspection(), this); final InspectorScrollPane vmLogViewScrollPane = new InspectorScrollPane(inspection(), table); contentPane = new InspectorPanel(inspection(), new BorderLayout()); contentPane.add(vmLogViewScrollPane, BorderLayout.CENTER); setContentPane(contentPane); // Populate menu bar makeMenu(MenuKind.DEFAULT_MENU).add(defaultMenuItems(MenuKind.DEFAULT_MENU)); final InspectorMenu viewMenu = makeMenu(MenuKind.VIEW_MENU); viewMenu.add(showFilterCheckboxMenuItem); viewMenu.addSeparator(); viewMenu.add(defaultMenuItems(MenuKind.VIEW_MENU)); if (inspection().vm().inspectionMode() == MaxInspectionMode.IMAGE) { File vmLogFile = inspection().vm().vmLogFile(); if (vmLogFile != null) { readLogFile(vmLogFile); } } } @Override protected void refreshState(boolean force) { if (inspection().hasProcess()) { table.refresh(force); } if (filterToolBar != null) { filterToolBar.refresh(force); } } @Override protected InspectorTable getTable() { return table; } @Override public InspectorAction getViewOptionsAction() { return new InspectorAction(inspection(), "View Options") { @Override public void procedure() { new TableColumnVisibilityPreferences.ColumnPreferencesDialog<VMLogColumnKind>(inspection(), viewManager.shortName() + " View Options", viewPreferences); } }; } public void tableColumnViewPreferencesChanged() { reconstructView(); } /** * @return the log being viewed */ public TeleVMLog vmLog() { return vmLog; } private static class VMLogElementsTable extends InspectorTable { private VMLogView vmLogView; VMLogElementsTable(Inspection inspection, VMLogView vmLogView) { super(inspection); this.vmLogView = vmLogView; String vmLogClassName = vmLogView.vmLog().classActorForObjectType().simpleName(); try { Class<?> klass = Class.forName(VMLogView.class.getPackage().getName() + "." + vmLogClassName + "ElementsTableModel"); Constructor<?> cons = klass.getDeclaredConstructor(Inspection.class, TeleVMLog.class); vmLogView.tableModel = (VMLogElementsTableModel) cons.newInstance(inspection, vmLogView.vmLog); } catch (Exception ex) { TeleError.unexpected("Exception instantiating VMLog subclass: " + vmLogClassName, ex); } VMLogColumnModel columnModel = new VMLogColumnModel(inspection, vmLogView); configureDefaultTable(vmLogView.tableModel, columnModel); } /** * Sets a display filter that will cause only the specified rows * to be displayed. * * @param displayedRows the rows to be displayed, sorted in ascending order, null if all should be displayed. */ public void setDisplayedRows(int[] displayedRows) { vmLogView.tableModel.setDisplayedRows(displayedRows); } public InspectorView getView() { return vmLogView; } } private static class VMLogColumnModel extends InspectorTableColumnModel<VMLogColumnKind> { private VMLogColumnModel(Inspection inspection, VMLogView vmLogView) { super(inspection, VMLogColumnKind.values().length, vmLogView.viewPreferences); addColumnIfSupported(VMLogColumnKind.ID, new IdCellRenderer(inspection, vmLogView), null); addColumnIfSupported(VMLogColumnKind.THREAD, new ThreadCellRenderer(inspection, vmLogView), null); addColumnIfSupported(VMLogColumnKind.OPERATION, new OperationCellRenderer(inspection, vmLogView), null); addColumnIfSupported(VMLogColumnKind.ARG1, new ArgCellRenderer(inspection, vmLogView, 1), null); addColumnIfSupported(VMLogColumnKind.ARG2, new ArgCellRenderer(inspection, vmLogView, 2), null); addColumnIfSupported(VMLogColumnKind.ARG3, new ArgCellRenderer(inspection, vmLogView, 3), null); addColumnIfSupported(VMLogColumnKind.ARG4, new ArgCellRenderer(inspection, vmLogView, 4), null); addColumnIfSupported(VMLogColumnKind.ARG5, new ArgCellRenderer(inspection, vmLogView, 5), null); addColumnIfSupported(VMLogColumnKind.ARG6, new ArgCellRenderer(inspection, vmLogView, 6), null); addColumnIfSupported(VMLogColumnKind.ARG7, new ArgCellRenderer(inspection, vmLogView, 7), null); addColumnIfSupported(VMLogColumnKind.ARG8, new ArgCellRenderer(inspection, vmLogView, 8), null); } } public static class LogViewPreferences extends TableColumnVisibilityPreferences<VMLogColumnKind> { private static LogViewPreferences globalPreferences; /** * @return the global, persistent set of user preferences for viewing a table of Log. */ static LogViewPreferences globalPreferences(Inspection inspection) { if (globalPreferences == null) { globalPreferences = new LogViewPreferences(inspection); } return globalPreferences; } // Prefix for all persistent column preferences in view private static final String Log_COLUMN_PREFERENCE = "LogViewColumn"; /** * @return a GUI panel suitable for setting global preferences for this kind of view. */ public static JPanel globalPreferencesPanel(Inspection inspection) { return globalPreferences(inspection).getPanel(); } /** * Creates a set of preferences specified for use by singleton instances, where local and * persistent global choices are identical. */ private LogViewPreferences(Inspection inspection) { super(inspection, Log_COLUMN_PREFERENCE, VMLogColumnKind.values()); // There are no view preferences beyond the column choices, so no additional machinery needed here. } } private static abstract class CellRendererHelper { protected final VMLogView vmLogView; CellRendererHelper(VMLogView vmLogView) { this.vmLogView = vmLogView; } public Component getRenderer(Object value, int row, int column) { if (value == null) { return vmLogView.gui().getUnavailableDataTableCellRenderer(); } return vmLogView.tableModel.getRenderer(row, column); } } private static class IdCellRenderer extends CellRendererHelper implements TableCellRenderer { private IdCellRenderer(Inspection inspection, VMLogView vmLogView) { super(vmLogView); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component renderer = super.getRenderer(value, row, column); if (renderer == null) { WordValueLabel wvl = new WordValueLabel(vmLogView.inspection(), WordValueLabel.ValueMode.WORD, vmLogView.table); int id = (Integer) value; wvl.setText(Integer.toString(id)); renderer = wvl; vmLogView.tableModel.setRenderer(row, column, wvl); } return renderer; } } static class ThreadCellRenderer extends CellRendererHelper implements TableCellRenderer { private static Map<Integer, Component> threadRenderers = new HashMap<Integer, Component>(); private static ThreadCellRenderer singleton; private ThreadCellRenderer(Inspection inspection, VMLogView vmLogView) { super(vmLogView); singleton = this; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component renderer = super.getRenderer(value, row, column); if (renderer == null) { int threadID = (Integer) value; renderer = getThreadRenderer(threadID); vmLogView.tableModel.setRenderer(row, column, renderer); } return renderer; } static Component getThreadRenderer(int threadID) { Component renderer = threadRenderers.get(threadID); if (renderer == null) { String name; if (threadID == 0) { name = "primordial"; } else { if (singleton.vmLogView.vm().inspectionMode() == MaxInspectionMode.IMAGE) { // VM log from a file name = singleton.vmLogView.tableModel.offLineThreadName(threadID); if (name == null) { name = "?id=" + Integer.valueOf(threadID); } } else { MaxThread thread = singleton.vmLogView.vm().threadManager().getThread(threadID); if (thread == null) { name = "?id=" + Integer.valueOf(threadID); } else { name = thread.vmThreadName(); } } } renderer = new PlainLabel(singleton.vmLogView.inspection(), name); threadRenderers.put(new Integer(threadID), renderer); } return renderer; } } private static class OperationCellRenderer extends CellRendererHelper implements TableCellRenderer { private Map<Integer, Component> operationRenderers = new HashMap<Integer, Component>(); private OperationCellRenderer(Inspection inspection, VMLogView vmLogView) { super(vmLogView); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component renderer = super.getRenderer(value, row, column); if (renderer == null) { int op = (Integer) value; int header = vmLogView.tableModel.getRecord(row).getHeader(); if (!vmLogView.tableModel.wellFormedHeader(header)) { return vmLogView.gui().getUnavailableDataTableCellRenderer(); } int loggerId = VMLog.Record.getLoggerId(header); int key = loggerId << 16 | op; renderer = operationRenderers.get(key); if (renderer == null) { VMLogger logger = vmLogView.vmLog().getLogger(loggerId); renderer = new PlainLabel(vmLogView.inspection(), logger.name + "." + logger.operationName(op)); operationRenderers.put(key, renderer); } vmLogView.tableModel.setRenderer(row, column, renderer); } return renderer; } } public static class ArgCellRenderer extends CellRendererHelper implements TableCellRenderer, Prober { private int argNum; private ArgCellRenderer(Inspection inspection, VMLogView vmLogView, int argNum) { super(vmLogView); this.argNum = argNum; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // null for an argument means absent if (value == null) { return emptyStringRenderer; } Component renderer = super.getRenderer(value, row, column); if (renderer == null) { int header = vmLogView.tableModel.getRecord(row).getHeader(); if (!vmLogView.tableModel.wellFormedHeader(header)) { return vmLogView.gui().getUnavailableDataTableCellRenderer(); } long argValue = ((Word) value).value; VMLogArgRenderer vmLogArgRenderer = VMLogArgRendererFactory.getArgRenderer(vmLogView.vmLog().getLogger(VMLog.Record.getLoggerId(header)).name, vmLogView); renderer = vmLogArgRenderer.getRenderer(header, argNum, argValue); vmLogView.tableModel.setRenderer(row, column, renderer); } return renderer; } public void refresh(boolean force) { vmLogView.tableModel.refreshColumnRenderers(argNum, force); } public void redisplay() { vmLogView.tableModel.redisplayColumnRenderers(argNum); } } }