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 2007 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 <Record> 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 <Record> (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 final 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(Record o)
196     {
197 	// Early error detection if native code adds the wrong type
198 	Record r = Record.class.cast(o);
199 
200 	nativeElements.add(o);
201     }
202 
203     /**
204      * Called by native code.
205      */
206     private void
207     addRecord(Record record)
208     {
209 	records.add(record);
210     }
211 
212     /**
213      * Called by native code.
214      */
215     private void
216     addTraceRecord(int i)
217     {
218 	// trace() value is preceded by one null for every D program
219 	// statement preceding trace() that is not a D action, such as
220 	// assignment to a variable (results in a native probedata
221 	// record with no data).
222 	int len = nativeElements.size();
223 	Record rec = null;
224 	for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i);
225 	records.add(rec);
226     }
227 
228     /**
229      * Called by native code.
230      */
231     private void
232     addSymbolRecord(int i, String lookupString)
233     {
234 	int len = nativeElements.size();
235 	Record rec = null;
236 	for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i);
237 	SymbolValueRecord symbol = SymbolValueRecord.class.cast(rec);
238 	if (symbol instanceof KernelSymbolRecord) {
239 	    KernelSymbolRecord.class.cast(symbol).setSymbol(lookupString);
240 	} else if (symbol instanceof UserSymbolRecord) {
241 	    UserSymbolRecord.class.cast(symbol).setSymbol(lookupString);
242 	} else {
243 	    throw new IllegalStateException("no symbol record at index " + i);
244 	}
245 	records.add(symbol);
246     }
247 
248     /**
249      * Called by native code.
250      */
251     private void
252     addStackRecord(int i, String framesString)
253     {
254 	int len = nativeElements.size();
255 	Record rec = null;
256 	for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i);
257 	StackValueRecord stack = StackValueRecord.class.cast(rec);
258 	StackFrame[] frames = KernelStackRecord.parse(framesString);
259 	if (stack instanceof KernelStackRecord) {
260 	    KernelStackRecord.class.cast(stack).setStackFrames(frames);
261 	} else if (stack instanceof UserStackRecord) {
262 	    UserStackRecord.class.cast(stack).setStackFrames(frames);
263 	} else {
264 	    throw new IllegalStateException("no stack record at index " + i);
265 	}
266 	records.add(stack);
267     }
268 
269     /**
270      * Called by native code.
271      */
272     private void
273     addPrintfRecord()
274     {
275 	records.add(new PrintfRecord());
276     }
277 
278     /**
279      * Called by native code.
280      */
281     private void
282     addPrintaRecord(long snaptimeNanos, boolean isFormatString)
283     {
284 	records.add(new PrintaRecord(snaptimeNanos, isFormatString));
285     }
286 
287     private PrintaRecord
288     getLastPrinta()
289     {
290 	ListIterator <Record> itr = records.listIterator(records.size());
291 	PrintaRecord printa = null;
292 	Record record;
293 	while (itr.hasPrevious() && (printa == null)) {
294 	    record = itr.previous();
295 	    if (record instanceof PrintaRecord) {
296 		printa = PrintaRecord.class.cast(record);
297 	    }
298 	}
299 	return printa;
300     }
301 
302     /**
303      * Called by native code.
304      */
305     private void
306     addAggregationRecord(String aggregationName, long aggid,
307 	    AggregationRecord rec)
308     {
309 	PrintaRecord printa = getLastPrinta();
310 	if (printa == null) {
311 	    throw new IllegalStateException(
312 		    "No PrintaRecord in this ProbeData");
313 	}
314 	printa.addRecord(aggregationName, aggid, rec);
315     }
316 
317     /**
318      * Called by native code.
319      */
320     private void
321     invalidatePrintaRecord()
322     {
323 	PrintaRecord printa = getLastPrinta();
324 	if (printa == null) {
325 	    throw new IllegalStateException(
326 		    "No PrintaRecord in this ProbeData");
327 	}
328 	printa.invalidate();
329     }
330 
331     /**
332      * Called by native code.
333      */
334     private void
335     addPrintaFormattedString(Tuple tuple, String s)
336     {
337 	PrintaRecord printa = getLastPrinta();
338 	if (printa == null) {
339 	    throw new IllegalStateException(
340 		    "No PrintaRecord in this ProbeData");
341 	}
342 	printa.addFormattedString(tuple, s);
343     }
344 
345     /**
346      * Called by native code.
347      */
348     private void
349     addExitRecord(int i)
350     {
351 	int len = nativeElements.size();
352 	Record rec = null;
353 	for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i);
354 	ScalarRecord scalar = ScalarRecord.class.cast(rec);
355 	Integer exitStatus = Integer.class.cast(scalar.getValue());
356 	records.add(new ExitRecord(exitStatus));
357     }
358 
359     /**
360      * Called by native code.  Attaches native probedata elements cached
361      * between the given first index and last index inclusive to the most
362      * recently added record if applicable.
363      */
364     private void
365     attachRecordElements(int first, int last)
366     {
367 	Record record = records.get(records.size() - 1);
368 	if (record instanceof PrintfRecord) {
369 	    PrintfRecord printf = PrintfRecord.class.cast(record);
370 	    Record e;
371 	    for (int i = first; i <= last; ++i) {
372 		e = nativeElements.get(i);
373 		if (e == null) {
374 		    // printf() unformatted elements are preceded by one
375 		    // null for every D program statement preceding the
376 		    // printf() that is not a D action, such as
377 		    // assignment to a variable (generates a probedata
378 		    // record with no data).
379 		    continue;
380 		}
381 		printf.addUnformattedElement(ScalarRecord.class.cast(e));
382 	    }
383 	}
384     }
385 
386     /**
387      * Called by native code.
388      */
389     void
390     clearNativeElements()
391     {
392 	nativeElements = null;
393     }
394 
395     /**
396      * Called by native code.
397      */
398     private void
399     setFormattedString(String s)
400     {
401 	Record record = records.get(records.size() - 1);
402 	if (record instanceof PrintfRecord) {
403 	    PrintfRecord printf = PrintfRecord.class.cast(record);
404 	    printf.setFormattedString(s);
405 	}
406     }
407 
408     /**
409      * Convenience method, gets a comparator that sorts multiple {@link
410      * ProbeDescription} instances by the specified field or fields.  If
411      * more than one sort field is specified, the probe data are sorted
412      * by the first field, and in case of a tie, by the second field,
413      * and so on, in the order that the fields are specified.
414      *
415      * @param f field specifiers given in descending order of sort
416      * priority; lower priority fields are only compared (as a tie
417      * breaker) when all higher priority fields are equal
418      * @return non-null probe data comparator that sorts by the
419      * specified sort fields in the given order
420      */
421     public static Comparator <ProbeData>
422     getComparator(KeyField ... f)
423     {
424 	return new Cmp(f);
425     }
426 
427     private static class Cmp implements Comparator <ProbeData> {
428 	private KeyField[] sortFields;
429 
430 	private
431 	Cmp(KeyField ... f)
432 	{
433 	    sortFields = f;
434 	}
435 
436 	public int
437 	compare(ProbeData d1, ProbeData d2)
438 	{
439 	    return ProbeData.compare(d1, d2, sortFields);
440 	}
441     }
442 
443     static int
444     compareUnsigned(int i1, int i2)
445     {
446 	int cmp;
447 
448 	if (i1 < 0) {
449 	    if (i2 < 0) {
450 		cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
451 	    } else {
452 		cmp = 1; // negative > positive
453 	    }
454 	} else if (i2 < 0) {
455 	    cmp = -1; // positive < negative
456 	} else {
457 	    cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
458 	}
459 
460 	return cmp;
461     }
462 
463     static int
464     compareUnsigned(long i1, long i2)
465     {
466 	int cmp;
467 
468 	if (i1 < 0) {
469 	    if (i2 < 0) {
470 		cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
471 	    } else {
472 		cmp = 1; // negative > positive
473 	    }
474 	} else if (i2 < 0) {
475 	    cmp = -1; // positive < negative
476 	} else {
477 	    cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
478 	}
479 
480 	return cmp;
481     }
482 
483     static int
484     compareUnsigned(byte i1, byte i2)
485     {
486 	int cmp;
487 
488 	if (i1 < 0) {
489 	    if (i2 < 0) {
490 		cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
491 	    } else {
492 		cmp = 1; // negative > positive
493 	    }
494 	} else if (i2 < 0) {
495 	    cmp = -1; // positive < negative
496 	} else {
497 	    cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
498 	}
499 
500 	return cmp;
501     }
502 
503     static int
504     compareByteArrays(byte[] a1, byte[] a2)
505     {
506 	int cmp = 0;
507 	int len1 = a1.length;
508 	int len2 = a2.length;
509 
510 	for (int i = 0; (cmp == 0) && (i < len1) && (i < len2); ++i) {
511 	    cmp = compareUnsigned(a1[i], a2[i]);
512 	}
513 
514 	if (cmp == 0) {
515 	    cmp = (len1 < len2 ? -1 : (len1 > len2 ? 1 : 0));
516 	}
517 
518 	return cmp;
519     }
520 
521     @SuppressWarnings("unchecked")
522     static int
523     compareUnsigned(Comparable v1, Comparable v2)
524     {
525 	int cmp;
526 
527 	if (v1 instanceof Integer) {
528 	    int i1 = Integer.class.cast(v1);
529 	    int i2 = Integer.class.cast(v2);
530 	    cmp = compareUnsigned(i1, i2);
531 	} else if (v1 instanceof Long) {
532 	    long i1 = Long.class.cast(v1);
533 	    long i2 = Long.class.cast(v2);
534 	    cmp = compareUnsigned(i1, i2);
535 	} else {
536 	    cmp = v1.compareTo(v2);
537 	}
538 
539 	return cmp;
540     }
541 
542     /**
543      * @throws ClassCastException if records or their data are not
544      * mutually comparable
545      */
546     @SuppressWarnings("unchecked")
547     private static int
548     compareRecords(Record r1, Record r2)
549     {
550 	int cmp;
551 	if (r1 instanceof ScalarRecord) {
552 	    ScalarRecord t1 = ScalarRecord.class.cast(r1);
553 	    ScalarRecord t2 = ScalarRecord.class.cast(r2);
554 	    Object o1 = t1.getValue();
555 	    Object o2 = t2.getValue();
556 	    if (o1 instanceof byte[]) {
557 		byte[] a1 = byte[].class.cast(o1);
558 		byte[] a2 = byte[].class.cast(o2);
559 		cmp = compareByteArrays(a1, a2);
560 	    } else {
561 		Comparable v1 = Comparable.class.cast(o1);
562 		Comparable v2 = Comparable.class.cast(o2);
563 		cmp = v1.compareTo(v2); // compare signed values
564 	    }
565 	} else if (r1 instanceof Comparable) {
566 	    // StackValueRecord, SymbolValueRecord
567 	    Comparable v1 = Comparable.class.cast(r1);
568 	    Comparable v2 = Comparable.class.cast(r2);
569 	    cmp = v1.compareTo(v2);
570 	} else if (r1 instanceof ExitRecord) {
571 	    ExitRecord e1 = ExitRecord.class.cast(r1);
572 	    ExitRecord e2 = ExitRecord.class.cast(r2);
573 	    int status1 = e1.getStatus();
574 	    int status2 = e2.getStatus();
575 	    cmp = (status1 < status2 ? -1 : (status1 > status2 ? 1 : 0));
576 	} else {
577 	    // PrintfRecord, PrintaRecord
578 	    r1.getClass().cast(r2);
579 	    String s1 = r1.toString();
580 	    String s2 = r2.toString();
581 	    cmp = s1.compareTo(s2);
582 	}
583 
584 	return cmp;
585     }
586 
587     /**
588      * @throws ClassCastException if lists are not mutually comparable
589      * because corresponding list elements are not comparable or the
590      * list themselves are different lengths
591      */
592     private static int
593     compareRecordLists(ProbeData d1, ProbeData d2)
594     {
595 	List <Record> list1 = d1.getRecords();
596 	List <Record> list2 = d2.getRecords();
597 	int len1 = list1.size();
598 	int len2 = list2.size();
599 	if (len1 != len2) {
600 	    throw new ClassCastException("Record lists of different " +
601 		    "length are not comparable (lengths are " +
602 		    len1 + " and " + len2 + ").");
603 	}
604 
605 	int cmp;
606 	Record r1;
607 	Record r2;
608 
609 	for (int i = 0; (i < len1) && (i < len2); ++i) {
610 	    r1 = list1.get(i);
611 	    r2 = list2.get(i);
612 
613 	    cmp = compareRecords(r1, r2);
614 	    if (cmp != 0) {
615 		return cmp;
616 	    }
617 	}
618 
619 	return 0;
620     }
621 
622     private static int
623     compare(ProbeData d1, ProbeData d2, KeyField[] comparedFields)
624     {
625 	int cmp;
626 	for (KeyField f : comparedFields) {
627 	    switch (f) {
628 		case CPU:
629 		    int cpu1 = d1.getCPU();
630 		    int cpu2 = d2.getCPU();
631 		    cmp = (cpu1 < cpu2 ? -1 : (cpu1 > cpu2 ? 1 : 0));
632 		    break;
633 		case PROBE:
634 		    ProbeDescription p1 = d1.getEnabledProbeDescription();
635 		    ProbeDescription p2 = d2.getEnabledProbeDescription();
636 		    cmp = p1.compareTo(p2);
637 		    break;
638 		case EPID:
639 		    int epid1 = d1.getEnabledProbeID();
640 		    int epid2 = d2.getEnabledProbeID();
641 		    cmp = (epid1 < epid2 ? -1 : (epid1 > epid2 ? 1 : 0));
642 		    break;
643 		case RECORDS:
644 		    cmp = compareRecordLists(d1, d2);
645 		    break;
646 		default:
647 		    throw new IllegalArgumentException(
648 			    "Unexpected sort field " + f);
649 	    }
650 
651 	    if (cmp != 0) {
652 		return cmp;
653 	    }
654 	}
655 
656 	return 0;
657     }
658 
659     /**
660      * Gets the enabled probe ID.  Identifies the enabled probe that
661      * fired and generated this {@code ProbeData}.  The "epid" is
662      * different from {@link ProbeDescription#getID()} in that it
663      * identifies a probe among all probes enabled by the source {@link
664      * Consumer}, rather than among all the probes on the system.
665      *
666      * @return the enabled probe ID generated by the native DTrace
667      * library
668      */
669     public int
670     getEnabledProbeID()
671     {
672 	return epid;
673     }
674 
675     /**
676      * Gets the ID of the CPU on which the probe fired.
677      *
678      * @return ID of the CPU on which the probe fired
679      */
680     public int
681     getCPU()
682     {
683 	return cpu;
684     }
685 
686     /**
687      * Gets the enabled probe description.  Identifies the enabled probe
688      * that fired and generated this {@code ProbeData}.
689      *
690      * @return non-null probe description
691      */
692     public ProbeDescription
693     getEnabledProbeDescription()
694     {
695 	return enabledProbeDescription;
696     }
697 
698     /**
699      * Gets the current state of control flow (function entry or return,
700      * and depth in call stack) at the time of the probe firing that
701      * generated this {@code ProbeData} instance, or {@code null} if
702      * such information was not requested with the {@code flowindent}
703      * option.
704      *
705      * @return a description of control flow across function boundaries,
706      * or {@code null} if {@code Consumer.getOption(Option.flowindent)}
707      * returns {@link Option#UNSET}
708      * @see Consumer#setOption(String option)
709      * @see Option#flowindent
710      */
711     public Flow
712     getFlow()
713     {
714 	return flow;
715     }
716 
717     /**
718      * Gets the records generated by the actions of the probe that
719      * fired, in the same order as the actions that generated the
720      * records.  The returned list includes one record for every
721      * record-generating D action (some D actions, such as {@code
722      * clear()}, do not generate records).
723      *
724      * @return non-null, unmodifiable list view of the records belonging
725      * to this {@code ProbeData} in the order of the actions in the
726      * DTrace probe that generated them (record-producing actions are
727      * generally those that produce output, such as {@code printf()},
728      * but also the {@code exit()} action)
729      */
730     public List <Record>
731     getRecords()
732     {
733 	return Collections.unmodifiableList(records);
734     }
735 
736     /**
737      * Natural ordering of probe data.  Sorts probe data by records
738      * first, then if record data is equal, by enabled probe ID.
739      *
740      * @param d probe data to be compared with this probe data
741      * @return a negative number, zero, or a positive number as this
742      * probe data is less than, equal to, or greater than the given
743      * probe data
744      * @see ProbeData#getComparator(KeyField[] f)
745      * @throws NullPointerException if the given probe data is
746      * {@code null}
747      * @throws ClassCastException if record lists of both {@code
748      * ProbeData} instances are not mutually comparable because
749      * corresponding list elements are not comparable or the lists
750      * themselves are different lengths
751      */
752     public int
753     compareTo(ProbeData d)
754     {
755 	return DEFAULT_CMP.compare(this, d);
756     }
757 
758     private void
759     readObject(ObjectInputStream s)
760             throws IOException, ClassNotFoundException
761     {
762 	s.defaultReadObject();
763 	// Defensively copy record list _before_ validating.
764 	int len = records.size();
765 	ArrayList <Record> copy = new ArrayList <Record> (len);
766 	copy.addAll(records);
767 	records = copy;
768 	// Check class invariants
769 	try {
770 	    validate();
771 	} catch (Exception e) {
772 	    InvalidObjectException x = new InvalidObjectException(
773 		    e.getMessage());
774 	    x.initCause(e);
775 	    throw x;
776 	}
777     }
778 
779     /**
780      * Gets a string representation of this {@code ProbeData} instance
781      * useful for logging and not intended for display.  The exact
782      * details of the representation are unspecified and subject to
783      * change, but the following format may be regarded as typical:
784      * <pre><code>
785      * class-name[property1 = value1, property2 = value2]
786      * </code></pre>
787      */
788     public String
789     toString()
790     {
791 	StringBuilder buf = new StringBuilder();
792 	buf.append(ProbeData.class.getName());
793 	buf.append("[epid = ");
794 	buf.append(epid);
795 	buf.append(", cpu = ");
796 	buf.append(cpu);
797 	buf.append(", enabledProbeDescription = ");
798 	buf.append(enabledProbeDescription);
799 	buf.append(", flow = ");
800 	buf.append(flow);
801 	buf.append(", records = ");
802 
803 	Record record;
804 	Object value;
805 	buf.append('[');
806 	for (int i = 0; i < records.size(); ++i) {
807 	    if (i > 0) {
808 		buf.append(", ");
809 	    }
810 	    record = records.get(i);
811 	    if (record instanceof ValueRecord) {
812 		value = ValueRecord.class.cast(record).getValue();
813 		if (value instanceof String) {
814 		    buf.append("\"");
815 		    buf.append(String.class.cast(value));
816 		    buf.append("\"");
817 		} else {
818 		    buf.append(record);
819 		}
820 	    } else {
821 		buf.append(record);
822 	    }
823 	}
824 	buf.append(']');
825 
826 	buf.append(']');
827 	return buf.toString();
828     }
829 }
830