1 /* 2 * Copyright (c) 2004, 2010, 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.v1_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 1.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 * 41 * @author Brian Doherty 42 * @since 1.5 43 * @see AbstractPerfDataBuffer 44 */ 45 public class PerfDataBuffer extends PerfDataBufferImpl { 46 47 private static final boolean DEBUG = false; 48 private static final int syncWaitMs = 49 Integer.getInteger("sun.jvmstat.perdata.syncWaitMs", 5000); 50 private static final ArrayList EMPTY_LIST = new ArrayList(0); 51 52 /* 53 * the following constants must be kept in sync with struct 54 * PerfDataEntry in perfMemory.hpp 55 */ 56 private final static int PERFDATA_ENTRYLENGTH_OFFSET=0; 57 private final static int PERFDATA_ENTRYLENGTH_SIZE=4; // sizeof(int) 58 private final static int PERFDATA_NAMELENGTH_OFFSET=4; 59 private final static int PERFDATA_NAMELENGTH_SIZE=4; // sizeof(int) 60 private final static int PERFDATA_VECTORLENGTH_OFFSET=8; 61 private final static int PERFDATA_VECTORLENGTH_SIZE=4; // sizeof(int) 62 private final static int PERFDATA_DATATYPE_OFFSET=12; 63 private final static int PERFDATA_DATATYPE_SIZE=1; // sizeof(byte) 64 private final static int PERFDATA_FLAGS_OFFSET=13; 65 private final static int PERFDATA_FLAGS_SIZE=1; // sizeof(byte) 66 private final static int PERFDATA_DATAUNITS_OFFSET=14; 67 private final static int PERFDATA_DATAUNITS_SIZE=1; // sizeof(byte) 68 private final static int PERFDATA_DATAATTR_OFFSET=15; 69 private final static int PERFDATA_DATAATTR_SIZE=1; // sizeof(byte) 70 private final static int PERFDATA_NAME_OFFSET=16; 71 72 PerfDataBufferPrologue prologue; 73 int nextEntry; 74 int pollForEntry; 75 int perfDataItem; 76 long lastModificationTime; 77 int lastUsed; 78 IntegerMonitor overflow; 79 ArrayList<Monitor> insertedMonitors; 80 81 /** 82 * Construct a PerfDataBufferImpl instance. 83 * <p> 84 * This class is dynamically loaded by 85 * {@link AbstractPerfDataBuffer#createPerfDataBuffer}, and this 86 * constructor is called to instantiate the instance. 87 * 88 * @param buffer the buffer containing the instrumentation data 89 * @param lvmid the Local Java Virtual Machine Identifier for this 90 * instrumentation buffer. 91 */ 92 public PerfDataBuffer(ByteBuffer buffer, int lvmid) 93 throws MonitorException { 94 super(buffer, lvmid); 95 prologue = new PerfDataBufferPrologue(buffer); 96 this.buffer.order(prologue.getByteOrder()); 97 } 98 99 /** 100 * {@inheritDoc} 101 */ 102 protected void buildMonitorMap(Map<String, Monitor> map) throws MonitorException { 103 assert Thread.holdsLock(this); 104 105 // start at the beginning of the buffer 106 buffer.rewind(); 107 108 // create pseudo monitors 109 buildPseudoMonitors(map); 110 111 // position buffer to start of the data section 112 buffer.position(prologue.getSize()); 113 nextEntry = buffer.position(); 114 perfDataItem = 0; 115 116 int used = prologue.getUsed(); 117 long modificationTime = prologue.getModificationTimeStamp(); 118 119 Monitor m = getNextMonitorEntry(); 120 while (m != null) { 121 map.put(m.getName(), m); 122 m = getNextMonitorEntry(); 123 } 124 125 /* 126 * set the last modification data. These are set to the values 127 * recorded before parsing the data structure. This allows the 128 * the data structure to be modified while the Map is being built. 129 * The Map may contain more entries than indicated based on the 130 * time stamp, but this is handled by ignoring duplicate entries 131 * when the Map is updated in getNewMonitors(). 132 */ 133 lastUsed = used; 134 lastModificationTime = modificationTime; 135 136 // synchronize with the target jvm 137 synchWithTarget(map); 138 139 // work around 1.4.2 counter inititization bugs 140 kludge(map); 141 142 insertedMonitors = new ArrayList<Monitor>(map.values()); 143 } 144 145 /** 146 * {@inheritDoc} 147 */ 148 protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException { 149 assert Thread.holdsLock(this); 150 151 int used = prologue.getUsed(); 152 long modificationTime = prologue.getModificationTimeStamp(); 153 154 if ((used > lastUsed) || (lastModificationTime > modificationTime)) { 155 156 lastUsed = used; 157 lastModificationTime = modificationTime; 158 159 Monitor monitor = getNextMonitorEntry(); 160 while (monitor != null) { 161 String name = monitor.getName(); 162 163 // guard against duplicate entries 164 if (!map.containsKey(name)) { 165 map.put(name, monitor); 166 167 /* 168 * insertedMonitors is null when called from pollFor() 169 * via buildMonitorMap(). Since we update insertedMonitors 170 * at the end of buildMonitorMap(), it's ok to skip the 171 * add here. 172 */ 173 if (insertedMonitors != null) { 174 insertedMonitors.add(monitor); 175 } 176 } 177 monitor = getNextMonitorEntry(); 178 } 179 } 180 } 181 182 /** 183 * {@inheritDoc} 184 */ 185 protected MonitorStatus getMonitorStatus(Map<String, Monitor> map) throws MonitorException { 186 assert Thread.holdsLock(this); 187 assert insertedMonitors != null; 188 189 // load any new monitors 190 getNewMonitors(map); 191 192 // current implementation doesn't support deletion or reuse of entries 193 ArrayList removed = EMPTY_LIST; 194 ArrayList inserted = insertedMonitors; 195 196 insertedMonitors = new ArrayList<Monitor>(); 197 return new MonitorStatus(inserted, removed); 198 } 199 200 /** 201 * Build the pseudo monitors used to map the prolog data into counters. 202 */ 203 protected void buildPseudoMonitors(Map<String, Monitor> map) { 204 Monitor monitor = null; 205 String name = null; 206 IntBuffer ib = null; 207 208 name = PerfDataBufferPrologue.PERFDATA_MAJOR_NAME; 209 ib = prologue.majorVersionBuffer(); 210 monitor = new PerfIntegerMonitor(name, Units.NONE, 211 Variability.CONSTANT, false, ib); 212 map.put(name, monitor); 213 214 name = PerfDataBufferPrologue.PERFDATA_MINOR_NAME; 215 ib = prologue.minorVersionBuffer(); 216 monitor = new PerfIntegerMonitor(name, Units.NONE, 217 Variability.CONSTANT, false, ib); 218 map.put(name, monitor); 219 220 name = PerfDataBufferPrologue.PERFDATA_BUFFER_SIZE_NAME; 221 ib = prologue.sizeBuffer(); 222 monitor = new PerfIntegerMonitor(name, Units.BYTES, 223 Variability.MONOTONIC, false, ib); 224 map.put(name, monitor); 225 226 name = PerfDataBufferPrologue.PERFDATA_BUFFER_USED_NAME; 227 ib = prologue.usedBuffer(); 228 monitor = new PerfIntegerMonitor(name, Units.BYTES, 229 Variability.MONOTONIC, false, ib); 230 map.put(name, monitor); 231 232 name = PerfDataBufferPrologue.PERFDATA_OVERFLOW_NAME; 233 ib = prologue.overflowBuffer(); 234 monitor = new PerfIntegerMonitor(name, Units.BYTES, 235 Variability.MONOTONIC, false, ib); 236 map.put(name, monitor); 237 this.overflow = (IntegerMonitor)monitor; 238 239 name = PerfDataBufferPrologue.PERFDATA_MODTIMESTAMP_NAME; 240 LongBuffer lb = prologue.modificationTimeStampBuffer(); 241 monitor = new PerfLongMonitor(name, Units.TICKS, 242 Variability.MONOTONIC, false, lb); 243 map.put(name, monitor); 244 } 245 246 /** 247 * Method to provide a gross level of synchronization with the 248 * target monitored jvm. 249 * 250 * gross synchronization works by polling for the hotspot.rt.hrt.ticks 251 * counter, which is the last counter created by the StatSampler 252 * initialization code. The counter is updated when the watcher thread 253 * starts scheduling tasks, which is the last thing done in vm 254 * initialization. 255 */ 256 protected void synchWithTarget(Map<String, Monitor> map) throws MonitorException { 257 /* 258 * synch must happen with syncWaitMs from now. Default is 5 seconds, 259 * which is reasonabally generous and should provide for extreme 260 * situations like startup delays due to allocation of large ISM heaps. 261 */ 262 long timeLimit = System.currentTimeMillis() + syncWaitMs; 263 264 String name = "hotspot.rt.hrt.ticks"; 265 LongMonitor ticks = (LongMonitor)pollFor(map, name, timeLimit); 266 267 /* 268 * loop waiting for the ticks counter to be non zero. This is 269 * an indication that the jvm is initialized. 270 */ 271 log("synchWithTarget: " + lvmid + " "); 272 while (ticks.longValue() == 0) { 273 log("."); 274 275 try { Thread.sleep(20); } catch (InterruptedException e) { } 276 277 if (System.currentTimeMillis() > timeLimit) { 278 lognl("failed: " + lvmid); 279 throw new MonitorException("Could Not Synchronize with target"); 280 } 281 } 282 lognl("success: " + lvmid); 283 } 284 285 /** 286 * Method to poll the instrumentation memory for a counter with 287 * the given name. The polling period is bounded by the timeLimit 288 * argument. 289 */ 290 protected Monitor pollFor(Map<String, Monitor> map, String name, long timeLimit) 291 throws MonitorException { 292 Monitor monitor = null; 293 294 log("polling for: " + lvmid + "," + name + " "); 295 296 pollForEntry = nextEntry; 297 while ((monitor = map.get(name)) == null) { 298 log("."); 299 300 try { Thread.sleep(20); } catch (InterruptedException e) { } 301 302 long t = System.currentTimeMillis(); 303 if ((t > timeLimit) || (overflow.intValue() > 0)) { 304 lognl("failed: " + lvmid + "," + name); 305 dumpAll(map, lvmid); 306 throw new MonitorException("Could not find expected counter"); 307 } 308 309 getNewMonitors(map); 310 } 311 lognl("success: " + lvmid + "," + name); 312 return monitor; 313 } 314 315 /** 316 * method to make adjustments for known counter problems. This 317 * method depends on the availability of certain counters, which 318 * is generally guaranteed by the synchWithTarget() method. 319 */ 320 protected void kludge(Map<String, Monitor> map) { 321 if (Boolean.getBoolean("sun.jvmstat.perfdata.disableKludge")) { 322 // bypass all kludges 323 return; 324 } 325 326 String name = "java.vm.version"; 327 StringMonitor jvm_version = (StringMonitor)map.get(name); 328 if (jvm_version == null) { 329 jvm_version = (StringMonitor)findByAlias(name); 330 } 331 332 name = "java.vm.name"; 333 StringMonitor jvm_name = (StringMonitor)map.get(name); 334 if (jvm_name == null) { 335 jvm_name = (StringMonitor)findByAlias(name); 336 } 337 338 name = "hotspot.vm.args"; 339 StringMonitor args = (StringMonitor)map.get(name); 340 if (args == null) { 341 args = (StringMonitor)findByAlias(name); 342 } 343 344 assert ((jvm_name != null) && (jvm_version != null) && (args != null)); 345 346 if (jvm_name.stringValue().indexOf("HotSpot") >= 0) { 347 if (jvm_version.stringValue().startsWith("1.4.2")) { 348 kludgeMantis(map, args); 349 } 350 } 351 } 352 353 /** 354 * method to repair the 1.4.2 parallel scavenge counters that are 355 * incorrectly initialized by the JVM when UseAdaptiveSizePolicy 356 * is set. This bug couldn't be fixed for 1.4.2 FCS due to putback 357 * restrictions. 358 */ 359 private void kludgeMantis(Map<String, Monitor> map, StringMonitor args) { 360 /* 361 * the HotSpot 1.4.2 JVM with the +UseParallelGC option along 362 * with its default +UseAdaptiveSizePolicy option has a bug with 363 * the initialization of the sizes of the eden and survivor spaces. 364 * See bugid 4890736. 365 * 366 * note - use explicit 1.4.2 counter names here - don't update 367 * to latest counter names or attempt to find aliases. 368 */ 369 370 String cname = "hotspot.gc.collector.0.name"; 371 StringMonitor collector = (StringMonitor)map.get(cname); 372 373 if (collector.stringValue().compareTo("PSScavenge") == 0) { 374 boolean adaptiveSizePolicy = true; 375 376 /* 377 * HotSpot processes the -XX:Flags/.hotspotrc arguments prior to 378 * processing the command line arguments. This allows the command 379 * line arguments to override any defaults set in .hotspotrc 380 */ 381 cname = "hotspot.vm.flags"; 382 StringMonitor flags = (StringMonitor)map.get(cname); 383 String allArgs = flags.stringValue() + " " + args.stringValue(); 384 385 /* 386 * ignore the -XX: prefix as it only applies to the arguments 387 * passed from the command line (i.e. the invocation api). 388 * arguments passed through .hotspotrc omit the -XX: prefix. 389 */ 390 int ahi = allArgs.lastIndexOf("+AggressiveHeap"); 391 int aspi = allArgs.lastIndexOf("-UseAdaptiveSizePolicy"); 392 393 if (ahi != -1) { 394 /* 395 * +AggressiveHeap was set, check if -UseAdaptiveSizePolicy 396 * is set after +AggressiveHeap. 397 */ 398 // 399 if ((aspi != -1) && (aspi > ahi)) { 400 adaptiveSizePolicy = false; 401 } 402 } else { 403 /* 404 * +AggressiveHeap not set, must be +UseParallelGC. The 405 * relative position of -UseAdaptiveSizePolicy is not 406 * important in this case, as it will override the 407 * UseParallelGC default (+UseAdaptiveSizePolicy) if it 408 * appears anywhere in the JVM arguments. 409 */ 410 if (aspi != -1) { 411 adaptiveSizePolicy = false; 412 } 413 } 414 415 if (adaptiveSizePolicy) { 416 // adjust the buggy AdaptiveSizePolicy size counters. 417 418 // first remove the real counters. 419 String eden_size = "hotspot.gc.generation.0.space.0.size"; 420 String s0_size = "hotspot.gc.generation.0.space.1.size"; 421 String s1_size = "hotspot.gc.generation.0.space.2.size"; 422 map.remove(eden_size); 423 map.remove(s0_size); 424 map.remove(s1_size); 425 426 // get the maximum new generation size 427 String new_max_name = "hotspot.gc.generation.0.capacity.max"; 428 LongMonitor new_max = (LongMonitor)map.get(new_max_name); 429 430 /* 431 * replace the real counters with pseudo counters that are 432 * initialized to to the correct values. The maximum size of 433 * the eden and survivor spaces are supposed to be: 434 * max_eden_size = new_size - (2*alignment). 435 * max_survivor_size = new_size - (2*alignment). 436 * since we don't know the alignment value used, and because 437 * of other parallel scavenge bugs that result in oversized 438 * spaces, we just set the maximum size of each space to the 439 * full new gen size. 440 */ 441 Monitor monitor = null; 442 443 LongBuffer lb = LongBuffer.allocate(1); 444 lb.put(new_max.longValue()); 445 monitor = new PerfLongMonitor(eden_size, Units.BYTES, 446 Variability.CONSTANT, false, lb); 447 map.put(eden_size, monitor); 448 449 monitor = new PerfLongMonitor(s0_size, Units.BYTES, 450 Variability.CONSTANT, false, lb); 451 map.put(s0_size, monitor); 452 453 monitor = new PerfLongMonitor(s1_size, Units.BYTES, 454 Variability.CONSTANT, false, lb); 455 map.put(s1_size, monitor); 456 } 457 } 458 } 459 460 /** 461 * method to extract the next monitor entry from the instrumentation memory. 462 * assumes that nextEntry is the offset into the byte array 463 * at which to start the search for the next entry. method leaves 464 * next entry pointing to the next entry or to the end of data. 465 */ 466 protected Monitor getNextMonitorEntry() throws MonitorException { 467 Monitor monitor = null; 468 469 // entries are always 4 byte aligned. 470 if ((nextEntry % 4) != 0) { 471 throw new MonitorStructureException( 472 "Entry index not properly aligned: " + nextEntry); 473 } 474 475 // protect against a corrupted shared memory region. 476 if ((nextEntry < 0) || (nextEntry > buffer.limit())) { 477 throw new MonitorStructureException( 478 "Entry index out of bounds: nextEntry = " + nextEntry 479 + ", limit = " + buffer.limit()); 480 } 481 482 // check for the end of the buffer 483 if (nextEntry == buffer.limit()) { 484 lognl("getNextMonitorEntry():" 485 + " nextEntry == buffer.limit(): returning"); 486 return null; 487 } 488 489 buffer.position(nextEntry); 490 491 int entryStart = buffer.position(); 492 int entryLength = buffer.getInt(); 493 494 // check for valid entry length 495 if ((entryLength < 0) || (entryLength > buffer.limit())) { 496 throw new MonitorStructureException( 497 "Invalid entry length: entryLength = " + entryLength); 498 } 499 500 // check if last entry occurs before the eof. 501 if ((entryStart + entryLength) > buffer.limit()) { 502 throw new MonitorStructureException( 503 "Entry extends beyond end of buffer: " 504 + " entryStart = " + entryStart 505 + " entryLength = " + entryLength 506 + " buffer limit = " + buffer.limit()); 507 } 508 509 if (entryLength == 0) { 510 // end of data 511 return null; 512 } 513 514 int nameLength = buffer.getInt(); 515 int vectorLength = buffer.getInt(); 516 byte dataType = buffer.get(); 517 byte flags = buffer.get(); 518 Units u = Units.toUnits(buffer.get()); 519 Variability v = Variability.toVariability(buffer.get()); 520 boolean supported = (flags & 0x01) != 0; 521 522 // defend against corrupt entries 523 if ((nameLength <= 0) || (nameLength > entryLength)) { 524 throw new MonitorStructureException( 525 "Invalid Monitor name length: " + nameLength); 526 } 527 528 if ((vectorLength < 0) || (vectorLength > entryLength)) { 529 throw new MonitorStructureException( 530 "Invalid Monitor vector length: " + vectorLength); 531 } 532 533 // read in the perfData item name, casting bytes to chars. skip the 534 // null terminator 535 // 536 byte[] nameBytes = new byte[nameLength-1]; 537 for (int i = 0; i < nameLength-1; i++) { 538 nameBytes[i] = buffer.get(); 539 } 540 541 // convert name into a String 542 String name = new String(nameBytes, 0, nameLength-1); 543 544 if (v == Variability.INVALID) { 545 throw new MonitorDataException("Invalid variability attribute:" 546 + " entry index = " + perfDataItem 547 + " name = " + name); 548 } 549 if (u == Units.INVALID) { 550 throw new MonitorDataException("Invalid units attribute: " 551 + " entry index = " + perfDataItem 552 + " name = " + name); 553 } 554 555 int offset; 556 if (vectorLength == 0) { 557 // scalar Types 558 if (dataType == BasicType.LONG.intValue()) { 559 offset = entryStart + entryLength - 8; /* 8 = sizeof(long) */ 560 buffer.position(offset); 561 LongBuffer lb = buffer.asLongBuffer(); 562 lb.limit(1); 563 monitor = new PerfLongMonitor(name, u, v, supported, lb); 564 perfDataItem++; 565 } else { 566 // bad data types. 567 throw new MonitorTypeException("Invalid Monitor type:" 568 + " entry index = " + perfDataItem 569 + " name = " + name 570 + " type = " + dataType); 571 } 572 } else { 573 // vector types 574 if (dataType == BasicType.BYTE.intValue()) { 575 if (u != Units.STRING) { 576 // only byte arrays of type STRING are currently supported 577 throw new MonitorTypeException("Invalid Monitor type:" 578 + " entry index = " + perfDataItem 579 + " name = " + name 580 + " type = " + dataType); 581 } 582 583 offset = entryStart + PERFDATA_NAME_OFFSET + nameLength; 584 buffer.position(offset); 585 ByteBuffer bb = buffer.slice(); 586 bb.limit(vectorLength); 587 bb.position(0); 588 589 if (v == Variability.CONSTANT) { 590 monitor = new PerfStringConstantMonitor(name, supported, 591 bb); 592 } else if (v == Variability.VARIABLE) { 593 monitor = new PerfStringVariableMonitor(name, supported, 594 bb, vectorLength-1); 595 } else { 596 // Monotonically increasing byte arrays are not supported 597 throw new MonitorDataException( 598 "Invalid variability attribute:" 599 + " entry index = " + perfDataItem 600 + " name = " + name 601 + " variability = " + v); 602 } 603 perfDataItem++; 604 } else { 605 // bad data types. 606 throw new MonitorTypeException( 607 "Invalid Monitor type:" + " entry index = " 608 + perfDataItem + " name = " + name 609 + " type = " + dataType); 610 } 611 } 612 613 // setup index to next entry for next iteration of the loop. 614 nextEntry = entryStart + entryLength; 615 return monitor; 616 } 617 618 /** 619 * Method to dump debugging information 620 */ 621 private void dumpAll(Map map, int lvmid) { 622 if (DEBUG) { 623 Set keys = map.keySet(); 624 625 System.err.println("Dump for " + lvmid); 626 int j = 0; 627 for (Iterator i = keys.iterator(); i.hasNext(); j++) { 628 Monitor monitor = (Monitor)map.get(i.next()); 629 System.err.println(j + "\t" + monitor.getName() 630 + "=" + monitor.getValue()); 631 } 632 System.err.println("nextEntry = " + nextEntry 633 + " pollForEntry = " + pollForEntry); 634 System.err.println("Buffer info:"); 635 System.err.println("buffer = " + buffer); 636 } 637 } 638 639 private void lognl(String s) { 640 if (DEBUG) { 641 System.err.println(s); 642 } 643 } 644 645 private void log(String s) { 646 if (DEBUG) { 647 System.err.print(s); 648 } 649 } 650 }