xref: /illumos-gate/usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LocalConsumer.java (revision 622200ad88c6c6382403a01985a94e22484baac6)
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.io.*;
31 import java.util.*;
32 import java.net.InetAddress;
33 import java.net.UnknownHostException;
34 import javax.swing.event.EventListenerList;
35 import java.util.logging.*;
36 
37 /**
38  * Interface to the native DTrace library, each instance is a single
39  * DTrace consumer.
40  *
41  * @author Tom Erickson
42  */
43 public class LocalConsumer implements Consumer {
44     //
45     // Implementation notes:
46     //
47     // libdtrace is *not* thread-safe.  You cannot make multiple calls
48     // into it simultaneously from different threads, even if those
49     // threads are operating on different dtrace_hdl_t's.  Calls to
50     // libdtrace are synchronized on a global lock, LocalConsumer.class.
51 
52     static Logger logger = Logger.getLogger(LocalConsumer.class.getName());
53 
54     private static final int DTRACE_JNI_VERSION = 1;
55 
56     private static final Option[] DEFAULT_OPTIONS = new Option[] {
57 	new Option(Option.bufsize, Option.kb(256)),
58 	new Option(Option.aggsize, Option.kb(256)),
59     };
60 
61     private static native void _loadJniTable();
62 
63     private static boolean debug;
64     private static int maxConsumers;
65 
66     static {
67 	// configure logging
68 	logger.addHandler(new ConsoleHandler());
69         logger.setLevel(Level.OFF);
70 
71 	// Undocumented configuration options using java -Doption=value
72 	String property;
73 	property = System.getProperty("JAVA_DTRACE_MAX_CONSUMERS");
74 	if (property != null && property.length() != 0) {
75 	    try {
76 		maxConsumers = Integer.parseInt(property);
77 	    } catch (NumberFormatException e) {
78 		e.printStackTrace();
79 		System.exit(1);
80 	    }
81 	}
82 	property = System.getProperty("JAVA_DTRACE_API_DEBUG");
83 	if (property != null && property.length() != 0) {
84 	    try {
85 		int i = Integer.parseInt(property);
86 		debug = (i != 0);
87 	    } catch (NumberFormatException e) {
88 		System.err.println("Warning: property ignored: " +
89 			"JAVA_DTRACE_API_DEBUG=" + property);
90 	    }
91 	}
92 
93 	Utility.loadLibrary("libdtrace_jni.so.1", debug);
94 
95 	_checkVersion(DTRACE_JNI_VERSION);
96 	if (maxConsumers > 0) {
97 	    _setMaximumConsumers(maxConsumers);
98 	}
99 	_setDebug(debug);
100 
101 	//
102 	// Last of all in case configuration options affect the loading
103 	// of the JNI table.
104 	//
105 	_loadJniTable();
106     }
107 
108     // Native JNI interface (see lib/libdtrace_jni/dtrace_jni.c)
109     private static native void _checkVersion(int version);
110     private native void _open(OpenFlag[] flags) throws DTraceException;
111     private native Program _compileString(String program, String[] args)
112 	    throws DTraceException;
113     private native Program.File _compileFile(String path, String[] args)
114 	    throws DTraceException;
115     private native void _exec(Program program) throws DTraceException;
116     private native void _getProgramInfo(Program program)
117 	    throws DTraceException;
118     private native void _setOption(String option, String value)
119 	    throws DTraceException;
120     private native long _getOption(String option) throws DTraceException;
121     private native boolean _isEnabled();
122     private native void _checkProgramEnabling();
123     private native void _go() throws DTraceException;
124     private native void _stop() throws DTraceException;
125     private native void _consume() throws DTraceException;
126     private native void _interrupt();
127     private native void _close();
128     private native Aggregate _getAggregate(AggregateSpec spec)
129 	    throws DTraceException;
130     private native int _createProcess(String cmd) throws DTraceException;
131     private native void _grabProcess(int pid) throws DTraceException;
132     private native void _listProbes(List <ProbeDescription> probeList,
133 	    ProbeDescription filter);
134     private native void _listProbeDetail(List <Probe> probeList,
135 	    ProbeDescription filter);
136     private native void _listCompiledProbes(
137 	    List <ProbeDescription> probeList, Program program);
138     private native void _listCompiledProbeDetail(
139 	    List <Probe> probeList, Program program);
140     private static native String _getVersion();
141     private static native int _openCount();
142     //
143     // Releases memory held in the JNI layer after dtrace_close() has
144     // released critical system resources like file descriptors, and
145     // calls to libdtrace are no longer needed (or possible).
146     //
147     private native void _destroy();
148     // Called by LogDistribution
149     static native long _quantizeBucket(int i);
150     //
151     // Cannot be static because the necessary dtrace handle is specific
152     // to this Consumer.
153     //
154     private native String _lookupKernelFunction(Number address);
155     private native String _lookupUserFunction(int pid, Number address);
156     private static native String _getExecutableName();
157 
158     // Undocumented configuration options
159     private static native void _setMaximumConsumers(int max);
160     private static native void _setDebug(boolean debug);
161 
162     protected EventListenerList listenerList;
163     protected ExceptionHandler exceptionHandler;
164 
165     private int _handle = -1;    // native C identifier (do not modify)
166     private final Identifier id; // java identifier
167 
168     private enum State {
169 	INIT,
170 	OPEN,
171 	COMPILED,
172 	GO,
173 	STARTED,
174 	STOPPED,
175 	CLOSED
176     }
177 
178     private State state = State.INIT;
179     private boolean stopCalled;
180 
181     //
182     // Per-consumer lock used in native code to prevent conflict between
183     // the native consumer loop and the getAggregate() thread without
184     // locking this LocalConsumer.  A distinct per-consumer lock allows
185     // the stop() method to be synchronized without causing deadlock
186     // when the consumer loop grabs the per-consumer lock before
187     // dtrace_work().
188     //
189     private Object consumerLock;
190     //
191     // Synchronization lock used to ensure that stop() does not return
192     // until this consumer is actually stopped.
193     //
194     private Object stopLock;
195     private boolean workEnded;
196 
197     private static int sequence = 0;
198 
199     /**
200      * Creates a consumer that interacts with the native DTrace library
201      * on the local system.
202      */
203     public
204     LocalConsumer()
205     {
206 	id = new LocalConsumer.Identifier(this);
207 	consumerLock = new Object();
208 	stopLock = new Object();
209 	listenerList = new EventListenerList();
210     }
211 
212     /**
213      * Called by native C code only
214      */
215     private int
216     getHandle()
217     {
218 	return _handle;
219     }
220 
221     /**
222      * Called by native C code only
223      */
224     private void
225     setHandle(int n)
226     {
227 	_handle = n;
228     }
229 
230     public synchronized void
231     open(OpenFlag ... flags) throws DTraceException
232     {
233 	if (state == State.CLOSED) {
234 	    throw new IllegalStateException("cannot reopen a closed consumer");
235 	}
236 	if (state != State.INIT) {
237 	    throw new IllegalStateException("consumer already open");
238 	}
239 
240 	for (OpenFlag flag : flags) {
241 	    if (flag == null) {
242 		throw new NullPointerException("open flag is null");
243 	    }
244 	}
245 
246 	synchronized (LocalConsumer.class) {
247 	    _open(flags);
248 	}
249 
250 	state = State.OPEN;
251 	setOptions(DEFAULT_OPTIONS);
252 
253 	if (logger.isLoggable(Level.INFO)) {
254 	    logger.info("consumer table count: " + _openCount());
255 	}
256     }
257 
258     private synchronized void
259     checkCompile()
260     {
261 	switch (state) {
262 	    case INIT:
263 		throw new IllegalStateException("consumer not open");
264 	    case OPEN:
265 	    case COMPILED: // caller may compile more than one program
266 		break;
267 	    case GO:
268 	    case STARTED:
269 		throw new IllegalStateException("go() already called");
270 	    case STOPPED:
271 		throw new IllegalStateException("consumer stopped");
272 	    case CLOSED:
273 		throw new IllegalStateException("consumer closed");
274 	}
275     }
276 
277     public synchronized Program
278     compile(String program, String ... macroArgs) throws DTraceException
279     {
280 	if (program == null) {
281 	    throw new NullPointerException("program string is null");
282 	}
283 	checkCompile();
284 	Program p = null;
285 
286 	String[] argv = null;
287 	if (macroArgs != null) {
288 	    for (String macroArg : macroArgs) {
289 		if (macroArg == null) {
290 		    throw new NullPointerException("macro argument is null");
291 		}
292 	    }
293 	    argv = new String[macroArgs.length + 1];
294 	    synchronized (LocalConsumer.class) {
295 		//
296 		// Could be an application with an embedded JVM, not
297 		// necessarily "java".
298 		//
299 		argv[0] = _getExecutableName();
300 	    }
301 	    System.arraycopy(macroArgs, 0, argv, 1, macroArgs.length);
302 	} else {
303 	    synchronized (LocalConsumer.class) {
304 		argv = new String[] { _getExecutableName() };
305 	    }
306 	}
307 	synchronized (LocalConsumer.class) {
308 	    p = _compileString(program, argv);
309 	}
310 	p.consumerID = id;
311 	p.contents = program;
312 	p.validate();
313 	state = State.COMPILED;
314 
315 	return p;
316     }
317 
318     public synchronized Program
319     compile(File program, String ... macroArgs) throws DTraceException,
320             IOException, SecurityException
321     {
322 	if (program == null) {
323 	    throw new NullPointerException("program file is null");
324 	}
325 	if (!program.canRead()) {
326 	    throw new FileNotFoundException("failed to open " +
327 		    program.getName());
328 	}
329 	checkCompile();
330 	Program.File p = null;
331 
332 	String[] argv = null;
333 	if (macroArgs != null) {
334 	    for (String macroArg : macroArgs) {
335 		if (macroArg == null) {
336 		    throw new NullPointerException("macro argument is null");
337 		}
338 	    }
339 	    argv = new String[macroArgs.length + 1];
340 	    argv[0] = program.getPath();
341 	    System.arraycopy(macroArgs, 0, argv, 1, macroArgs.length);
342 	} else {
343 	    macroArgs = new String[] { program.getPath() };
344 	}
345 	synchronized (LocalConsumer.class) {
346 	    p = _compileFile(program.getPath(), argv);
347 	}
348 	p.consumerID = id;
349 	p.contents = Program.getProgramString(program);
350 	p.file = program;
351 	p.validate();
352 	state = State.COMPILED;
353 
354 	return p;
355     }
356 
357     private synchronized void
358     checkProgram(Program program)
359     {
360 	if (program == null) {
361 	    throw new NullPointerException("program is null");
362 	}
363 	if (!id.equals(program.consumerID)) {
364 	    throw new IllegalArgumentException("program not compiled " +
365 		    "by this consumer");
366 	}
367     }
368 
369     public void
370     enable() throws DTraceException
371     {
372 	enable(null);
373     }
374 
375     public synchronized void
376     enable(Program program) throws DTraceException
377     {
378 	switch (state) {
379 	    case INIT:
380 		throw new IllegalStateException("consumer not open");
381 	    case OPEN:
382 		throw new IllegalStateException("no compiled program");
383 	    case COMPILED:
384 		break;
385 	    case GO:
386 	    case STARTED:
387 		throw new IllegalStateException("go() already called");
388 	    case STOPPED:
389 		throw new IllegalStateException("consumer stopped");
390 	    case CLOSED:
391 		throw new IllegalStateException("consumer closed");
392 	}
393 
394 	// Compile all programs if null
395 	if (program != null) {
396 	    checkProgram(program);
397 	}
398 
399 	//
400 	// Left to native code to throw IllegalArgumentException if the
401 	// program is already enabled, since only the native code knows
402 	// the enabled state.
403 	//
404 	synchronized (LocalConsumer.class) {
405 	    _exec(program);
406 	}
407     }
408 
409     public synchronized void
410     getProgramInfo(Program program) throws DTraceException
411     {
412 	checkProgram(program);
413 	if (state == State.CLOSED) {
414 	    throw new IllegalStateException("consumer closed");
415 	}
416 
417 	//
418 	// The given program was compiled by this consumer, so we can
419 	// assert the following:
420 	//
421 	assert ((state != State.INIT) && (state != State.OPEN));
422 
423 	synchronized (LocalConsumer.class) {
424 	    _getProgramInfo(program);
425 	}
426     }
427 
428     private void
429     setOptions(Option[] options) throws DTraceException
430     {
431 	for (Option o : options) {
432 	    setOption(o.getOption(), o.getValue());
433 	}
434     }
435 
436     public void
437     setOption(String option) throws DTraceException
438     {
439 	setOption(option, Option.VALUE_SET);
440     }
441 
442     public void
443     unsetOption(String option) throws DTraceException
444     {
445 	setOption(option, Option.VALUE_UNSET);
446     }
447 
448     public synchronized void
449     setOption(String option, String value) throws DTraceException
450     {
451 	if (option == null) {
452 	    throw new NullPointerException("option is null");
453 	}
454 	if (value == null) {
455 	    throw new NullPointerException("option value is null");
456 	}
457 
458 	switch (state) {
459 	    case INIT:
460 		throw new IllegalStateException("consumer not open");
461 	    case OPEN:
462 	    case COMPILED:
463 	    case GO:
464 	    case STARTED: // Some options can be set on a running consumer
465 	    case STOPPED: // Allowed (may affect getAggregate())
466 		break;
467 	    case CLOSED:
468 		throw new IllegalStateException("consumer closed");
469 	}
470 
471 	synchronized (LocalConsumer.class) {
472 	    _setOption(option, value);
473 	}
474     }
475 
476     public synchronized long
477     getOption(String option) throws DTraceException
478     {
479 	if (option == null) {
480 	    throw new NullPointerException("option is null");
481 	}
482 
483 	switch (state) {
484 	    case INIT:
485 		throw new IllegalStateException("consumer not open");
486 	    case OPEN:
487 	    case COMPILED:
488 	    case GO:
489 	    case STARTED:
490 	    case STOPPED:
491 		break;
492 	    case CLOSED:
493 		throw new IllegalStateException("consumer closed");
494 	}
495 
496 	long value;
497 	synchronized (LocalConsumer.class) {
498 	    value = _getOption(option);
499 	}
500 	return value;
501     }
502 
503     public final synchronized boolean
504     isOpen()
505     {
506 	return ((state != State.INIT) && (state != State.CLOSED));
507     }
508 
509     public final synchronized boolean
510     isEnabled()
511     {
512 	if (state != State.COMPILED) {
513 	    return false;
514 	}
515 
516 	return _isEnabled();
517     }
518 
519     public final synchronized boolean
520     isRunning()
521     {
522 	return (state == State.STARTED);
523     }
524 
525     public final synchronized boolean
526     isClosed()
527     {
528 	return (state == State.CLOSED);
529     }
530 
531     /**
532      * Called in the runnable target of the thread returned by {@link
533      * #createThread()} to run this DTrace consumer.
534      *
535      * @see #createThread()
536      */
537     protected final void
538     work()
539     {
540 	try {
541 	    synchronized (this) {
542 		if (state != State.GO) {
543 		    //
544 		    // stop() was called after go() but before the
545 		    // consumer started
546 		    //
547 		    return; // executes finally block before returning
548 		}
549 
550 		state = State.STARTED;
551 		fireConsumerStarted(new ConsumerEvent(this,
552 			System.nanoTime()));
553 	    }
554 
555 	    //
556 	    // We should not prevent other consumers from running
557 	    // concurrently while this consumer blocks on the native
558 	    // consumer loop.  Instead, native code will acquire the
559 	    // LocalConsumer.class monitor as needed before calling
560 	    // libdtrace functions.
561 	    //
562 	    _consume();
563 
564 	} catch (Throwable e) {
565 	    if (exceptionHandler != null) {
566 		exceptionHandler.handleException(e);
567 	    } else {
568 		e.printStackTrace();
569 	    }
570 	} finally {
571 	    synchronized (stopLock) {
572 		// Notify the stop() method to stop waiting
573 		workEnded = true;
574 		stopLock.notifyAll();
575 	    }
576 
577 	    // Waits for stop() to finish
578 	    synchronized (this) {
579 		//
580 		// The stop() method may have updated the state and
581 		// notified listeners already.  Note that whoever
582 		// updates the state and notifies listeners must be
583 		// holding the lock on this LocalConsumer.  If we update
584 		// the state and notify listeners here while stop() is
585 		// waiting, counting on stop() to hold the lock for us
586 		// in the meantime, then a ConsumerListener could
587 		// deadlock the application by calling a synchronized
588 		// LocalConsumer method.
589 		//
590 		if (state == State.STARTED) {
591 		    state = State.STOPPED;
592 		    fireConsumerStopped(new ConsumerEvent(this,
593 			    System.nanoTime()));
594 		}
595 	    }
596 	}
597     }
598 
599     /**
600      * Creates the background thread started by {@link #go()} to run
601      * this consumer.  Override this method if you need to set
602      * non-default {@code Thread} options or create the thread in a
603      * {@code ThreadGroup}.  If you don't need to create the thread
604      * yourself, set the desired options on {@code super.createThread()}
605      * before returning it.  Otherwise, the {@code Runnable} target of
606      * the created thread must call {@link #work()} in order to run this
607      * DTrace consumer.  For example, to modify the default background
608      * consumer thread:
609      * <pre><code>
610      *	protected Thread
611      *	createThread()
612      *	{
613      *		Thread t = super.createThread();
614      *		t.setPriority(Thread.MIN_PRIORITY);
615      *		return t;
616      *	}
617      * </code></pre>
618      * Or if you need to create your own thread:
619      * <pre></code>
620      *	protected Thread
621      *	createThread()
622      *	{
623      *		Runnable target = new Runnable() {
624      *			public void run() {
625      *				work();
626      *			}
627      *		};
628      *		String name = "Consumer " + UserApplication.sequence++;
629      *		Thread t = new Thread(UserApplication.threadGroup,
630      *			target, name);
631      *		return t;
632      *	}
633      * </code></pre>
634      * Do not start the returned thread, otherwise {@code go()} will
635      * throw an {@link IllegalThreadStateException} when it tries to
636      * start the returned thread a second time.
637      */
638     protected Thread
639     createThread()
640     {
641 	Thread t = new Thread(new Runnable() {
642 	    public void run() {
643 		work();
644 	    }
645 	}, "DTrace consumer " + id);
646 	return t;
647     }
648 
649     /**
650      * @inheritDoc
651      * @throws IllegalThreadStateException if a subclass calls {@link
652      * Thread#start()} on the value of {@link #createThread()}
653      * @see #createThread()
654      */
655     public void
656     go() throws DTraceException
657     {
658 	go(null);
659     }
660 
661     /**
662      * @inheritDoc
663      * @throws IllegalThreadStateException if a subclass calls {@link
664      * Thread#start()} on the value of {@link #createThread()}
665      * @see #createThread()
666      */
667     public synchronized void
668     go(ExceptionHandler h) throws DTraceException
669     {
670 	switch (state) {
671 	    case INIT:
672 		throw new IllegalStateException("consumer not open");
673 	    case OPEN:
674 		throw new IllegalStateException("no compiled program");
675 	    case COMPILED:
676 		//
677 		// Throws IllegalStateException if not all compiled programs are
678 		// also enabled.  Does not make any calls to libdtrace.
679 		//
680 		_checkProgramEnabling();
681 		break;
682 	    case GO:
683 	    case STARTED:
684 		throw new IllegalStateException("go() already called");
685 	    case STOPPED:
686 		throw new IllegalStateException("consumer stopped");
687 	    case CLOSED:
688 		throw new IllegalStateException("consumer closed");
689 	    default:
690 		throw new IllegalArgumentException("unknown state: " + state);
691 	}
692 
693 	synchronized (LocalConsumer.class) {
694 	    _go();
695 	}
696 
697 	state = State.GO;
698 	exceptionHandler = h;
699 	Thread t = createThread();
700 	t.start();
701     }
702 
703     public synchronized void
704     stop()
705     {
706 	switch (state) {
707 	    case INIT:
708 		throw new IllegalStateException("consumer not open");
709 	    case OPEN:
710 	    case COMPILED:
711 		throw new IllegalStateException("go() not called");
712 	    case GO:
713 		try {
714 		    synchronized (LocalConsumer.class) {
715 			_stop();
716 		    }
717 		    state = State.STOPPED;
718 		} catch (DTraceException e) {
719 		    if (exceptionHandler != null) {
720 			exceptionHandler.handleException(e);
721 		    } else {
722 			e.printStackTrace();
723 		    }
724 		}
725 		break;
726 	    case STARTED:
727 		//
728 		// Calls no libdtrace methods, so no synchronization is
729 		// needed.  Sets a native flag that causes the consumer
730 		// thread to exit the consumer loop and call native
731 		// dtrace_stop() at the end of the current interval
732 		// (after grabbing the global Consumer.class lock
733 		// required for any libdtrace call).
734 		//
735 		_interrupt();
736 
737 		synchronized (stopLock) {
738 		    //
739 		    // If the work() thread got the stopLock first, then
740 		    // we have nothing to wait for.  (Calling wait() on
741 		    // the stopLock can only deadlock this LocalConsumer
742 		    // in that case.)  However, we still need to notify
743 		    // listeners here, since we're holding the lock on
744 		    // this LocalConsumer.
745 		    //
746 		    while (!workEnded) {
747 			try {
748 			    stopLock.wait();
749 			} catch (InterruptedException e) {
750 			    logger.warning(e.toString());
751 			    // do nothing but re-check the condition for waiting
752 			}
753 		    }
754 		}
755 
756 		state = State.STOPPED;
757 		fireConsumerStopped(new ConsumerEvent(this,
758 			System.nanoTime()));
759 		break;
760 	    case STOPPED:
761 		//
762 		// The work() thread that runs the native consumer loop
763 		// may have terminated because of the exit() action in a
764 		// DTrace program.  In that case, a RuntimeException is
765 		// inappropriate because there is no misuse of the API
766 		// Creating a new checked exception type to handle this
767 		// case seems to offer no benefit for the trouble to the
768 		// caller.  Instead, the situation calls for stop() to
769 		// be quietly tolerant.
770 		//
771 		if (stopCalled) {
772 		    throw new IllegalStateException("consumer already stopped");
773 		}
774 		logger.info("consumer already stopped");
775 		break;
776 	    case CLOSED:
777 		throw new IllegalStateException("consumer closed");
778 	    default:
779 		throw new IllegalArgumentException("unknown state: " + state);
780 	}
781 
782 	stopCalled = true;
783     }
784 
785     public synchronized void
786     close()
787     {
788 	if ((state == State.INIT) || (state == State.CLOSED)) {
789 	    state = State.CLOSED;
790 	    return;
791 	}
792 
793 	if ((state == State.STARTED) || (state == State.GO)) {
794 	    stop();
795 	}
796 
797 	synchronized (LocalConsumer.class) {
798 	    _close();
799 	}
800 	_destroy();
801 	state = State.CLOSED;
802 
803 	if (logger.isLoggable(Level.INFO)) {
804 	    logger.info("consumer table count: " + _openCount());
805 	}
806     }
807 
808     public void
809     addConsumerListener(ConsumerListener l)
810     {
811         listenerList.add(ConsumerListener.class, l);
812     }
813 
814     public void
815     removeConsumerListener(ConsumerListener l)
816     {
817         listenerList.remove(ConsumerListener.class, l);
818     }
819 
820     public Aggregate
821     getAggregate() throws DTraceException
822     {
823 	// include all, clear none
824 	return getAggregate(null, Collections. <String> emptySet());
825     }
826 
827     public Aggregate
828     getAggregate(Set <String> includedAggregationNames)
829             throws DTraceException
830     {
831 	return getAggregate(includedAggregationNames,
832 		Collections. <String> emptySet());
833     }
834 
835     public Aggregate
836     getAggregate(Set <String> includedAggregationNames,
837 	    Set <String> clearedAggregationNames)
838             throws DTraceException
839     {
840 	AggregateSpec spec = new AggregateSpec();
841 
842 	if (includedAggregationNames == null) {
843 	    spec.setIncludeByDefault(true);
844 	} else {
845 	    spec.setIncludeByDefault(false);
846 	    for (String included : includedAggregationNames) {
847 		spec.addIncludedAggregationName(included);
848 	    }
849 	}
850 
851 	if (clearedAggregationNames == null) {
852 	    spec.setClearByDefault(true);
853 	} else {
854 	    spec.setClearByDefault(false);
855 	    for (String cleared : clearedAggregationNames) {
856 		spec.addClearedAggregationName(cleared);
857 	    }
858 	}
859 
860 	return getAggregate(spec);
861     }
862 
863     private synchronized Aggregate
864     getAggregate(AggregateSpec spec) throws DTraceException
865     {
866 	//
867 	// It should be possible to request aggregation data after a
868 	// consumer has stopped but not after it has been closed.
869 	//
870 	checkGoCalled();
871 
872 	//
873 	// Getting the aggregate is a time-consuming request that should not
874 	// prevent other consumers from running concurrently.  Instead,
875 	// native code will acquire the LocalConsumer.class monitor as
876 	// needed before calling libdtrace functions.
877 	//
878 	Aggregate aggregate = _getAggregate(spec);
879 	return aggregate;
880     }
881 
882     private synchronized void
883     checkGoCalled()
884     {
885 	switch (state) {
886 	    case INIT:
887 		throw new IllegalStateException("consumer not open");
888 	    case OPEN:
889 	    case COMPILED:
890 		throw new IllegalStateException("go() not called");
891 	    case GO:
892 	    case STARTED:
893 	    case STOPPED:
894 		break;
895 	    case CLOSED:
896 		throw new IllegalStateException("consumer closed");
897 	}
898     }
899 
900     private synchronized void
901     checkGoNotCalled()
902     {
903 	switch (state) {
904 	    case INIT:
905 		throw new IllegalStateException("consumer not open");
906 	    case OPEN:
907 	    case COMPILED:
908 		break;
909 	    case GO:
910 	    case STARTED:
911 		throw new IllegalStateException("go() already called");
912 	    case STOPPED:
913 		throw new IllegalStateException("consumer stopped");
914 	    case CLOSED:
915 		throw new IllegalStateException("consumer closed");
916 	}
917     }
918 
919     public synchronized int
920     createProcess(String command) throws DTraceException
921     {
922 	if (command == null) {
923 	    throw new NullPointerException("command is null");
924 	}
925 
926 	checkGoNotCalled();
927 
928 	int pid;
929 	synchronized (LocalConsumer.class) {
930 	    pid = _createProcess(command);
931 	}
932 	return pid;
933     }
934 
935     public synchronized void
936     grabProcess(int pid) throws DTraceException
937     {
938 	checkGoNotCalled();
939 
940 	synchronized (LocalConsumer.class) {
941 	    _grabProcess(pid);
942 	}
943     }
944 
945     public synchronized List <ProbeDescription>
946     listProbes(ProbeDescription filter) throws DTraceException
947     {
948 	checkGoNotCalled();
949 	List <ProbeDescription> probeList =
950 		new LinkedList <ProbeDescription> ();
951 	if (filter == ProbeDescription.EMPTY) {
952 	    filter = null;
953 	}
954 	synchronized (LocalConsumer.class) {
955 	    _listProbes(probeList, filter);
956 	}
957 	return probeList;
958     }
959 
960     public synchronized List <Probe>
961     listProbeDetail(ProbeDescription filter) throws DTraceException
962     {
963 	checkGoNotCalled();
964 	List <Probe> probeList = new LinkedList <Probe> ();
965 	if (filter == ProbeDescription.EMPTY) {
966 	    filter = null;
967 	}
968 	synchronized (LocalConsumer.class) {
969 	    _listProbeDetail(probeList, filter);
970 	}
971 	return probeList;
972     }
973 
974     public synchronized List <ProbeDescription>
975     listProgramProbes(Program program) throws DTraceException
976     {
977 	checkProgram(program);
978 	checkGoNotCalled();
979 	List <ProbeDescription> probeList =
980 		new LinkedList <ProbeDescription> ();
981 	synchronized (LocalConsumer.class) {
982 	    _listCompiledProbes(probeList, program);
983 	}
984 	return probeList;
985     }
986 
987     public synchronized List <Probe>
988     listProgramProbeDetail(Program program) throws DTraceException
989     {
990 	checkProgram(program);
991 	checkGoNotCalled();
992 	List <Probe> probeList = new LinkedList <Probe> ();
993 	synchronized (LocalConsumer.class) {
994 	    _listCompiledProbeDetail(probeList, program);
995 	}
996 	return probeList;
997     }
998 
999     public synchronized String
1000     lookupKernelFunction(int address)
1001     {
1002 	checkGoCalled();
1003 	synchronized (LocalConsumer.class) {
1004 	    return _lookupKernelFunction(new Integer(address));
1005 	}
1006     }
1007 
1008     public synchronized String
1009     lookupKernelFunction(long address)
1010     {
1011 	checkGoCalled();
1012 	synchronized (LocalConsumer.class) {
1013 	    return _lookupKernelFunction(new Long(address));
1014 	}
1015     }
1016 
1017     public synchronized String
1018     lookupUserFunction(int pid, int address)
1019     {
1020 	checkGoCalled();
1021 	synchronized (LocalConsumer.class) {
1022 	    return _lookupUserFunction(pid, new Integer(address));
1023 	}
1024     }
1025 
1026     public synchronized String
1027     lookupUserFunction(int pid, long address)
1028     {
1029 	checkGoCalled();
1030 	synchronized (LocalConsumer.class) {
1031 	    return _lookupUserFunction(pid, new Long(address));
1032 	}
1033     }
1034 
1035     public String
1036     getVersion()
1037     {
1038 	synchronized (LocalConsumer.class) {
1039 	    return LocalConsumer._getVersion();
1040 	}
1041     }
1042 
1043     /**
1044      * Called by native code.
1045      */
1046     private void
1047     nextProbeData(ProbeData probeData) throws ConsumerException
1048     {
1049 	fireDataReceived(new DataEvent(this, probeData));
1050     }
1051 
1052     /**
1053      * Called by native code.
1054      */
1055     private void
1056     dataDropped(Drop drop) throws ConsumerException
1057     {
1058 	fireDataDropped(new DropEvent(this, drop));
1059     }
1060 
1061     /**
1062      * Called by native code.
1063      */
1064     private void
1065     errorEncountered(Error error) throws ConsumerException
1066     {
1067 	fireErrorEncountered(new ErrorEvent(this, error));
1068     }
1069 
1070     /**
1071      * Called by native code.
1072      */
1073     private void
1074     processStateChanged(ProcessState processState) throws ConsumerException
1075     {
1076 	fireProcessStateChanged(new ProcessEvent(this, processState));
1077     }
1078 
1079     protected void
1080     fireDataReceived(DataEvent e) throws ConsumerException
1081     {
1082         // Guaranteed to return a non-null array
1083         Object[] listeners = listenerList.getListenerList();
1084         // Process the listeners last to first, notifying
1085         // those that are interested in this event
1086         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1087             if (listeners[i] == ConsumerListener.class) {
1088                 ((ConsumerListener)listeners[i + 1]).dataReceived(e);
1089             }
1090         }
1091     }
1092 
1093     protected void
1094     fireDataDropped(DropEvent e) throws ConsumerException
1095     {
1096         // Guaranteed to return a non-null array
1097         Object[] listeners = listenerList.getListenerList();
1098         // Process the listeners last to first, notifying
1099         // those that are interested in this event
1100         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1101             if (listeners[i] == ConsumerListener.class) {
1102                 ((ConsumerListener)listeners[i + 1]).dataDropped(e);
1103             }
1104         }
1105     }
1106 
1107     protected void
1108     fireErrorEncountered(ErrorEvent e) throws ConsumerException
1109     {
1110         // Guaranteed to return a non-null array
1111         Object[] listeners = listenerList.getListenerList();
1112         // Process the listeners last to first, notifying
1113         // those that are interested in this event
1114         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1115             if (listeners[i] == ConsumerListener.class) {
1116                 ((ConsumerListener)listeners[i + 1]).errorEncountered(e);
1117             }
1118         }
1119     }
1120 
1121     protected void
1122     fireProcessStateChanged(ProcessEvent e) throws ConsumerException
1123     {
1124         // Guaranteed to return a non-null array
1125         Object[] listeners = listenerList.getListenerList();
1126         // Process the listeners last to first, notifying
1127         // those that are interested in this event
1128         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1129             if (listeners[i] == ConsumerListener.class) {
1130                 ((ConsumerListener)listeners[i + 1]).processStateChanged(e);
1131             }
1132         }
1133     }
1134 
1135     protected void
1136     fireConsumerStarted(ConsumerEvent e)
1137     {
1138         // Guaranteed to return a non-null array
1139         Object[] listeners = listenerList.getListenerList();
1140         // Process the listeners last to first, notifying
1141         // those that are interested in this event
1142         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1143             if (listeners[i] == ConsumerListener.class) {
1144                 ((ConsumerListener)listeners[i + 1]).consumerStarted(e);
1145             }
1146         }
1147     }
1148 
1149     protected void
1150     fireConsumerStopped(ConsumerEvent e)
1151     {
1152         // Guaranteed to return a non-null array
1153         Object[] listeners = listenerList.getListenerList();
1154         // Process the listeners last to first, notifying
1155         // those that are interested in this event
1156         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1157             if (listeners[i] == ConsumerListener.class) {
1158                 ((ConsumerListener)listeners[i + 1]).consumerStopped(e);
1159             }
1160         }
1161     }
1162 
1163     // Called by native code
1164     private void
1165     intervalBegan()
1166     {
1167 	fireIntervalBegan(new ConsumerEvent(this, System.nanoTime()));
1168     }
1169 
1170     protected void
1171     fireIntervalBegan(ConsumerEvent e)
1172     {
1173         // Guaranteed to return a non-null array
1174         Object[] listeners = listenerList.getListenerList();
1175         // Process the listeners last to first, notifying
1176         // those that are interested in this event
1177         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1178             if (listeners[i] == ConsumerListener.class) {
1179                 ((ConsumerListener)listeners[i + 1]).intervalBegan(e);
1180             }
1181         }
1182     }
1183 
1184     // Called by native code
1185     private void
1186     intervalEnded()
1187     {
1188 	fireIntervalEnded(new ConsumerEvent(this, System.nanoTime()));
1189     }
1190 
1191     protected void
1192     fireIntervalEnded(ConsumerEvent e)
1193     {
1194         // Guaranteed to return a non-null array
1195         Object[] listeners = listenerList.getListenerList();
1196         // Process the listeners last to first, notifying
1197         // those that are interested in this event
1198         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1199             if (listeners[i] == ConsumerListener.class) {
1200                 ((ConsumerListener)listeners[i + 1]).intervalEnded(e);
1201             }
1202         }
1203     }
1204 
1205     /**
1206      * Gets a string representation of this consumer useful for logging
1207      * and not intended for display.  The exact details of the
1208      * representation are unspecified and subject to change, but the
1209      * following format may be regarded as typical:
1210      * <pre><code>
1211      * class-name[property1 = value1, property2 = value2]
1212      * </code></pre>
1213      */
1214     public String
1215     toString()
1216     {
1217 	StringBuffer buf = new StringBuffer(LocalConsumer.class.getName());
1218 	synchronized (this) {
1219 	    buf.append("[open = ");
1220 	    buf.append(isOpen());
1221 	    buf.append(", enabled = ");
1222 	    buf.append(isEnabled());
1223 	    buf.append(", running = ");
1224 	    buf.append(isRunning());
1225 	    buf.append(", closed = ");
1226 	    buf.append(isClosed());
1227 	}
1228 	buf.append(']');
1229 	return buf.toString();
1230     }
1231 
1232     /**
1233      * Ensures that the {@link #close()} method of this consumer has
1234      * been called before it is garbage-collected.  The intended safety
1235      * net is weak because the JVM does not guarantee that an object
1236      * will be garbage-collected when it is no longer referenced.  Users
1237      * of the API should call {@code close()} to ensure that all
1238      * resources associated with this consumer are reclaimed in a timely
1239      * manner.
1240      *
1241      * @see #close()
1242      */
1243     protected void
1244     finalize()
1245     {
1246 	close();
1247     }
1248 
1249     private String
1250     getTag()
1251     {
1252 	return super.toString();
1253     }
1254 
1255     //
1256     // Uniquely identifies a consumer across systems so it is possible
1257     // to validate that an object such as a Program passed to a remote
1258     // client over a socket was created by this consumer and no other.
1259     //
1260     static class Identifier implements Serializable {
1261 	static final long serialVersionUID = 2183165132305302834L;
1262 
1263 	// local identifier
1264 	private int id;
1265 	private long timestamp;
1266 	// remote identifier
1267 	private InetAddress localHost;
1268 	private String tag; // in case localHost not available
1269 
1270 	private
1271 	Identifier(LocalConsumer consumer)
1272 	{
1273 	    id = LocalConsumer.sequence++;
1274 	    timestamp = System.currentTimeMillis();
1275 	    try {
1276 		localHost = InetAddress.getLocalHost();
1277 	    } catch (UnknownHostException e) {
1278 		localHost = null;
1279 	    }
1280 	    tag = consumer.getTag();
1281 	}
1282 
1283 	@Override
1284 	public boolean
1285 	equals(Object o)
1286 	{
1287 	    if (o == this) {
1288 		return true;
1289 	    }
1290 	    if (o instanceof Identifier) {
1291 		Identifier i = (Identifier)o;
1292 		return ((id == i.id) &&
1293 			(timestamp == i.timestamp) &&
1294 			((localHost == null) ? (i.localHost == null) :
1295 			 localHost.equals(i.localHost)) &&
1296 			tag.equals(i.tag));
1297 	    }
1298 	    return false;
1299 	}
1300 
1301 	@Override
1302 	public int
1303 	hashCode()
1304 	{
1305 	    int hash = 17;
1306 	    hash = (37 * hash) + id;
1307 	    hash = (37 * hash) + ((int)(timestamp ^ (timestamp >>> 32)));
1308 	    hash = (37 * hash) + (localHost == null ? 0 :
1309 		    localHost.hashCode());
1310 	    hash = (37 * hash) + tag.hashCode();
1311 	    return hash;
1312 	}
1313 
1314 	@Override
1315 	public String
1316 	toString()
1317 	{
1318 	    StringBuffer buf = new StringBuffer();
1319 	    buf.append(Identifier.class.getName());
1320 	    buf.append("[id = ");
1321 	    buf.append(id);
1322 	    buf.append(", timestamp = ");
1323 	    buf.append(timestamp);
1324 	    buf.append(", localHost = ");
1325 	    buf.append(localHost);
1326 	    buf.append(", tag = ");
1327 	    buf.append(tag);
1328 	    buf.append(']');
1329 	    return buf.toString();
1330 	}
1331     }
1332 }
1333