1 Get Started With DTrace

The topics in this section provide guidance on how to perform particular operations with DTrace and serve as an introduction to installing and using DTrace. By following steps in this guide, you can get started with DTrace immediately. After you have explored these topics, you can either review DTrace Concepts to get a better understanding of how DTrace works and how you can improve the way that you use it, or you can use the various references that are included to find out more about writing D programs that do what you need them to do.

Install DTrace

Install the dtrace package to use the DTrace command line utility and to run D programs.

  1. Enable the appropriate yum repository for the system.

    On x86_64 systems, you can install the DTrace v2 user space tools from any of the following yum repositories, or from the equivalent channels on the Unbreakable Linux Network (ULN):

    • ol8_UEKR6
    • ol8_UEKR7
    • ol9_UEKR7

    For example, if using the Oracle Linux yum server on an Oracle Linux 9 x86_64 system, run:

    sudo dnf config-manager --enable ol9_UEKR7

    Note:

    Oracle releases UEK and DTrace packages in the baseos repository for aarch64 platforms. You don't need to enable any other repositories to access the DTrace packages for aarch64 platforms.

  2. Install the dtrace package.
    sudo dnf install -y dtrace
  3. Check that DTrace installed to the correct location and verify the DTrace version.
    Run ls -lah /usr/sbin/dtrace to verify that the DTrace utility is present:
    ls -lah /usr/sbin/dtrace

    Run the dtrace -V command to display the version number.

    dtrace -V

List and Enable Probes

This topic explores how you can list and enable the probes that are available to DTrace.

DTrace providers publish available probes to DTrace so that you can enable them to perform functions when they fire. You can use the dtrace command to list all available probes or to enable a probe.

  1. List available probes.

    To list all available probes, run:

    sudo dtrace -l

    Note:

    Most uses of DTrace require root privileges. This document assumes that you run commands with the appropriate privileges. Use the sudo command to escalate to root user privileges before you run the commands presented in this document.

    The command returns output similar to the following:

    DTrace 2.0.0 [Pre-Release with limited functionality]
        ID   PROVIDER            MODULE                          FUNCTION NAME
         1     dtrace                                                     BEGIN
         2     dtrace                                                     END
         3     dtrace                                                     ERROR
         4        fbt           vmlinux        __traceiter_initcall_level entry
         5        fbt           vmlinux        __traceiter_initcall_level return
         6        fbt           vmlinux        __traceiter_initcall_start entry
         7        fbt           vmlinux        __traceiter_initcall_start return
         8        fbt           vmlinux       __traceiter_initcall_finish entry
         9        fbt           vmlinux       __traceiter_initcall_finish return
    ...
    144917        sdt               rtc                                   rtc_set_time
    144918        sdt               i2c                                   i2c_result
    144919        sdt               i2c                                   i2c_reply
    144920        sdt               i2c                                   i2c_read
    144921        sdt               i2c                                   i2c_write
    144922        sdt             smbus                                   smbus_result
    144923        sdt             smbus                                   smbus_reply
    144924        sdt             smbus                                   smbus_read
    144925        sdt             smbus                                   smbus_write
    144926        sdt             hwmon                                   hwmon_attr_show_string
    144927        sdt             hwmon                                   hwmon_attr_store
    144928        sdt             hwmon                                   hwmon_attr_show
    144929        sdt           thermal                                   thermal_zone_trip
    144930        sdt           thermal                                   cdev_update
    144931        sdt           thermal                                   thermal_temperature
    144932        sdt            bcache    
    ...
    145763    syscall           vmlinux                            listen entry
    145764    syscall           vmlinux                              bind return
    145765    syscall           vmlinux                              bind entry
    145766    syscall           vmlinux                        socketpair return
    145767    syscall           vmlinux                        socketpair entry
    145768    syscall           vmlinux                            socket return
    145769    syscall           vmlinux                            socket entry

    Tip:

    You can get a unique list of providers available for DTrace by running:
    sudo dtrace -l|tail -n +3|awk '{print $2}'|uniq
    You can limit the list of probes to a particular provider by using the -P option. You can also limit to a particular module by using the -m option. For example:
    sudo dtrace -l -P sdt
    sudo dtrace -l -m thermal
  2. Run dtrace -n to enable a named probe using the command line utility.
    You can enable any probe matching a name. Although you can specify only the name part for a probe's full name, using the full name helps to avoid unpredictable behavior:
    sudo dtrace -n dtrace:::BEGIN

    Output similar to the following is displayed:

    dtrace: description 'dtrace:::BEGIN' matched 1 probe
    CPU     ID                    FUNCTION:NAME
      2      1                           :BEGIN
    The dtrace:::BEGIN probe fires once when you start a new tracing request. Tabulated output shows the CPU where the probe fired, and the ID, function, and name for the probe.

    DTrace continues to run, waiting for other probes to fire. To exit, press Ctrl-C.

  3. Enable several probes by chaining them together in a request.

    You can construct DTrace requests by using arbitrary numbers of probes and functions. For example, create a request using two probes by adding the BEGIN and END probes.

    Type the following command, and then press Ctrl-C in the shell again, after you see the line of output for the BEGIN probe:

    sudo dtrace -n dtrace:::BEGIN -n dtrace:::END 
    dtrace: description 'dtrace:::BEGIN' matched 1 probe
    dtrace: description 'dtrace:::END' matched 1 probe
    CPU     ID                    FUNCTION:NAME
      0      1                           :BEGIN 
    ^C
      1      2                             :END

    The dtrace:::BEGIN probe fires when the tracing request starts. DTrace waits for further probes to activate until you press Ctrl-C to exit. The dtrace:::END probe activates once when tracing completes. The dtrace command reports the probe firing before exiting.

  4. Enable all probes for a function by using the -f option, or use the -m option to enable all probes for a module.
    You can match and enable probes for functions or for whole modules. For example, to enable both the entry and return probes for the syscall:vmlinux:socket function, run:
    sudo dtrace -f syscall:vmlinux:socket

    You can also enable probes for an entire module. For example, to enable all probes for the sdt:tcp module, run:

    sudo dtrace -m sdt:tcp

