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 }