| /* |
| * Copyright (c) 2004, 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 sun.management.counter.Units; |
| import sun.management.counter.Variability; |
| 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. |
| * <p> |
| * The structure of the 2.0 entry is defined in struct PerfDataEnry |
| * as decsribed in perfMemory.hpp. This structure looks like: |
| * <pre> |
| * 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; |
| * </pre> |
| * |
| * @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<Monitor> insertedMonitors; |
| |
| /** |
| * Construct a PerfDataBuffer instance. |
| * <p> |
| * 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<String, Monitor> 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<Monitor>(map.values()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected void getNewMonitors(Map<String, Monitor> 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<String, Monitor> 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<Monitor>(); |
| return new MonitorStatus(inserted, removed); |
| } |
| |
| /** |
| * Build the pseudo monitors used to map the prolog data into counters. |
| */ |
| protected void buildPseudoMonitors(Map<String, Monitor> 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<String, Monitor> map, int lvmid) { |
| if (DEBUG) { |
| Set<String> 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); |
| } |
| } |
| } |