LIBTRACEFS(3)

πŸ’– NAME πŸ’– β€” LIBTRACEFS(3) Β· libtracefs Manual

okay bestie, this is the libtracefs manual page for the histogram squad, and they are all about creating and destroying event histograms. The whole lineup is:

  • tracefs_hist_alloc
  • tracefs_hist_alloc_2d
  • tracefs_hist_alloc_nd
  • tracefs_hist_alloc_nd_cnt
  • tracefs_hist_free
  • tracefs_hist_add_key
  • tracefs_hist_add_key_cnt
  • tracefs_hist_add_value

No cap, that’s the whole NAME list β€” every single one matters. πŸ’…


✨ SYNOPSIS ✨

       #include <tracefs.h>

       enum tracefs_hist_key_type {
               TRACEFS_HIST_KEY_NORMAL = 0,
               TRACEFS_HIST_KEY_HEX,
               TRACEFS_HIST_KEY_SYM,
               TRACEFS_HIST_KEY_SYM_OFFSET,
               TRACEFS_HIST_KEY_SYSCALL,
               TRACEFS_HIST_KEY_EXECNAME,
               TRACEFS_HIST_KEY_LOG,
               TRACEFS_HIST_KEY_USECS,
               TRACEFS_HIST_KEY_MAX
       };

       struct tracefs_hist_axis {
               const char *key;
               enum tracefs_hist_key_type type;
       };

       struct tracefs_hist *tracefs_hist_alloc(struct tracefs_tep * tep,
                               const char *system, const char *event,
                               const char *key, enum tracefs_hist_key_type type);
       struct tracefs_hist *tracefs_hist_alloc_2d(struct tracefs_tep * tep,
                               const char *system, const char *event,
                               const char *key1, enum tracefs_hist_key_type type1,
                               const char *key2, enum tracefs_hist_key_type type2));
       struct tracefs_hist *tracefs_hist_alloc_nd(struct tracefs_tep * tep,
                               const char *system, const char *event,
                               struct tracefs_hist_axis *axes);
       struct tracefs_hist *tracefs_hist_alloc_nd_cnt(struct tep_handle *tep,
                                 const char *system, const char *event_name,
                                 struct tracefs_hist_axis_cnt *axes);
       void tracefs_hist_free(struct tracefs_hist *hist);

       int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key,
                                enum tracefs_hist_key_type type);
       int tracefs_hist_add_key_cnt(struct tracefs_hist *hist, const char *key,
                                enum tracefs_hist_key_type type, int cnt);
       int tracefs_hist_add_value(struct tracefs_hist *hist, const char *value);

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so event histograms get created by the trigger file in the event directory. The syntax? lowkey complex and honestly difficult to get correct. This API handles the syntax for you and makes creating and interacting with event histograms way less cursed. For more info, see https://www.kernel.org/doc/html/latest/trace/histogram.html.

tracefs_hist_alloc() allocates a struct tracefs_hist descriptor of a one-dimensional histogram and returns the address of it. This descriptor must be freed by tracefs_hist_free(). The tep is a trace event handle (see tracefs_local_events(3)), that holds the system and event that the histogram will be attached to. The system is the system or group of the event. The event is the event to attach the histogram to. The key is a field of the event that will be used as the key (dimension) of the histogram. The type is the type of the key. See KEY TYPES below.

tracefs_hist_alloc_2d() allocates a struct tracefs_hist descriptor of a two-dimensional histogram and returns the address of it. This descriptor must be freed by tracefs_hist_free(). The tep is a trace event handle (see tracefs_local_events(3)), that holds the system and event that the histogram will be attached to. The system is the system or group of the event. The event is the event to attach the histogram to. The key1 is the first field of the event that will be used as the key (dimension) of the histogram. The type1 is the type of key1. See KEY TYPES below. The key2 is the second field of the event that will be used as the key (dimension) of the histogram. The type2 is the type of key2. See KEY TYPES below.

tracefs_hist_alloc_nd() allocates a struct tracefs_hist descriptor of an N-dimensional histogram and returns the address of it. This descriptor must be freed by tracefs_hist_free(). The tep is a trace event handle (see tracefs_local_events(3)), that holds the system and event that the histogram will be attached to. The system is the system or group of the event. The event is the event to attach the histogram to. The axes is an array of key / type pairs, defining the dimensions of the histogram.

