/* * 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 * @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. * <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); // 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} */ 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} */ 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. */ 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. */ 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. */ 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. */ 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. */ 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. */ 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 */ 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); } } }