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