2

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.

Mark Hildreth
  • 42,023
  • 11
  • 120
  • 109
  • 1
    DTrace doesn't have facilities for doing the kind of thread-safe data sharing that you are proposing here. Can you provide a little more context about what you're trying to do? Would `proc:exit/execname == $$1/{ exit(0) }` work? Does the `-c` or `-p` option already do what you want? – ahl May 29 '20 at 15:59
  • @ahl Thanks, that makes sense. I am hoping to have dtrace start before the process starts so that it can capture the lwp-start probe for that process (which doesn't seem to happen when I am using `-c`, at least on MacOS). Then I start the process, and observe until it ends, at which point I would like dtrace to exit. It seems like proc:::exit works, so long as I don't run the process multiple times (which I don't plan to). If you'd like, write up that as an answer and I'll accept, or I'll write up an answer. – Mark Hildreth May 29 '20 at 21:09

1 Answers1

2

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.

ahl
  • 902
  • 5
  • 12