# Quick Start Guide

# to the Java DTrace API

## Contents

- "hello, world" Example

- Writing a Simple Consumer
- Running the `hello.d` Script

- Aggregations
- Target Process ID
- Closing Consumers
- Learning More

## "hello, world" Example

To demonstrate how to use the Java DTrace API, let's write a simple Java program that runs a D script, in this case `hello.d` (prints "hello, world" and exits). You will need root permission to use the Java DTrace API (just as you do to use the `dtrace(1M)` command). You may want to eliminate this inconvenience by adding the following line to `/etc/user_attr`:
`user-name::::defaultpriv=basic,dtrace_kernel,dtrace_proc`
(Substitute your user name.) See the Security chapter of the Solaris Dynamic Tracing Guide for more information.

#### Writing a Simple Consumer

Creating a DTrace consumer is easy:

        Consumer consumer = new LocalConsumer();

Before you can do anything with the consumer, you must first open it. Then you simply compile and enable one or more D programs and run it:

        consumer.open();
        consumer.compile(program);
        consumer.enable();
        consumer.go();	// runs in a background thread

To get the data generated by DTrace, you also need to add a listener:

        consumer.addConsumerListener(new ConsumerAdapter() {
    	public void dataReceived(DataEvent e) {
    	    System.out.println(e.getProbeData());
    	}
        });

Here is a simple example that runs a given D script:
Java program (TestAPI.java)

        import org.opensolaris.os.dtrace.*;
        import java.io.File;

        public class TestAPI {
    	public static void
    	main(String[] args)
    	{
    	    if (args.length < 1) {
    		System.err.println("Usage: java TestAPI <script> [ macroargs... ]");
    		System.exit(2);
    	    }

    	    File file = new File(args[0]);
    	    String[] macroArgs = new String[args.length - 1];
    	    System.arraycopy(args, 1, macroArgs, 0, (args.length - 1));

    	    Consumer consumer = new LocalConsumer();
    	    consumer.addConsumerListener(new ConsumerAdapter() {
    		public void dataReceived(DataEvent e) {
    		    System.out.println(e.getProbeData());
    		}
    	    });

    	    try {
    		consumer.open();
    		consumer.compile(file, macroArgs);
    		consumer.enable();
    		consumer.go();
    	    } catch (Exception e) {
    		e.printStackTrace();
    		System.exit(1);
    	    }
    	}
        }

Compile the test program as follows:

        java -cp dtrace.jar TestAPI.java

#### Running the `hello.d` Script

Now we need a D script for the program to run. The following is a simple example that prints "hello, world" and exits:
D script (hello.d)

        dtrace:::BEGIN
        {
    	    trace("hello, world");
    	    exit(0);
        }

Run as follows:
On i86:

        java -cp .:dtrace.jar TestAPI hello.d

On sparc you need to add the `-d64` option to java:

        java -d64 -cp .:dtrace.jar TestAPI hello.d

You may need to set `LD_LIBRARY_PATH` so that Java can find `libdtrace_jni.so` on your system. The output should look like this:

        org.opensolaris.os.dtrace.ProbeData[epid = 1, cpu = 1,
        enabledProbeDescription = dtrace:::BEGIN, flow = null, records =
        ["hello, world", 0]]

There is one record in the `ProbeData` for each action in the D script. The first record is generated by the `trace()` action. The second is generated by the `exit()` action. For prettier output, you could change the `ConsumerAdapter dataReceived()` implementation as follows:

        consumer.addConsumerListener(new ConsumerAdapter() {
    	public void dataReceived(DataEvent e) {
    	    // System.out.println(e.getProbeData());
    	    ProbeData data = e.getProbeData();
    	    java.util.List < Record > records = data.getRecords();
    	    for (Record r : records) {
    		if (r instanceof ExitRecord) {
    		} else {
    		    System.out.println(r);
    		}
    	    }
    	}
        });

## Aggregations

The example Java program can just as easily run a more complex script, such as an aggregation:
D script (syscall.d)

        syscall:::entry
        / execname == $$1 /
        {
    	    @[probefunc] = count();
        }

        profile:::tick-1sec
        {
    	    printa(@);
    	    clear(@);
        }

