Search code examples
macosdtrace

Can you compare values across probes in a multi-CPU safe way in DTrace?


I'm trying to write a DTrace script which does the following:

  1. Whenever a new thread is started, increment a count.
  2. Whenever one of these threads exits, decrement the count, and exit the script if the count is now zero.

I have something like this:

BEGIN {
  threads_alive = 0;
}

proc:::lwp-start /execname == $$1/ {
  self->started = timestamp;
  threads_alive += 1;
}

proc:::lwp-exit /self->started/ {
  threads_alive -= 1;
  if (threads_alive == 0) {
    exit(0);
  }
}

However, this doesn't work, because threads_alive is a scalar variable and thus it is not multi-cpu safe. As a result, multiple threads will overwrite each other's changes to the variable.

I have also tried using an aggregate variable instead:

@thread_count = sum(1)
//or
@threads_entered = count();
@threads_exitted = count();

Unfortunately, I haven't found syntax to be able to do something like @thread_count == 0 or @threads_started == @threads_stopped.


Solution

  • DTrace doesn't have facilities for doing the kind of thread-safe data sharing you're proposing, but you have a few options depending on precisely what you're trying to do.

    If the executable name is unique, you can use the proc:::start and proc:::exit probes for the start of the first thread and the exit of the last thread respectively:

    proc:::start
    /execname == $$1/
    {
            my_pid = pid;
    }
    
    proc:::exit
    /pid == my_pid/
    {
            exit(0);
    }
    

    If you're using the -c option to dtrace, the BEGIN probe fires very shortly after the corresponding proc:::start. Internally, dtrace -c starts the specified forks the specified command and then starts tracing at one of four points: exec (before the first instruction of the new program), preinit (after ld has loaded all libraries), postinit (after each library's _init has run), or main (right before the first instruction of the program's main function, though this is not supported in macOS).

    If you use dtrace -x evaltime=exec -c <program> BEGIN will fire right before the first instruction of the program executes:

    # dtrace -xevaltime=exec -c /usr/bin/true -n 'BEGIN{ts = timestamp}' -n 'pid$target:::entry{printf("%dus", (timestamp - ts)/1000); exit(0); }'
    dtrace: description 'BEGIN' matched 1 probe
    dtrace: description 'pid$target:::entry' matched 1767 probes
    dtrace: pid 1848 has exited
    CPU     ID                    FUNCTION:NAME
     10  16757                _dyld_start:entry 285us
    

    The 285us is due to the time it takes dtrace to resume the process via /proc or ptrace(2) on macOS. Rather than proc:::start or proc:::lwp-start you may be able to use BEGIN, pid$target::_dyld_start:entry, or pid$target::main:entry.