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