Create a DTrace Script

This tutorial describes how to create a DTrace script. The tutorial provides steps to develop understanding of the D Programming language and to illustrate DTrace at work.

Ensure that DTrace is installed on the system and that you can list and enable probes. See Install DTrace and List and Enable Probes.

This tutorial provides successive steps toward developing a DTrace script that you can use on a system to gather useful information. You can use this tutorial as a framework to create other scripts for DTrace, in future.

  1. In a text editor, create a file named hello.d and write a DTrace clause to fire for the dtrace:::BEGIN probe.

    Enter the following text into the editor:

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

    Save the file.

  2. Run the hello.d program by using the dtrace -s command.
    sudo dtrace -s hello.d

    Output similar to the following is displayed:

    dtrace: script 'hello.d' matched 1 probe
    CPU     ID                    FUNCTION:NAME
      0      1                           :BEGIN   hello, world    

    Note that you didn't have to press Ctrl-c to exit because you specified the exit function for the BEGIN probe in the program.

  3. Open hello.d in the text editor and add an interpreter line to the beginning of the script.

    Edit the file and add the following line of text to the top of the file:

    #!/usr/sbin/dtrace -s
    The complete script follows:
    #!/usr/sbin/dtrace -s
    dtrace:::BEGIN
    {
      trace("hello, world");
      exit(0);
    }

    Save the file.

  4. Change the permissions on the hello.d file to make it executable.

    Run the chmod command to update the file permissions:

    chmod a+rx hello.d
  5. Run the new executable script file.
    Use the sudo command so that the DTrace script still runs with root privileges so that it can access all DTrace features:
    sudo ./hello.d

    Note that by including an interpreter line at the beginning of the program, you can run the script without even specifying the dtrace command.

  6. Change the script to use an external macro variable.

    Edit the file to greet a person by name, when you specify a name as an argument to the script:

    #!/usr/sbin/dtrace -s
    dtrace:::BEGIN
    {
      printf("hello, %s", $$1);
      exit(0);
    }

    Notice how the trace function is now replaced with the printf() function, which lets you insert the macro variable $1 into the string by using variable substitution. The $$ syntax is used when referencing the macro variable, to express it as a string value.

  7. Run the script to see how the modification has altered behavior.

    Run the script as before, using the command:

    sudo ./hello.d

    An error similar to the following is generated.

    dtrace: failed to compile script ./hello.d: line 4: macro argument $$1 is not defined
    The error is generated because the script now expects you to provide another argument when you run it. Try to run the script again, this time specifying a name:
    sudo ./hello.d bob

    The script returns output similar to the following:

    dtrace: script './hello.d' matched 1 probe
    CPU     ID                    FUNCTION:NAME
      3      1                           :BEGIN hello, bob
  8. Change the script to use a pragma statement.

    To reduce how verbose the script is and to limit output to only what's functionally returned by the clause, add a pragma statement to set the runtime quiet option. Edit the script to add the pragma statement, as follows:

    #!/usr/sbin/dtrace -s
     #pragma D option quiet
     dtrace:::BEGIN
     {
       printf("hello, %s", $$1);
       exit(0);
     }
  9. Run the script to see how the modification has altered behavior.

    Run the script as before, using the command:

    sudo ./hello.d sally

    The script output is reduced to only what's returned by the printf() function.

  10. Change the script to use a predicate to control when to process the clause.

    You can use a predicate to control the script so that it only runs when a certain condition is true. Edit the script to add a predicate line to evaluate whether the string value of the macro variable is equal to 'bob', as follows:

    #!/usr/sbin/dtrace -s
     #pragma D option quiet
    
     dtrace:::BEGIN
     /$$1=="bob"/
     {
       printf("hello, %s", $$1);
       exit(0);
     }
  11. Run the script to see how the modification has altered behavior.

    Run the script as before, using the command:

    sudo ./hello.d sally

    The script doesn't exit and you need to press Ctrl-C to force quit the process. This is because the exit() function is part of the clause that evaluates whether the first argument of the script is equal to 'bob'. Try running the script again, using bob as the argument.

    sudo ./hello.d bob

    The script runs as before, illustrating that the predicate is working.

