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