The above script uses the `$$1` macro variable as a placeholder for whatever executable you'd like to trace. See the Macro Arguments section of the Scripting chapter of the Solaris Dynamic Tracing Guide. Using two dollar signs (`$$1`) instead of one (`$1`) forces expansion of the macro variable to type string.
To run the example Java program using the above D script, you need to specify an argument to the `execname` placeholder. To be sure of getting output, let's use "java":
On i86:

        java -cp .:dtrace.jar TestAPI syscall.d java

On sparc:

        java -d64 -cp .:dtrace.jar TestAPI syscall.d java

A data record generated by the `printa()` action is printed to the console once every second. It contains counts of system calls by function name made by java. No record is generated by the `clear()` action.
If you omit the argument to the `execname` placeholder, the program fails to compile and the API throws the following exception:

        org.opensolaris.os.dtrace.DTraceException: failed to compile script
        syscall.d: line 2: macro argument $$1 is not defined
    	at org.opensolaris.os.dtrace.LocalConsumer._compileFile(Native Method)
    	at org.opensolaris.os.dtrace.LocalConsumer.compile(LocalConsumer.java:342)
    	at TestAPI.main(TestAPI.java:26)

A DTrace script may have more than one aggregation. In that case, each aggregation needs a distinct name:
D script (intrstat.d)

        sdt:::interrupt-start
        {
    	    self->ts = vtimestamp;
        }

        sdt:::interrupt-complete
        / self->ts && arg0 /
        {
    	    this->devi = (struct dev_info *)arg0;
    	    @counts[stringof(`devnamesp[this->devi->devi_major].dn_name),
    		this->devi->devi_instance, cpu] = count();
    	    @times[stringof(`devnamesp[this->devi->devi_major].dn_name),
    		this->devi->devi_instance, cpu] = sum(vtimestamp - self->ts);
    	    self->ts = 0;
        }

The `@counts` and `@times` aggregations both accumulate values for each unique combination of device name, device instance, and CPU (a three-element tuple). In this example we drop the `tick` probe to demonstrate a more convenient way to get aggregation data without the use of the `printa()` action. The `getAggregate()` method allows us to get all aggregations at once on a programmatic interval.
Java program (TestAPI2.java)

        ...

        try {
    	consumer.open();
    	consumer.compile(file, macroArgs);
    	consumer.enable();
    	consumer.go();

    	Aggregate a;
    	do {
    	    Thread.sleep(1000); // 1 second
    	    a = consumer.getAggregate();
    	    if (!a.asMap().isEmpty()) {
    		System.out.println(a);
    	    }
    	} while (consumer.isRunning());
        } catch (Exception e) {
    	e.printStackTrace();
    	System.exit(1);
        }

        ...

Compile and run:

        javac -cp dtrace.jar TestAPI2.java

On i86:

        java -cp .:dtrace.jar TestAPI2 intrstat.d

On sparc:

        java -d64 -cp .:dtrace.jar TestAPI2 intrstat.d

Try removing the `tick` probe from the `syscall.d` example and running it again with the above modification (`TestAPI2`).
By default, the requested aggregate includes every aggregation and accumulates running totals. To display values per time interval (instead of running totals), clear the aggregations each time you call `getAggregate()`. Clearing an aggregation resets all counts to zero without removing any tuples. The following modification to the example above clears all aggregations:

    	    // a = consumer.getAggregate();
    	    a = consumer.getAggregate(null, null); // included, cleared

Each `Set` of aggregation names, `included` and `cleared`, specifies all aggregations if `null` and no aggregations if empty. Any subset is possible. However, if an aggregation has ever been used in the `printa()` action, it is no longer available to the `getAggregate()` method.
Be aware that you cannot call `getAggregate()` on an interval faster that the `aggrate` setting. See the Options and Tunables chapter of the Solaris Dynamic Tracing Guide. See also the Minimizing Drops section of the Aggregations chapter for specific information about the `aggrate` option. The default `aggrate` is once per second. Here's an example of how you might double the `aggrate` to minimize drops:

        consumer.setOption(Option.aggrate, Option.millis(500)); // every half second

Even a single drop terminates the consumer unless you override the `dataDropped()` method of `ConsumerAdapter` to handle drops differently. To avoid drops, it is probably better to increase the `aggsize` option, since increasing the `aggrate` makes the consumer work harder. In most cases, the `aggrate` should only be increased when you need to update a display of aggregation data more frequently than once per second. Many runtime options, including `aggrate`, can be changed dynamically while the consumer is running.
It's also worth mentioning that a D aggregation may omit square brackets and aggregate only a single value:

        @total = count();

