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 import org.opensolaris.os.dtrace.*;
29 import java.io.*;
30 import java.util.*;
31 import java.util.logging.*;
32 
33 /**
34  * Emulates {@code dtrace(1M)} using the Java DTrace API.
35  */
36 public class JDTrace {
37     static Logger logger = Logger.getLogger(JDTrace.class.getName());
38 
39     static Consumer dtrace;
40 
41     static {
42 	Handler handler = new ConsoleHandler();
43 	handler.setLevel(Level.ALL);
44 	logger.addHandler(handler);
45     }
46 
47     static final String CLASSNAME = "JDTrace";
48     static final String OPTSTR =
49 	    "3:6:b:c:CD:ef:Fi:I:lL:m:n:o:p:P:qs:U:vVwx:X:Z";
50     static boolean heading = false;
51     static boolean quiet = false;
52     static boolean flow = false;
53     static int stackindent = 14;
54     static int exitStatus = 0;
55     static boolean started;
56     static boolean stopped;
57     static PrintStream out = System.out;
58     static final String ATS = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";
59     static final String SPACES = "                                        ";
60     static final int QUANTIZE_ZERO_BUCKET = 63;
61 
62     enum Mode {
63 	EXEC,
64 	INFO,
65 	LIST,
66 	VERSION
67     }
68 
69     enum ProgramType {
70 	STRING,
71 	FILE
72     }
73 
74     static class CompileRequest {
75 	String s;
76 	ProgramType type;
77 	ProbeDescription.Spec probespec;
78     }
79 
80     // Modify program string by expanding an incomplete probe
81     // description according to the requested probespec.
82     static void
83     applyProbespec(CompileRequest req)
84     {
85 	ProbeDescription.Spec spec = ((req.probespec == null)
86 		? ProbeDescription.Spec.NAME
87 		: req.probespec);
88 
89 	int colons = 0;
90 	switch (req.probespec) {
91 	    case PROVIDER:
92 		colons = 3;
93 		break;
94 	    case MODULE:
95 		colons = 2;
96 		break;
97 	    case FUNCTION:
98 		colons = 1;
99 		break;
100 	}
101 
102 	StringBuffer buf = new StringBuffer();
103 	if (colons > 0) {
104 	    char ch;
105 	    int len = req.s.length();
106 
107 	    int i = 0;
108 	    // Find first whitespace character not including leading
109 	    // whitespace (end of first token).  Ignore whitespace
110 	    // inside a block if the block is concatenated with the
111 	    // probe description.
112 	    for (; (i < len) && Character.isWhitespace(req.s.charAt(i)); ++i);
113 	    int npos = i;
114 	    boolean inBlock = false;
115 	    for (; (npos < len) &&
116 		    (!Character.isWhitespace(ch = req.s.charAt(npos)) ||
117 		    inBlock); ++npos) {
118 		if (ch == '{') {
119 		    inBlock = true;
120 		} else if (ch == '}') {
121 		    inBlock = false;
122 		}
123 	    }
124 
125 	    // libdtrace lets you concatenate multiple probe
126 	    // descriptions separated by code blocks in curly braces,
127 	    // for example genunix::'{printf("FOUND");}'::entry, as long
128 	    // as the concatenated probe descriptions begin with ':' and
129 	    // not a specific field such as 'syscall'.  So to expand the
130 	    // possibly multiple probe descriptions, we need to insert
131 	    // colons before each open curly brace, and again at the end
132 	    // only if there is at least one non-whitespace (probe
133 	    // specifying) character after the last closing curly brace.
134 
135 	    int prev_i = 0;
136 	    while (i < npos) {
137 		for (; (i < npos) && (req.s.charAt(i) != '{'); ++i);
138 		buf.append(req.s.substring(prev_i, i));
139 		if ((i < npos) || ((i > 0) && (req.s.charAt(i - 1) != '}'))) {
140 		    for (int c = 0; c < colons; ++c) {
141 			buf.append(':');
142 		    }
143 		}
144 		if (i < npos) {
145 		    buf.append(req.s.charAt(i++));
146 		}
147 		prev_i = i;
148 	    }
149 
150 	    // append remainder of program text
151 	    buf.append(req.s.substring(i));
152 
153 	    req.s = buf.toString();
154 	}
155     }
156 
157     static void
158     printValue(Object value, int bytes, String stringFormat)
159     {
160 	if (value instanceof Integer) {
161 	    if (bytes == 1) {
162 		out.printf(" %3d", (Integer)value);
163 	    } else if (bytes == 2) {
164 		out.printf(" %5d", (Integer)value);
165 	    } else {
166 		out.printf(" %8d", (Integer)value);
167 	    }
168 	} else if (value instanceof Long) {
169 	    out.printf(" %16d", (Long)value);
170 	} else {
171 	    out.printf(stringFormat, value.toString());
172 	}
173     }
174 
175     static void
176     consumeProbeData(ProbeData data)
177     {
178 	if (logger.isLoggable(Level.FINER)) {
179 	    logger.finer(data.toString());
180 	}
181 
182 	if (!heading) {
183 	    if (flow) {
184 		out.printf("%3s %-41s\n", "CPU", "FUNCTION");
185 	    } else {
186 		if (!quiet) {
187 		    out.printf("%3s %6s %32s\n",
188 			    "CPU", "ID", "FUNCTION:NAME");
189 		}
190 	    }
191 	    heading = true;
192 	}
193 	ProbeDescription probe = data.getEnabledProbeDescription();
194 	if (flow) {
195 	    Flow flow = data.getFlow();
196 	    int indent = (flow.getDepth() * 2);
197 	    StringBuffer buf = new StringBuffer();
198 	    // indent
199 	    buf.append(' ');
200 	    for (int i = 0; i < indent; ++i) {
201 		buf.append(' ');
202 	    }
203 	    // prefix
204 	    switch (flow.getKind()) {
205 		case ENTRY:
206 		    if (indent == 0) {
207 			buf.append("=> ");
208 		    } else {
209 			buf.append("-> ");
210 		    }
211 		    break;
212 		case RETURN:
213 		    if (indent == 0) {
214 			buf.append("<= ");
215 		    } else {
216 			buf.append("<- ");
217 		    }
218 		    break;
219 	    }
220 
221 	    switch (flow.getKind()) {
222 		case NONE:
223 		    buf.append(probe.getFunction());
224 		    buf.append(':');
225 		    buf.append(probe.getName());
226 		    break;
227 		default:
228 		    buf.append(probe.getFunction());
229 	    }
230 
231 	    out.printf("%3s %-41s ", data.getCPU(),
232 		    buf.toString());
233 	} else {
234 	    if (!quiet) {
235 		StringBuffer buf = new StringBuffer();
236 		buf.append(probe.getFunction());
237 		buf.append(':');
238 		buf.append(probe.getName());
239 		out.printf("%3s %6s %32s ",
240 			data.getCPU(), probe.getID(),
241 			buf.toString());
242 	    }
243 	}
244 	Record record = null;
245 	Object value;
246 	List <Record> records = data.getRecords();
247 	Iterator <Record> itr = records.iterator();
248 	while (itr.hasNext()) {
249 	    record = itr.next();
250 
251 	    if (record instanceof ExitRecord) {
252 		exitStatus = ((ExitRecord)record).getStatus();
253 	    } else if (record instanceof ScalarRecord) {
254 		ScalarRecord scalar = (ScalarRecord)record;
255 		value = scalar.getValue();
256 		if (value instanceof byte[]) {
257 		    out.print(record.toString());
258 		} else {
259 		    if (quiet) {
260 			out.print(value);
261 		    } else {
262 			printValue(value, scalar.getNumberOfBytes(),
263 				"  %-33s");
264 		    }
265 		}
266 	    } else if (record instanceof PrintfRecord) {
267 		out.print(record);
268 	    } else if (record instanceof PrintaRecord) {
269 		PrintaRecord printa = (PrintaRecord)record;
270 		List <Tuple> tuples = printa.getTuples();
271 		if (tuples.isEmpty()) {
272 		    out.print(printa.getOutput());
273 		} else {
274 		    for (Tuple t : tuples) {
275 			out.print(printa.getFormattedString(t));
276 		    }
277 		}
278 
279 		if (logger.isLoggable(Level.FINE)) {
280 		    logger.fine(printa.toString());
281 		}
282 	    } else if (record instanceof StackValueRecord) {
283 		printStack((StackValueRecord)record);
284 	    }
285 	}
286 	if (!quiet) {
287 	    out.println();
288 	}
289     }
290 
291     static void
292     printDistribution(Distribution d)
293     {
294 	out.printf("\n%16s %41s %-9s\n", "value",
295 		"------------- Distribution -------------",
296 		"count");
297 	long v; // bucket frequency (value)
298 	long b; // lower bound of bucket range
299 	double total = 0;
300 	boolean positives = false;
301 	boolean negatives = false;
302 
303 	Distribution.Bucket bucket;
304 	int b1 = 0; // first displayed bucket
305 	int b2 = d.size() - 1; // last displayed bucket
306 	for (; (b1 <= b2) && (d.get(b1).getFrequency() == 0); ++b1);
307 	// If possible, get one bucket before the first non-zero
308 	// bucket and one bucket after the last.
309 	if (b1 > b2) {
310 	    // There isn't any data.  This is possible if (and only if)
311 	    // negative increment values have been used.  In this case,
312 	    // print the buckets around the base.
313 	    if (d instanceof LinearDistribution) {
314 		b1 = 0;
315 		b2 = 2;
316 	    } else {
317 		b1 = QUANTIZE_ZERO_BUCKET - 1;
318 		b2 = QUANTIZE_ZERO_BUCKET + 1;
319 	    }
320 	} else {
321 	    if (b1 > 0) --b1;
322 	    for (; (b2 > 0) && (d.get(b2).getFrequency() == 0); --b2);
323 	    if (b2 < (d.size() - 1)) ++b2;
324 	}
325 	for (int i = b1; i <= b2; ++i) {
326 	    v = d.get(i).getFrequency();
327 	    if (v > 0) {
328 		positives = true;
329 	    }
330 	    if (v < 0) {
331 		negatives = true;
332 	    }
333 	    total += Math.abs((double)v);
334 	}
335 	for (int i = b1; i <= b2; ++i) {
336 	    bucket = d.get(i);
337 	    v = bucket.getFrequency();
338 	    b = bucket.getMin();
339 
340 	    if (d instanceof LinearDistribution) {
341 		if (b == Long.MIN_VALUE) {
342 		    String lt = "< " + ((LinearDistribution)d).getBase();
343 		    out.printf("%16s ", lt);
344 		} else if (bucket.getMax() == Long.MAX_VALUE) {
345 		    String ge = ">= " + b;
346 		    out.printf("%16s ", ge);
347 		} else {
348 		    out.printf("%16d ", b);
349 		}
350 	    } else {
351 		out.printf("%16d ", b);
352 	    }
353 
354 	    printDistributionLine(v, total, positives, negatives);
355 	}
356     }
357 
358     static void
359     printDistributionLine(long val, double total, boolean positives,
360 	    boolean negatives)
361     {
362 	double f;
363 	int depth, len = 40;
364 
365 	assert (ATS.length() == len && SPACES.length() == len);
366 	assert (!(total == 0 && (positives || negatives)));
367 	assert (!(val < 0 && !negatives));
368 	assert (!(val > 0 && !positives));
369 	assert (!(val != 0 && total == 0));
370 
371 	if (!negatives) {
372 	    if (positives) {
373 		f = (Math.abs((double)val) * (double)len) / total;
374 		    depth = (int)(f + 0.5);
375 	    } else {
376 		depth = 0;
377 	    }
378 
379 	    out.printf("|%s%s %-9d\n", ATS.substring(len - depth),
380 		    SPACES.substring(depth), val);
381 	    return;
382 	}
383 
384 	if (!positives) {
385 	    f = (Math.abs((double)val) * (double)len) / total;
386 	    depth = (int)(f + 0.5);
387 
388 	    out.printf("%s%s| %-9d\n", SPACES.substring(depth),
389 		    ATS.substring(len - depth), val);
390 	    return;
391 	}
392 
393 	/*
394 	 * If we're here, we have both positive and negative bucket values.
395 	 * To express this graphically, we're going to generate both positive
396 	 * and negative bars separated by a centerline.  These bars are half
397 	 * the size of normal quantize()/lquantize() bars, so we divide the
398 	 * length in half before calculating the bar length.
399 	 */
400 	len /= 2;
401 	String ats = ATS.substring(len);
402 	String spaces = SPACES.substring(len);
403 
404 	f = (Math.abs((double)val) * (double)len) / total;
405 	depth = (int)(f + 0.5);
406 
407 	if (val <= 0) {
408 	    out.printf("%s%s|%s %-9d\n", spaces.substring(depth),
409 		    ats.substring(len - depth), repeat(" ", len), val);
410 	    return;
411 	} else {
412 	    out.printf("%20s|%s%s %-9d\n", "", ats.substring(len - depth),
413 		    spaces.substring(depth), val);
414 	}
415     }
416 
417     public static String
418     repeat(String s, int n)
419     {
420         StringBuffer buf = new StringBuffer();
421         for (int i = 0; i < n; ++i) {
422             buf.append(s);
423         }
424         return buf.toString();
425     }
426 
427     static void
428     printStack(StackValueRecord rec)
429     {
430 	StackFrame[] frames = rec.getStackFrames();
431 	int i;
432 	out.println();
433 	String s;
434 	for (StackFrame f : frames) {
435 	    for (i = 0; i < stackindent; ++i) {
436 		out.print(' ');
437 	    }
438 	    s = f.getFrame();
439 	    if (s.indexOf('[') == 0) {
440 		out.print("  ");
441 	    }
442 	    out.println(s);
443 	}
444     }
445 
446     static int
447     compareTuples(Tuple t1, Tuple t2, int pos)
448     {
449 	int cmp = 0;
450 	int len1 = t1.size();
451 	int len2 = t2.size();
452 	int index;
453 
454 	for (int i = 0; (cmp == 0) && (i < len1 && i < len2); ++i) {
455 	    index = i + pos;
456 	    if (index >= len1) {
457 		index = index - len1;
458 	    }
459 	    cmp = Tuple.compare(t1, t2, index);
460 	}
461 
462 	if (cmp == 0) {
463 	    cmp = (len1 < len2 ? -1 : (len1 > len2 ? 1 : 0));
464 	}
465 
466 	return cmp;
467     }
468 
469     static int
470     compareValues(AggregationValue v1, AggregationValue v2)
471     {
472 	int cmp;
473 
474 	if ((v1 instanceof LinearDistribution) &&
475 		(v2 instanceof LinearDistribution)) {
476 	    LinearDistribution l1 = (LinearDistribution)v1;
477 	    LinearDistribution l2 = (LinearDistribution)v2;
478 	    cmp = l1.compareTo(l2);
479 	} else if ((v1 instanceof LogDistribution) &&
480 		(v2 instanceof LogDistribution)) {
481 	    LogDistribution l1 = (LogDistribution)v1;
482 	    LogDistribution l2 = (LogDistribution)v2;
483 	    cmp = l1.compareTo(l2);
484 	} else {
485 	    double n1 = v1.getValue().doubleValue();
486 	    double n2 = v2.getValue().doubleValue();
487 	    cmp = (n1 < n2 ? -1 : (n1 > n2 ? 1 : 0));
488 	}
489 
490 	return cmp;
491     }
492 
493     static Comparator <Object[]>
494     getAggValCmp(final int keypos)
495     {
496 	Comparator <Object[]> CMP = new Comparator <Object[]> () {
497 	    public int
498 	    compare(Object[] pair1, Object[] pair2)
499 	    {
500 		int cmp;
501 		long id1 = (Long)pair1[1];
502 		long id2 = (Long)pair2[1];
503 		cmp = (id1 < id2 ? -1 : (id1 > id2 ? 1 : 0));
504 		if (cmp != 0) {
505 		    return cmp;
506 		}
507 
508 		AggregationRecord r1 = (AggregationRecord)pair1[0];
509 		AggregationRecord r2 = (AggregationRecord)pair2[0];
510 		AggregationValue v1 = r1.getValue();
511 		AggregationValue v2 = r2.getValue();
512 		cmp = compareValues(v1, v2);
513 		if (cmp != 0) {
514 		    return cmp;
515 		}
516 
517 		cmp = compareTuples(r1.getTuple(), r2.getTuple(), keypos);
518 		return cmp;
519 	    }
520 	};
521 	return CMP;
522     };
523 
524     static Comparator <Object[]>
525     getAggKeyCmp(final int keypos)
526     {
527 	Comparator <Object[]> CMP = new Comparator <Object[]> () {
528 	    public int
529 	    compare(Object[] pair1, Object[] pair2)
530 	    {
531 		int cmp;
532 		long id1 = (Long)pair1[1];
533 		long id2 = (Long)pair2[1];
534 		cmp = (id1 < id2 ? -1 : (id1 > id2 ? 1 : 0));
535 		if (cmp != 0) {
536 		    return cmp;
537 		}
538 
539 		AggregationRecord r1 = (AggregationRecord)pair1[0];
540 		AggregationRecord r2 = (AggregationRecord)pair2[0];
541 		cmp = compareTuples(r1.getTuple(), r2.getTuple(), keypos);
542 
543 		return cmp;
544 	    }
545 	};
546 	return CMP;
547     }
548 
549     // Consumer getAggregate()
550     static void
551     printAggregate(Aggregate aggregate)
552     {
553 	List <AggregationRecord> list =
554 		new ArrayList <AggregationRecord> ();
555 	List <Object[]> sortList = new ArrayList <Object[]> ();
556 	for (Aggregation a : aggregate.getAggregations()) {
557 	    for (AggregationRecord rec : a.asMap().values()) {
558 		sortList.add(new Object[] {rec, new Long(a.getID())});
559 	    }
560 	}
561 
562 	try {
563 	    // aggsortkeypos
564 	    long optval = dtrace.getOption(Option.aggsortkeypos);
565 	    int keypos;
566 	    if (optval == Option.UNSET) {
567 		keypos = 0;
568 	    } else {
569 		keypos = (int)optval;
570 	    }
571 
572 	    // aggsortkey
573 	    if (dtrace.getOption(Option.aggsortkey) == Option.UNSET) {
574 		Collections.sort(sortList, getAggValCmp(keypos));
575 	    } else {
576 		Collections.sort(sortList, getAggKeyCmp(keypos));
577 	    }
578 
579 	    for (Object[] pair : sortList) {
580 		list.add((AggregationRecord)pair[0]);
581 	    }
582 
583 	    // aggsortrev
584 	    if (dtrace.getOption(Option.aggsortrev) != Option.UNSET) {
585 		Collections.reverse(list);
586 	    }
587 	} catch (DTraceException e) {
588 	    e.printStackTrace();
589 	    return;
590 	}
591 
592 	printAggregationRecords(list);
593     }
594 
595     static void
596     printAggregationRecords(List <AggregationRecord> list)
597     {
598 	Tuple tuple;
599 	AggregationValue value;
600 	ValueRecord tupleRecord;
601 	int i;
602 	int len;
603 	for (AggregationRecord r : list) {
604 	    tuple = r.getTuple();
605 	    value = r.getValue();
606 	    len = tuple.size();
607 	    for (i = 0; i < len; ++i) {
608 		tupleRecord = tuple.get(i);
609 		if (tupleRecord instanceof StackValueRecord) {
610 		    printStack((StackValueRecord)tupleRecord);
611 		} else if (tupleRecord instanceof SymbolValueRecord) {
612 		    printValue(tupleRecord.toString(), -1, "  %-50s");
613 		} else {
614 		    printValue(tupleRecord.getValue(),
615 			    ((ScalarRecord)tupleRecord).getNumberOfBytes(),
616 			    "  %-50s");
617 		}
618 	    }
619 	    if (value instanceof Distribution) {
620 		Distribution d = (Distribution)value;
621 		printDistribution(d);
622 	    } else {
623 		Number v = value.getValue();
624 		printValue(v, -1, "  %-50s");
625 	    }
626 	    out.println();
627 	}
628     }
629 
630     static void
631     exit(int status)
632     {
633 	out.flush();
634 	System.err.flush();
635 	if (status == 0) {
636 	    status = exitStatus;
637 	}
638 	System.exit(status);
639     }
640 
641     static void
642     usage()
643     {
644 	String predact = "[[ predicate ] action ]";
645 	System.err.printf("Usage: java %s [-32|-64] [-CeFlqvVwZ] " +
646 	    "[-b bufsz] [-c cmd] [-D name[=def]]\n\t[-I path] [-L path] " +
647 	    "[-o output] [-p pid] [-s script] [-U name]\n\t" +
648 	    "[-x opt[=val]] [-X a|c|s|t]\n\n" +
649 	    "\t[-P provider %s]\n" +
650 	    "\t[-m [ provider: ] module %s]\n" +
651 	    "\t[-f [[ provider: ] module: ] func %s]\n" +
652 	    "\t[-n [[[ provider: ] module: ] func: ] name %s]\n" +
653 	    "\t[-i probe-id %s] [ args ... ]\n\n", CLASSNAME,
654 	    predact, predact, predact, predact, predact);
655 	System.err.printf("\tpredicate -> '/' D-expression '/'\n");
656 	System.err.printf("\t   action -> '{' D-statements '}'\n");
657 	System.err.printf("\n" +
658 	    "\t-32 generate 32-bit D programs\n" +
659 	    "\t-64 generate 64-bit D programs\n\n" +
660 	    "\t-b  set trace buffer size\n" +
661 	    "\t-c  run specified command and exit upon its completion\n" +
662 	    "\t-C  run cpp(1) preprocessor on script files\n" +
663 	    "\t-D  define symbol when invoking preprocessor\n" +
664 	    "\t-e  exit after compiling request but prior to enabling " +
665 		    "probes\n" +
666 	    "\t-f  enable or list probes matching the specified " +
667 		    "function name\n" +
668 	    "\t-F  coalesce trace output by function\n" +
669 	    "\t-i  enable or list probes matching the specified probe id\n" +
670 	    "\t-I  add include directory to preprocessor search path\n" +
671 	    "\t-l  list probes matching specified criteria\n" +
672 	    "\t-L  add library directory to library search path\n" +
673 	    "\t-m  enable or list probes matching the specified " +
674 		    "module name\n" +
675 	    "\t-n  enable or list probes matching the specified probe name\n" +
676 	    "\t-o  set output file\n" +
677 	    "\t-p  grab specified process-ID and cache its symbol tables\n" +
678 	    "\t-P  enable or list probes matching the specified " +
679 		    "provider name\n" +
680 	    "\t-q  set quiet mode (only output explicitly traced data)\n" +
681 	    "\t-s  enable or list probes according to the specified " +
682 		    "D script\n" +
683 	    "\t-U  undefine symbol when invoking preprocessor\n" +
684 	    "\t-v  set verbose mode (report stability attributes, " +
685 		    "arguments)\n" +
686 	    "\t-V  report DTrace API version\n" +
687 	    "\t-w  permit destructive actions\n" +
688 	    "\t-x  enable or modify compiler and tracing options\n" +
689 	    "\t-X  specify ISO C conformance settings for preprocessor\n" +
690 	    "\t-Z  permit probe descriptions that match zero probes\n" +
691 	    "\n" +
692 	    "\tTo log PrintaRecord, set this environment variable:\n" +
693 	    "\t\tJDTRACE_LOGGING_LEVEL=FINE\n" +
694 	    "\tTo log ProbeData, set JDTRACE_LOGGING_LEVEL=FINER\n");
695 	exit(2);
696     }
697 
698     static void
699     printProgramStability(String programType, String programDescription,
700 	    ProgramInfo info)
701     {
702 	out.println();
703 	out.printf("Stability data for %s %s:\n\n",
704 		programType, programDescription);
705 	InterfaceAttributes a;
706 	out.println("\tMinimum probe description " +
707 		"attributes");
708 	a = info.getMinimumProbeAttributes();
709 	out.printf("\t\tIdentifier Names: %s\n",
710 		a.getNameStability());
711 	out.printf("\t\tData Semantics:   %s\n",
712 		a.getDataStability());
713 	out.printf("\t\tDependency Class: %s\n",
714 		a.getDependencyClass());
715 	out.println("\tMinimum probe statement attributes");
716 	a = info.getMinimumStatementAttributes();
717 	out.printf("\t\tIdentifier Names: %s\n",
718 		a.getNameStability());
719 	out.printf("\t\tData Semantics:   %s\n",
720 		a.getDataStability());
721 	out.printf("\t\tDependency Class: %s\n",
722 		a.getDependencyClass());
723     }
724 
725     static void
726     printProbeDescription(ProbeDescription p)
727     {
728 	out.printf("%5d %10s %17s %33s %s\n", p.getID(),
729 	    p.getProvider(), p.getModule(),
730 	    p.getFunction(), p.getName());
731     }
732 
733     static void
734     printProbeInfo(ProbeInfo p)
735     {
736 	InterfaceAttributes a;
737 	out.println("\n\tProbe Description Attributes");
738 
739 	a = p.getProbeAttributes();
740 	out.printf("\t\tIdentifier Names: %s\n",
741 	    a.getNameStability());
742 	out.printf("\t\tData Semantics:   %s\n",
743 	    a.getDataStability());
744 	out.printf("\t\tDependency Class: %s\n",
745 	    a.getDependencyClass());
746 
747 	out.println("\n\tArgument Attributes");
748 
749 	a = p.getArgumentAttributes();
750 	out.printf("\t\tIdentifier Names: %s\n",
751 	    a.getNameStability());
752 	out.printf("\t\tData Semantics:   %s\n",
753 	    a.getDataStability());
754 	out.printf("\t\tDependency Class: %s\n",
755 	    a.getDependencyClass());
756 
757 	// Argument types unsupported for now.
758 
759 	out.println();
760     }
761 
762     public static void
763     main(String[] args)
764     {
765 	String loggingLevel = System.getenv().get("JDTRACE_LOGGING_LEVEL");
766 	try {
767 	    logger.setLevel(Level.parse(loggingLevel));
768 	} catch (Exception e) {
769 	    logger.setLevel(Level.OFF);
770 	}
771 
772 	if (args.length == 0) {
773 	    usage();
774 	}
775 
776 	List <CompileRequest> compileRequests = new LinkedList
777 		<CompileRequest> ();
778 	List <Program> programList = new LinkedList <Program> ();
779 	boolean verbose = false;
780 	Mode mode = Mode.EXEC;
781 
782 	final ExceptionHandler exceptionHandler = new ExceptionHandler() {
783 	    public void handleException(Throwable e) {
784 		if (e instanceof DTraceException) {
785 		    DTraceException de = (DTraceException)e;
786 		    System.err.printf("dtrace: %s\n", de.getMessage());
787 		} else if (e instanceof ConsumerException) {
788 		    ConsumerException ce = (ConsumerException)e;
789 		    Object msg = ce.getNotificationObject();
790 		    if ((msg instanceof org.opensolaris.os.dtrace.Error) ||
791 			(msg instanceof Drop)) {
792 			System.err.printf("dtrace: %s\n", ce.getMessage());
793 		    } else {
794 			ce.printStackTrace();
795 		    }
796 		} else {
797 		    e.printStackTrace();
798 		}
799 		exit(1);
800 	    }
801 	};
802 
803 	Getopt g = new Getopt(CLASSNAME, args, OPTSTR);
804 	int c = 0;
805 
806 	List <Consumer.OpenFlag> openFlags =
807 		new ArrayList <Consumer.OpenFlag> ();
808 
809 	while ((c = g.getopt()) != -1) {
810 	    switch (c) {
811 		case '3': {
812 		    String s = g.getOptarg();
813 		    if (!s.equals("2")) {
814 			System.err.println("dtrace: illegal option -- 3" + s);
815 			usage();
816 		    }
817 		    openFlags.add(Consumer.OpenFlag.ILP32);
818 		    break;
819 		}
820 		case '6': {
821 		    String s = g.getOptarg();
822 		    if (!s.equals("4")) {
823 			System.err.println("dtrace: illegal option -- 6" + s);
824 			usage();
825 		    }
826 		    openFlags.add(Consumer.OpenFlag.LP64);
827 		    break;
828 		}
829 	    }
830 	}
831 
832 	Consumer.OpenFlag[] oflags = new Consumer.OpenFlag[openFlags.size()];
833 	oflags = (Consumer.OpenFlag[])openFlags.toArray(oflags);
834 
835 	dtrace = new LocalConsumer() {
836 	    protected Thread createThread() {
837 		Thread t = super.createThread();
838 		t.setDaemon(false);
839 		t.setPriority(Thread.MIN_PRIORITY);
840 		return t;
841 	    }
842 	};
843 
844 	g = new Getopt(CLASSNAME, args, OPTSTR);
845 	c = 0;
846 
847 	try {
848 	    dtrace.open(oflags);
849 
850 	    // Set default options that may be overriden by options or #pragma
851 	    dtrace.setOption(Option.bufsize, Option.mb(4));
852 	    dtrace.setOption(Option.aggsize, Option.mb(4));
853 
854 	    CompileRequest r;
855 	    while ((c = g.getopt()) != -1) {
856 		switch (c) {
857 		    case 'b':
858 			dtrace.setOption(Option.bufsize, g.getOptarg());
859 			break;
860 		    case 'c':
861 			dtrace.createProcess(g.getOptarg());
862 			break;
863 		    case 'C':
864 			dtrace.setOption(Option.cpp);
865 			break;
866 		    case 'D':
867 			dtrace.setOption(Option.define, g.getOptarg());
868 			break;
869 		    case 'e':
870 			mode = Mode.INFO;
871 			break;
872 		    case 'f':
873 			r = new CompileRequest();
874 			r.s = g.getOptarg();
875 			r.type = ProgramType.STRING;
876 			r.probespec = ProbeDescription.Spec.FUNCTION;
877 			compileRequests.add(r);
878 			break;
879 		    case 'F':
880 			dtrace.setOption(Option.flowindent);
881 			break;
882 		    case 'i':
883 			r = new CompileRequest();
884 			r.s = g.getOptarg();
885 			r.type = ProgramType.STRING;
886 			r.probespec = ProbeDescription.Spec.NAME;
887 			compileRequests.add(r);
888 			break;
889 		    case 'I':
890 			dtrace.setOption(Option.incdir, g.getOptarg());
891 			break;
892 		    case 'l':
893 			mode = Mode.LIST;
894 			dtrace.setOption(Option.zdefs); // -l implies -Z
895 			break;
896 		    case 'L':
897 			dtrace.setOption(Option.libdir, g.getOptarg());
898 			break;
899 		    case 'm':
900 			r = new CompileRequest();
901 			r.s = g.getOptarg();
902 			r.type = ProgramType.STRING;
903 			r.probespec = ProbeDescription.Spec.MODULE;
904 			compileRequests.add(r);
905 			break;
906 		    case 'n':
907 			r = new CompileRequest();
908 			r.s = g.getOptarg();
909 			r.type = ProgramType.STRING;
910 			r.probespec = ProbeDescription.Spec.NAME;
911 			compileRequests.add(r);
912 			break;
913 		    case 'o':
914 			String outFileName = g.getOptarg();
915 			File outFile = new File(outFileName);
916 			try {
917 			    FileOutputStream fos = new FileOutputStream(
918 				    outFile, true);
919 			    out = new PrintStream(fos);
920 			} catch (FileNotFoundException e) {
921 			    System.err.println("failed to open " +
922 				outFileName + " in write mode");
923 			    exit(1);
924 			} catch (SecurityException e) {
925 			    System.err.println("failed to open " +
926 				outFileName);
927 			    exit(1);
928 			}
929 			break;
930 		    case 'p':
931 			String pidstr = g.getOptarg();
932 			int pid = -1;
933 			try {
934 			    pid = Integer.parseInt(pidstr);
935 			} catch (NumberFormatException e) {
936 			    System.err.println("invalid pid: " + pidstr);
937 			    exit(1);
938 			}
939 			dtrace.grabProcess(pid);
940 			break;
941 		    case 'P':
942 			r = new CompileRequest();
943 			r.s = g.getOptarg();
944 			r.type = ProgramType.STRING;
945 			r.probespec = ProbeDescription.Spec.PROVIDER;
946 			compileRequests.add(r);
947 			break;
948 		    case 'q':
949 			dtrace.setOption(Option.quiet);
950 			break;
951 		    case 's':
952 			r = new CompileRequest();
953 			r.s = g.getOptarg();
954 			r.type = ProgramType.FILE;
955 			compileRequests.add(r);
956 			break;
957 		    case 'U':
958 			dtrace.setOption(Option.undef, g.getOptarg());
959 			break;
960 		    case 'v':
961 			verbose = true;
962 			break;
963 		    case 'V':
964 			mode = Mode.VERSION;
965 			break;
966 		    case 'w':
967 			dtrace.setOption(Option.destructive);
968 			break;
969 		    case 'x':
970 			String[] xarg = g.getOptarg().split("=", 2);
971 			if (xarg.length > 1) {
972 			    dtrace.setOption(xarg[0], xarg[1]);
973 			} else if (xarg.length == 1) {
974 			    dtrace.setOption(xarg[0]);
975 			}
976 			break;
977 		    case 'X':
978 			dtrace.setOption(Option.stdc, g.getOptarg());
979 			break;
980 		    case 'Z':
981 			dtrace.setOption(Option.zdefs);
982 			break;
983 		    case '?':
984 			usage(); // getopt() already printed an error
985 			break;
986 		    default:
987 			System.err.print("getopt() returned " + c + "\n");
988 			c = 0;
989 		 }
990 	    }
991 	    c = 0;
992 	    List <String> argList = new LinkedList <String> ();
993 	    for (int i = g.getOptind(); i < args.length; ++i) {
994 		argList.add(args[i]);
995 	    }
996 
997 	    if (mode == Mode.VERSION) {
998 		out.printf("dtrace: %s\n", dtrace.getVersion());
999 		dtrace.close();
1000 		exit(0);
1001 	    }
1002 
1003 	    String[] compileArgs = new String[argList.size()];
1004 	    compileArgs = (String[])argList.toArray(compileArgs);
1005 
1006 	    if (compileRequests.isEmpty()) {
1007 		System.err.println("dtrace: no probes specified");
1008 		exit(1);
1009 	    }
1010 
1011 	    Program program;
1012 	    for (CompileRequest req : compileRequests) {
1013 		switch (req.type) {
1014 		    case STRING:
1015 			applyProbespec(req);
1016 			program = dtrace.compile(req.s, compileArgs);
1017 			break;
1018 		    case FILE:
1019 			File file = new File(req.s);
1020 			program = dtrace.compile(file, compileArgs);
1021 			break;
1022 		    default:
1023 			throw new IllegalArgumentException(
1024 				"Unexpected program type: " + req.type);
1025 		}
1026 
1027 		programList.add(program);
1028 	    }
1029 
1030 	    // Get options set by #pragmas in compiled program
1031 	    long optval;
1032 	    quiet = (dtrace.getOption(Option.quiet) != Option.UNSET);
1033 	    flow = (dtrace.getOption(Option.flowindent) != Option.UNSET);
1034 	    optval = dtrace.getOption("stackindent");
1035 	    if (optval != Option.UNSET) {
1036 		stackindent = (int)optval;
1037 	    }
1038 
1039 	    if (mode == Mode.LIST) {
1040 		out.printf("%5s %10s %17s %33s %s\n",
1041 		    "ID", "PROVIDER", "MODULE", "FUNCTION", "NAME");
1042 
1043 		if (verbose) {
1044 		    List <List <Probe>> lists =
1045 			    new LinkedList <List <Probe>> ();
1046 		    for (Program p : programList) {
1047 			lists.add(dtrace.listProgramProbeDetail(p));
1048 		    }
1049 		    ProbeDescription p;
1050 		    ProbeInfo pinfo;
1051 		    for (List <Probe> list : lists) {
1052 			for (Probe probe : list) {
1053 			    p = probe.getDescription();
1054 			    pinfo = probe.getInfo();
1055 			    printProbeDescription(p);
1056 			    printProbeInfo(pinfo);
1057 			}
1058 		    }
1059 		} else {
1060 		    List <List <ProbeDescription>> lists =
1061 			    new LinkedList <List <ProbeDescription>> ();
1062 		    for (Program p : programList) {
1063 			lists.add(dtrace.listProgramProbes(p));
1064 		    }
1065 		    for (List <ProbeDescription> list : lists) {
1066 			for (ProbeDescription p : list) {
1067 			    printProbeDescription(p);
1068 			}
1069 		    }
1070 		}
1071 		exit(0);
1072 	    }
1073 
1074 	    String programType;
1075 	    String programDescription;
1076 	    ProgramInfo info;
1077 	    for (Program p : programList) {
1078 		if (p instanceof Program.File) {
1079 		    Program.File pf = (Program.File)p;
1080 		    programType = "script";
1081 		    programDescription = pf.getFile().getPath();
1082 		} else {
1083 		    programType = "description";
1084 		    programDescription =
1085 			p.getContents().split("[/{;]", 2)[0];
1086 		}
1087 
1088 		if (mode == Mode.EXEC) {
1089 		    dtrace.enable(p);
1090 		} else {
1091 		    dtrace.getProgramInfo(p);
1092 		}
1093 		info = p.getInfo();
1094 		if (!quiet) {
1095 		    System.err.printf("dtrace: %s '%s' matched %d probe%s\n",
1096 			    programType, programDescription,
1097 			    info.getMatchingProbeCount(),
1098 			    info.getMatchingProbeCount() == 1 ? "" : "s");
1099 		}
1100 		if (verbose) {
1101 		    printProgramStability(programType,
1102 			    programDescription, info);
1103 		}
1104 	    }
1105 	    if (mode != Mode.EXEC) {
1106 		exit(0);
1107 	    }
1108 	    dtrace.addConsumerListener(new ConsumerAdapter() {
1109 		public void consumerStarted(ConsumerEvent e) {
1110 		    started = true;
1111 		}
1112 		public void consumerStopped(ConsumerEvent e) {
1113 		    stopped = true;
1114 		    out.println();
1115 		    try {
1116 			Aggregate aggregate = dtrace.getAggregate();
1117 			if (aggregate != null) {
1118 			    printAggregate(aggregate);
1119 			}
1120 			dtrace.close();
1121 		    } catch (Throwable x) {
1122 			exceptionHandler.handleException(x);
1123 		    }
1124 		    exit(0);
1125 		}
1126 		public void dataDropped(DropEvent e) {
1127 		    System.err.printf("dtrace: %s",
1128 			    e.getDrop().getDefaultMessage());
1129 		}
1130 		public void errorEncountered(ErrorEvent e)
1131 			throws ConsumerException {
1132 		    org.opensolaris.os.dtrace.Error error = e.getError();
1133 		    if (logger.isLoggable(Level.FINE)) {
1134 			logger.fine(error.toString());
1135 		    }
1136 		    System.err.printf("dtrace: %s",
1137 			    error.getDefaultMessage());
1138 		}
1139 		public void dataReceived(DataEvent e)
1140 			throws ConsumerException {
1141 		    consumeProbeData(e.getProbeData());
1142 		}
1143 		public void processStateChanged(ProcessEvent e)
1144 			throws ConsumerException {
1145 		    if (logger.isLoggable(Level.FINE)) {
1146 			logger.fine(e.getProcessState().toString());
1147 		    }
1148 		}
1149 	    });
1150 	    // Print unprinted aggregations after Ctrl-C
1151 	    Runtime.getRuntime().addShutdownHook(new Thread() {
1152 		public void run() {
1153 		    if (stopped || !started) {
1154 			return;
1155 		    }
1156 
1157 		    try {
1158 			Aggregate aggregate = dtrace.getAggregate();
1159 			if (aggregate != null) {
1160 			    out.println();
1161 			    out.println();
1162 			    printAggregate(aggregate);
1163 			}
1164 		    } catch (Throwable x) {
1165 			exceptionHandler.handleException(x);
1166 		    }
1167 		}
1168 	    });
1169 	    dtrace.go(exceptionHandler);
1170 	} catch (DTraceException e) {
1171 	    if (c > 0) {
1172 		// set option error
1173 		if (g.getOptarg() == null) {
1174 		    System.err.printf("dtrace: failed to set -%c: %s\n",
1175 			c, e.getMessage());
1176 		} else {
1177 		    System.err.printf("dtrace: failed to set -%c %s: %s\n",
1178 			c, g.getOptarg(), e.getMessage());
1179 		}
1180 	    } else {
1181 		// any other error
1182 		System.err.printf("dtrace: %s\n", e.getMessage());
1183 	    }
1184 	    exit(1);
1185 	} catch (Exception e) {
1186 	    e.printStackTrace();
1187 	    exit(1);
1188 	}
1189     }
1190 }
1191