/*
* 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.v1_0;
import sun.management.counter.Units;
import sun.management.counter.Variability;
import sun.jvmstat.monitor.*;
import sun.jvmstat.perfdata.monitor.*;
import java.util.*;
import java.util.regex.*;
import java.nio.*;
The concrete implementation of version 1.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.
Author: Brian Doherty See Also: Since: 1.5
/**
* The concrete implementation of version 1.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.
*
* @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);
/*
* the following constants must be kept in sync with struct
* PerfDataEntry in perfMemory.hpp
*/
private final static int PERFDATA_ENTRYLENGTH_OFFSET=0;
private final static int PERFDATA_ENTRYLENGTH_SIZE=4; // sizeof(int)
private final static int PERFDATA_NAMELENGTH_OFFSET=4;
private final static int PERFDATA_NAMELENGTH_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_DATAATTR_OFFSET=15;
private final static int PERFDATA_DATAATTR_SIZE=1; // sizeof(byte)
private final static int PERFDATA_NAME_OFFSET=16;
PerfDataBufferPrologue prologue;
int nextEntry;
int pollForEntry;
int perfDataItem;
long lastModificationTime;
int lastUsed;
IntegerMonitor overflow;
ArrayList<Monitor> insertedMonitors;
Construct a PerfDataBufferImpl instance.
This class is dynamically loaded by AbstractPerfDataBuffer.createPerfDataBuffer
, and this constructor is called to instantiate the instance.
Params: - buffer – the buffer containing the instrumentation data
- lvmid – the Local Java Virtual Machine Identifier for this
instrumentation buffer.
/**
* Construct a PerfDataBufferImpl 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}
/**
* {@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);
// position buffer to start of the data section
buffer.position(prologue.getSize());
nextEntry = buffer.position();
perfDataItem = 0;
int used = prologue.getUsed();
long modificationTime = prologue.getModificationTimeStamp();
Monitor m = getNextMonitorEntry();
while (m != null) {
map.put(m.getName(), m);
m = getNextMonitorEntry();
}
/*
* set the last modification data. These are set to the values
* recorded before parsing the data structure. This allows the
* the data structure to be modified while the Map is being built.
* The Map may contain more entries than indicated based on the
* time stamp, but this is handled by ignoring duplicate entries
* when the Map is updated in getNewMonitors().
*/
lastUsed = used;
lastModificationTime = modificationTime;
// synchronize with the target jvm
synchWithTarget(map);
// work around 1.4.2 counter inititization bugs
kludge(map);
insertedMonitors = new ArrayList<Monitor>(map.values());
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException {
assert Thread.holdsLock(this);
int used = prologue.getUsed();
long modificationTime = prologue.getModificationTimeStamp();
if ((used > lastUsed) || (lastModificationTime > modificationTime)) {
lastUsed = used;
lastModificationTime = modificationTime;
Monitor monitor = getNextMonitorEntry();
while (monitor != null) {
String name = monitor.getName();
// guard against duplicate entries
if (!map.containsKey(name)) {
map.put(name, monitor);
/*
* insertedMonitors is null when called from pollFor()
* via buildMonitorMap(). Since we update insertedMonitors
* at the end of buildMonitorMap(), it's ok to skip the
* add here.
*/
if (insertedMonitors != null) {
insertedMonitors.add(monitor);
}
}
monitor = getNextMonitorEntry();
}
}
}
{@inheritDoc}
/**
* {@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 or 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.
/**
* 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 to provide a gross level of synchronization with the
target monitored jvm.
gross synchronization works by polling for the hotspot.rt.hrt.ticks
counter, which is the last counter created by the StatSampler
initialization code. The counter is updated when the watcher thread
starts scheduling tasks, which is the last thing done in vm
initialization.
/**
* Method to provide a gross level of synchronization with the
* target monitored jvm.
*
* gross synchronization works by polling for the hotspot.rt.hrt.ticks
* counter, which is the last counter created by the StatSampler
* initialization code. The counter is updated when the watcher thread
* starts scheduling tasks, which is the last thing done in vm
* initialization.
*/
protected void synchWithTarget(Map<String, Monitor> map) 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;
String name = "hotspot.rt.hrt.ticks";
LongMonitor ticks = (LongMonitor)pollFor(map, name, timeLimit);
/*
* loop waiting for the ticks counter to be non zero. This is
* an indication that the jvm is initialized.
*/
log("synchWithTarget: " + lvmid + " ");
while (ticks.longValue() == 0) {
log(".");
try { Thread.sleep(20); } catch (InterruptedException e) { }
if (System.currentTimeMillis() > timeLimit) {
lognl("failed: " + lvmid);
throw new MonitorException("Could Not Synchronize with target");
}
}
lognl("success: " + lvmid);
}
Method to poll the instrumentation memory for a counter with
the given name. The polling period is bounded by the timeLimit
argument.
/**
* Method to poll the instrumentation memory for a counter with
* the given name. The polling period is bounded by the timeLimit
* argument.
*/
protected Monitor pollFor(Map<String, Monitor> map, String name, long timeLimit)
throws MonitorException {
Monitor monitor = null;
log("polling for: " + lvmid + "," + name + " ");
pollForEntry = nextEntry;
while ((monitor = map.get(name)) == null) {
log(".");
try { Thread.sleep(20); } catch (InterruptedException e) { }
long t = System.currentTimeMillis();
if ((t > timeLimit) || (overflow.intValue() > 0)) {
lognl("failed: " + lvmid + "," + name);
dumpAll(map, lvmid);
throw new MonitorException("Could not find expected counter");
}
getNewMonitors(map);
}
lognl("success: " + lvmid + "," + name);
return monitor;
}
method to make adjustments for known counter problems. This
method depends on the availability of certain counters, which
is generally guaranteed by the synchWithTarget() method.
/**
* method to make adjustments for known counter problems. This
* method depends on the availability of certain counters, which
* is generally guaranteed by the synchWithTarget() method.
*/
protected void kludge(Map<String, Monitor> map) {
if (Boolean.getBoolean("sun.jvmstat.perfdata.disableKludge")) {
// bypass all kludges
return;
}
String name = "java.vm.version";
StringMonitor jvm_version = (StringMonitor)map.get(name);
if (jvm_version == null) {
jvm_version = (StringMonitor)findByAlias(name);
}
name = "java.vm.name";
StringMonitor jvm_name = (StringMonitor)map.get(name);
if (jvm_name == null) {
jvm_name = (StringMonitor)findByAlias(name);
}
name = "hotspot.vm.args";
StringMonitor args = (StringMonitor)map.get(name);
if (args == null) {
args = (StringMonitor)findByAlias(name);
}
assert ((jvm_name != null) && (jvm_version != null) && (args != null));
if (jvm_name.stringValue().indexOf("HotSpot") >= 0) {
if (jvm_version.stringValue().startsWith("1.4.2")) {
kludgeMantis(map, args);
}
}
}
method to repair the 1.4.2 parallel scavenge counters that are
incorrectly initialized by the JVM when UseAdaptiveSizePolicy
is set. This bug couldn't be fixed for 1.4.2 FCS due to putback
restrictions.
/**
* method to repair the 1.4.2 parallel scavenge counters that are
* incorrectly initialized by the JVM when UseAdaptiveSizePolicy
* is set. This bug couldn't be fixed for 1.4.2 FCS due to putback
* restrictions.
*/
private void kludgeMantis(Map<String, Monitor> map, StringMonitor args) {
/*
* the HotSpot 1.4.2 JVM with the +UseParallelGC option along
* with its default +UseAdaptiveSizePolicy option has a bug with
* the initialization of the sizes of the eden and survivor spaces.
* See bugid 4890736.
*
* note - use explicit 1.4.2 counter names here - don't update
* to latest counter names or attempt to find aliases.
*/
String cname = "hotspot.gc.collector.0.name";
StringMonitor collector = (StringMonitor)map.get(cname);
if (collector.stringValue().compareTo("PSScavenge") == 0) {
boolean adaptiveSizePolicy = true;
/*
* HotSpot processes the -XX:Flags/.hotspotrc arguments prior to
* processing the command line arguments. This allows the command
* line arguments to override any defaults set in .hotspotrc
*/
cname = "hotspot.vm.flags";
StringMonitor flags = (StringMonitor)map.get(cname);
String allArgs = flags.stringValue() + " " + args.stringValue();
/*
* ignore the -XX: prefix as it only applies to the arguments
* passed from the command line (i.e. the invocation api).
* arguments passed through .hotspotrc omit the -XX: prefix.
*/
int ahi = allArgs.lastIndexOf("+AggressiveHeap");
int aspi = allArgs.lastIndexOf("-UseAdaptiveSizePolicy");
if (ahi != -1) {
/*
* +AggressiveHeap was set, check if -UseAdaptiveSizePolicy
* is set after +AggressiveHeap.
*/
//
if ((aspi != -1) && (aspi > ahi)) {
adaptiveSizePolicy = false;
}
} else {
/*
* +AggressiveHeap not set, must be +UseParallelGC. The
* relative position of -UseAdaptiveSizePolicy is not
* important in this case, as it will override the
* UseParallelGC default (+UseAdaptiveSizePolicy) if it
* appears anywhere in the JVM arguments.
*/
if (aspi != -1) {
adaptiveSizePolicy = false;
}
}
if (adaptiveSizePolicy) {
// adjust the buggy AdaptiveSizePolicy size counters.
// first remove the real counters.
String eden_size = "hotspot.gc.generation.0.space.0.size";
String s0_size = "hotspot.gc.generation.0.space.1.size";
String s1_size = "hotspot.gc.generation.0.space.2.size";
map.remove(eden_size);
map.remove(s0_size);
map.remove(s1_size);
// get the maximum new generation size
String new_max_name = "hotspot.gc.generation.0.capacity.max";
LongMonitor new_max = (LongMonitor)map.get(new_max_name);
/*
* replace the real counters with pseudo counters that are
* initialized to to the correct values. The maximum size of
* the eden and survivor spaces are supposed to be:
* max_eden_size = new_size - (2*alignment).
* max_survivor_size = new_size - (2*alignment).
* since we don't know the alignment value used, and because
* of other parallel scavenge bugs that result in oversized
* spaces, we just set the maximum size of each space to the
* full new gen size.
*/
Monitor monitor = null;
LongBuffer lb = LongBuffer.allocate(1);
lb.put(new_max.longValue());
monitor = new PerfLongMonitor(eden_size, Units.BYTES,
Variability.CONSTANT, false, lb);
map.put(eden_size, monitor);
monitor = new PerfLongMonitor(s0_size, Units.BYTES,
Variability.CONSTANT, false, lb);
map.put(s0_size, monitor);
monitor = new PerfLongMonitor(s1_size, Units.BYTES,
Variability.CONSTANT, false, lb);
map.put(s1_size, monitor);
}
}
}
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.
/**
* 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(
"Entry index not properly aligned: " + nextEntry);
}
// protect against a corrupted shared memory region.
if ((nextEntry < 0) || (nextEntry > buffer.limit())) {
throw new MonitorStructureException(
"Entry index out of bounds: nextEntry = " + nextEntry
+ ", limit = " + buffer.limit());
}
// check for the end of the buffer
if (nextEntry == buffer.limit()) {
lognl("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);
}
// check if last entry occurs before the eof.
if ((entryStart + entryLength) > buffer.limit()) {
throw new MonitorStructureException(
"Entry extends beyond end of buffer: "
+ " entryStart = " + entryStart
+ " entryLength = " + entryLength
+ " buffer limit = " + buffer.limit());
}
if (entryLength == 0) {
// end of data
return null;
}
int nameLength = buffer.getInt();
int vectorLength = buffer.getInt();
byte dataType = buffer.get();
byte flags = buffer.get();
Units u = Units.toUnits(buffer.get());
Variability v = Variability.toVariability(buffer.get());
boolean supported = (flags & 0x01) != 0;
// defend against corrupt entries
if ((nameLength <= 0) || (nameLength > entryLength)) {
throw new MonitorStructureException(
"Invalid Monitor name length: " + nameLength);
}
if ((vectorLength < 0) || (vectorLength > entryLength)) {
throw new MonitorStructureException(
"Invalid Monitor vector length: " + vectorLength);
}
// read in the perfData item name, casting bytes to chars. skip the
// null terminator
//
byte[] nameBytes = new byte[nameLength-1];
for (int i = 0; i < nameLength-1; i++) {
nameBytes[i] = buffer.get();
}
// convert name into a String
String name = new String(nameBytes, 0, nameLength-1);
if (v == Variability.INVALID) {
throw new MonitorDataException("Invalid variability attribute:"
+ " entry index = " + perfDataItem
+ " name = " + name);
}
if (u == Units.INVALID) {
throw new MonitorDataException("Invalid units attribute: "
+ " entry index = " + perfDataItem
+ " name = " + name);
}
int offset;
if (vectorLength == 0) {
// scalar Types
if (dataType == BasicType.LONG.intValue()) {
offset = entryStart + entryLength - 8; /* 8 = sizeof(long) */
buffer.position(offset);
LongBuffer lb = buffer.asLongBuffer();
lb.limit(1);
monitor = new PerfLongMonitor(name, u, v, supported, lb);
perfDataItem++;
} else {
// bad data types.
throw new MonitorTypeException("Invalid Monitor type:"
+ " entry index = " + perfDataItem
+ " name = " + name
+ " type = " + dataType);
}
} else {
// vector types
if (dataType == BasicType.BYTE.intValue()) {
if (u != Units.STRING) {
// only byte arrays of type STRING are currently supported
throw new MonitorTypeException("Invalid Monitor type:"
+ " entry index = " + perfDataItem
+ " name = " + name
+ " type = " + dataType);
}
offset = entryStart + PERFDATA_NAME_OFFSET + nameLength;
buffer.position(offset);
ByteBuffer bb = buffer.slice();
bb.limit(vectorLength);
bb.position(0);
if (v == Variability.CONSTANT) {
monitor = new PerfStringConstantMonitor(name, supported,
bb);
} else if (v == Variability.VARIABLE) {
monitor = new PerfStringVariableMonitor(name, supported,
bb, vectorLength-1);
} else {
// Monotonically increasing byte arrays are not supported
throw new MonitorDataException(
"Invalid variability attribute:"
+ " entry index = " + perfDataItem
+ " name = " + name
+ " variability = " + v);
}
perfDataItem++;
} else {
// bad data types.
throw new MonitorTypeException(
"Invalid Monitor type:" + " entry index = "
+ perfDataItem + " name = " + name
+ " type = " + dataType);
}
}
// setup index to next entry for next iteration of the loop.
nextEntry = entryStart + entryLength;
return monitor;
}
Method to dump debugging information
/**
* 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 = (Monitor)map.get(i.next());
System.err.println(j + "\t" + monitor.getName()
+ "=" + monitor.getValue());
}
System.err.println("nextEntry = " + nextEntry
+ " pollForEntry = " + pollForEntry);
System.err.println("Buffer info:");
System.err.println("buffer = " + buffer);
}
}
private void lognl(String s) {
if (DEBUG) {
System.err.println(s);
}
}
private void log(String s) {
if (DEBUG) {
System.err.print(s);
}
}
}