Go to main content

Oracle® Solaris 11.4 DTrace (Dynamic Tracing) Guide

Exit Print View

Updated: September 2020
 
 

DTrace Consumer Functions

    When a consumer is invoked, it performs the following activities using a few DTrace consumer functions:

  • Compile a D program

  • Pass the resulting object to the DTrace framework

  • Set any options to allow the program to run

  • Run the program

  • Generate output

Example 35  Embedding DTrace in a Consumer

The example shows a consumer that runs an embedded D program to perform kernel stack profiling.

#include <dtrace.h>
#include <stdarg.h>
#include <stdlib.h>
#include <strings.h>
static char *g_prog = ""
"profile-997"
"/arg0 && curthread->t_pri != -1/"
"{"
"       @c[stack()] = count();"
"}";
static dtrace_hdl_t *g_dtp;

static void
fatal(const char *fmt, ...)
{  
        va_list ap;  
        va_start(ap, fmt);

        (void) vfprintf(stderr, fmt, ap);

        if (fmt[strlen(fmt) - 1] != '\n')
                (void) fprintf(stderr, ": %s\n",
                    dtrace_errmsg(g_dtp, dtrace_errno(g_dtp)));
        exit(EXIT_FAILURE);
}

static int
chewrec(const dtrace_probedata_t *data, const dtrace_recdesc_t *rec,
    void *arg)
{ 
        return (DTRACE_CONSUME_THIS);
}

static int
chew(const dtrace_probedata_t *data, void *arg)
{
        return (DTRACE_CONSUME_THIS);
}

