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