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 }