xref: /illumos-gate/usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LocalConsumer.java (revision 150d2c5288c645a1c1a7d2bee61199a3729406c7)
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 2007 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 = 2;
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 	p.validateFile();
415 	state = State.COMPILED;
416 
417 	return p;
418     }
419 
420     private synchronized void
421     checkProgram(Program program)
422     {
423 	if (program == null) {
424 	    throw new NullPointerException("program is null");
425 	}
426 	if (!id.equals(program.consumerID)) {
427 	    throw new IllegalArgumentException("program not compiled " +
428 		    "by this consumer");
429 	}
430     }
431 
432     public void
433     enable() throws DTraceException
434     {
435 	enable(null);
436     }
437 
438     public synchronized void
439     enable(Program program) throws DTraceException
440     {
441 	switch (state) {
442 	    case INIT:
443 		throw new IllegalStateException("consumer not open");
444 	    case OPEN:
445 		throw new IllegalStateException("no compiled program");
446 	    case COMPILED:
447 		break;
448 	    case GO:
449 	    case STARTED:
450 		throw new IllegalStateException("go() already called");
451 	    case STOPPED:
452 		throw new IllegalStateException("consumer stopped");
453 	    case CLOSED:
454 		throw new IllegalStateException("consumer closed");
455 	}
456 
457 	// Compile all programs if null
458 	if (program != null) {
459 	    checkProgram(program);
460 	}
461 
462 	//
463 	// Left to native code to throw IllegalArgumentException if the
464 	// program is already enabled, since only the native code knows
465 	// the enabled state.
466 	//
467 	synchronized (LocalConsumer.class) {
468 	    _exec(program);
469 	}
470     }
471 
472     public synchronized void
473     getProgramInfo(Program program) throws DTraceException
474     {
475 	checkProgram(program);
476 	if (state == State.CLOSED) {
477 	    throw new IllegalStateException("consumer closed");
478 	}
479 
480 	//
481 	// The given program was compiled by this consumer, so we can
482 	// assert the following:
483 	//
484 	assert ((state != State.INIT) && (state != State.OPEN));
485 
486 	synchronized (LocalConsumer.class) {
487 	    _getProgramInfo(program);
488 	}
489     }
490 
491     private void
492     setOptions(Option[] options) throws DTraceException
493     {
494 	for (Option o : options) {
495 	    setOption(o.getName(), o.getValue());
496 	}
497     }
498 
499     public void
500     setOption(String option) throws DTraceException
501     {
502 	setOption(option, Option.VALUE_SET);
503     }
504 
505     public void
506     unsetOption(String option) throws DTraceException
507     {
508 	setOption(option, Option.VALUE_UNSET);
509     }
510 
511     public synchronized void
512     setOption(String option, String value) throws DTraceException
513     {
514 	if (option == null) {
515 	    throw new NullPointerException("option is null");
516 	}
517 	if (value == null) {
518 	    throw new NullPointerException("option value is null");
519 	}
520 
521 	switch (state) {
522 	    case INIT:
523 		throw new IllegalStateException("consumer not open");
524 	    case OPEN:
525 	    case COMPILED:
526 	    case GO:
527 	    case STARTED: // Some options can be set on a running consumer
528 	    case STOPPED: // Allowed (may affect getAggregate())
529 		break;
530 	    case CLOSED:
531 		throw new IllegalStateException("consumer closed");
532 	}
533 
534 	synchronized (LocalConsumer.class) {
535 	    _setOption(option, value);
536 	}
537     }
538 
539     public synchronized long
540     getOption(String option) throws DTraceException
541     {
542 	if (option == null) {
543 	    throw new NullPointerException("option is null");
544 	}
545 
546 	switch (state) {
547 	    case INIT:
548 		throw new IllegalStateException("consumer not open");
549 	    case OPEN:
550 	    case COMPILED:
551 	    case GO:
552 	    case STARTED:
553 	    case STOPPED:
554 		break;
555 	    case CLOSED:
556 		throw new IllegalStateException("consumer closed");
557 	}
558 
559 	long value;
560 	synchronized (LocalConsumer.class) {
561 	    value = _getOption(option);
562 	}
563 	return value;
564     }
565 
566     public final synchronized boolean
567     isOpen()
568     {
569 	return ((state != State.INIT) && (state != State.CLOSED));
570     }
571 
572     public final synchronized boolean
573     isEnabled()
574     {
575 	if (state != State.COMPILED) {
576 	    return false;
577 	}
578 
579 	return _isEnabled();
580     }
581 
582     public final synchronized boolean
583     isRunning()
584     {
585 	return (state == State.STARTED);
586     }
587 
588     public final synchronized boolean
589     isClosed()
590     {
591 	return (state == State.CLOSED);
592     }
593 
594     /**
595      * Called in the runnable target of the thread returned by {@link
596      * #createThread()} to run this DTrace consumer.
597      *
598      * @see #createThread()
599      */
600     protected final void
601     work()
602     {
603 	try {
604 	    synchronized (this) {
605 		if (state != State.GO) {
606 		    //
607 		    // stop() was called after go() but before the
608 		    // consumer started
609 		    //
610 		    return; // executes finally block before returning
611 		}
612 
613 		state = State.STARTED;
614 		fireConsumerStarted(new ConsumerEvent(this,
615 			System.nanoTime()));
616 	    }
617 
618 	    //
619 	    // We should not prevent other consumers from running
620 	    // concurrently while this consumer blocks on the native
621 	    // consumer loop.  Instead, native code will acquire the
622 	    // LocalConsumer.class monitor as needed before calling
623 	    // libdtrace functions.
624 	    //
625 	    _consume();
626 
627 	} catch (Throwable e) {
628 	    if (exceptionHandler != null) {
629 		exceptionHandler.handleException(e);
630 	    } else {
631 		e.printStackTrace();
632 	    }
633 	} finally {
634 	    synchronized (stopLock) {
635 		// Notify listeners while holding stopLock to guarantee
636 		// that listeners finish executing consumerStopped()
637 		// before the stop() method returns.
638 		synchronized (this) {
639 		    if (state == State.STOPPED || state == state.CLOSED) {
640 			//
641 			// This consumer was stopped just after calling
642 			// go() but before starting (the premature return
643 			// case at the top of this work() method). It is
644 			// possible to call close() on a consumer that has
645 			// been stopped before starting. In that case the
646 			// premature return above still takes us here in the
647 			// finally clause, and we must not revert the CLOSED
648 			// state to STOPPED.
649 			//
650 		    } else {
651 			state = State.STOPPED;
652 			fireConsumerStopped(new ConsumerEvent(this,
653 				System.nanoTime()));
654 		    }
655 		}
656 
657 		// Notify the stop() method to stop waiting
658 		workEnded = true;
659 		stopLock.notifyAll();
660 	    }
661 	}
662     }
663 
664     /**
665      * Creates the background thread started by {@link #go()} to run
666      * this consumer.  Override this method if you need to set
667      * non-default {@code Thread} options or create the thread in a
668      * {@code ThreadGroup}.  If you don't need to create the thread
669      * yourself, set the desired options on {@code super.createThread()}
670      * before returning it.  Otherwise, the {@code Runnable} target of
671      * the created thread must call {@link #work()} in order to run this
672      * DTrace consumer.  For example, to modify the default background
673      * consumer thread:
674      * <pre><code>
675      *	protected Thread
676      *	createThread()
677      *	{
678      *		Thread t = super.createThread();
679      *		t.setPriority(Thread.MIN_PRIORITY);
680      *		return t;
681      *	}
682      * </code></pre>
683      * Or if you need to create your own thread:
684      * <pre></code>
685      *	protected Thread
686      *	createThread()
687      *	{
688      *		Runnable target = new Runnable() {
689      *			public void run() {
690      *				work();
691      *			}
692      *		};
693      *		String name = "Consumer " + UserApplication.sequence++;
694      *		Thread t = new Thread(UserApplication.threadGroup,
695      *			target, name);
696      *		return t;
697      *	}
698      * </code></pre>
699      * Do not start the returned thread, otherwise {@code go()} will
700      * throw an {@link IllegalThreadStateException} when it tries to
701      * start the returned thread a second time.
702      */
703     protected Thread
704     createThread()
705     {
706 	Thread t = new Thread(new Runnable() {
707 	    public void run() {
708 		work();
709 	    }
710 	}, "DTrace consumer " + id);
711 	return t;
712     }
713 
714     /**
715      * @inheritDoc
716      * @throws IllegalThreadStateException if a subclass calls {@link
717      * Thread#start()} on the value of {@link #createThread()}
718      * @see #createThread()
719      */
720     public void
721     go() throws DTraceException
722     {
723 	go(null);
724     }
725 
726     /**
727      * @inheritDoc
728      * @throws IllegalThreadStateException if a subclass calls {@link
729      * Thread#start()} on the value of {@link #createThread()}
730      * @see #createThread()
731      */
732     public synchronized void
733     go(ExceptionHandler h) throws DTraceException
734     {
735 	switch (state) {
736 	    case INIT:
737 		throw new IllegalStateException("consumer not open");
738 	    case OPEN:
739 		throw new IllegalStateException("no compiled program");
740 	    case COMPILED:
741 		//
742 		// Throws IllegalStateException if not all compiled programs are
743 		// also enabled.  Does not make any calls to libdtrace.
744 		//
745 		_checkProgramEnabling();
746 		break;
747 	    case GO:
748 	    case STARTED:
749 		throw new IllegalStateException("go() already called");
750 	    case STOPPED:
751 		throw new IllegalStateException("consumer stopped");
752 	    case CLOSED:
753 		throw new IllegalStateException("consumer closed");
754 	    default:
755 		throw new IllegalArgumentException("unknown state: " + state);
756 	}
757 
758 	synchronized (LocalConsumer.class) {
759 	    _go();
760 	}
761 
762 	state = State.GO;
763 	exceptionHandler = h;
764 	Thread t = createThread();
765 	t.start();
766     }
767 
768     /**
769      * @inheritDoc
770      *
771      * @throws IllegalThreadStateException if attempting to {@code
772      * stop()} a running consumer while holding the lock on that
773      * consumer
774      */
775     public void
776     stop()
777     {
778 	boolean running = false;
779 
780 	synchronized (this) {
781 	    switch (state) {
782 		case INIT:
783 		    throw new IllegalStateException("consumer not open");
784 		case OPEN:
785 		case COMPILED:
786 		    throw new IllegalStateException("go() not called");
787 		case GO:
788 		    try {
789 			synchronized (LocalConsumer.class) {
790 			    _stop();
791 			}
792 			state = State.STOPPED;
793 			fireConsumerStopped(new ConsumerEvent(this,
794 				System.nanoTime()));
795 		    } catch (DTraceException e) {
796 			if (exceptionHandler != null) {
797 			    exceptionHandler.handleException(e);
798 			} else {
799 			    e.printStackTrace();
800 			}
801 		    }
802 		    break;
803 		case STARTED:
804 		    running = true;
805 		    break;
806 		case STOPPED:
807 		    //
808 		    // The work() thread that runs the native consumer
809 		    // loop may have terminated because of the exit()
810 		    // action in a DTrace program.  In that case, a
811 		    // RuntimeException is inappropriate because there
812 		    // is no misuse of the API.  Creating a new checked
813 		    // exception type to handle this case seems to offer
814 		    // no benefit for the trouble to the caller.
815 		    // Instead, the situation calls for stop() to be
816 		    // quietly tolerant.
817 		    //
818 		    if (stopCalled) {
819 			throw new IllegalStateException(
820 				"consumer already stopped");
821 		    }
822 		    logger.fine("consumer already stopped");
823 		    break;
824 		case CLOSED:
825 		    throw new IllegalStateException("consumer closed");
826 		default:
827 		    throw new IllegalArgumentException("unknown state: " +
828 			    state);
829 	    }
830 
831 	    stopCalled = true;
832 	}
833 
834 	if (running) {
835 	    if (Thread.holdsLock(this)) {
836 		throw new IllegalThreadStateException("The current " +
837 			"thread cannot stop this LocalConsumer while " +
838 			"holding the lock on this LocalConsumer");
839 	    }
840 
841 	    //
842 	    // Calls no libdtrace methods, so no synchronization is
843 	    // needed.  Sets a native flag that causes the consumer
844 	    // thread to exit the consumer loop and call native
845 	    // dtrace_stop() at the end of the current interval (after
846 	    // grabbing the global Consumer.class lock required for any
847 	    // libdtrace call).
848 	    //
849 	    _interrupt();
850 
851 	    synchronized (stopLock) {
852 		//
853 		// Wait for work() to set workEnded.  If the work()
854 		// thread got the stopLock first, then workEnded is
855 		// already set.
856 		//
857 		while (!workEnded) {
858 		    try {
859 			stopLock.wait();
860 		    } catch (InterruptedException e) {
861 			logger.warning(e.toString());
862 			// do nothing but re-check the condition for
863 			// waiting
864 		    }
865 		}
866 	    }
867 	}
868     }
869 
870     public synchronized void
871     abort()
872     {
873 	if ((state != State.INIT) && (state != State.CLOSED)) {
874 	    _interrupt();
875 	}
876 	abortCalled = true;
877     }
878 
879     /**
880      * @inheritDoc
881      *
882      * @throws IllegalThreadStateException if attempting to {@code
883      * close()} a running consumer while holding the lock on that
884      * consumer
885      */
886     public void
887     close()
888     {
889 	synchronized (this) {
890 	    if ((state == State.INIT) || (state == State.CLOSED)) {
891 		state = State.CLOSED;
892 		return;
893 	    }
894 	}
895 
896 	try {
897 	    stop();
898 	} catch (IllegalStateException e) {
899 	    // ignore (we don't have synchronized state access because
900 	    // it is illegal to call stop() while holding the lock on
901 	    // this consumer)
902 	}
903 
904 	synchronized (this) {
905 	    if (state != State.CLOSED) {
906 		synchronized (LocalConsumer.class) {
907 		    _close();
908 		}
909 		_destroy();
910 		state = State.CLOSED;
911 
912 		if (logger.isLoggable(Level.INFO)) {
913 		    logger.info("consumer table count: " + _openCount());
914 		}
915 	    }
916 	}
917     }
918 
919     public void
920     addConsumerListener(ConsumerListener l)
921     {
922         listenerList.add(ConsumerListener.class, l);
923     }
924 
925     public void
926     removeConsumerListener(ConsumerListener l)
927     {
928         listenerList.remove(ConsumerListener.class, l);
929     }
930 
931     public Aggregate
932     getAggregate() throws DTraceException
933     {
934 	// include all, clear none
935 	return getAggregate(null, Collections. <String> emptySet());
936     }
937 
938     public Aggregate
939     getAggregate(Set <String> includedAggregationNames)
940             throws DTraceException
941     {
942 	return getAggregate(includedAggregationNames,
943 		Collections. <String> emptySet());
944     }
945 
946     public Aggregate
947     getAggregate(Set <String> includedAggregationNames,
948 	    Set <String> clearedAggregationNames)
949             throws DTraceException
950     {
951 	AggregateSpec spec = new AggregateSpec();
952 
953 	if (includedAggregationNames == null) {
954 	    spec.setIncludeByDefault(true);
955 	} else {
956 	    spec.setIncludeByDefault(false);
957 	    for (String included : includedAggregationNames) {
958 		spec.addIncludedAggregationName(included);
959 	    }
960 	}
961 
962 	if (clearedAggregationNames == null) {
963 	    spec.setClearByDefault(true);
964 	} else {
965 	    spec.setClearByDefault(false);
966 	    for (String cleared : clearedAggregationNames) {
967 		spec.addClearedAggregationName(cleared);
968 	    }
969 	}
970 
971 	return getAggregate(spec);
972     }
973 
974     private synchronized Aggregate
975     getAggregate(AggregateSpec spec) throws DTraceException
976     {
977 	//
978 	// It should be possible to request aggregation data after a
979 	// consumer has stopped but not after it has been closed.
980 	//
981 	checkGoCalled();
982 
983 	//
984 	// Getting the aggregate is a time-consuming request that should not
985 	// prevent other consumers from running concurrently.  Instead,
986 	// native code will acquire the LocalConsumer.class monitor as
987 	// needed before calling libdtrace functions.
988 	//
989 	Aggregate aggregate = _getAggregate(spec);
990 	return aggregate;
991     }
992 
993     private synchronized void
994     checkGoCalled()
995     {
996 	switch (state) {
997 	    case INIT:
998 		throw new IllegalStateException("consumer not open");
999 	    case OPEN:
1000 	    case COMPILED:
1001 		throw new IllegalStateException("go() not called");
1002 	    case GO:
1003 	    case STARTED:
1004 	    case STOPPED:
1005 		break;
1006 	    case CLOSED:
1007 		throw new IllegalStateException("consumer closed");
1008 	}
1009     }
1010 
1011     private synchronized void
1012     checkGoNotCalled()
1013     {
1014 	switch (state) {
1015 	    case INIT:
1016 		throw new IllegalStateException("consumer not open");
1017 	    case OPEN:
1018 	    case COMPILED:
1019 		break;
1020 	    case GO:
1021 	    case STARTED:
1022 		throw new IllegalStateException("go() already called");
1023 	    case STOPPED:
1024 		throw new IllegalStateException("consumer stopped");
1025 	    case CLOSED:
1026 		throw new IllegalStateException("consumer closed");
1027 	}
1028     }
1029 
1030     public synchronized int
1031     createProcess(String command) throws DTraceException
1032     {
1033 	if (command == null) {
1034 	    throw new NullPointerException("command is null");
1035 	}
1036 
1037 	checkGoNotCalled();
1038 
1039 	int pid;
1040 	synchronized (LocalConsumer.class) {
1041 	    pid = _createProcess(command);
1042 	}
1043 	return pid;
1044     }
1045 
1046     public synchronized void
1047     grabProcess(int pid) throws DTraceException
1048     {
1049 	checkGoNotCalled();
1050 
1051 	synchronized (LocalConsumer.class) {
1052 	    _grabProcess(pid);
1053 	}
1054     }
1055 
1056     public synchronized List <ProbeDescription>
1057     listProbes(ProbeDescription filter) throws DTraceException
1058     {
1059 	checkGoNotCalled();
1060 	List <ProbeDescription> probeList =
1061 		new LinkedList <ProbeDescription> ();
1062 	if (filter == ProbeDescription.EMPTY) {
1063 	    filter = null;
1064 	}
1065 	synchronized (LocalConsumer.class) {
1066 	    _listProbes(probeList, filter);
1067 	}
1068 	return probeList;
1069     }
1070 
1071     public synchronized List <Probe>
1072     listProbeDetail(ProbeDescription filter) throws DTraceException
1073     {
1074 	checkGoNotCalled();
1075 	List <Probe> probeList = new LinkedList <Probe> ();
1076 	if (filter == ProbeDescription.EMPTY) {
1077 	    filter = null;
1078 	}
1079 	synchronized (LocalConsumer.class) {
1080 	    _listProbeDetail(probeList, filter);
1081 	}
1082 	return probeList;
1083     }
1084 
1085     public synchronized List <ProbeDescription>
1086     listProgramProbes(Program program) throws DTraceException
1087     {
1088 	checkProgram(program);
1089 	checkGoNotCalled();
1090 	List <ProbeDescription> probeList =
1091 		new LinkedList <ProbeDescription> ();
1092 	synchronized (LocalConsumer.class) {
1093 	    _listCompiledProbes(probeList, program);
1094 	}
1095 	return probeList;
1096     }
1097 
1098     public synchronized List <Probe>
1099     listProgramProbeDetail(Program program) throws DTraceException
1100     {
1101 	checkProgram(program);
1102 	checkGoNotCalled();
1103 	List <Probe> probeList = new LinkedList <Probe> ();
1104 	synchronized (LocalConsumer.class) {
1105 	    _listCompiledProbeDetail(probeList, program);
1106 	}
1107 	return probeList;
1108     }
1109 
1110     public synchronized String
1111     lookupKernelFunction(int address)
1112     {
1113 	checkGoCalled();
1114 	synchronized (LocalConsumer.class) {
1115 	    return _lookupKernelFunction(new Integer(address));
1116 	}
1117     }
1118 
1119     public synchronized String
1120     lookupKernelFunction(long address)
1121     {
1122 	checkGoCalled();
1123 	synchronized (LocalConsumer.class) {
1124 	    return _lookupKernelFunction(new Long(address));
1125 	}
1126     }
1127 
1128     public synchronized String
1129     lookupUserFunction(int pid, int address)
1130     {
1131 	checkGoCalled();
1132 	synchronized (LocalConsumer.class) {
1133 	    return _lookupUserFunction(pid, new Integer(address));
1134 	}
1135     }
1136 
1137     public synchronized String
1138     lookupUserFunction(int pid, long address)
1139     {
1140 	checkGoCalled();
1141 	synchronized (LocalConsumer.class) {
1142 	    return _lookupUserFunction(pid, new Long(address));
1143 	}
1144     }
1145 
1146     public String
1147     getVersion()
1148     {
1149 	synchronized (LocalConsumer.class) {
1150 	    return LocalConsumer._getVersion();
1151 	}
1152     }
1153 
1154     /**
1155      * Called by native code.
1156      */
1157     private void
1158     nextProbeData(ProbeData probeData) throws ConsumerException
1159     {
1160 	fireDataReceived(new DataEvent(this, probeData));
1161     }
1162 
1163     /**
1164      * Called by native code.
1165      */
1166     private void
1167     dataDropped(Drop drop) throws ConsumerException
1168     {
1169 	fireDataDropped(new DropEvent(this, drop));
1170     }
1171 
1172     /**
1173      * Called by native code.
1174      */
1175     private void
1176     errorEncountered(Error error) throws ConsumerException
1177     {
1178 	fireErrorEncountered(new ErrorEvent(this, error));
1179     }
1180 
1181     /**
1182      * Called by native code.
1183      */
1184     private void
1185     processStateChanged(ProcessState processState) throws ConsumerException
1186     {
1187 	fireProcessStateChanged(new ProcessEvent(this, processState));
1188     }
1189 
1190     protected void
1191     fireDataReceived(DataEvent e) throws ConsumerException
1192     {
1193         // Guaranteed to return a non-null array
1194         Object[] listeners = listenerList.getListenerList();
1195         // Process the listeners last to first, notifying
1196         // those that are interested in this event
1197         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1198             if (listeners[i] == ConsumerListener.class) {
1199                 ((ConsumerListener)listeners[i + 1]).dataReceived(e);
1200             }
1201         }
1202     }
1203 
1204     protected void
1205     fireDataDropped(DropEvent e) throws ConsumerException
1206     {
1207         // Guaranteed to return a non-null array
1208         Object[] listeners = listenerList.getListenerList();
1209         // Process the listeners last to first, notifying
1210         // those that are interested in this event
1211         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1212             if (listeners[i] == ConsumerListener.class) {
1213                 ((ConsumerListener)listeners[i + 1]).dataDropped(e);
1214             }
1215         }
1216     }
1217 
1218     protected void
1219     fireErrorEncountered(ErrorEvent e) throws ConsumerException
1220     {
1221         // Guaranteed to return a non-null array
1222         Object[] listeners = listenerList.getListenerList();
1223         // Process the listeners last to first, notifying
1224         // those that are interested in this event
1225         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1226             if (listeners[i] == ConsumerListener.class) {
1227                 ((ConsumerListener)listeners[i + 1]).errorEncountered(e);
1228             }
1229         }
1230     }
1231 
1232     protected void
1233     fireProcessStateChanged(ProcessEvent e) throws ConsumerException
1234     {
1235         // Guaranteed to return a non-null array
1236         Object[] listeners = listenerList.getListenerList();
1237         // Process the listeners last to first, notifying
1238         // those that are interested in this event
1239         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1240             if (listeners[i] == ConsumerListener.class) {
1241                 ((ConsumerListener)listeners[i + 1]).processStateChanged(e);
1242             }
1243         }
1244     }
1245 
1246     protected void
1247     fireConsumerStarted(ConsumerEvent e)
1248     {
1249         // Guaranteed to return a non-null array
1250         Object[] listeners = listenerList.getListenerList();
1251         // Process the listeners last to first, notifying
1252         // those that are interested in this event
1253         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1254             if (listeners[i] == ConsumerListener.class) {
1255                 ((ConsumerListener)listeners[i + 1]).consumerStarted(e);
1256             }
1257         }
1258     }
1259 
1260     protected void
1261     fireConsumerStopped(ConsumerEvent e)
1262     {
1263         // Guaranteed to return a non-null array
1264         Object[] listeners = listenerList.getListenerList();
1265         // Process the listeners last to first, notifying
1266         // those that are interested in this event
1267         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1268             if (listeners[i] == ConsumerListener.class) {
1269                 ((ConsumerListener)listeners[i + 1]).consumerStopped(e);
1270             }
1271         }
1272     }
1273 
1274     // Called by native code
1275     private void
1276     intervalBegan()
1277     {
1278 	fireIntervalBegan(new ConsumerEvent(this, System.nanoTime()));
1279     }
1280 
1281     protected void
1282     fireIntervalBegan(ConsumerEvent e)
1283     {
1284         // Guaranteed to return a non-null array
1285         Object[] listeners = listenerList.getListenerList();
1286         // Process the listeners last to first, notifying
1287         // those that are interested in this event
1288         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1289             if (listeners[i] == ConsumerListener.class) {
1290                 ((ConsumerListener)listeners[i + 1]).intervalBegan(e);
1291             }
1292         }
1293     }
1294 
1295     // Called by native code
1296     private void
1297     intervalEnded()
1298     {
1299 	fireIntervalEnded(new ConsumerEvent(this, System.nanoTime()));
1300     }
1301 
1302     protected void
1303     fireIntervalEnded(ConsumerEvent e)
1304     {
1305         // Guaranteed to return a non-null array
1306         Object[] listeners = listenerList.getListenerList();
1307         // Process the listeners last to first, notifying
1308         // those that are interested in this event
1309         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1310             if (listeners[i] == ConsumerListener.class) {
1311                 ((ConsumerListener)listeners[i + 1]).intervalEnded(e);
1312             }
1313         }
1314     }
1315 
1316     /**
1317      * Gets a string representation of this consumer useful for logging
1318      * and not intended for display.  The exact details of the
1319      * representation are unspecified and subject to change, but the
1320      * following format may be regarded as typical:
1321      * <pre><code>
1322      * class-name[property1 = value1, property2 = value2]
1323      * </code></pre>
1324      */
1325     public String
1326     toString()
1327     {
1328 	StringBuffer buf = new StringBuffer(LocalConsumer.class.getName());
1329 	synchronized (this) {
1330 	    buf.append("[open = ");
1331 	    buf.append(isOpen());
1332 	    buf.append(", enabled = ");
1333 	    buf.append(isEnabled());
1334 	    buf.append(", running = ");
1335 	    buf.append(isRunning());
1336 	    buf.append(", closed = ");
1337 	    buf.append(isClosed());
1338 	}
1339 	buf.append(']');
1340 	return buf.toString();
1341     }
1342 
1343     /**
1344      * Ensures that the {@link #close()} method of this consumer has
1345      * been called before it is garbage-collected.  The intended safety
1346      * net is weak because the JVM does not guarantee that an object
1347      * will be garbage-collected when it is no longer referenced.  Users
1348      * of the API should call {@code close()} to ensure that all
1349      * resources associated with this consumer are reclaimed in a timely
1350      * manner.
1351      *
1352      * @see #close()
1353      */
1354     protected void
1355     finalize()
1356     {
1357 	close();
1358     }
1359 
1360     private String
1361     getTag()
1362     {
1363 	return super.toString();
1364     }
1365 
1366     //
1367     // Uniquely identifies a consumer across systems so it is possible
1368     // to validate that an object such as a Program passed to a remote
1369     // client over a socket was created by this consumer and no other.
1370     //
1371     static class Identifier implements Serializable {
1372 	static final long serialVersionUID = 2183165132305302834L;
1373 
1374 	// local identifier
1375 	private int id;
1376 	private long timestamp;
1377 	// remote identifier
1378 	private InetAddress localHost;
1379 	private String tag; // in case localHost not available
1380 
1381 	private
1382 	Identifier(LocalConsumer consumer)
1383 	{
1384 	    id = LocalConsumer.sequence++;
1385 	    timestamp = System.currentTimeMillis();
1386 	    try {
1387 		localHost = InetAddress.getLocalHost();
1388 	    } catch (UnknownHostException e) {
1389 		localHost = null;
1390 	    }
1391 	    tag = consumer.getTag();
1392 	}
1393 
1394 	@Override
1395 	public boolean
1396 	equals(Object o)
1397 	{
1398 	    if (o == this) {
1399 		return true;
1400 	    }
1401 	    if (o instanceof Identifier) {
1402 		Identifier i = (Identifier)o;
1403 		return ((id == i.id) &&
1404 			(timestamp == i.timestamp) &&
1405 			((localHost == null) ? (i.localHost == null) :
1406 			 localHost.equals(i.localHost)) &&
1407 			tag.equals(i.tag));
1408 	    }
1409 	    return false;
1410 	}
1411 
1412 	@Override
1413 	public int
1414 	hashCode()
1415 	{
1416 	    int hash = 17;
1417 	    hash = (37 * hash) + id;
1418 	    hash = (37 * hash) + ((int)(timestamp ^ (timestamp >>> 32)));
1419 	    hash = (37 * hash) + (localHost == null ? 0 :
1420 		    localHost.hashCode());
1421 	    hash = (37 * hash) + tag.hashCode();
1422 	    return hash;
1423 	}
1424 
1425 	@Override
1426 	public String
1427 	toString()
1428 	{
1429 	    StringBuffer buf = new StringBuffer();
1430 	    buf.append(Identifier.class.getName());
1431 	    buf.append("[id = ");
1432 	    buf.append(id);
1433 	    buf.append(", timestamp = ");
1434 	    buf.append(timestamp);
1435 	    buf.append(", localHost = ");
1436 	    buf.append(localHost);
1437 	    buf.append(", tag = ");
1438 	    buf.append(tag);
1439 	    buf.append(']');
1440 	    return buf.toString();
1441 	}
1442     }
1443 }
1444