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