libstapsdt explained

libstapsdt has a small API with only 7 functions and one possible flow. To use libstapsdt you need to create a provider and register probes to it. A provider has two possible states: unloaded and loaded. After creating it, it’s state is unloaded. After all probes are registered, you need to load your provider, effectively allowing tracing tools to access the provider’s probes.

After you finish using those probes, you can unload the provider, freeing all structures allocated to it during the load step. Only after that you can destroy the provider, removing all registered probes with it.

This flow is illustrated in the image below:

../_images/usage-workflow.svg

Example

The following example goes through all steps of libstapsdt’s flow:

#include <unistd.h>
#include <stdio.h>
#include <libstapsdt.h>

int main() {
  int i=1;
  SDTProvider_t *provider;
  SDTProbe_t *probe;

  provider = providerInit("myLittleProvider");
  probe = providerAddProbe(provider, "myLittleProbe", 1, uint64);

  if(providerLoad(provider) == -1) {
    printf("Something went wrong...\n");
    return -1;
  }

  while(i) {
    printf("Firing probe...\n");
    if(probeIsEnabled(probe)) {
      probeFire(probe, "I'm a runner!");
      printf("Probe fired!\n");
      i = 0;
    }
    sleep(1);
  }

  providerUnload(provider);
  providerDestroy(provider);

  return 0;
}

Let’s take a better look at the code. The first lines are used to create a provider named "myLittleProvider" (using providerInit()) and then it registers a probe named "myLittleProbe" (using providerAddProbe()). You may have noticed those extra arguments to providerAddProbe(): they are used to determine how many arguments the probe will accept when fired, and the type of those parameters (you can see all available types in SDTArgTypes_t).

SDTProvider_t *provider;
SDTProbe_t *probe;

provider = providerInit("myLittleProvider");
probe = providerAddProbe(provider, "myLittleProbe", 1, uint64);

After creating our provider and registering our probe, we need to load our provider (otherwise we won’t be able to fire our probes). This is done by simply calling providerLoad() with the provider as argument. It’s important to handle any errors that may occur to avoid problems in execution later on.

if(providerLoad(provider) == -1) {
  printf("Something went wrong...\n");
  return -1;
}

Now we can use probeIsEnabled() and probeFire(). probeIsEnabled() will only return True if the program is being traced. Therefore, in this example we’ll be on an infinite loop until our program is traced. You can use iovisor/bcc trace tool for this (sudo /usr/share/bcc/tools/trace -p PID 'u::myLittleProbe').

After using the trace tool, our probe will be replaced by a breakpoint, and probeIsEnabled() will return True, firing the probe with probeFire() inside our if-statement and then stepping out of our loop.

while(i) {
  printf("Firing probe...\n");
  if(probeIsEnabled(probe)) {
    probeFire(probe, "I'm a runner!");
    printf("Probe fired!\n");
    i = 0;
  }
  sleep(1);
}

Those last lines of code are used to unload and cleanup our provider. It is imporant that you run both providerUnload() and providerDestroy() in this exact order after you don’t need the probes anymore, to avoid memory leaks and Segmentation faults.

providerUnload(provider);
providerDestroy(provider);