The resulting singleton `Aggregation` instance has one record that may be obtained as follows:

        Aggregate a = consumer.getAggregate();
        Aggregation total = a.getAggregation("total");
        AggregationRecord totalRecord = total.getRecord(Tuple.EMPTY);

## Target Process ID

In addition to supporting macro arguments (see the `syscall.d` aggregation example above), the Java DTrace API also supports the `$target` macro variable. (See the Target Process ID section of the Scripting chapter of the Solaris Dynamic Tracing Guide.) This allows you to trace a process from the very beginning of its execution, rather than sometime after you manually obtain its process ID. The API does this by creating a process that is initially suspended and allowed to start only after `go()` has initiated tracing. For example, we might want to aggregate all the system calls from start to finish made by the `date` command:
D script (target.d)

        syscall:::entry
        / pid == $target /
        {
    	    @[probefunc] = count();
        }

A modified version of the `TestAPI.java` program adds the `createProcess()` call to execute the given command but prevent it from starting until the consumer is running:
Java program (TestTarget.java)

        ...
        consumer.open();
        consumer.createProcess(command);
        consumer.compile(file);
        consumer.enable();
        consumer.go();
        ...

It also overrides the `processStateChanged()` method of the `ConsumerAdapter` to print a notification when the process has ended:

        ...
        consumer.addConsumerListener(new ConsumerAdapter() {
    	public void dataReceived(DataEvent e) {
    	    System.out.println(e.getProbeData());
    	}
    	public void consumerStopped(ConsumerEvent e) {
    	    try {
    		Aggregate a = consumer.getAggregate();
    		for (Aggregation agg : a.asMap().values()) {
    		    for (AggregationRecord rec : agg.asMap().values()) {
    			System.out.println(rec.getTuple() + " " +
    				rec.getValue());
    		    }
    		}
    	    } catch (Exception x) {
    		x.printStackTrace();
    		System.exit(1);
    	    }
    	    consumer.close();
    	}
    	public void processStateChanged(ProcessEvent e) {
    	    System.out.println(e.getProcessState());
    	}
        });
        ...

Compile and run:

        javac -cp dtrace.jar TestTarget.java

On i86:

        java -cp .:dtrace.jar TestTarget target.d date

On sparc:

        java -d64 -cp .:dtrace.jar TestTarget target.d date

The consumer exits automatically when the target `date` process completes.

## Closing Consumers

An application using the Java DTrace API may run multiple consumers simultaneously. When a consumer stops running, the programmer is responsible for closing it in order to release the system resources it holds. A consumer may stop running for any of the following reasons:

- It was stopped explicitly by a call to its `stop()` method
- It encountered the `exit()` action
- Its `$target` process or processes (if any) all completed
- It encountered an exception

By default, an exception prints a stack trace to `stderr` before notifying listeners that the consumer has stopped. You can define different behavior by setting an `ExceptionHandler`, but the consumer is still stopped.
The same listener that receives probe data generated by DTrace is also notified when the consumer stops. This is a good place to close the consumer:

        consumer.addConsumerListener(new ConsumerAdapter() {
    	public void dataReceived(DataEvent e) {
    	    System.out.println(e.getProbeData());
    	}
    	public void consumerStopped(ConsumerEvent e) {
    	    Consumer consumer = (Consumer)e.getSource();
    		consumer.close();
    	    }
    	}
        });

This releases the resources held by the consumer in all cases, i.e. after it exits for any of the reasons listed above.
You can request the last aggregate snapshot made by a stopped consumer, as long as it has not yet been closed:

        Aggregate a = consumer.getAggregate();

Note however that any aggregation that has already appeared in a `PrintaRecord` as a result of the `printa()` action action will not be included in the requested aggregate.

## Learning More

The OpenSolaris DTrace page has links to resources to help you learn DTrace. In particular, you should read the Solaris Dynamic Tracing Guide.
Try the example Java programs on this page with other D scripts. You need not remove `#!/usr/sbin/dtrace -s` from the top of an executable script. You may want to remove `profile:::tick*` clauses if you plan to use the `Consumer` `getAggregate()` method and control the aggregating interval programmatically. If the script uses the pre-compiler, you will need to call the `Consumer` `setOption()` method with the `Option.cpp` argument.
To quickly familiarize yourself with the Java DTrace API, take a look at the overview diagram.
Back to top