1 /* 2 * Copyright (c) 2004, 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 26 package sun.jvmstat.perfdata.monitor.v2_0; 27 28 import sun.jvmstat.monitor.*; 29 import sun.jvmstat.perfdata.monitor.*; 30 import java.util.*; 31 import java.util.regex.*; 32 import 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 */ 63 public class PerfDataBuffer extends PerfDataBufferImpl { 64 65 private static final boolean DEBUG = false; 66 private static final int syncWaitMs = 67 Integer.getInteger("sun.jvmstat.perdata.syncWaitMs", 5000); 68 private static final ArrayList EMPTY_LIST = new ArrayList(0); 69 70 /* 71 * These are primarily for documentary purposes and the match up 72 * with the PerfDataEntry structure in perfMemory.hpp. They are 73 * generally unused in this code, but they are kept consistent with 74 * the data structure just in case some unforseen need arrises. 75 */ 76 private final static int PERFDATA_ENTRYLENGTH_OFFSET=0; 77 private final static int PERFDATA_ENTRYLENGTH_SIZE=4; // sizeof(int) 78 private final static int PERFDATA_NAMEOFFSET_OFFSET=4; 79 private final static int PERFDATA_NAMEOFFSET_SIZE=4; // sizeof(int) 80 private final static int PERFDATA_VECTORLENGTH_OFFSET=8; 81 private final static int PERFDATA_VECTORLENGTH_SIZE=4; // sizeof(int) 82 private final static int PERFDATA_DATATYPE_OFFSET=12; 83 private final static int PERFDATA_DATATYPE_SIZE=1; // sizeof(byte) 84 private final static int PERFDATA_FLAGS_OFFSET=13; 85 private final static int PERFDATA_FLAGS_SIZE=1; // sizeof(byte) 86 private final static int PERFDATA_DATAUNITS_OFFSET=14; 87 private final static int PERFDATA_DATAUNITS_SIZE=1; // sizeof(byte) 88 private final static int PERFDATA_DATAVAR_OFFSET=15; 89 private final static int PERFDATA_DATAVAR_SIZE=1; // sizeof(byte) 90 private final static int PERFDATA_DATAOFFSET_OFFSET=16; 91 private final static int PERFDATA_DATAOFFSET_SIZE=4; // sizeof(int) 92 93 PerfDataBufferPrologue prologue; 94 int nextEntry; 95 long lastNumEntries; 96 IntegerMonitor overflow; 97 ArrayList<Monitor> insertedMonitors; 98 99 /** 100 * Construct a PerfDataBuffer instance. 101 * <p> 102 * This class is dynamically loaded by 103 * {@link AbstractPerfDataBuffer#createPerfDataBuffer}, and this 104 * constructor is called to instantiate the instance. 105 * 106 * @param buffer the buffer containing the instrumentation data 107 * @param lvmid the Local Java Virtual Machine Identifier for this 108 * instrumentation buffer. 109 */ 110 public PerfDataBuffer(ByteBuffer buffer, int lvmid) 111 throws MonitorException { 112 super(buffer, lvmid); 113 prologue = new PerfDataBufferPrologue(buffer); 114 this.buffer.order(prologue.getByteOrder()); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 protected void buildMonitorMap(Map<String, Monitor> map) throws MonitorException { 121 assert Thread.holdsLock(this); 122 123 // start at the beginning of the buffer 124 buffer.rewind(); 125 126 // create pseudo monitors 127 buildPseudoMonitors(map); 128 129 // wait for the target JVM to indicate that it's intrumentation 130 // buffer is safely accessible 131 synchWithTarget(); 132 133 // parse the currently defined entries starting at the first entry. 134 nextEntry = prologue.getEntryOffset(); 135 136 // record the number of entries before parsing the structure 137 int numEntries = prologue.getNumEntries(); 138 139 // start parsing 140 Monitor monitor = getNextMonitorEntry(); 141 while (monitor != null) { 142 map.put(monitor.getName(), monitor); 143 monitor = getNextMonitorEntry(); 144 } 145 146 /* 147 * keep track of the current number of entries in the shared 148 * memory for new entry detection purposes. It's possible for 149 * the data structure to be modified while the Map is being 150 * built and the entry count in the header might change while 151 * we are parsing it. The map will contain all the counters 152 * found, but the number recorded in numEntries might be small 153 * than what than the number we actually parsed (due to asynchronous 154 * updates). This discrepency is handled by ignoring any re-parsed 155 * entries when updating the Map in getNewMonitors(). 156 */ 157 lastNumEntries = numEntries; 158 159 // keep track of the monitors just added. 160 insertedMonitors = new ArrayList<Monitor>(map.values()); 161 } 162 163 /** 164 * {@inheritDoc} 165 */ 166 protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException { 167 assert Thread.holdsLock(this); 168 169 int numEntries = prologue.getNumEntries(); 170 171 if (numEntries > lastNumEntries) { 172 lastNumEntries = numEntries; 173 Monitor monitor = getNextMonitorEntry(); 174 175 while (monitor != null) { 176 String name = monitor.getName(); 177 178 // guard against re-parsed entries 179 if (!map.containsKey(name)) { 180 map.put(name, monitor); 181 if (insertedMonitors != null) { 182 insertedMonitors.add(monitor); 183 } 184 } 185 monitor = getNextMonitorEntry(); 186 } 187 } 188 } 189 190 /** 191 * {@inheritDoc} 192 */ 193 protected MonitorStatus getMonitorStatus(Map<String, Monitor> map) throws MonitorException { 194 assert Thread.holdsLock(this); 195 assert insertedMonitors != null; 196 197 // load any new monitors 198 getNewMonitors(map); 199 200 // current implementation doesn't support deletion of reuse of entries 201 ArrayList removed = EMPTY_LIST; 202 ArrayList inserted = insertedMonitors; 203 204 insertedMonitors = new ArrayList<Monitor>(); 205 return new MonitorStatus(inserted, removed); 206 } 207 208 /** 209 * Build the pseudo monitors used to map the prolog data into counters. 210 */ 211 protected void buildPseudoMonitors(Map<String, Monitor> map) { 212 Monitor monitor = null; 213 String name = null; 214 IntBuffer ib = null; 215 216 name = PerfDataBufferPrologue.PERFDATA_MAJOR_NAME; 217 ib = prologue.majorVersionBuffer(); 218 monitor = new PerfIntegerMonitor(name, Units.NONE, 219 Variability.CONSTANT, false, ib); 220 map.put(name, monitor); 221 222 name = PerfDataBufferPrologue.PERFDATA_MINOR_NAME; 223 ib = prologue.minorVersionBuffer(); 224 monitor = new PerfIntegerMonitor(name, Units.NONE, 225 Variability.CONSTANT, false, ib); 226 map.put(name, monitor); 227 228 name = PerfDataBufferPrologue.PERFDATA_BUFFER_SIZE_NAME; 229 ib = prologue.sizeBuffer(); 230 monitor = new PerfIntegerMonitor(name, Units.BYTES, 231 Variability.MONOTONIC, false, ib); 232 map.put(name, monitor); 233 234 name = PerfDataBufferPrologue.PERFDATA_BUFFER_USED_NAME; 235 ib = prologue.usedBuffer(); 236 monitor = new PerfIntegerMonitor(name, Units.BYTES, 237 Variability.MONOTONIC, false, ib); 238 map.put(name, monitor); 239 240 name = PerfDataBufferPrologue.PERFDATA_OVERFLOW_NAME; 241 ib = prologue.overflowBuffer(); 242 monitor = new PerfIntegerMonitor(name, Units.BYTES, 243 Variability.MONOTONIC, false, ib); 244 map.put(name, monitor); 245 this.overflow = (IntegerMonitor)monitor; 246 247 name = PerfDataBufferPrologue.PERFDATA_MODTIMESTAMP_NAME; 248 LongBuffer lb = prologue.modificationTimeStampBuffer(); 249 monitor = new PerfLongMonitor(name, Units.TICKS, 250 Variability.MONOTONIC, false, lb); 251 map.put(name, monitor); 252 } 253 254 /** 255 * Method that waits until the target jvm indicates that 256 * its shared memory is safe to access. 257 */ 258 protected void synchWithTarget() throws MonitorException { 259 /* 260 * synch must happen with syncWaitMs from now. Default is 5 seconds, 261 * which is reasonabally generous and should provide for extreme 262 * situations like startup delays due to allocation of large ISM heaps. 263 */ 264 long timeLimit = System.currentTimeMillis() + syncWaitMs; 265 266 // loop waiting for the accessible indicater to be non-zero 267 log("synchWithTarget: " + lvmid + " "); 268 while (!prologue.isAccessible()) { 269 270 log("."); 271 272 // give the target jvm a chance to complete initializatoin 273 try { Thread.sleep(20); } catch (InterruptedException e) { } 274 275 if (System.currentTimeMillis() > timeLimit) { 276 logln("failed: " + lvmid); 277 throw new MonitorException("Could not synchronize with target"); 278 } 279 } 280 logln("success: " + lvmid); 281 } 282 283 /** 284 * method to extract the next monitor entry from the instrumentation memory. 285 * assumes that nextEntry is the offset into the byte array 286 * at which to start the search for the next entry. method leaves 287 * next entry pointing to the next entry or to the end of data. 288 */ 289 protected Monitor getNextMonitorEntry() throws MonitorException { 290 Monitor monitor = null; 291 292 // entries are always 4 byte aligned. 293 if ((nextEntry % 4) != 0) { 294 throw new MonitorStructureException( 295 "Misaligned entry index: " 296 + Integer.toHexString(nextEntry)); 297 } 298 299 // protect againt a corrupted shard memory region. 300 if ((nextEntry < 0) || (nextEntry > buffer.limit())) { 301 throw new MonitorStructureException( 302 "Entry index out of bounds: " 303 + Integer.toHexString(nextEntry) 304 + ", limit = " + Integer.toHexString(buffer.limit())); 305 } 306 307 // check for end of the buffer 308 if (nextEntry == buffer.limit()) { 309 logln("getNextMonitorEntry():" 310 + " nextEntry == buffer.limit(): returning"); 311 return null; 312 } 313 314 buffer.position(nextEntry); 315 316 int entryStart = buffer.position(); 317 int entryLength = buffer.getInt(); 318 319 // check for valid entry length 320 if ((entryLength < 0) || (entryLength > buffer.limit())) { 321 throw new MonitorStructureException( 322 "Invalid entry length: entryLength = " + entryLength 323 + " (0x" + Integer.toHexString(entryLength) + ")"); 324 } 325 326 // check if last entry occurs before the eof. 327 if ((entryStart + entryLength) > buffer.limit()) { 328 throw new MonitorStructureException( 329 "Entry extends beyond end of buffer: " 330 + " entryStart = 0x" + Integer.toHexString(entryStart) 331 + " entryLength = 0x" + Integer.toHexString(entryLength) 332 + " buffer limit = 0x" + Integer.toHexString(buffer.limit())); 333 } 334 335 if (entryLength == 0) { 336 // end of data 337 return null; 338 } 339 340 // we can safely read this entry 341 int nameOffset = buffer.getInt(); 342 int vectorLength = buffer.getInt(); 343 byte typeCodeByte = buffer.get(); 344 byte flags = buffer.get(); 345 byte unitsByte = buffer.get(); 346 byte varByte = buffer.get(); 347 int dataOffset = buffer.getInt(); 348 349 dump_entry_fixed(entryStart, nameOffset, vectorLength, typeCodeByte, 350 flags, unitsByte, varByte, dataOffset); 351 352 // convert common attributes to their object types 353 Units units = Units.toUnits(unitsByte); 354 Variability variability = Variability.toVariability(varByte); 355 TypeCode typeCode = null; 356 boolean supported = (flags & 0x01) != 0; 357 358 try { 359 typeCode = TypeCode.toTypeCode(typeCodeByte); 360 361 } catch (IllegalArgumentException e) { 362 throw new MonitorStructureException( 363 "Illegal type code encountered:" 364 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 365 + ", type_code = " + Integer.toHexString(typeCodeByte)); 366 } 367 368 // verify that the name_offset is contained within the entry bounds 369 if (nameOffset > entryLength) { 370 throw new MonitorStructureException( 371 "Field extends beyond entry bounds" 372 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 373 + ", name_offset = 0x" + Integer.toHexString(nameOffset)); 374 } 375 376 // verify that the data_offset is contained within the entry bounds 377 if (dataOffset > entryLength) { 378 throw new MonitorStructureException( 379 "Field extends beyond entry bounds:" 380 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 381 + ", data_offset = 0x" + Integer.toHexString(dataOffset)); 382 } 383 384 // validate the variability and units fields 385 if (variability == Variability.INVALID) { 386 throw new MonitorDataException( 387 "Invalid variability attribute:" 388 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 389 + ", variability = 0x" + Integer.toHexString(varByte)); 390 } 391 392 if (units == Units.INVALID) { 393 throw new MonitorDataException( 394 "Invalid units attribute: entry_offset = 0x" 395 + Integer.toHexString(nextEntry) 396 + ", units = 0x" + Integer.toHexString(unitsByte)); 397 } 398 399 // the entry looks good - parse the variable length components 400 401 /* 402 * The name starts at nameOffset and continues up to the first null 403 * byte. however, we don't know the length, but we can approximate it 404 * without searching for the null by using the offset for the data 405 * field, which follows the name field. 406 */ 407 assert (buffer.position() == (entryStart + nameOffset)); 408 assert (dataOffset > nameOffset); 409 410 // include possible pad space 411 int maxNameLength = dataOffset-nameOffset; 412 413 // maxNameLength better be less than the total entry length 414 assert (maxNameLength < entryLength); 415 416 // collect the characters, but do not collect the null byte, 417 // as the String(byte[]) constructor does not ignore it! 418 byte[] nameBytes = new byte[maxNameLength]; 419 int nameLength = 0; 420 byte b; 421 while (((b = buffer.get()) != 0) && (nameLength < maxNameLength)) { 422 nameBytes[nameLength++] = b; 423 } 424 425 assert (nameLength < maxNameLength); 426 427 // we should before or at the start of the data field 428 assert (buffer.position() <= (entryStart + dataOffset)); 429 430 // convert the name bytes into a String 431 String name = new String(nameBytes, 0, nameLength); 432 433 /* 434 * compute the size of the data item - this includes pad 435 * characters used to align the next entry. 436 */ 437 int dataSize = entryLength - dataOffset; 438 439 // set the position to the start of the data item 440 buffer.position(entryStart + dataOffset); 441 442 dump_entry_variable(name, buffer, dataSize); 443 444 if (vectorLength == 0) { 445 // create a scalar Monitor object 446 if (typeCode == TypeCode.LONG) { 447 LongBuffer lb = buffer.asLongBuffer(); 448 lb.limit(1); // limit buffer size to one long value. 449 monitor = new PerfLongMonitor(name, units, variability, 450 supported, lb); 451 } else { 452 /* 453 * unexpected type code - coding error or uncoordinated 454 * JVM change 455 */ 456 throw new MonitorTypeException( 457 "Unexpected type code encountered:" 458 + " entry_offset = 0x" + Integer.toHexString(nextEntry) 459 + ", name = " + name 460 + ", type_code = " + typeCode 461 + " (0x" + Integer.toHexString(typeCodeByte) + ")"); 462 } 463 } else { 464 // create a vector Monitor object 465 if (typeCode == TypeCode.BYTE) { 466 if (units != Units.STRING) { 467 // only byte arrays of type STRING are currently supported 468 throw new MonitorTypeException( 469 "Unexpected vector type encounterd:" 470 + " entry_offset = " 471 + Integer.toHexString(nextEntry) 472 + ", name = " + name 473 + ", type_code = " + typeCode + " (0x" 474 + Integer.toHexString(typeCodeByte) + ")" 475 + ", units = " + units + " (0x" 476 + Integer.toHexString(unitsByte) + ")"); 477 } 478 479 ByteBuffer bb = buffer.slice(); 480 bb.limit(vectorLength); // limit buffer length to # of chars 481 482 if (variability == Variability.CONSTANT) { 483 monitor = new PerfStringConstantMonitor(name, supported, 484 bb); 485 } else if (variability == Variability.VARIABLE) { 486 monitor = new PerfStringVariableMonitor(name, supported, 487 bb, vectorLength-1); 488 } else if (variability == Variability.MONOTONIC) { 489 // Monotonically increasing byte arrays are not supported 490 throw new MonitorDataException( 491 "Unexpected variability attribute:" 492 + " entry_offset = 0x" 493 + Integer.toHexString(nextEntry) 494 + " name = " + name 495 + ", variability = " + variability + " (0x" 496 + Integer.toHexString(varByte) + ")"); 497 } else { 498 // variability was validated above, so this unexpected 499 assert false; 500 } 501 } else { 502 // coding error or uncoordinated JVM change 503 throw new MonitorTypeException( 504 "Unexpected type code encountered:" 505 + " entry_offset = 0x" 506 + Integer.toHexString(nextEntry) 507 + ", name = " + name 508 + ", type_code = " + typeCode + " (0x" 509 + Integer.toHexString(typeCodeByte) + ")"); 510 } 511 } 512 513 // setup index to next entry for next iteration of the loop. 514 nextEntry = entryStart + entryLength; 515 return monitor; 516 } 517 518 /** 519 * Method to dump debugging information 520 */ 521 private void dumpAll(Map<String, Monitor> map, int lvmid) { 522 if (DEBUG) { 523 Set<String> keys = map.keySet(); 524 525 System.err.println("Dump for " + lvmid); 526 int j = 0; 527 for (Iterator i = keys.iterator(); i.hasNext(); j++) { 528 Monitor monitor = map.get(i.next()); 529 System.err.println(j + "\t" + monitor.getName() 530 + "=" + monitor.getValue()); 531 } 532 System.err.println("nextEntry = " + nextEntry); 533 System.err.println("Buffer info:"); 534 System.err.println("buffer = " + buffer); 535 } 536 } 537 538 /** 539 * Method to dump the fixed portion of an entry. 540 */ 541 private void dump_entry_fixed(int entry_start, int nameOffset, 542 int vectorLength, byte typeCodeByte, 543 byte flags, byte unitsByte, byte varByte, 544 int dataOffset) { 545 if (DEBUG) { 546 System.err.println("Entry at offset: 0x" 547 + Integer.toHexString(entry_start)); 548 System.err.println("\tname_offset = 0x" 549 + Integer.toHexString(nameOffset)); 550 System.err.println("\tvector_length = 0x" 551 + Integer.toHexString(vectorLength)); 552 System.err.println("\tdata_type = 0x" 553 + Integer.toHexString(typeCodeByte)); 554 System.err.println("\tflags = 0x" 555 + Integer.toHexString(flags)); 556 System.err.println("\tdata_units = 0x" 557 + Integer.toHexString(unitsByte)); 558 System.err.println("\tdata_variability = 0x" 559 + Integer.toHexString(varByte)); 560 System.err.println("\tdata_offset = 0x" 561 + Integer.toHexString(dataOffset)); 562 } 563 } 564 565 private void dump_entry_variable(String name, ByteBuffer bb, int size) { 566 if (DEBUG) { 567 char[] toHex = new char[] { '0', '1', '2', '3', 568 '4', '5', '6', '7', 569 '8', '9', 'a', 'b', 570 'c', 'd', 'e', 'f' }; 571 572 ByteBuffer data = bb.slice(); 573 data.limit(size); 574 575 System.err.println("\tname = " + name); 576 System.err.println("\tdata = "); 577 578 int count=0; 579 while (data.hasRemaining()) { 580 byte b = data.get(); 581 byte high = (byte)((b >> 8) & 0x0f); 582 byte low = (byte)(b & 0x0f); 583 584 if (count % 16 == 0) { 585 System.err.print("\t\t" + Integer.toHexString(count / 16) 586 + ": "); 587 } 588 589 System.err.print(String.valueOf(toHex[high]) 590 + String.valueOf(toHex[low])); 591 592 count++; 593 if (count % 16 == 0) { 594 System.err.println(); 595 } else { 596 System.err.print(" "); 597 } 598 } 599 if (count % 16 != 0) { 600 System.err.println(); 601 } 602 } 603 } 604 605 private void logln(String s) { 606 if (DEBUG) { 607 System.err.println(s); 608 } 609 } 610 611 private void log(String s) { 612 if (DEBUG) { 613 System.err.print(s); 614 } 615 } 616 }