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