blob: 10220861594d61b4f1256305ad0a4165c6bc8966 [file] [log] [blame]
/*
* 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);
}
}
}