Home | History | Annotate | Download | only in dtrace
      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