/* * Copyright (c) 2004, 2010, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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 sun.jvmstat.perfdata.monitor.v2_0; import sun.jvmstat.monitor.*; import sun.jvmstat.perfdata.monitor.*; import java.util.*; import java.util.regex.*; import java.nio.*; /** * The concrete implementation of version 2.0 of the HotSpot PerfData * Instrumentation buffer. This class is responsible for parsing the * instrumentation memory and constructing the necessary objects to * represent and access the instrumentation objects contained in the * memory buffer. *

* The structure of the 2.0 entry is defined in struct PerfDataEnry * as decsribed in perfMemory.hpp. This structure looks like: *

 * typedef struct {
 *   jint entry_length;         // entry length in bytes
 *   jint name_offset;          // offset to entry name, relative to start
 *                              // of entry
 *   jint vector_length;        // length of the vector. If 0, then scalar.
 *   jbyte data_type;           // JNI field descriptor type
 *   jbyte flags;               // miscellaneous attribute flags
 *                              // 0x01 - supported
 *   jbyte data_units;          // unit of measure attribute
 *   jbyte data_variability;    // variability attribute
 *   jbyte data_offset;         // offset to data item, relative to start
 *                              // of entry.
 * } PerfDataEntry;
 * 
* * @author Brian Doherty * @since 1.5 * @see AbstractPerfDataBuffer */ public class PerfDataBuffer extends PerfDataBufferImpl { private static final boolean DEBUG = false; private static final int syncWaitMs = Integer.getInteger("sun.jvmstat.perdata.syncWaitMs", 5000); private static final ArrayList EMPTY_LIST = new ArrayList(0); /* * These are primarily for documentary purposes and the match up * with the PerfDataEntry structure in perfMemory.hpp. They are * generally unused in this code, but they are kept consistent with * the data structure just in case some unforseen need arrises. */ private final static int PERFDATA_ENTRYLENGTH_OFFSET=0; private final static int PERFDATA_ENTRYLENGTH_SIZE=4; // sizeof(int) private final static int PERFDATA_NAMEOFFSET_OFFSET=4; private final static int PERFDATA_NAMEOFFSET_SIZE=4; // sizeof(int) private final static int PERFDATA_VECTORLENGTH_OFFSET=8; private final static int PERFDATA_VECTORLENGTH_SIZE=4; // sizeof(int) private final static int PERFDATA_DATATYPE_OFFSET=12; private final static int PERFDATA_DATATYPE_SIZE=1; // sizeof(byte) private final static int PERFDATA_FLAGS_OFFSET=13; private final static int PERFDATA_FLAGS_SIZE=1; // sizeof(byte) private final static int PERFDATA_DATAUNITS_OFFSET=14; private final static int PERFDATA_DATAUNITS_SIZE=1; // sizeof(byte) private final static int PERFDATA_DATAVAR_OFFSET=15; private final static int PERFDATA_DATAVAR_SIZE=1; // sizeof(byte) private final static int PERFDATA_DATAOFFSET_OFFSET=16; private final static int PERFDATA_DATAOFFSET_SIZE=4; // sizeof(int) PerfDataBufferPrologue prologue; int nextEntry; long lastNumEntries; IntegerMonitor overflow; ArrayList insertedMonitors; /** * Construct a PerfDataBuffer instance. *

* This class is dynamically loaded by * {@link AbstractPerfDataBuffer#createPerfDataBuffer}, and this * constructor is called to instantiate the instance. * * @param buffer the buffer containing the instrumentation data * @param lvmid the Local Java Virtual Machine Identifier for this * instrumentation buffer. */ public PerfDataBuffer(ByteBuffer buffer, int lvmid) throws MonitorException { super(buffer, lvmid); prologue = new PerfDataBufferPrologue(buffer); this.buffer.order(prologue.getByteOrder()); } /** * {@inheritDoc} */ protected void buildMonitorMap(Map map) throws MonitorException { assert Thread.holdsLock(this); // start at the beginning of the buffer buffer.rewind(); // create pseudo monitors buildPseudoMonitors(map); // wait for the target JVM to indicate that it's intrumentation // buffer is safely accessible synchWithTarget(); // parse the currently defined entries starting at the first entry. nextEntry = prologue.getEntryOffset(); // record the number of entries before parsing the structure int numEntries = prologue.getNumEntries(); // start parsing Monitor monitor = getNextMonitorEntry(); while (monitor != null) { map.put(monitor.getName(), monitor); monitor = getNextMonitorEntry(); } /* * keep track of the current number of entries in the shared * memory for new entry detection purposes. It's possible for * the data structure to be modified while the Map is being * built and the entry count in the header might change while * we are parsing it. The map will contain all the counters * found, but the number recorded in numEntries might be small * than what than the number we actually parsed (due to asynchronous * updates). This discrepency is handled by ignoring any re-parsed * entries when updating the Map in getNewMonitors(). */ lastNumEntries = numEntries; // keep track of the monitors just added. insertedMonitors = new ArrayList(map.values()); } /** * {@inheritDoc} */ protected void getNewMonitors(Map map) throws MonitorException { assert Thread.holdsLock(this); int numEntries = prologue.getNumEntries(); if (numEntries > lastNumEntries) { lastNumEntries = numEntries; Monitor monitor = getNextMonitorEntry(); while (monitor != null) { String name = monitor.getName(); // guard against re-parsed entries if (!map.containsKey(name)) { map.put(name, monitor); if (insertedMonitors != null) { insertedMonitors.add(monitor); } } monitor = getNextMonitorEntry(); } } } /** * {@inheritDoc} */ protected MonitorStatus getMonitorStatus(Map map) throws MonitorException { assert Thread.holdsLock(this); assert insertedMonitors != null; // load any new monitors getNewMonitors(map); // current implementation doesn't support deletion of reuse of entries ArrayList removed = EMPTY_LIST; ArrayList inserted = insertedMonitors; insertedMonitors = new ArrayList(); return new MonitorStatus(inserted, removed); } /** * Build the pseudo monitors used to map the prolog data into counters. */ protected void buildPseudoMonitors(Map map) { Monitor monitor = null; String name = null; IntBuffer ib = null; name = PerfDataBufferPrologue.PERFDATA_MAJOR_NAME; ib = prologue.majorVersionBuffer(); monitor = new PerfIntegerMonitor(name, Units.NONE, Variability.CONSTANT, false, ib); map.put(name, monitor); name = PerfDataBufferPrologue.PERFDATA_MINOR_NAME; ib = prologue.minorVersionBuffer(); monitor = new PerfIntegerMonitor(name, Units.NONE, Variability.CONSTANT, false, ib); map.put(name, monitor); name = PerfDataBufferPrologue.PERFDATA_BUFFER_SIZE_NAME; ib = prologue.sizeBuffer(); monitor = new PerfIntegerMonitor(name, Units.BYTES, Variability.MONOTONIC, false, ib); map.put(name, monitor); name = PerfDataBufferPrologue.PERFDATA_BUFFER_USED_NAME; ib = prologue.usedBuffer(); monitor = new PerfIntegerMonitor(name, Units.BYTES, Variability.MONOTONIC, false, ib); map.put(name, monitor); name = PerfDataBufferPrologue.PERFDATA_OVERFLOW_NAME; ib = prologue.overflowBuffer(); monitor = new PerfIntegerMonitor(name, Units.BYTES, Variability.MONOTONIC, false, ib); map.put(name, monitor); this.overflow = (IntegerMonitor)monitor; name = PerfDataBufferPrologue.PERFDATA_MODTIMESTAMP_NAME; LongBuffer lb = prologue.modificationTimeStampBuffer(); monitor = new PerfLongMonitor(name, Units.TICKS, Variability.MONOTONIC, false, lb); map.put(name, monitor); } /** * Method that waits until the target jvm indicates that * its shared memory is safe to access. */ protected void synchWithTarget() throws MonitorException { /* * synch must happen with syncWaitMs from now. Default is 5 seconds, * which is reasonabally generous and should provide for extreme * situations like startup delays due to allocation of large ISM heaps. */ long timeLimit = System.currentTimeMillis() + syncWaitMs; // loop waiting for the accessible indicater to be non-zero log("synchWithTarget: " + lvmid + " "); while (!prologue.isAccessible()) { log("."); // give the target jvm a chance to complete initializatoin try { Thread.sleep(20); } catch (InterruptedException e) { } if (System.currentTimeMillis() > timeLimit) { logln("failed: " + lvmid); throw new MonitorException("Could not synchronize with target"); } } logln("success: " + lvmid); } /** * method to extract the next monitor entry from the instrumentation memory. * assumes that nextEntry is the offset into the byte array * at which to start the search for the next entry. method leaves * next entry pointing to the next entry or to the end of data. */ protected Monitor getNextMonitorEntry() throws MonitorException { Monitor monitor = null; // entries are always 4 byte aligned. if ((nextEntry % 4) != 0) { throw new MonitorStructureException( "Misaligned entry index: " + Integer.toHexString(nextEntry)); } // protect againt a corrupted shard memory region. if ((nextEntry < 0) || (nextEntry > buffer.limit())) { throw new MonitorStructureException( "Entry index out of bounds: " + Integer.toHexString(nextEntry) + ", limit = " + Integer.toHexString(buffer.limit())); } // check for end of the buffer if (nextEntry == buffer.limit()) { logln("getNextMonitorEntry():" + " nextEntry == buffer.limit(): returning"); return null; } buffer.position(nextEntry); int entryStart = buffer.position(); int entryLength = buffer.getInt(); // check for valid entry length if ((entryLength < 0) || (entryLength > buffer.limit())) { throw new MonitorStructureException( "Invalid entry length: entryLength = " + entryLength + " (0x" + Integer.toHexString(entryLength) + ")"); } // check if last entry occurs before the eof. if ((entryStart + entryLength) > buffer.limit()) { throw new MonitorStructureException( "Entry extends beyond end of buffer: " + " entryStart = 0x" + Integer.toHexString(entryStart) + " entryLength = 0x" + Integer.toHexString(entryLength) + " buffer limit = 0x" + Integer.toHexString(buffer.limit())); } if (entryLength == 0) { // end of data return null; } // we can safely read this entry int nameOffset = buffer.getInt(); int vectorLength = buffer.getInt(); byte typeCodeByte = buffer.get(); byte flags = buffer.get(); byte unitsByte = buffer.get(); byte varByte = buffer.get(); int dataOffset = buffer.getInt(); dump_entry_fixed(entryStart, nameOffset, vectorLength, typeCodeByte, flags, unitsByte, varByte, dataOffset); // convert common attributes to their object types Units units = Units.toUnits(unitsByte); Variability variability = Variability.toVariability(varByte); TypeCode typeCode = null; boolean supported = (flags & 0x01) != 0; try { typeCode = TypeCode.toTypeCode(typeCodeByte); } catch (IllegalArgumentException e) { throw new MonitorStructureException( "Illegal type code encountered:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", type_code = " + Integer.toHexString(typeCodeByte)); } // verify that the name_offset is contained within the entry bounds if (nameOffset > entryLength) { throw new MonitorStructureException( "Field extends beyond entry bounds" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", name_offset = 0x" + Integer.toHexString(nameOffset)); } // verify that the data_offset is contained within the entry bounds if (dataOffset > entryLength) { throw new MonitorStructureException( "Field extends beyond entry bounds:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", data_offset = 0x" + Integer.toHexString(dataOffset)); } // validate the variability and units fields if (variability == Variability.INVALID) { throw new MonitorDataException( "Invalid variability attribute:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", variability = 0x" + Integer.toHexString(varByte)); } if (units == Units.INVALID) { throw new MonitorDataException( "Invalid units attribute: entry_offset = 0x" + Integer.toHexString(nextEntry) + ", units = 0x" + Integer.toHexString(unitsByte)); } // the entry looks good - parse the variable length components /* * The name starts at nameOffset and continues up to the first null * byte. however, we don't know the length, but we can approximate it * without searching for the null by using the offset for the data * field, which follows the name field. */ assert (buffer.position() == (entryStart + nameOffset)); assert (dataOffset > nameOffset); // include possible pad space int maxNameLength = dataOffset-nameOffset; // maxNameLength better be less than the total entry length assert (maxNameLength < entryLength); // collect the characters, but do not collect the null byte, // as the String(byte[]) constructor does not ignore it! byte[] nameBytes = new byte[maxNameLength]; int nameLength = 0; byte b; while (((b = buffer.get()) != 0) && (nameLength < maxNameLength)) { nameBytes[nameLength++] = b; } assert (nameLength < maxNameLength); // we should before or at the start of the data field assert (buffer.position() <= (entryStart + dataOffset)); // convert the name bytes into a String String name = new String(nameBytes, 0, nameLength); /* * compute the size of the data item - this includes pad * characters used to align the next entry. */ int dataSize = entryLength - dataOffset; // set the position to the start of the data item buffer.position(entryStart + dataOffset); dump_entry_variable(name, buffer, dataSize); if (vectorLength == 0) { // create a scalar Monitor object if (typeCode == TypeCode.LONG) { LongBuffer lb = buffer.asLongBuffer(); lb.limit(1); // limit buffer size to one long value. monitor = new PerfLongMonitor(name, units, variability, supported, lb); } else { /* * unexpected type code - coding error or uncoordinated * JVM change */ throw new MonitorTypeException( "Unexpected type code encountered:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", name = " + name + ", type_code = " + typeCode + " (0x" + Integer.toHexString(typeCodeByte) + ")"); } } else { // create a vector Monitor object if (typeCode == TypeCode.BYTE) { if (units != Units.STRING) { // only byte arrays of type STRING are currently supported throw new MonitorTypeException( "Unexpected vector type encounterd:" + " entry_offset = " + Integer.toHexString(nextEntry) + ", name = " + name + ", type_code = " + typeCode + " (0x" + Integer.toHexString(typeCodeByte) + ")" + ", units = " + units + " (0x" + Integer.toHexString(unitsByte) + ")"); } ByteBuffer bb = buffer.slice(); bb.limit(vectorLength); // limit buffer length to # of chars if (variability == Variability.CONSTANT) { monitor = new PerfStringConstantMonitor(name, supported, bb); } else if (variability == Variability.VARIABLE) { monitor = new PerfStringVariableMonitor(name, supported, bb, vectorLength-1); } else if (variability == Variability.MONOTONIC) { // Monotonically increasing byte arrays are not supported throw new MonitorDataException( "Unexpected variability attribute:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + " name = " + name + ", variability = " + variability + " (0x" + Integer.toHexString(varByte) + ")"); } else { // variability was validated above, so this unexpected assert false; } } else { // coding error or uncoordinated JVM change throw new MonitorTypeException( "Unexpected type code encountered:" + " entry_offset = 0x" + Integer.toHexString(nextEntry) + ", name = " + name + ", type_code = " + typeCode + " (0x" + Integer.toHexString(typeCodeByte) + ")"); } } // setup index to next entry for next iteration of the loop. nextEntry = entryStart + entryLength; return monitor; } /** * Method to dump debugging information */ private void dumpAll(Map map, int lvmid) { if (DEBUG) { Set keys = map.keySet(); System.err.println("Dump for " + lvmid); int j = 0; for (Iterator i = keys.iterator(); i.hasNext(); j++) { Monitor monitor = map.get(i.next()); System.err.println(j + "\t" + monitor.getName() + "=" + monitor.getValue()); } System.err.println("nextEntry = " + nextEntry); System.err.println("Buffer info:"); System.err.println("buffer = " + buffer); } } /** * Method to dump the fixed portion of an entry. */ private void dump_entry_fixed(int entry_start, int nameOffset, int vectorLength, byte typeCodeByte, byte flags, byte unitsByte, byte varByte, int dataOffset) { if (DEBUG) { System.err.println("Entry at offset: 0x" + Integer.toHexString(entry_start)); System.err.println("\tname_offset = 0x" + Integer.toHexString(nameOffset)); System.err.println("\tvector_length = 0x" + Integer.toHexString(vectorLength)); System.err.println("\tdata_type = 0x" + Integer.toHexString(typeCodeByte)); System.err.println("\tflags = 0x" + Integer.toHexString(flags)); System.err.println("\tdata_units = 0x" + Integer.toHexString(unitsByte)); System.err.println("\tdata_variability = 0x" + Integer.toHexString(varByte)); System.err.println("\tdata_offset = 0x" + Integer.toHexString(dataOffset)); } } private void dump_entry_variable(String name, ByteBuffer bb, int size) { if (DEBUG) { char[] toHex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; ByteBuffer data = bb.slice(); data.limit(size); System.err.println("\tname = " + name); System.err.println("\tdata = "); int count=0; while (data.hasRemaining()) { byte b = data.get(); byte high = (byte)((b >> 8) & 0x0f); byte low = (byte)(b & 0x0f); if (count % 16 == 0) { System.err.print("\t\t" + Integer.toHexString(count / 16) + ": "); } System.err.print(String.valueOf(toHex[high]) + String.valueOf(toHex[low])); count++; if (count % 16 == 0) { System.err.println(); } else { System.err.print(" "); } } if (count % 16 != 0) { System.err.println(); } } } private void logln(String s) { if (DEBUG) { System.err.println(s); } } private void log(String s) { if (DEBUG) { System.err.print(s); } } }