1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * ident "%Z%%M% %I% %E% SMI" 27 */ 28 package org.opensolaris.os.dtrace; 29 30 import java.util.*; 31 import java.io.*; 32 import java.beans.*; 33 34 /** 35 * Data generated when a DTrace probe fires, contains one record for 36 * every record-generating action in the probe. (Some D actions, such 37 * as {@code clear()}, do not generate a {@code ProbeData} record.) A 38 * {@link Consumer} gets data from DTrace by registering a {@link 39 * ConsumerListener listener} to get probe data whenever a probe fires: 40 * <pre><code> 41 * Consumer consumer = new LocalConsumer(); 42 * consumer.addConsumerListener(new ConsumerAdapter() { 43 * public void dataReceived(DataEvent e) { 44 * ProbeData probeData = e.getProbeData(); 45 * System.out.println(probeData); 46 * } 47 * }); 48 * </code></pre> 49 * Getting DTrace to generate that probe data involves compiling, 50 * enabling, and running a D program: 51 * <pre><code> 52 * try { 53 * consumer.open(); 54 * consumer.compile(program); 55 * consumer.enable(); // instruments code at matching probe points 56 * consumer.go(); // non-blocking; generates probe data in background 57 * } catch (DTraceException e) { 58 * e.printStackTrace(); 59 * } 60 * </code></pre> 61 * Currently the {@code ProbeData} instance does not record a timestamp. 62 * If you need a timestamp, trace the built-in {@code timestamp} 63 * variable in your D program. (See the 64 * <a href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidlfv?a=view> 65 * <b>Built-in Variables</b></a> section of the <b>Variables</b> chapter of 66 * the <i>Solaris Dynamic Tracing Guide</i>). 67 * <p> 68 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 69 * 70 * @see Consumer#addConsumerListener(ConsumerListener l) 71 * @see ConsumerListener#dataReceived(DataEvent e) 72 * 73 * @author Tom Erickson 74 */ 75 public final class ProbeData implements Serializable, Comparable <ProbeData> { 76 static final long serialVersionUID = -7021504416192099215L; 77 78 static { 79 try { 80 BeanInfo info = Introspector.getBeanInfo(ProbeData.class); 81 PersistenceDelegate persistenceDelegate = 82 new DefaultPersistenceDelegate( 83 new String[] {"enabledProbeID", "CPU", 84 "enabledProbeDescription", "flow", "records"}); 85 BeanDescriptor d = info.getBeanDescriptor(); 86 d.setValue("persistenceDelegate", persistenceDelegate); 87 } catch (IntrospectionException e) { 88 System.out.println(e); 89 } 90 } 91 92 private static Comparator <ProbeData> DEFAULT_CMP; 93 94 static { 95 try { 96 DEFAULT_CMP = ProbeData.getComparator(KeyField.RECORDS, 97 KeyField.EPID); 98 } catch (Throwable e) { 99 e.printStackTrace(); 100 System.exit(1); 101 } 102 } 103 104 /** @serial */ 105 private int epid; 106 /** @serial */ 107 private int cpu; 108 /** @serial */ 109 private ProbeDescription enabledProbeDescription; 110 /** @serial */ 111 private Flow flow; 112 // Scratch data, one element per native probedata->dtpda_edesc->dtepd_nrecs 113 // element, cleared after records list is fully populated. 114 private transient List <Object> nativeElements; 115 /** @serial */ 116 private List <Record> records; 117 118 /** 119 * Enumerates the fields by which {@link ProbeData} may be sorted 120 * using the {@link #getComparator(KeyField[] f) getComparator()} 121 * convenience method. 122 */ 123 public enum KeyField { 124 /** Specifies {@link ProbeData#getCPU()} */ 125 CPU, 126 /** Specifies {@link ProbeData#getEnabledProbeDescription()} */ 127 PROBE, 128 /** Specifies {@link ProbeData#getEnabledProbeID()} */ 129 EPID, 130 /** Specifies {@link ProbeData#getRecords()} */ 131 RECORDS 132 } 133 134 /** 135 * Called by native code. 136 */ 137 private 138 ProbeData(int enabledProbeID, int cpuID, ProbeDescription p, 139 Flow f, int nativeElementCount) 140 { 141 epid = enabledProbeID; 142 cpu = cpuID; 143 enabledProbeDescription = p; 144 flow = f; 145 nativeElements = new ArrayList <Object> (nativeElementCount); 146 records = new ArrayList <Record> (); 147 validate(); 148 } 149 150 /** 151 * Creates a probe data instance with the given properties and list 152 * of records. Supports XML persistence. 153 * 154 * @param enabledProbeID identifies the enabled probe that fired; 155 * the ID is generated by the native DTrace library to distinguish 156 * all probes enabled by the source consumer (as opposed to 157 * all probes on the system) 158 * @param cpuID non-negative ID, identifies the CPU on which the 159 * probe fired 160 * @param p identifies the enabled probe that fired 161 * @param f current state of control flow (entry or return and depth 162 * in call stack) at time of probe firing, included if {@link 163 * Option#flowindent flowindent} option used, {@code null} otherwise 164 * @param recordList list of records generated by D actions in the 165 * probe that fired, one record per action, may be empty 166 * @throws NullPointerException if the given probe description or 167 * list of records is {@code null} 168 */ 169 public 170 ProbeData(int enabledProbeID, int cpuID, ProbeDescription p, 171 Flow f, List <Record> recordList) 172 { 173 epid = enabledProbeID; 174 cpu = cpuID; 175 enabledProbeDescription = p; 176 flow = f; 177 records = new ArrayList <Record> (recordList.size()); 178 records.addAll(recordList); 179 validate(); 180 } 181 182 private void 183 validate() 184 { 185 if (enabledProbeDescription == null) { 186 throw new NullPointerException( 187 "enabled probe description is null"); 188 } 189 if (records == null) { 190 throw new NullPointerException("record list is null"); 191 } 192 } 193 194 private void 195 addDataElement(Object o) 196 { 197 nativeElements.add(o); 198 } 199 200 /** 201 * Called by native code. 202 */ 203 private void 204 addRecord(Record record) 205 { 206 records.add(record); 207 } 208 209 /** 210 * Called by native code. 211 */ 212 private void 213 addTraceRecord(int i) 214 { 215 // trace() value is preceded by one null for every D program 216 // statement preceding trace() that is not a D action, such as 217 // assignment to a variable (results in a native probedata 218 // record with no data). 219 int len = nativeElements.size(); 220 Object o = null; 221 for (; ((o = nativeElements.get(i)) == null) && (i < len); ++i); 222 records.add(new ScalarRecord(o)); 223 } 224 225 /** 226 * Called by native code. 227 */ 228 private void 229 addStackRecord(int i, String framesString) 230 { 231 int len = nativeElements.size(); 232 Object o = null; 233 for (; ((o = nativeElements.get(i)) == null) && (i < len); ++i); 234 StackValueRecord stack = (StackValueRecord)o; 235 StackFrame[] frames = KernelStackRecord.parse(framesString); 236 if (stack instanceof KernelStackRecord) { 237 ((KernelStackRecord)stack).setStackFrames(frames); 238 } else if (stack instanceof UserStackRecord) { 239 ((UserStackRecord)stack).setStackFrames(frames); 240 } else { 241 throw new IllegalStateException("no stack record at index " + i); 242 } 243 records.add(stack); 244 } 245 246 /** 247 * Called by native code. 248 */ 249 private void 250 addPrintfRecord() 251 { 252 records.add(new PrintfRecord()); 253 } 254 255 /** 256 * Called by native code. 257 */ 258 private void 259 addPrintaRecord(long snaptimeNanos, boolean isFormatString) 260 { 261 records.add(new PrintaRecord(snaptimeNanos, isFormatString)); 262 } 263 264 private PrintaRecord 265 getLastPrinta() 266 { 267 ListIterator <Record> itr = records.listIterator(records.size()); 268 PrintaRecord printa = null; 269 Record record; 270 while (itr.hasPrevious() && (printa == null)) { 271 record = itr.previous(); 272 if (record instanceof PrintaRecord) { 273 printa = (PrintaRecord)record; 274 } 275 } 276 return printa; 277 } 278 279 /** 280 * Called by native code. 281 */ 282 private void 283 addAggregationRecord(String aggregationName, long aggid, 284 AggregationRecord rec) 285 { 286 PrintaRecord printa = getLastPrinta(); 287 if (printa == null) { 288 throw new IllegalStateException( 289 "No PrintaRecord in this ProbeData"); 290 } 291 printa.addRecord(aggregationName, aggid, rec); 292 } 293 294 /** 295 * Called by native code. 296 */ 297 private void 298 invalidatePrintaRecord() 299 { 300 PrintaRecord printa = getLastPrinta(); 301 if (printa == null) { 302 throw new IllegalStateException( 303 "No PrintaRecord in this ProbeData"); 304 } 305 printa.invalidate(); 306 } 307 308 /** 309 * Called by native code. 310 */ 311 private void 312 addPrintaFormattedString(Tuple tuple, String s) 313 { 314 PrintaRecord printa = getLastPrinta(); 315 if (printa == null) { 316 throw new IllegalStateException( 317 "No PrintaRecord in this ProbeData"); 318 } 319 printa.addFormattedString(tuple, s); 320 } 321 322 /** 323 * Called by native code. 324 */ 325 private void 326 addExitRecord(int i) 327 { 328 int len = nativeElements.size(); 329 Object o = null; 330 for (; ((o = nativeElements.get(i)) == null) && (i < len); ++i); 331 Integer exitStatus = (Integer)o; 332 records.add(new ExitRecord(exitStatus)); 333 } 334 335 /** 336 * Called by native code. Attaches native probedata elements cached 337 * between the given first index and last index inclusive to the most 338 * recently added record if applicable. 339 */ 340 private void 341 attachRecordElements(int first, int last) 342 { 343 Record record = records.get(records.size() - 1); 344 if (record instanceof PrintfRecord) { 345 PrintfRecord printf = (PrintfRecord)record; 346 Object e; 347 for (int i = first; i <= last; ++i) { 348 e = nativeElements.get(i); 349 if (e == null) { 350 // printf() unformatted elements are preceded by one 351 // null for every D program statement preceding the 352 // printf() that is not a D action, such as 353 // assignment to a variable (generates a probedata 354 // record with no data). 355 continue; 356 } 357 printf.addUnformattedElement(e); 358 } 359 } 360 } 361 362 /** 363 * Called by native code. 364 */ 365 void 366 clearNativeElements() 367 { 368 nativeElements = null; 369 } 370 371 /** 372 * Called by native code. 373 */ 374 private void 375 setFormattedString(String s) 376 { 377 Record record = records.get(records.size() - 1); 378 if (record instanceof PrintfRecord) { 379 PrintfRecord printf = (PrintfRecord)record; 380 printf.setFormattedString(s); 381 } 382 } 383 384 /** 385 * Convenience method, gets a comparator that sorts multiple {@link 386 * ProbeDescription} instances by the specified field or fields. If 387 * more than one sort field is specified, the probe data are sorted 388 * by the first field, and in case of a tie, by the second field, 389 * and so on, in the order that the fields are specified. 390 * 391 * @param f field specifiers given in descending order of sort 392 * priority; lower priority fields are only compared (as a tie 393 * breaker) when all higher priority fields are equal 394 * @return non-null probe data comparator that sorts by the 395 * specified sort fields in the given order 396 */ 397 public static Comparator <ProbeData> 398 getComparator(KeyField ... f) 399 { 400 return new Cmp(f); 401 } 402 403 private static class Cmp implements Comparator <ProbeData> { 404 private KeyField[] sortFields; 405 406 private 407 Cmp(KeyField ... f) 408 { 409 sortFields = f; 410 } 411 412 public int 413 compare(ProbeData d1, ProbeData d2) 414 { 415 return ProbeData.compare(d1, d2, sortFields); 416 } 417 } 418 419 /** 420 * @throws ClassCastException if records or their data are are not 421 * mutually comparable 422 */ 423 @SuppressWarnings("unchecked") 424 private static int 425 compareRecords(Record r1, Record r2) 426 { 427 int cmp; 428 if (r1 instanceof ScalarRecord) { 429 ScalarRecord t1 = ScalarRecord.class.cast(r1); 430 ScalarRecord t2 = ScalarRecord.class.cast(r2); 431 Comparable v1 = Comparable.class.cast(t1.getValue()); 432 Comparable v2 = Comparable.class.cast(t2.getValue()); 433 cmp = v1.compareTo(v2); 434 } else if (r1 instanceof PrintfRecord) { 435 PrintfRecord t1 = PrintfRecord.class.cast(r1); 436 PrintfRecord t2 = PrintfRecord.class.cast(r2); 437 String s1 = t1.toString(); 438 String s2 = t2.toString(); 439 cmp = s1.compareTo(s2); 440 } else if (r1 instanceof ExitRecord) { 441 ExitRecord e1 = ExitRecord.class.cast(r1); 442 ExitRecord e2 = ExitRecord.class.cast(r2); 443 int status1 = e1.getStatus(); 444 int status2 = e2.getStatus(); 445 cmp = (status1 < status2 ? -1 : (status1 > status2 ? 1 : 0)); 446 } else { 447 throw new IllegalArgumentException("Unexpected record type: " + 448 r1.getClass()); 449 } 450 451 return cmp; 452 } 453 454 /** 455 * @throws ClassCastException if lists are not mutually comparable 456 * because corresponding list elements are not comparable or the 457 * list themselves are different lengths 458 */ 459 private static int 460 compareRecordLists(ProbeData d1, ProbeData d2) 461 { 462 List <Record> list1 = d1.getRecords(); 463 List <Record> list2 = d2.getRecords(); 464 int len1 = list1.size(); 465 int len2 = list2.size(); 466 if (len1 != len2) { 467 throw new ClassCastException("Record lists of different " + 468 "length are not comparable (lengths are " + 469 len1 + " and " + len2 + ")."); 470 } 471 472 int cmp; 473 Record r1; 474 Record r2; 475 476 for (int i = 0; (i < len1) && (i < len2); ++i) { 477 r1 = list1.get(i); 478 r2 = list2.get(i); 479 480 cmp = compareRecords(r1, r2); 481 if (cmp != 0) { 482 return cmp; 483 } 484 } 485 486 return 0; 487 } 488 489 private static int 490 compare(ProbeData d1, ProbeData d2, KeyField[] comparedFields) 491 { 492 int cmp; 493 for (KeyField f : comparedFields) { 494 switch (f) { 495 case CPU: 496 int cpu1 = d1.getCPU(); 497 int cpu2 = d2.getCPU(); 498 cmp = (cpu1 < cpu2 ? -1 : (cpu1 > cpu2 ? 1 : 0)); 499 break; 500 case PROBE: 501 ProbeDescription p1 = d1.getEnabledProbeDescription(); 502 ProbeDescription p2 = d2.getEnabledProbeDescription(); 503 cmp = p1.compareTo(p2); 504 break; 505 case EPID: 506 int epid1 = d1.getEnabledProbeID(); 507 int epid2 = d2.getEnabledProbeID(); 508 cmp = (epid1 < epid2 ? -1 : (epid1 > epid2 ? 1 : 0)); 509 break; 510 case RECORDS: 511 cmp = compareRecordLists(d1, d2); 512 break; 513 default: 514 throw new IllegalArgumentException( 515 "Unexpected sort field " + f); 516 } 517 518 if (cmp != 0) { 519 return cmp; 520 } 521 } 522 523 return 0; 524 } 525 526 /** 527 * Gets the enabled probe ID. Identifies the enabled probe that 528 * fired and generated this {@code ProbeData}. The "epid" is 529 * different from {@link ProbeDescription#getID()} in that it 530 * identifies a probe among all probes enabled by the source {@link 531 * Consumer}, rather than among all the probes on the system. 532 * 533 * @return the enabled probe ID generated by the native DTrace 534 * library 535 */ 536 public int 537 getEnabledProbeID() 538 { 539 return epid; 540 } 541 542 /** 543 * Gets the ID of the CPU on which the probe fired. 544 * 545 * @return ID of the CPU on which the probe fired 546 */ 547 public int 548 getCPU() 549 { 550 return cpu; 551 } 552 553 /** 554 * Gets the enabled probe description. Identifies the enabled probe 555 * that fired and generated this {@code ProbeData}. 556 * 557 * @return non-null probe description 558 */ 559 public ProbeDescription 560 getEnabledProbeDescription() 561 { 562 return enabledProbeDescription; 563 } 564 565 /** 566 * Gets the current state of control flow (function entry or return, 567 * and depth in call stack) at the time of the probe firing that 568 * generated this {@code ProbeData} instance, or {@code null} if 569 * such information was not requested with the {@code flowindent} 570 * option. 571 * 572 * @return a description of control flow across function boundaries, 573 * or {@code null} if {@code Consumer.getOption(Option.flowindent)} 574 * returns {@link Option#UNSET} 575 * @see Consumer#setOption(String option) 576 * @see Option#flowindent 577 */ 578 public Flow 579 getFlow() 580 { 581 return flow; 582 } 583 584 /** 585 * Gets the records generated by the actions of the probe that 586 * fired, in the same order as the actions that generated the 587 * records. The returned list includes one record for every 588 * record-generating D action (some D actions, such as {@code 589 * clear()}, do not generate records). 590 * 591 * @return non-null, unmodifiable list view of the records belonging 592 * to this {@code ProbeData} in the order of the actions in the 593 * DTrace probe that generated them (record-producing actions are 594 * generally those that produce output, such as {@code printf()}, 595 * but also the {@code exit()} action) 596 */ 597 public List <Record> 598 getRecords() 599 { 600 return Collections.unmodifiableList(records); 601 } 602 603 /** 604 * Natural ordering of probe data. Sorts probe data by records 605 * first, then if record data is equal, by enabled probe ID. 606 * 607 * @param d probe data to be compared with this probe data 608 * @return a negative number, zero, or a positive number as this 609 * probe data is less than, equal to, or greater than the given 610 * probe data 611 * @see ProbeData#getComparator(KeyField[] f) 612 * @throws NullPointerException if the given probe data is 613 * {@code null} 614 * @throws ClassCastException if record lists of both {@code 615 * ProbeData} instances are not mutually comparable because 616 * corresponding list elements are not comparable or the lists 617 * themselves are different lengths 618 */ 619 public int 620 compareTo(ProbeData d) 621 { 622 return DEFAULT_CMP.compare(this, d); 623 } 624 625 private void 626 readObject(ObjectInputStream s) 627 throws IOException, ClassNotFoundException 628 { 629 s.defaultReadObject(); 630 // Defensively copy record list _before_ validating. 631 int len = records.size(); 632 ArrayList <Record> copy = new ArrayList <Record> (len); 633 copy.addAll(records); 634 records = copy; 635 // Check class invariants 636 try { 637 validate(); 638 } catch (Exception e) { 639 throw new InvalidObjectException(e.getMessage()); 640 } 641 } 642 643 /** 644 * Gets a string representation of this {@code ProbeData} instance 645 * useful for logging and not intended for display. The exact 646 * details of the representation are unspecified and subject to 647 * change, but the following format may be regarded as typical: 648 * <pre><code> 649 * class-name[property1 = value1, property2 = value2] 650 * </code></pre> 651 */ 652 public String 653 toString() 654 { 655 StringBuffer buf = new StringBuffer(); 656 buf.append(ProbeData.class.getName()); 657 buf.append("[epid = "); 658 buf.append(epid); 659 buf.append(", cpu = "); 660 buf.append(cpu); 661 buf.append(", enabledProbeDescription = "); 662 buf.append(enabledProbeDescription); 663 buf.append(", flow = "); 664 buf.append(flow); 665 buf.append(", records = "); 666 667 Record record; 668 Object value; 669 buf.append('['); 670 for (int i = 0; i < records.size(); ++i) { 671 if (i > 0) { 672 buf.append(", "); 673 } 674 record = records.get(i); 675 if (record instanceof ValueRecord) { 676 value = ((ValueRecord)record).getValue(); 677 if (value instanceof String) { 678 buf.append("\""); 679 buf.append((String)value); 680 buf.append("\""); 681 } else { 682 buf.append(record); 683 } 684 } else { 685 buf.append(record); 686 } 687 } 688 buf.append(']'); 689 690 buf.append(']'); 691 return buf.toString(); 692 } 693 } 694