1/* 2 * Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.jvmstat.perfdata.monitor.v2_0; 27 28import sun.jvmstat.monitor.*; 29import sun.jvmstat.perfdata.monitor.*; 30import java.util.*; 31import java.util.regex.*; 32import java.nio.*; 33 34/** 35 * The concrete implementation of version 2.0 of the HotSpot PerfData 36 * Instrumentation buffer. This class is responsible for parsing the 37 * instrumentation memory and constructing the necessary objects to 38 * represent and access the instrumentation objects contained in the 39 * memory buffer. 40 * <p> 41 * The structure of the 2.0 entry is defined in struct PerfDataEnry 42 * as decsribed in perfMemory.hpp. This structure looks like: 43 * <pre> 44 * typedef struct { 45 * jint entry_length; // entry length in bytes 46 * jint name_offset; // offset to entry name, relative to start 47 * // of entry 48 * jint vector_length; // length of the vector. If 0, then scalar. 49 * jbyte data_type; // JNI field descriptor type 50 * jbyte flags; // miscellaneous attribute flags 51 * // 0x01 - supported 52 * jbyte data_units; // unit of measure attribute 53 * jbyte data_variability; // variability attribute 54 * jbyte data_offset; // offset to data item, relative to start 55 * // of entry. 56 * } PerfDataEntry; 57 * </pre> 58 * 59 * @author Brian Doherty 60 * @since 1.5 61 * @see AbstractPerfDataBuffer 62 */ 63public class PerfDataBuffer extends PerfDataBufferImpl { 64 65 // 8028357 removed old, inefficient debug logging 66 67 private static final int syncWaitMs = 68 Integer.getInteger("sun.jvmstat.perdata.syncWaitMs", 5000); 69 private static final ArrayList<Monitor> EMPTY_LIST = new ArrayList<>(0); 70 71 /* 72 * These are primarily for documentary purposes and the match up 73 * with the PerfDataEntry structure in perfMemory.hpp. They are 74 * generally unused in this code, but they are kept consistent with 75 * the data structure just in case some unforseen need arrises. 76 */ 77 private final static int PERFDATA_ENTRYLENGTH_OFFSET=0; 78 private final static int PERFDATA_ENTRYLENGTH_SIZE=4; // sizeof(int) 79 private final static int PERFDATA_NAMEOFFSET_OFFSET=4; 80 private final static int PERFDATA_NAMEOFFSET_SIZE=4; // sizeof(int) 81 private final static int PERFDATA_VECTORLENGTH_OFFSET=8; 82 private final static int PERFDATA_VECTORLENGTH_SIZE=4; // sizeof(int) 83 private final static int PERFDATA_DATATYPE_OFFSET=12; 84 private final static int PERFDATA_DATATYPE_SIZE=1; // sizeof(byte) 85 private final static int PERFDATA_FLAGS_OFFSET=13; 86 private final static int PERFDATA_FLAGS_SIZE=1; // sizeof(byte) 87 private final static int PERFDATA_DATAUNITS_OFFSET=14; 88 private final static int PERFDATA_DATAUNITS_SIZE=1; // sizeof(byte) 89 private final static int PERFDATA_DATAVAR_OFFSET=15; 90 private final static int PERFDATA_DATAVAR_SIZE=1; // sizeof(byte) 91 private final static int PERFDATA_DATAOFFSET_OFFSET=16; 92 private final static int PERFDATA_DATAOFFSET_SIZE=4; // sizeof(int) 93 94 PerfDataBufferPrologue prologue; 95 int nextEntry; 96 long lastNumEntries; 97 IntegerMonitor overflow; 98 ArrayList<Monitor> insertedMonitors; 99 100 /** 101 * Construct a PerfDataBuffer instance. 102 * <p> 103 * This class is dynamically loaded by 104 * {@link AbstractPerfDataBuffer#createPerfDataBuffer}, and this 105 * constructor is called to instantiate the instance. 106 * 107 * @param buffer the buffer containing the instrumentation data 108 * @param lvmid the Local Java Virtual Machine Identifier for this 109 * instrumentation buffer. 110 */ 111 public PerfDataBuffer(ByteBuffer buffer, int lvmid) 112 throws MonitorException { 113 super(buffer, lvmid); 114 prologue = new PerfDataBufferPrologue(buffer); 115 this.buffer.order(prologue.getByteOrder()); 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 protected void buildMonitorMap(Map<String, Monitor> map) throws MonitorException { 122 assert Thread.holdsLock(this); 123 124 // start at the beginning of the buffer 125 buffer.rewind(); 126 127 // create pseudo monitors 128 buildPseudoMonitors(map); 129 130 // wait for the target JVM to indicate that it's intrumentation 131 // buffer is safely accessible 132 synchWithTarget(); 133 134 // parse the currently defined entries starting at the first entry. 135 nextEntry = prologue.getEntryOffset(); 136 137 // record the number of entries before parsing the structure 138 int numEntries = prologue.getNumEntries(); 139 140 // start parsing 141 Monitor monitor = getNextMonitorEntry(); 142 while (monitor != null) { 143 map.put(monitor.getName(), monitor); 144 monitor = getNextMonitorEntry(); 145 } 146 147 /* 148 * keep track of the current number of entries in the shared 149 * memory for new entry detection purposes. It's possible for 150 * the data structure to be modified while the Map is being 151 * built and the entry count in the header might change while 152 * we are parsing it. The map will contain all the counters 153 * found, but the number recorded in numEntries might be small 154 * than what than the number we actually parsed (due to asynchronous 155 * updates). This discrepency is handled by ignoring any re-parsed 156 * entries when updating the Map in getNewMonitors(). 157 */ 158 lastNumEntries = numEntries; 159 160 // keep track of the monitors just added. 161 insertedMonitors = new ArrayList<Monitor>(map.values()); 162 } 163 164 /** 165 * {@inheritDoc} 166 */ 167 protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException { 168 assert Thread.holdsLock(this); 169 170 int numEntries = prologue.getNumEntries(); 171 172 if (numEntries > lastNumEntries) { 173 lastNumEntries = numEntries; 174 Monitor monitor = getNextMonitorEntry(); 175 176 while (monitor != null) { 177 String name = monitor.getName(); 178 179 // guard against re-parsed entries 180 if (!map.containsKey(name)) { 181 map.put(name, monitor); 182 if (insertedMonitors != null) { 183 insertedMonitors.add(monitor); 184 } 185 } 186 monitor = getNextMonitorEntry(); 187 } 188 } 189 } 190 191 /** 192 * {@inheritDoc} 193 */ 194 protected MonitorStatus getMonitorStatus(Map<String, Monitor> map) throws MonitorException { 195 assert Thread.holdsLock(this); 196 assert insertedMonitors != null; 197 198 // load any new monitors 199 getNewMonitors(map); 200 201 // current implementation doesn't support deletion of reuse of entries 202 ArrayList<Monitor> removed = EMPTY_LIST; 203 ArrayList<Monitor> inserted = insertedMonitors; 204 205 insertedMonitors = new ArrayList<>(); 206 return new MonitorStatus(inserted, removed); 207 } 208 209 /** 210 * Build the pseudo monitors used to map the prolog data into counters. 211 */ 212 protected void buildPseudoMonitors(Map<String, Monitor> map) { 213 Monitor monitor = null; 214 String name = null; 215 IntBuffer ib = null; 216 217 name = PerfDataBufferPrologue.PERFDATA_MAJOR_NAME; 218 ib = prologue.majorVersionBuffer(); 219 monitor = new PerfIntegerMonitor(name, Units.NONE, 220 Variability.CONSTANT, false, ib); 221 map.put(name, monitor); 222 223 name = PerfDataBufferPrologue.PERFDATA_MINOR_NAME; 224 ib = prologue.minorVersionBuffer(); 225 monitor = new PerfIntegerMonitor(name, Units.NONE, 226 Variability.CONSTANT, false, ib); 227 map.put(name, monitor); 228 229 name = PerfDataBufferPrologue.PERFDATA_BUFFER_SIZE_NAME; 230 ib = prologue.sizeBuffer(); 231 monitor = new PerfIntegerMonitor(name, Units.BYTES, 232 Variability.MONOTONIC, false, ib); 233 map.put(name, monitor); 234 235 name = PerfDataBufferPrologue.PERFDATA_BUFFER_USED_NAME; 236 ib = prologue.usedBuffer(); 237 monitor = new PerfIntegerMonitor(name, Units.BYTES, 238 Variability.MONOTONIC, false, ib); 239 map.put(name, monitor); 240 241 name = PerfDataBufferPrologue.PERFDATA_OVERFLOW_NAME; 242 ib = prologue.overflowBuffer(); 243 monitor = new PerfIntegerMonitor(name, Units.BYTES, 244 Variability.MONOTONIC, false, ib); 245 map.put(name, monitor); 246 this.overflow = (IntegerMonitor)monitor; 247 248 name = PerfDataBufferPrologue.PERFDATA_MODTIMESTAMP_NAME; 249 LongBuffer lb = prologue.modificationTimeStampBuffer(); 250 monitor = new PerfLongMonitor(name, Units.TICKS, 251 Variability.MONOTONIC, false, lb); 252 map.put(name, monitor); 253 } 254 255 /** 256 * Method that waits until the target jvm indicates that 257 * its shared memory is safe to access. 258 */ 259 protected void synchWithTarget() throws MonitorException { 260 /* 261 * synch must happen with syncWaitMs from now. Default is 5 seconds, 262 * which is reasonabally generous and should provide for extreme 263 * situations like startup delays due to allocation of large ISM heaps. 264 */ 265 long timeLimit = System.currentTimeMillis() + syncWaitMs; 266 267 // loop waiting for the accessible indicater to be non-zero 268 while (!prologue.isAccessible()) { 269 270 // give the target jvm a chance to complete initializatoin 271 try { Thread.sleep(20); } catch (InterruptedException e) { } 272 273 if (System.currentTimeMillis() > timeLimit) { 274 throw new MonitorException("Could not synchronize with target"); 275 } 276 } 277 } 278 279 /** 280 * method to extract the next monitor entry from the instrumentation memory. 281 * assumes that nextEntry is the offset into the byte array 282 * at which to start the search for the next entry. method leaves 283 * next entry pointing to the next entry or to the end of data. 284 */ 285 protected Monitor getNextMonitorEntry() throws MonitorException { 286 Monitor monitor = null; 287 288 // entries are always 4 byte aligned. 289 if ((nextEntry % 4) != 0) { 290 throw new MonitorStructureException( 291 "Misaligned entry index: " 292 + Integer.toHexString(nextEntry)); 293 } 294 295 // protect againt a corrupted shard memory region. 296 if ((nextEntry < 0) || (nextEntry > buffer.limit())) { 297 throw new MonitorStructureException( 298 "Entry index out of bounds: " 299 + Integer.toHexString(nextEntry) 300 + ", limit = " + Integer.toHexString(buffer.limit())); 301 } 302 303 // check for end of the buffer 304 if (nextEntry == buffer.limit()) { 305 return null; 306 } 307 308 buffer.position(nextEntry); 309 310 int entryStart = buffer.position(); 311 int entryLength = buffer.getInt(); 312 313 // check for valid entry length 314 if ((entryLength < 0) || (entryLength > buffer.limit())) { 315 throw new MonitorStructureException( 316 "Invalid entry length: entryLength = " + entryLength 317 + " (0x" + Integer.toHexString(entryLength) + ")"); 318 } 319 320 // check if last entry occurs before the eof. 321 if ((entryStart + entryLength) > buffer.limit()) { 322 throw new MonitorStructureException( 323 "Entry extends beyond end of buffer: " 324 + " entryStart = 0x" + Integer.toHexString(entryStart) 325 + " entryLength = 0x" + Integer.toHexString(entryLength) 326 + " buffer limit = 0x" + Integer.toHexString(buffer.limit())); 327 } 328 329 if (entryLength == 0) { 330 // end of data 331 return null; 332 } 333 334 // we can safely read this entry 335 int nameOffset = buffer.getInt(); 336 int vectorLength = buffer.getInt(); 337 byte typeCodeByte = buffer.get(); 338 byte flags = buffer.get(); 339 byte unitsByte = buffer.get(); 340 byte varByte = buffer.get(); 341 int dataOffset = buffer.getInt(); 342 343 // convert common attributes to their object types 344 Units units = Units.toUnits(unitsByte); 345 Variability variability = Variability.toVariability(varByte); 346 TypeCode typeCode = null; 347 boolean supported = (flags & 0x01) != 0; 348 349 try { 350 typeCode = TypeCode.toTypeCode(typeCodeByte); 351 352 } catch (IllegalArgumentException e) { 353 throw new MonitorStructureException( 354 "Illegal type code encountered:" 355 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 356 + ", type_code = " + Integer.toHexString(typeCodeByte)); 357 } 358 359 // verify that the name_offset is contained within the entry bounds 360 if (nameOffset > entryLength) { 361 throw new MonitorStructureException( 362 "Field extends beyond entry bounds" 363 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 364 + ", name_offset = 0x" + Integer.toHexString(nameOffset)); 365 } 366 367 // verify that the data_offset is contained within the entry bounds 368 if (dataOffset > entryLength) { 369 throw new MonitorStructureException( 370 "Field extends beyond entry bounds:" 371 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 372 + ", data_offset = 0x" + Integer.toHexString(dataOffset)); 373 } 374 375 // validate the variability and units fields 376 if (variability == Variability.INVALID) { 377 throw new MonitorDataException( 378 "Invalid variability attribute:" 379 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 380 + ", variability = 0x" + Integer.toHexString(varByte)); 381 } 382 383 if (units == Units.INVALID) { 384 throw new MonitorDataException( 385 "Invalid units attribute: entry_offset = 0x" 386 + Integer.toHexString(nextEntry) 387 + ", units = 0x" + Integer.toHexString(unitsByte)); 388 } 389 390 // the entry looks good - parse the variable length components 391 392 /* 393 * The name starts at nameOffset and continues up to the first null 394 * byte. however, we don't know the length, but we can approximate it 395 * without searching for the null by using the offset for the data 396 * field, which follows the name field. 397 */ 398 assert (buffer.position() == (entryStart + nameOffset)); 399 assert (dataOffset > nameOffset); 400 401 // include possible pad space 402 int maxNameLength = dataOffset-nameOffset; 403 404 // maxNameLength better be less than the total entry length 405 assert (maxNameLength < entryLength); 406 407 // collect the characters, but do not collect the null byte, 408 // as the String(byte[]) constructor does not ignore it! 409 byte[] nameBytes = new byte[maxNameLength]; 410 int nameLength = 0; 411 byte b; 412 while (((b = buffer.get()) != 0) && (nameLength < maxNameLength)) { 413 nameBytes[nameLength++] = b; 414 } 415 416 assert (nameLength < maxNameLength); 417 418 // we should before or at the start of the data field 419 assert (buffer.position() <= (entryStart + dataOffset)); 420 421 // convert the name bytes into a String 422 String name = new String(nameBytes, 0, nameLength); 423 424 /* 425 * compute the size of the data item - this includes pad 426 * characters used to align the next entry. 427 */ 428 int dataSize = entryLength - dataOffset; 429 430 // set the position to the start of the data item 431 buffer.position(entryStart + dataOffset); 432 433 if (vectorLength == 0) { 434 // create a scalar Monitor object 435 if (typeCode == TypeCode.LONG) { 436 LongBuffer lb = buffer.asLongBuffer(); 437 lb.limit(1); // limit buffer size to one long value. 438 monitor = new PerfLongMonitor(name, units, variability, 439 supported, lb); 440 } else { 441 /* 442 * unexpected type code - coding error or uncoordinated 443 * JVM change 444 */ 445 throw new MonitorTypeException( 446 "Unexpected type code encountered:" 447 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 448 + ", name = " + name 449 + ", type_code = " + typeCode 450 + " (0x" + Integer.toHexString(typeCodeByte) + ")"); 451 } 452 } else { 453 // create a vector Monitor object 454 if (typeCode == TypeCode.BYTE) { 455 if (units != Units.STRING) { 456 // only byte arrays of type STRING are currently supported 457 throw new MonitorTypeException( 458 "Unexpected vector type encounterd:" 459 + " entry_offset = " 460 + Integer.toHexString(nextEntry) 461 + ", name = " + name 462 + ", type_code = " + typeCode + " (0x" 463 + Integer.toHexString(typeCodeByte) + ")" 464 + ", units = " + units + " (0x" 465 + Integer.toHexString(unitsByte) + ")"); 466 } 467 468 ByteBuffer bb = buffer.slice(); 469 bb.limit(vectorLength); // limit buffer length to # of chars 470 471 if (variability == Variability.CONSTANT) { 472 monitor = new PerfStringConstantMonitor(name, supported, 473 bb); 474 } else if (variability == Variability.VARIABLE) { 475 monitor = new PerfStringVariableMonitor(name, supported, 476 bb, vectorLength-1); 477 } else if (variability == Variability.MONOTONIC) { 478 // Monotonically increasing byte arrays are not supported 479 throw new MonitorDataException( 480 "Unexpected variability attribute:" 481 + " entry_offset = 0x" 482 + Integer.toHexString(nextEntry) 483 + " name = " + name 484 + ", variability = " + variability + " (0x" 485 + Integer.toHexString(varByte) + ")"); 486 } else { 487 // variability was validated above, so this unexpected 488 assert false; 489 } 490 } else { 491 // coding error or uncoordinated JVM change 492 throw new MonitorTypeException( 493 "Unexpected type code encountered:" 494 + " entry_offset = 0x" 495 + Integer.toHexString(nextEntry) 496 + ", name = " + name 497 + ", type_code = " + typeCode + " (0x" 498 + Integer.toHexString(typeCodeByte) + ")"); 499 } 500 } 501 502 // setup index to next entry for next iteration of the loop. 503 nextEntry = entryStart + entryLength; 504 return monitor; 505 } 506} 507