tracefs_hist_alloc_nd_cnt() will initialize a histogram descriptor that will be attached to the system/event. This only initializes the descriptor with the given axes keys as primaries. This only initializes the descriptor, it does not start the histogram in the kernel. The difference between this and tracefs_hist_alloc_nd() is that the axes parameter is of type struct tracefs_hist_axis_cnt and not struct tracefs_hist_axis.

tracefs_hist_free() frees the tracefs_hist descriptor. Note, it does not stop or disable the running histogram if it was started. tracefs_hist_destroy() needs to be called to do so.

tracefs_hist_add_key() adds a secondary or tertiary key to the histogram. The key passed to tracefs_hist_alloc_nd() is the primary key of the histogram. The first time this function is called, it will add a secondary key (or two-dimensional histogram). If this function is called again on the same histogram, it will add a tertiary key (or three-dimensional histogram). The hist parameter is the histogram descriptor to add the key to. The type is the type of key to add (See KEY TYPES below).

tracefs_hist_add_key_cnt() is the same as tracefs_hist_add_key() except that it allows adding a counter for the given type. Currently, there’s only the buckets type that requires a counter. When adding a key with the buckets type, tracefs_hist_add_key() is not sufficient, as the buckets type requires a counter or bucket size. Use tracefs_hist_add_key_cnt() when specifying a key that is broken up into buckets, and pass in the size of those buckets into cnt.

tracefs_hist_add_value() will add a value to record. By default, the value is simply the "hitcount" of the number of times an instance of the histogram’s key was hit. The hist is the histogram descriptor to add the value to. The value is a field in the histogram to add to when an instance of the key is hit.


πŸ”‘ KEY TYPES πŸ”‘

tracefs_hist_alloc_nd() and tracefs_hist_add_key() both add a key and require that key to have a type. The types may be:

  • TRACEFS_HIST_KEY_NORMAL or zero (0) β€” which is to not modify the type.
  • TRACEFS_HIST_KEY_HEX β€” display the key in hex.
  • TRACEFS_HIST_KEY_SYM β€” display the key as a kernel symbol (if found). If the key is an address, this is useful because it will display the function names instead of just a number.
  • TRACEFS_HIST_KEY_SYM_OFFSET β€” similar to TRACEFS_HIST_KEY_SYM but will also include the offset of the function to match the exact address.
  • TRACEFS_HIST_KEY_SYSCALL β€” if the key is a system call "id" (the number passed from user space to the kernel to tell it what system call it is calling), then the name of the system call is displayed.
  • TRACEFS_HIST_KEY_EXECNAME β€” if "common_pid" is the key (the pid of the executing task), instead of showing the number, show the name of the running task.
  • TRACEFS_HIST_KEY_LOG β€” will display the key in a binary logarithmic scale.
  • TRACEFS_HIST_KEY_USECS β€” for use with "common_timestamp" or TRACEFS_HIST_TIMESTAMP, in which case it will show the timestamp in microseconds instead of nanoseconds.

🎁 RETURN VALUE 🎁

tracefs_hist_alloc_nd() returns an allocated histogram descriptor which must be freed by tracefs_hist_free() or NULL on error.

All the other functions return zero on success or -1 on error.

If tracefs_hist_start() returns an error, a message may be displayed in the kernel that can be retrieved by tracefs_error_last().


🌸 EXAMPLE 🌸