int
main()
{
        dtrace_prog_t *prog;
        dtrace_proginfo_t info; 
        dtrace_optval_t statustime;
        int err;

        if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL)
                fatal("cannot open dtrace library: %s\n",
                    dtrace_errmsg(NULL, err));

        if ((prog = dtrace_program_strcompile(g_dtp, g_prog,
             DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL)
                 fatal("invalid program");

        if (dtrace_program_exec(g_dtp, prog, &info) == -1)
                fatal("failed to enable probes");

        if (dtrace_setopt(g_dtp, "aggsize", "512k") == -1)
                fatal("failed to set aggsize");

        if (dtrace_go(g_dtp) != 0)
                fatal("dtrace_go()");

        for (int i = 0; i < 10; i++) {
       
                dtrace_sleep(g_dtp);
                switch (dtrace_work(g_dtp, stdout, chew, chewrec, NULL)) {
                case DTRACE_WORKSTATUS_DONE:
                        break;
                case DTRACE_WORKSTATUS_OKAY:
                        break;
                default:
                        fatal("processing aborted");
                }
         }
         
         if (dtrace_stop(g_dtp) == -1)
                 fatal("dtrace_stop()");

         if (dtrace_aggregate_print(g_dtp, stdout, NULL) == -1)
                 fatal("failed to print aggregation");

         dtrace_close(g_dtp);
         return (0);
}

dtrace_open() Function

The dtrace_open() function opens the DTrace driver and returns a handle to the consumer, which can be used for subsequent interactions with DTrace. dtrace_hdl_t is an opaque data structure that contains all the information related to the invocation of DTrace. The DTrace handle is passed to the other libdtrace functions, but a consumer does not need to access individual members of the dtrace_hdl_t structure.

dtrace_hdl_t *dtrace_open(int version, int flags, int *errp)

The arguments to the dtrace_open() function are:

  • The DTrace version in use, DTRACE_VERSION. Specifying any version other than the current version causes DTrace to fail.

  • Flags. The available flags are:

    DTRACE_O_NODEV

    Does not open the DTrace device. This flag is used when instrumentation is not enabled. For example, the –G option to dtrace generates an ELF file that contain an embedded D program but does not enable the specified probes.

    DTRACE_O_NOSYS

    Does not load the /system/object modules. By default, the libdtrace library gathers information about each of the loaded kernel modules. This flag is also used in a typical consumer.

    DTRACE_O_LP64

    Forces the D compiler to be 64-bit.

    DTRACE_O_ILP32

    Forces the D compiler to be 32-bit.

  • A pass-by-reference error variable. The dtrace_errmsg() function uses the value passed back by this variable to generate an error string.

In Example 35, Embedding DTrace in a Consumer, the consumer first calls the dtrace_open() function to open the DTrace driver and return the DTrace handle.

dtrace_program_strcompile() Function

After the program has a DTrace handle, the next step is to compile the D program. The consumer uses the dtrace_program_strcompile() function to compile the string that contains the D program. You can also use the dtrace_program_fcompile() function to compile a file that contains a D program. Both of these functions return a pointer to a data structure that describes the compiled program.

dtrace_prog_t *dtrace_program_strcompile(dtrace_hdl_t *dtp, const char *s, dtrace_probespec_t spec, uint_t cflags, int argc, char *const argv[])

    The arguments to the dtrace_program_strcompile() function are:

  • The DTrace handle.

  • A string containing the D program. The dtrace_program_fcompile() function passes a file handle.

  • A dtrace_probespec_t spec to indicate the context of the probe you are using, which can be a provider, a module, a function, or a name. For example, if you specify the DTRACE_PROBESPEC_PROVIDER probe, you can specify only provider names. The typical consumer uses only the DTRACE_PROBESPEC_NAME probe.

  • Flags. The full list of flags can be found in the /usr/include/dtrace.h file. Some of the common options are:

    DTRACE_C_DIFV

    Shows the target language instructions that results from the compilation and additional information to execute the target language instructions.

    DTRACE_C_ZDEFS

    Instructs the compiler to permit probes, whose definitions do not match the existing probes. By default, the compiler does not allow probe definitions that do not match existing probes.

    DTRACE_C_CPP

    Instructs the compiler to preprocess the input program with the C preprocessor. For more information, see the cpp(1) man page.

  • Number of arguments, which are passed to the program.

  • Arguments passed to the program.

The arguments to the dtrace_program_fcompile() function are fewer because the dtrace_probespec_t argument is not passed. You can modify the consumer to accept a file specified in the command line by replacing the dtrace_program_strcompile() function with code similar to the following example:

if ((fp = fopen(argv[1], "r")) == NULL)
        fatal("failed to open %s", argv[1]);

if ((prog = dtrace_program_fcompile(g_dtp, fp, 0, 0, NULL)) == NULL) 
        fatal("invalid program");

dtrace_program_exec() Function

After the D program is compiled, the consumer calls the dtrace_program_exec() function to create the object file for the program and download the object file to the kernel. The object file contains all the information necessary for the DTrace framework in the kernel to execute the D program.

int dtrace_program_exec(dtrace_hdl_t *dtp, dtrace_prog_t *pgp, dtrace_proginfo_t *pip)

    The arguments to the dtrace_program_exec() function are as follows:

  • The DTrace handle.

  • A pointer to the data structure representing the compiled program. This pointer is returned by the dtrace_strcompile() function.

  • A pass-by-reference variable, which contains information about the D program. The definition of the dtrace_proginfo_t structure is provided in the /usr/include/dtrace.h file.

dtrace_setopt() Function

Use the setopt() function options for a D program. The arguments to the dtrace_setopt() function are DTrace handle and the name and value of any argument.

int dtrace_setopt(dtrace_hdl_t *dtp, const char *opt, const char *val)

Example 35, Embedding DTrace in a Consumer uses aggregation in the D program and uses aggsize to specify the size of the aggregation buffer. For non-aggregation tracing, you must use bufsize specify the size of the primary buffer.


Note -  The dtrace_getopt() function gets the value of a DTrace option.

dtrace_go() Function

After setting the options, the consumer prepares the D program to run, and the dtrace_go() function starts the execution of the program. This action enables the specified probes so that the probes become active and execute the associated clauses. The dtrace_go() function has only one argument, the DTrace handle that contains information about the program to run.

int dtrace_go(dtrace_hdl_t *dtp)

After the dtrace_go() function is called, the probes start to generate data. The buffers in the DTrace framework that hold the generated data start filling. The consumer periodically consumes the generated data.

dtrace_sleep() Function

The dtrace_sleep() function determines the minimum amount of time a consumer needs to pause before it interacts with the framework. This function has only one argument, the DTrace handle.

void dtrace_sleep(dtrace_hdl_t *dtp)

    DTrace maintains three values that specify the rate at which a consumer needs to interact with the framework. These values also indicate whether the tracing continues or is stopped for some reason. The values that DTrace maintains are:

  • switchrate Specifies how often the principal buffers must be consumed. Principal buffers are maintained as active and passive pairs per-CPU. These pairs can switch, which allows the data to be consumed from one while the new data is written to the other. The rate at which these buffers switch determines the switchrate.

  • statusrate – Specifies how often the consumer should check the DTrace status.

  • aggrate Specifies how often the aggregation buffers are consumed. Aggregation buffers are not maintained as pairs in the same way as principal buffers. There is a single aggregation buffer per CPU. For more information about the buffer management mechanism, see DTrace Buffers and Buffering.

These three rates determine the amount of time that the dtrace_sleep() function must pause. The dtrace_sleep() function calculates the earliest time for it to wake up based on the last occurrence of these three events and their associated rates. If that earliest time is in the past, the dtrace_sleep() function returns, otherwise it sleeps until that time. If the wakeup time is in the past, the dtrace_sleep() function returns immediately, otherwise, it sleeps until the calculated wakeup time before returning.

You do not have to call the dtrace_sleep() function itself from a consumer. For example, neither intrstat or lockstat use the dtrace_sleep() function to control the rate of data consumption. You can use the dtrace_getopt() function to get the values of the appropriate rate and use timers based on those values. For more information, see the intrstat(8) and lockstat(8) man pages.

dtrace_work() Function

The dtrace_work() function performs all of the work that must to be done periodically by a consumer. This work corresponds to the statusrate, switchrate, and aggrate rates. The dtrace_work() function first calls dtrace_status() to determine the status of the trace and then calls dtrace_aggregate_snap() and dtrace_consume() to consume any aggregation buffer or principal buffer data. The dtrace_work() function is a wrapper around these three function calls and a DTrace consumer can call these three functions separately. For more information about the dtrace_aggregate_snap() function, see Periodic Processing of Aggregation.

dtrace_workstatus_t dtrace_work(dtrace_hdl_t *dtp, FILE *fp, dtrace_consume_probe_f *pfunc, dtrace_consume_rec_f *rfunc, void *arg)

    The arguments to the dtrace_work() function are:

  • The DTrace handle.

  • A file handle for output.

  • Two function pointers.

  • An optional argument to be passed to the two function pointers. This argument can maintain any state between successive invocations of the functions.

The two function pointers passed to dtrace_work() in the Example 35, Embedding DTrace in a Consumer are chew() and chewrec(). They are called while processing the data from the primary buffer. DTrace Primary Buffer shows the layout of a primary buffer. For more information about the DTrace buffer mechanism, see DTrace Buffers and Buffering.

Figure 4  DTrace Primary Buffer

image:Graphic shows layout of DTrace Primary Buffer

The EPID is the enabled probe ID, which maps to a specific clause in a D program and determines the length and layout of the data after the EPID.

In Example 35, Embedding DTrace in a Consumer, for each EPID that is processed from the buffer, the chew() function is called. The data that corresponds to an EPID might consist of a number of records. For each record that is processed for an EPID, the chewrec() function is called. These two function pointers enable you to augment or replace the default format for this data provided by the libdtrace library.

The return value for these two functions pointers can be one of the following four values:

DTRACE_CONSUME_THIS

Indicates that the libdtrace library must consume the EPID or record. A consumer uses this value to augment the default behavior. In Example 35, Embedding DTrace in a Consumer, both the chew() and chewrec() functions return DTRACE_CONSUME_THIS without adding any output.

DTRACE_CONSUME_NEXT

Indicates that the libdtrace library must proceed to the next EPID or record. In Example 35, Embedding DTrace in a Consumer, the consumer processes the EPID or record itself. If the action specified in a record is an exit(), the chewrec() function extracts the exit code from the record and returns DTRACE_CONSUME_NEXT to indicate that the record has been processed.

DTRACE_CONSUME_ERROR

Indicates that an error has occurred while processing an EPID or record.

DTRACE_CONSUME_ABORT

Indicates that consumption of the buffer must be terminated.

If a record specifies an exit() function, the chewrec() function extracts the exit code. The function also outputs a newline after processing the final record for an EPID. The chew() function prints the information about the CPU on which the probe was fired, and the probe ID, function, and name for each EPID processed. It also handles the flowindent output processing if that option is specified.

The source for dtrace in the usr/src/cmd/dtrace/dtrace.c file provides good examples of the chew() and chewrec() functions.

dtrace_stop() Function

In Example 35, Embedding DTrace in a Consumer, the consumer exits the loop and stops the data consumption but continues to generate data. The call to the dtrace_stop() function communicates to the kernel that this consumer no longer consumes data. The kernel disables any enabled probe and frees the memory for the buffers associated with this DTrace handle.

int dtrace_stop(dtrace_hdl_t *dtp)

If the consumer does not call the dtrace_stop() function, the kernel eventually performs the cleanup. The data gathering stops either when the deadman timer fires or when the DTrace device is closed. The buffers are freed when the device closes. The DTrace device closes either when the consumer calls the dtrace_close() function or when the consumer exits. It is best practice is for the consumer to call the dtrace_stop() function.

dtrace_aggregate_print() Function

In Example 35, Embedding DTrace in a Consumer, when the loop is finished, the consumer calls the dtrace_aggregate_print() function to print the results of any aggregation data that was collected during the run.

int dtrace_aggregate_print(dtrace_hdl_t *dtp, FILE *fp, dtrace_aggregate_walk_f *func)

    The arguments to the dtrace_aggregate_print() function are:

  • DTrace handle.

  • File handle to which the output data is directed.

  • Pointer to a function, which controls how the data is printed. The libdtrace library provides a number of options to print aggregation data. You can also write a custom printing function. If the consumer does not specify any function, the default DTrace behavior prints the output sorted by the aggregated value for each tuple.

dtrace_close() Function

The final action performed by the consumer is to call the dtrace_close() function on the DTrace handle. This function performs user space cleanup if required, such as freeing any memory and closing any open file descriptors associated with this DTrace handle.

void dtrace_close(dtrace_hdl_t *dtp)

dtrace_errmsg() and dtrace_errno() Functions

Example 35, Embedding DTrace in a Consumer uses two functions, dtrace_errno() and dtrace_errmsg(), for error reporting. The dtrace_errno() function returns the error number set by the last call to the DTrace API by using a particular DTrace handle. The dtrace_errmsg() function takes a DTrace handle and an error number, and returns a corresponding error message. The messages for most errors are stored in a table in the libdtrace library. For compiler errors, the error string is written to an error message buffer of the DTrace handle. Errors that deal with compact C type format (CTF) are handled separately based on a CTF-specific error number stored in the DTrace handle. For more information, see the libdtrace(3LIB) man page.

const char *dtrace_errmsg(dtrace_hdl_t *dtp, int error)

int dtrace_errno(dtrace_hdl_t *dtp)