Use Predicates For Control Flow

For runtime safety, one major difference between D and other programming languages such as C, C++, and the Java programming language is the absence of control-flow constructs such as if-statements and loops. D program clauses are written as single straight-line statement lists that trace an optional, fixed amount of data. D does provide the ability to conditionally trace data and change control flow using logical expressions called predicates. This tutorial shows how to use predicates to control D programs.

To illustrate predicates at work, you can create a D program that implements a 10-second countdown timer. When the program runs, it counts down from 10 and then prints a message and exits. The program uses a variable and predicates to evaluate how much time has passed and what to print.

  1. Design a logical flow for the program.

    Consider designing the logical flow for a program before trying to write the program itself. When the flow is clearly defined, it's possible to transform conditional constructs into separate clauses and predicates. The logical flow for the program might look as follows:

    i = 10
    once per second,
      if i is greater than zero
        trace(i--);
      if i is equal to zero
        trace("blastoff!");
        exit(0);

    By creating two clauses with the same probe description but different predicates and functions it's possible to achieve the required logical flow for this program.

  2. Write the program code using predicates to decide whether the functions for the specified probe description are permitted to run or not when the probe fires.

    The program source code follows. Copy this code and save it in a file named countdown.d:

    dtrace:::BEGIN 
    {
      i = 10;
    }
    
    profile:::tick-1sec
    /i > 0/
    {
      trace(i--);
    }
    
    profile:::tick-1sec
    /i == 0/
    {
      trace("blastoff!");
      exit(0);
    }
  3. Run the program.
    sudo dtrace -s countdown.d

    Output similar to the following is displayed:

    dtrace: script 'countdown.d' matched 3 probes
    CPU     ID                    FUNCTION:NAME
      0    638                       :tick-1sec        10
      0    638                       :tick-1sec         9
      0    638                       :tick-1sec         8
      0    638                       :tick-1sec         7
      0    638                       :tick-1sec         6
      0    638                       :tick-1sec         5
      0    638                       :tick-1sec         4
      0    638                       :tick-1sec         3
      0    638                       :tick-1sec         2
      0    638                       :tick-1sec         1
      0    638                       :tick-1sec   blastoff!       
    #

This tutorial uses the BEGIN probe to initialize a variable integer i to 10 to begin the countdown. Next, the program uses the tick-1sec probe to implement a timer that fires once every second. Notice that in countdown.d, the tick-1sec probe description is used in two different clauses, each with a different predicate and function list. The predicate is a logical expression surrounded by enclosing slashes // that appears after the probe name and before the braces {} that surround the clause statement list.

The first predicate tests whether i is greater than zero, indicating that the timer is still running:

profile:::tick-1sec
/i > 0/
{
  trace(i--);
}

The relational operator > means greater than and returns the integer value zero for false and one for true. If i isn't yet zero, the script traces i and then decrements it by one using the -- operator.

The second predicate uses the == operator to return true when i is exactly equal to zero, indicating that the countdown is complete:

profile:::tick-1sec
/i == 0/
{
  trace("blastoff!");
  exit(0);
}

The second clause uses the trace function on a sequence of characters inside double quotes, called a string constant, to print a final message when the countdown is complete. The exit function is then used to end all tracing and to perform any remaining tasks such as consuming the final data, printing aggregations (as needed), and performing cleanup before returning to the shell prompt.

Example 1-1 How to use a predicate to monitor system calls for a process ID

You can create a D Program to trace system calls for a process ID, by using a predicate to limit the default tracing function to match the process ID that you want to trace.

syscall:::entry
/pid == 2860/
{
}

Note that in this example, the built-in variable pid is evaluated to match a particular ID, 2860 in this example. You could further change this script to take advantage of shell macro variables, so that it becomes more extensible and can be run for any process ID at runtime. Edit the script as follows and save it to a file called strace.ds:

#!/usr/sbin/dtrace -s

syscall:::entry
/pid == $1/
{
}

Change the file mode to make it executable:

sudo chmod +x strace.ds

Now you can use this script to monitor all the system calls made by any process on the system. For example, you could run the script to monitor system calls made by the cron daemon:

sudo ./strace.ds $(pidof /usr/sbin/crond)