Here’s the code, bestie β€” copy it exactly if you’re following along:

           #include <stdlib.h>
           #include <ctype.h>
           #include <unistd.h>
           #include <tracefs.h>

           enum commands {
                   START,
                   PAUSE,
                   CONT,
                   RESET,
                   DELETE,
                   SHOW,
           };

           static void parse_system_event(char *group, char **system, char **event)
           {
                   *system = strtok(group, "/");
                   *event = strtok(NULL, "/");
                   if (!*event) {
                           *event = *system;
                           *system = NULL;
                   }
           }

           static int parse_keys(char *keys, struct tracefs_hist_axis_cnt **axes)
           {
                   char *sav = NULL;
                   char *key;
                   int cnt = 0;

                   for (key = strtok_r(keys, ",", &sav); key; key = strtok_r(NULL, ",", &sav)) {
                           struct tracefs_hist_axis_cnt *ax;
                           char *att;

                           ax = realloc(*axes, sizeof(*ax) * (cnt + 2));
                           if (!ax) {
                                   perror("Failed to allocate axes");
                                   exit(-1);
                           }
                           ax[cnt].key = key;
                           ax[cnt].type = 0;
                           ax[cnt + 1].key = NULL;
                           ax[cnt + 1].type = 0;

                           *axes = ax;

…and bestie, we are still inside parse_keys() πŸ’… this is the part where it finds the . in your key, splits the attribute off, and goes absolutely feral with the strcmp checks πŸŽ€

                           att = strchr(key, '.');
                           if (att) {
                                   *att++ = '\0';
                                   if (strcmp(att, "hex") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_HEX;
                                   else if (strcmp(att, "sym") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_SYM;
                                   else if (strcmp(att, "sym_offset") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_SYM_OFFSET;
                                   else if (strcmp(att, "syscall") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_SYSCALL;
                                   else if (strcmp(att, "exec") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_EXECNAME;
                                   else if (strcmp(att, "log") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_LOG;
                                   else if (strcmp(att, "usecs") == 0)
                                           ax[cnt].type = TRACEFS_HIST_KEY_USECS;
                                   else if (strncmp(att, "buckets", 7) == 0) {
                                           if (att[7] != '=' && !isdigit(att[8])) {
                                                   fprintf(stderr, "'buckets' key type requires '=<size>'\n");
                                                   exit(-1);
                                           }
                                           ax[cnt].type = TRACEFS_HIST_KEY_BUCKETS;
                                           ax[cnt].cnt = atoi(&att[8]);
                                   } else {
                                           fprintf(stderr, "Undefined attribute '%s'\n", att);
                                           fprintf(stderr,"  Acceptable attributes:\n");
                                           fprintf(stderr,"    hex, sym, sym_offset, syscall, exe, log, usecs\n");
                                           exit(-1);
                                   }
                           }
                           cnt++;
                   }
                   return cnt;
           }

Lowkey iconic how buckets gets its own little validation cameo πŸͺ£ β€” it checks att[7] for '=' and att[8] for a digit, and if you didn't give it =<size> it hits you with 'buckets' key type requires '=<size>' and bails. Everybody else just gets compared. Main character energy fr fr ✨

Notice the acceptable-attributes list says exe, not exec, and it doesn't mention buckets at all even though buckets totally works. That's just what the code says, bestie β€” I don't make the rules, I just report them πŸ’€

🌸 the process_hist() glow-up 🌸

Okay so this is the function that actually does the thing. It takes a command, an instance name, a group, keys, vals, sort, ascend, and desc, and then it's go time πŸƒβ€β™€οΈπŸ’¨

           static void process_hist(enum commands cmd, const char *instance_name,
                                    char *group, char *keys, char *vals, char *sort,
                                    char *ascend, char *desc)
           {
                   struct tracefs_instance *instance = NULL;
                   struct tracefs_hist *hist;
                   struct tep_handle *tep;
                   struct tracefs_hist_axis_cnt *axes = NULL;
                   char *system;
                   char *event;
                   char *sav;
                   char *val;
                   int ret;
                   int cnt;

Setup era πŸŽ€ Instance gets created if you handed over a name:

                   if (instance_name) {
                           instance = tracefs_instance_create(instance_name);
                           if (!instance) {
                                   fprintf(stderr, "Failed instance create\n");
                                   exit(-1);
                           }
                   }

Then it reads the local events (tracefs_local_events(NULL)), and if that flops you get perror("Could not read events") and an exit(-1). Then parse_system_event(group, &system, &event) splits your group string into a system and an event, no cap βœ‚οΈ

If the command is SHOW, it literally just reads the hist file and prints it, then dips out with return:

                   if (cmd == SHOW) {
                           char *content;
                           content = tracefs_event_file_read(instance, system, event,
                                                             "hist", NULL);
                           if (!content) {
                                   perror("Reading hist file");
                                   exit(-1);
                           }
                           printf("%s\n", content);
                           free(content);
                           return;
                   }

Reading the hist file failed? perror("Reading hist file"), bye ✌️

No keys? No service. Command needs -k option 🚫 Then cnt = parse_keys(keys, &axes); and if that comes back 0, it's No keys?? and out πŸ™ˆ

Then the buckets situation β€” because buckets needs the nd_cnt function, it flips cnt to -1 if either axis is a buckets key. And yes, the case 2: legitimately falls through to case 1: β€” that's intentional, bestie, don't come for it πŸ”ͺ

                   /* buckets require the nd_cnt function */
                   switch (cnt) {
                   case 2:
                           if (axes[1].type == TRACEFS_HIST_KEY_BUCKETS)
                                   cnt = -1;
                           /* fall through */
                   case 1:
                           if (axes[0].type == TRACEFS_HIST_KEY_BUCKETS)
                                   cnt = -1;
                   }

Then the hist gets allocated β€” one axis = tracefs_hist_alloc(), two axes = tracefs_hist_alloc_2d(), anything else = tracefs_hist_alloc_nd_cnt(). And the comment is a whole mood:

                   /* Show examples of hist1d and hist2d */
                   switch (cnt) {
                   case 1:
                           hist = tracefs_hist_alloc(tep, system, event,
                                                     axes[0].key, axes[0].type);
                           break;
                   case 2:
                           hist = tracefs_hist_alloc_2d(tep, system, event,
                                                        axes[0].key, axes[0].type,
                                                        axes[1].key, axes[1].type);
                           break;
                   default:
                           /* Really, 1 and 2 could use this too */
                           hist = tracefs_hist_alloc_nd_cnt(tep, system, event, axes);
                   }
                   if (!hist) {
                           fprintf(stderr, "Failed hist create\n");
                           exit(-1);
                   }

Failed hist create = we packing up 😭

Now the comma-splitting era begins πŸ“ β€” everything you pass in gets sliced with strtok_r on "," and handed to the right tracefs_hist_* call. Vals first:

                   if (vals) {
                           sav = NULL;
                           for (val = strtok_r(vals, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) {
                                   ret = tracefs_hist_add_value(hist, val);
                                   if (ret) {
                                           fprintf(stderr, "Failed to add value %s\n", val);
                                           exit(-1);
                                   }
                           }
                   }

Then sort keys:

                   if (sort) {
                           sav = NULL;
                           for (val = strtok_r(sort, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) {
                                   ret = tracefs_hist_add_sort_key(hist, val);
                                   if (ret) {
                                           fprintf(stderr, "Failed to add sort key/val %s\n", val);
                                           exit(-1);
                                   }
                           }
                   }

Then ascending πŸ”Ό:

                   if (ascend) {
                           sav = NULL;
                           for (val = strtok_r(ascend, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) {
                                   ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_ASCENDING);
                                   if (ret) {
                                           fprintf(stderr, "Failed to add ascending key/val %s\n", val);
                                           exit(-1);
                                   }
                           }
                   }

Then descending πŸ”½:

                   if (desc) {
                           sav = NULL;
                           for (val = strtok_r(desc, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) {
                                   ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_DESCENDING);
                                   if (ret) {
                                           fprintf(stderr, "Failed to add descending key/val %s\n", val);
                                           exit(-1);
                                   }
                           }
                   }

Then tracefs_error_clear(instance); β€” clean slate, fresh start, we move 🧼✨

And the final switch is where the command actually gets executed:

                   tracefs_error_clear(instance);

                   switch (cmd) {
                   case START:
                           ret = tracefs_hist_start(instance, hist);
                           if (ret) {
                                   char *err = tracefs_error_last(instance);
                                   if (err)
                                           fprintf(stderr, "\n%s\n", err);
                           }
                           break;
                   case PAUSE:
                           ret = tracefs_hist_pause(instance, hist);
                           break;
                   case CONT:
                           ret = tracefs_hist_continue(instance, hist);
                           break;
                   case RESET:
                           ret = tracefs_hist_reset(instance, hist);
                           break;
                   case DELETE:
                           ret = tracefs_hist_destroy(instance, hist);
                           break;
                   case SHOW:
                           /* Show was already done */
                           break;
                   }
                   if (ret)
                           fprintf(stderr, "Failed: command\n");
                   exit(ret);
           }

Heads up: START is special πŸ‘‘ β€” if it fails it grabs tracefs_error_last(instance) and prints that error too, so you actually know what went wrong instead of just vibing in the dark. Everybody else just gets the generic Failed: command treatment. And yes, SHOW in this switch is a literal no-op with a comment because it was already handled up top. Efficient queen πŸ’…


πŸ¦‹ and finally, main() πŸ¦‹

           int main (int argc, char **argv, char **env)
           {
                   enum commands cmd;
                   char *instance = NULL;
                   char *cmd_str;
                   char *event = NULL;
                   char *keys = NULL;
                   char *vals = NULL;
                   char *sort = NULL;
                   char *desc = NULL;
                   char *ascend = NULL;

It starts with a usage check, and if you gave it less than 2 args, it prints this whole two-liner at you and dips:

                   if (argc < 2) {
                           fprintf(stderr, "usage: %s command [-B instance][-e [system/]event][-k keys][-v vals][-s sort]\n", argv[0]);
                           fprintf(stderr, "      [-a ascending][-d descending]\n");
                           exit(-1);
                   }

Then the command string gets matched against all the literal command names β€” start, pause, cont, reset, delete, show β€” and if it's none of those? Unknown command %s and out 😀 Not me getting rejected by a strcmp chain πŸ’€

                   cmd_str = argv[1];

                   if (!strcmp(cmd_str, "start"))
                           cmd = START;
                   else if (!strcmp(cmd_str, "pause"))
                           cmd = PAUSE;
                   else if (!strcmp(cmd_str, "cont"))
                           cmd = CONT;
                   else if (!strcmp(cmd_str, "reset"))
                           cmd = RESET;
                   else if (!strcmp(cmd_str, "delete"))
                           cmd = DELETE;
                   else if (!strcmp(cmd_str, "show"))
                           cmd = SHOW;
                   else {
                           fprintf(stderr, "Unknown command %s\n", cmd_str);
                           exit(-1);
                   }

And then β€” drumroll πŸ₯ β€” the option loop. Note it's getopt(argc - 1, argv + 1, ...) with that optstring "e:k:v:B:s:d:a:", so every one of these flags wants an argument: -e, -k, -v, -B, -s, -d, -a. No lonely flags allowed, bestie 🌈

                   for (;;) {
                           int c;

                           c = getopt(argc - 1, argv + 1, "e:k:v:B:s:d:a:");
                           if (c == -1)
                                   break;

…and that's where we're gonna pause, because there's more main() coming and I am not about to cliffhanger you with zero payoff 🫢 keep scrolling bestie, part 3 has the rest πŸ’–

                           switch (c) {
                           case 'e':
                                   event = optarg;
                                   break;
                           case 'k':
                                   keys = optarg;
                                   break;
                           case 'v':
                                   vals = optarg;
                                   break;
                           case 'B':
                                   instance = optarg;
                                   break;
                           case 's':
                                   sort = optarg;
                                   break;
                           case 'd':
                                   desc = optarg;
                                   break;
                           case 'a':
                                   ascend = optarg;
                                   break;
                           }
                   }
                   if (!event) {
                           event = "kmem/kmalloc";
                           if (!keys)
                                   keys = "call_site.sym,bytes_req";
                           if (!vals)
                                   vals = "bytes_alloc";
                           if (!sort)
                                   sort = "bytes_req,bytes_alloc";
                           if (!desc)
                                   desc = "bytes_alloc";
                   }
                   process_hist(cmd, instance, event, keys, vals, sort, ascend, desc);
           }

And that's the example, bestie β€” it ate and left no crumbs. πŸ’…

πŸ“ FILES πŸ“

  • tracefs.h β€” the header file you include so you can actually access the library APIs. Include it, bestie, it's giving "required."
  • -ltracefs β€” the linker switch you add when building a program that uses the library. Don't forget it, or the linker will be like "who?" 😭

πŸ‘€ SEE ALSO πŸ‘€

Go peek at these icons, fr fr:


✍️ AUTHOR ✍️

The main characters behind this masterpiece:

  • Steven Rostedt β€” rostedt@goodmis.org [1]
  • Tzvetomir Stoyanov β€” tz.stoyanov@gmail.com [2]
  • sameeruddin shaik β€” sameeruddin.shaik8@gmail.com [3]

πŸ› REPORTING BUGS πŸ›

Found a bug? Don't suffer in silence, bestie β€” report bugs to linux-trace-devel@vger.kernel.org [4]. 🫢


βš–οΈ LICENSE βš–οΈ

libtracefs is Free Software licensed under the GNU LGPL 2.1. Slay, but legally. πŸ’–


πŸ”— RESOURCES πŸ”—

The sacred link:

https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/


πŸ“ COPYING πŸ“

Copyright (C) 2020 VMware, Inc. Free use of this software is granted under the terms of the GNU Public License (GPL).


πŸ“Œ NOTES πŸ“Œ

  1. rostedt@goodmis.org mailto:rostedt@goodmis.org
  2. tz.stoyanov@gmail.com mailto:tz.stoyanov@gmail.com
  3. sameeruddin.shaik8@gmail.com mailto:sameeruddin.shaik8@gmail.com
  4. linux-trace-devel@vger.kernel.org mailto:linux-trace-devel@vger.kernel.org

That's the whole page, bestie β€” brought to you by libtracefs 1.8.4, dated 10/21/2026, and this iconic little number is LIBTRACEFS(3). Byeee! βœ¨πŸ’…πŸ¦‹

πŸ“¦ where this page comes from