| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> |
| * |
| * Updates: |
| * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> |
| * |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| |
| #include "tracefs.h" |
| #include "tracefs-local.h" |
| |
| #define HIST_FILE "hist" |
| |
| #define ASCENDING ".ascending" |
| #define DESCENDING ".descending" |
| |
| #define SYNTHETIC_GROUP "synthetic" |
| |
| struct tracefs_hist { |
| struct tep_handle *tep; |
| struct tep_event *event; |
| char *system; |
| char *event_name; |
| char *name; |
| char **keys; |
| char **values; |
| char **sort; |
| char *filter; |
| int size; |
| unsigned int filter_parens; |
| unsigned int filter_state; |
| }; |
| |
| /* |
| * tracefs_hist_get_name - get the name of the histogram |
| * @hist: The histogram to get the name for |
| * |
| * Returns name string owned by @hist on success, or NULL on error. |
| */ |
| const char *tracefs_hist_get_name(struct tracefs_hist *hist) |
| { |
| return hist ? hist->name : NULL; |
| } |
| |
| /* |
| * tracefs_hist_get_event - get the event name of the histogram |
| * @hist: The histogram to get the event name for |
| * |
| * Returns event name string owned by @hist on success, or NULL on error. |
| */ |
| const char *tracefs_hist_get_event(struct tracefs_hist *hist) |
| { |
| return hist ? hist->event_name : NULL; |
| } |
| |
| /* |
| * tracefs_hist_get_system - get the system name of the histogram |
| * @hist: The histogram to get the system name for |
| * |
| * Returns system name string owned by @hist on success, or NULL on error. |
| */ |
| const char *tracefs_hist_get_system(struct tracefs_hist *hist) |
| { |
| return hist ? hist->system : NULL; |
| } |
| |
| static void add_list(struct trace_seq *seq, const char *start, |
| char **list) |
| { |
| int i; |
| |
| trace_seq_puts(seq, start); |
| for (i = 0; list[i]; i++) { |
| if (i) |
| trace_seq_putc(seq, ','); |
| trace_seq_puts(seq, list[i]); |
| } |
| } |
| |
| static void add_hist_commands(struct trace_seq *seq, struct tracefs_hist *hist, |
| enum tracefs_hist_command command) |
| { |
| if (command == TRACEFS_HIST_CMD_DESTROY) |
| trace_seq_putc(seq, '!'); |
| |
| add_list(seq, "hist:keys=", hist->keys); |
| |
| if (hist->values) |
| add_list(seq, ":vals=", hist->values); |
| |
| if (hist->sort) |
| add_list(seq, ":sort=", hist->sort); |
| |
| if (hist->size) |
| trace_seq_printf(seq, ":size=%d", hist->size); |
| |
| switch(command) { |
| case TRACEFS_HIST_CMD_START: break; |
| case TRACEFS_HIST_CMD_PAUSE: trace_seq_puts(seq, ":pause"); break; |
| case TRACEFS_HIST_CMD_CONT: trace_seq_puts(seq, ":cont"); break; |
| case TRACEFS_HIST_CMD_CLEAR: trace_seq_puts(seq, ":clear"); break; |
| default: break; |
| } |
| |
| if (hist->name) |
| trace_seq_printf(seq, ":name=%s", hist->name); |
| |
| if (hist->filter) |
| trace_seq_printf(seq, " if %s", hist->filter); |
| |
| } |
| |
| /* |
| * trace_hist_echo_cmd - show how to start the histogram |
| * @seq: A trace_seq to store the commands to create |
| * @hist: The histogram to write into the trigger file |
| * @command: If not zero, can pause, continue or clear the histogram |
| * |
| * This shows the echo commands to create the histogram for an event |
| * with the given fields. |
| * |
| * Returns 0 on succes -1 on error. |
| */ |
| int |
| tracefs_hist_echo_cmd(struct trace_seq *seq, struct tracefs_instance *instance, |
| struct tracefs_hist *hist, |
| enum tracefs_hist_command command) |
| { |
| const char *system = hist->system; |
| const char *event = hist->event_name; |
| char *path; |
| |
| if (!hist->keys) { |
| errno = -EINVAL; |
| return -1; |
| } |
| |
| path = tracefs_event_get_file(instance, system, event, "trigger"); |
| if (!path) |
| return -1; |
| |
| trace_seq_puts(seq, "echo '"); |
| |
| add_hist_commands(seq, hist, command); |
| |
| trace_seq_printf(seq, "' > %s\n", path); |
| |
| tracefs_put_tracing_file(path); |
| |
| return 0; |
| } |
| |
| /* |
| * tracefs_hist_command - Create, start, pause, destroy a histogram for an event |
| * @instance: The instance the histogram will be in (NULL for toplevel) |
| * @hist: The histogram to write into the trigger file |
| * @command: Command to perform on a histogram. |
| * |
| * Creates, pause, continue, clears, or destroys a histogram. |
| * |
| * Returns 0 on succes -1 on error. |
| */ |
| int tracefs_hist_command(struct tracefs_instance *instance, |
| struct tracefs_hist *hist, |
| enum tracefs_hist_command command) |
| { |
| const char *system = hist->system; |
| const char *event = hist->event_name; |
| struct trace_seq seq; |
| int ret; |
| |
| if (!tracefs_event_file_exists(instance, system, event, HIST_FILE)) |
| return -1; |
| |
| errno = -EINVAL; |
| if (!hist->keys) |
| return -1; |
| |
| trace_seq_init(&seq); |
| |
| add_hist_commands(&seq, hist, command); |
| |
| trace_seq_putc(&seq, '\n'); |
| trace_seq_terminate(&seq); |
| |
| ret = -1; |
| if (seq.state == TRACE_SEQ__GOOD) |
| ret = tracefs_event_file_append(instance, system, event, |
| "trigger", seq.buffer); |
| |
| trace_seq_destroy(&seq); |
| |
| return ret < 0 ? -1 : 0; |
| } |
| |
| /** |
| * tracefs_hist_free - free a tracefs_hist element |
| * @hist: The histogram to free |
| */ |
| void tracefs_hist_free(struct tracefs_hist *hist) |
| { |
| if (!hist) |
| return; |
| |
| tep_unref(hist->tep); |
| free(hist->system); |
| free(hist->event_name); |
| free(hist->name); |
| free(hist->filter); |
| tracefs_list_free(hist->keys); |
| tracefs_list_free(hist->values); |
| tracefs_list_free(hist->sort); |
| free(hist); |
| } |
| |
| /** |
| * tracefs_hist_alloc - Initialize one-dimensional histogram |
| * @tep: The tep handle that has the @system and @event. |
| * @system: The system the histogram event is in. |
| * @event_name: The name of the event that the histogram will be attached to. |
| * @key: The primary key the histogram will use |
| * @type: The format type of the key. |
| * |
| * Will initialize a histogram descriptor that will be attached to |
| * the @system/@event with the given @key as the primary. This only |
| * initializes the descriptor, it does not start the histogram |
| * in the kernel. |
| * |
| * Returns an initialized histogram on success. |
| * NULL on failure. |
| */ |
| struct tracefs_hist * |
| tracefs_hist_alloc(struct tep_handle *tep, |
| const char *system, const char *event_name, |
| const char *key, enum tracefs_hist_key_type type) |
| { |
| struct tracefs_hist_axis axis[] = {{key, type}, {NULL, 0}}; |
| |
| return tracefs_hist_alloc_nd(tep, system, event_name, axis); |
| } |
| |
| /** |
| * tracefs_hist_alloc_2d - Initialize two-dimensional histogram |
| * @tep: The tep handle that has the @system and @event. |
| * @system: The system the histogram event is in. |
| * @event: The event that the histogram will be attached to. |
| * @key1: The first primary key the histogram will use |
| * @type1: The format type of the first key. |
| * @key2: The second primary key the histogram will use |
| * @type2: The format type of the second key. |
| * |
| * Will initialize a histogram descriptor that will be attached to |
| * the @system/@event with the given @key1 and @key2 as the primaries. |
| * This only initializes the descriptor, it does not start the histogram |
| * in the kernel. |
| * |
| * Returns an initialized histogram on success. |
| * NULL on failure. |
| */ |
| struct tracefs_hist * |
| tracefs_hist_alloc_2d(struct tep_handle *tep, |
| const char *system, const char *event_name, |
| const char *key1, enum tracefs_hist_key_type type1, |
| const char *key2, enum tracefs_hist_key_type type2) |
| { |
| struct tracefs_hist_axis axis[] = {{key1, type1}, |
| {key2, type2}, |
| {NULL, 0}}; |
| |
| return tracefs_hist_alloc_nd(tep, system, event_name, axis); |
| } |
| |
| static struct tracefs_hist * |
| hist_alloc_nd(struct tep_handle *tep, |
| const char *system, const char *event_name, |
| struct tracefs_hist_axis *axes, |
| struct tracefs_hist_axis_cnt *axes_cnt) |
| { |
| struct tep_event *event; |
| struct tracefs_hist *hist; |
| |
| if (!system || !event_name) |
| return NULL; |
| |
| event = tep_find_event_by_name(tep, system, event_name); |
| if (!event) |
| return NULL; |
| |
| hist = calloc(1, sizeof(*hist)); |
| if (!hist) |
| return NULL; |
| |
| tep_ref(tep); |
| hist->tep = tep; |
| hist->event = event; |
| hist->system = strdup(system); |
| hist->event_name = strdup(event_name); |
| if (!hist->system || !hist->event_name) |
| goto fail; |
| |
| for (; axes && axes->key; axes++) |
| if (tracefs_hist_add_key(hist, axes->key, axes->type) < 0) |
| goto fail; |
| |
| for (; axes_cnt && axes_cnt->key; axes_cnt++) |
| if (tracefs_hist_add_key_cnt(hist, axes_cnt->key, axes_cnt->type, axes_cnt->cnt) < 0) |
| goto fail; |
| |
| return hist; |
| |
| fail: |
| tracefs_hist_free(hist); |
| return NULL; |
| } |
| /** |
| * tracefs_hist_alloc_nd - Initialize N-dimensional histogram |
| * @tep: The tep handle that has the @system and @event. |
| * @system: The system the histogram event is in |
| * @event: The event that the histogram will be attached to |
| * @axes: An array of histogram axes, terminated by a {NULL, 0} entry |
| * |
| * 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. |
| * |
| * Returns an initialized histogram on success. |
| * NULL on failure. |
| */ |
| struct tracefs_hist * |
| tracefs_hist_alloc_nd(struct tep_handle *tep, |
| const char *system, const char *event_name, |
| struct tracefs_hist_axis *axes) |
| { |
| return hist_alloc_nd(tep, system, event_name, axes, NULL); |
| } |
| |
| /** |
| * tracefs_hist_alloc_nd_cnt - Initialize N-dimensional histogram |
| * @tep: The tep handle that has the @system and @event. |
| * @system: The system the histogram event is in |
| * @event: The event that the histogram will be attached to |
| * @axes: An array of histogram axes, terminated by a {NULL, 0} entry |
| * |
| * 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. |
| * |
| * Returns an initialized histogram on success. |
| * NULL on failure. |
| */ |
| 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) |
| { |
| return hist_alloc_nd(tep, system, event_name, NULL, axes); |
| } |
| |
| /** |
| * tracefs_hist_add_key - add to a key to a histogram |
| * @hist: The histogram to add the key to. |
| * @key: The name of the key field. |
| * @type: The type of the key format. |
| * @cnt: Some types require a counter, for those, this is used |
| * |
| * This adds a secondary or tertiary key to the histogram. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_add_key_cnt(struct tracefs_hist *hist, const char *key, |
| enum tracefs_hist_key_type type, int cnt) |
| { |
| bool use_key = false; |
| char *key_type = NULL; |
| char **new_list; |
| int ret = -1; |
| |
| switch (type) { |
| case TRACEFS_HIST_KEY_NORMAL: |
| use_key = true; |
| ret = 0; |
| break; |
| case TRACEFS_HIST_KEY_HEX: |
| ret = asprintf(&key_type, "%s.hex", key); |
| break; |
| case TRACEFS_HIST_KEY_SYM: |
| ret = asprintf(&key_type, "%s.sym", key); |
| break; |
| case TRACEFS_HIST_KEY_SYM_OFFSET: |
| ret = asprintf(&key_type, "%s.sym-offset", key); |
| break; |
| case TRACEFS_HIST_KEY_SYSCALL: |
| ret = asprintf(&key_type, "%s.syscall", key); |
| break; |
| case TRACEFS_HIST_KEY_EXECNAME: |
| ret = asprintf(&key_type, "%s.execname", key); |
| break; |
| case TRACEFS_HIST_KEY_LOG: |
| ret = asprintf(&key_type, "%s.log2", key); |
| break; |
| case TRACEFS_HIST_KEY_USECS: |
| ret = asprintf(&key_type, "%s.usecs", key); |
| break; |
| case TRACEFS_HIST_KEY_BUCKETS: |
| ret = asprintf(&key_type, "%s.buckets=%d", key, cnt); |
| break; |
| case TRACEFS_HIST_KEY_MAX: |
| /* error */ |
| break; |
| } |
| |
| if (ret < 0) |
| return -1; |
| |
| new_list = tracefs_list_add(hist->keys, use_key ? key : key_type); |
| free(key_type); |
| if (!new_list) |
| return -1; |
| |
| hist->keys = new_list; |
| |
| return 0; |
| } |
| |
| /** |
| * tracefs_hist_add_key - add to a key to a histogram |
| * @hist: The histogram to add the key to. |
| * @key: The name of the key field. |
| * @type: The type of the key format. |
| * |
| * This adds a secondary or tertiary key to the histogram. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, |
| enum tracefs_hist_key_type type) |
| { |
| return tracefs_hist_add_key_cnt(hist, key, type, 0); |
| } |
| |
| /** |
| * tracefs_hist_add_value - add to a value to a histogram |
| * @hist: The histogram to add the value to. |
| * @key: The name of the value field. |
| * |
| * This adds a value field to the histogram. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_add_value(struct tracefs_hist *hist, const char *value) |
| { |
| char **new_list; |
| |
| new_list = tracefs_list_add(hist->values, value); |
| if (!new_list) |
| return -1; |
| |
| hist->values = new_list; |
| |
| return 0; |
| } |
| |
| /** |
| * tracefs_hist_add_name - name a histogram |
| * @hist: The histogram to name. |
| * @name: The name of the histogram. |
| * |
| * Adds a name to the histogram. Named histograms will share their |
| * data with other events that have the same name as if it was |
| * a single histogram. |
| * |
| * If the histogram already has a name, this will fail. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_add_name(struct tracefs_hist *hist, const char *name) |
| { |
| if (hist->name) |
| return -1; |
| |
| hist->name = strdup(name); |
| |
| return hist->name ? 0 : -1; |
| } |
| |
| static char ** |
| add_sort_key(struct tracefs_hist *hist, const char *sort_key, char **list) |
| { |
| char **key_list = hist->keys; |
| char **val_list = hist->values; |
| int i; |
| |
| if (strcmp(sort_key, TRACEFS_HIST_HITCOUNT) == 0) |
| goto out; |
| |
| for (i = 0; key_list[i]; i++) { |
| if (strcmp(key_list[i], sort_key) == 0) |
| break; |
| } |
| |
| if (!key_list[i] && val_list) { |
| for (i = 0; val_list[i]; i++) { |
| if (strcmp(val_list[i], sort_key) == 0) |
| break; |
| if (!val_list[i]) |
| return NULL; |
| } |
| } |
| |
| |
| out: |
| return tracefs_list_add(list, sort_key); |
| } |
| |
| /** |
| * tracefs_hist_add_sort_key - add a key for sorting the histogram |
| * @hist: The histogram to add the sort key to |
| * @sort_key: The key to sort |
| * |
| * Call the function to add to the list of sort keys of the histohram in |
| * the order of priority that the keys would be sorted on output. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_add_sort_key(struct tracefs_hist *hist, |
| const char *sort_key) |
| { |
| char **list = hist->sort; |
| |
| if (!hist || !sort_key) |
| return -1; |
| |
| list = add_sort_key(hist, sort_key, hist->sort); |
| if (!list) |
| return -1; |
| |
| hist->sort = list; |
| |
| return 0; |
| } |
| |
| /** |
| * tracefs_hist_set_sort_key - set a key for sorting the histogram |
| * @hist: The histogram to set the sort key to |
| * @sort_key: The key to sort (and the strings after it) |
| * Last one must be NULL. |
| * |
| * Set a list of sort keys in the order of priority that the |
| * keys would be sorted on output. Keys must be added first. |
| * |
| * Returns 0 on success, -1 on error. |
| */ |
| int tracefs_hist_set_sort_key(struct tracefs_hist *hist, |
| const char *sort_key, ...) |
| { |
| char **list = NULL; |
| char **tmp; |
| va_list ap; |
| |
| if (!hist || !sort_key) |
| return -1; |
| |
| tmp = add_sort_key(hist, sort_key, list); |
| if (!tmp) |
| goto fail; |
| list = tmp; |
| |
| va_start(ap, sort_key); |
| for (;;) { |
| sort_key = va_arg(ap, const char *); |
| if (!sort_key) |
| break; |
| tmp = add_sort_key(hist, sort_key, list); |
| if (!tmp) |
| goto fail; |
| list = tmp; |
| } |
| va_end(ap); |
| |
| tracefs_list_free(hist->sort); |
| hist->sort = list; |
| |
| return 0; |
| fail: |
| tracefs_list_free(list); |
| return -1; |
| } |
| |
| static int end_match(const char *sort_key, const char *ending) |
| { |
| int key_len = strlen(sort_key); |
| int end_len = strlen(ending); |
| |
| if (key_len <= end_len) |
| return 0; |
| |
| sort_key += key_len - end_len; |
| |
| return strcmp(sort_key, ending) == 0 ? key_len - end_len : 0; |
| } |
| |
| /** |
| * tracefs_hist_sort_key_direction - set direction of a sort key |
| * @hist: The histogram to modify. |
| * @sort_str: The sort key to set the direction for |
| * @dir: The direction to set the sort key to. |
| * |
| * Returns 0 on success, and -1 on error; |
| */ |
| int tracefs_hist_sort_key_direction(struct tracefs_hist *hist, |
| const char *sort_str, |
| enum tracefs_hist_sort_direction dir) |
| { |
| char **sort = hist->sort; |
| char *sort_key; |
| char *direct; |
| int match; |
| int i; |
| |
| if (!sort) |
| return -1; |
| |
| for (i = 0; sort[i]; i++) { |
| if (strcmp(sort[i], sort_str) == 0) |
| break; |
| } |
| if (!sort[i]) |
| return -1; |
| |
| sort_key = sort[i]; |
| |
| switch (dir) { |
| case TRACEFS_HIST_SORT_ASCENDING: |
| direct = ASCENDING; |
| break; |
| case TRACEFS_HIST_SORT_DESCENDING: |
| direct = DESCENDING; |
| break; |
| default: |
| return -1; |
| } |
| |
| match = end_match(sort_key, ASCENDING); |
| if (match) { |
| /* Already match? */ |
| if (dir == TRACEFS_HIST_SORT_ASCENDING) |
| return 0; |
| } else { |
| match = end_match(sort_key, DESCENDING); |
| /* Already match? */ |
| if (match && dir == TRACEFS_HIST_SORT_DESCENDING) |
| return 0; |
| } |
| |
| if (match) |
| /* Clear the original text */ |
| sort_key[match] = '\0'; |
| |
| sort_key = realloc(sort_key, strlen(sort_key) + strlen(direct) + 1); |
| if (!sort_key) { |
| /* Failed to alloc, may need to put back the match */ |
| sort_key = sort[i]; |
| if (match) |
| sort_key[match] = '.'; |
| return -1; |
| } |
| |
| strcat(sort_key, direct); |
| sort[i] = sort_key; |
| return 0; |
| } |
| |
| int tracefs_hist_append_filter(struct tracefs_hist *hist, |
| enum tracefs_filter type, |
| const char *field, |
| enum tracefs_compare compare, |
| const char *val) |
| { |
| return trace_append_filter(&hist->filter, &hist->filter_state, |
| &hist->filter_parens, |
| hist->event, |
| type, field, compare, val); |
| } |
| |
| enum action_type { |
| ACTION_NONE, |
| ACTION_TRACE, |
| ACTION_SNAPSHOT, |
| ACTION_SAVE, |
| }; |
| |
| struct action { |
| struct action *next; |
| enum action_type type; |
| enum tracefs_synth_handler handler; |
| char *handle_field; |
| char *save; |
| }; |
| |
| struct name_hash { |
| struct name_hash *next; |
| char *name; |
| char *hash; |
| }; |
| |
| /* |
| * @name: name of the synthetic event |
| * @start_system: system of the starting event |
| * @start_event: the starting event |
| * @end_system: system of the ending event |
| * @end_event: the ending event |
| * @actions: List of actions to take |
| * @match_names: If a match set is to be a synthetic field, it has a name |
| * @start_match: list of keys in the start event that matches end event |
| * @end_match: list of keys in the end event that matches the start event |
| * @compare_names: The synthetic field names of the compared fields |
| * @start_compare: A list of compare fields in the start to compare to end |
| * @end_compare: A list of compare fields in the end to compare to start |
| * @compare_ops: The type of operations to perform between the start and end |
| * @start_names: The fields in the start event to record |
| * @end_names: The fields in the end event to record |
| * @start_filters: The fields in the end event to record |
| * @end_filters: The fields in the end event to record |
| * @start_parens: Current parenthesis level for start event |
| * @end_parens: Current parenthesis level for end event |
| * @new_format: onmatch().trace(synth_event,..) or onmatch().synth_event(...) |
| */ |
| struct tracefs_synth { |
| struct tracefs_instance *instance; |
| struct tep_handle *tep; |
| struct tep_event *start_event; |
| struct tep_event *end_event; |
| struct action *actions; |
| struct action **next_action; |
| struct tracefs_dynevent *dyn_event; |
| struct name_hash *name_hash[1 << HASH_BITS]; |
| char *start_hist; |
| char *end_hist; |
| char *name; |
| char **synthetic_fields; |
| char **synthetic_args; |
| char **start_selection; |
| char **start_keys; |
| char **end_keys; |
| char **start_vars; |
| char **end_vars; |
| char *start_filter; |
| char *end_filter; |
| unsigned int start_parens; |
| unsigned int start_state; |
| unsigned int end_parens; |
| unsigned int end_state; |
| int *start_type; |
| char arg_name[16]; |
| int arg_cnt; |
| bool new_format; |
| }; |
| |
| /* |
| * tracefs_synth_get_name - get the name of the synthetic event |
| * @synth: The synthetic event to get the name for |
| * |
| * Returns name string owned by @synth on success, or NULL on error. |
| */ |
| const char *tracefs_synth_get_name(struct tracefs_synth *synth) |
| { |
| return synth ? synth->name : NULL; |
| } |
| |
| static void action_free(struct action *action) |
| { |
| free(action->handle_field); |
| free(action->save); |
| free(action); |
| } |
| |
| static void free_name_hash(struct name_hash **hash) |
| { |
| struct name_hash *item; |
| int i; |
| |
| for (i = 0; i < 1 << HASH_BITS; i++) { |
| while ((item = hash[i])) { |
| hash[i] = item->next; |
| free(item->name); |
| free(item->hash); |
| free(item); |
| } |
| } |
| } |
| |
| /** |
| * tracefs_synth_free - free the resources alloced to a synth |
| * @synth: The tracefs_synth descriptor |
| * |
| * Frees the resources allocated for a @synth created with |
| * tracefs_synth_alloc(). It does not touch the system. That is, |
| * any synthetic event created, will not be destroyed by this |
| * function. |
| */ |
| void tracefs_synth_free(struct tracefs_synth *synth) |
| { |
| struct action *action; |
| |
| if (!synth) |
| return; |
| |
| free(synth->name); |
| free(synth->start_hist); |
| free(synth->end_hist); |
| tracefs_list_free(synth->synthetic_fields); |
| tracefs_list_free(synth->synthetic_args); |
| tracefs_list_free(synth->start_selection); |
| tracefs_list_free(synth->start_keys); |
| tracefs_list_free(synth->end_keys); |
| tracefs_list_free(synth->start_vars); |
| tracefs_list_free(synth->end_vars); |
| free_name_hash(synth->name_hash); |
| free(synth->start_filter); |
| free(synth->end_filter); |
| free(synth->start_type); |
| |
| tep_unref(synth->tep); |
| |
| while ((action = synth->actions)) { |
| synth->actions = action->next; |
| action_free(action); |
| } |
| tracefs_dynevent_free(synth->dyn_event); |
| |
| free(synth); |
| } |
| |
| static bool verify_event_fields(struct tep_event *start_event, |
| struct tep_event *end_event, |
| const char *start_field_name, |
| const char *end_field_name, |
| const struct tep_format_field **ptr_start_field) |
| { |
| const struct tep_format_field *start_field; |
| const struct tep_format_field *end_field; |
| int start_flags, end_flags; |
| |
| if (!trace_verify_event_field(start_event, start_field_name, |
| &start_field)) |
| return false; |
| |
| if (end_event) { |
| if (!trace_verify_event_field(end_event, end_field_name, |
| &end_field)) |
| return false; |
| |
| /* A pointer can still match a long */ |
| start_flags = start_field->flags & ~TEP_FIELD_IS_POINTER; |
| end_flags = end_field->flags & ~TEP_FIELD_IS_POINTER; |
| |
| if (start_flags != end_flags || |
| start_field->size != end_field->size) { |
| errno = EBADE; |
| return false; |
| } |
| } |
| |
| if (ptr_start_field) |
| *ptr_start_field = start_field; |
| |
| return true; |
| } |
| |
| __hidden char *append_string(char *str, const char *space, const char *add) |
| { |
| char *new; |
| int len; |
| |
| /* String must already be allocated */ |
| if (!str) |
| return NULL; |
| |
| len = strlen(str) + strlen(add) + 2; |
| if (space) |
| len += strlen(space); |
| |
| new = realloc(str, len); |
| if (!new) { |
| free(str); |
| return NULL; |
| } |
| str = new; |
| |
| if (space) |
| strcat(str, space); |
| strcat(str, add); |
| |
| return str; |
| } |
| |
| static char *add_synth_field(const struct tep_format_field *field, |
| const char *name) |
| { |
| const char *type; |
| char size[64]; |
| char *str; |
| bool sign; |
| |
| if (field->flags & TEP_FIELD_IS_ARRAY) { |
| str = strdup("char"); |
| str = append_string(str, " ", name); |
| str = append_string(str, NULL, "["); |
| |
| if (!(field->flags & TEP_FIELD_IS_DYNAMIC)) { |
| snprintf(size, 64, "%d", field->size); |
| str = append_string(str, NULL, size); |
| } |
| return append_string(str, NULL, "];"); |
| } |
| |
| /* Synthetic events understand pid_t, gfp_t and bool */ |
| if (strcmp(field->type, "pid_t") == 0 || |
| strcmp(field->type, "gfp_t") == 0 || |
| strcmp(field->type, "bool") == 0) { |
| str = strdup(field->type); |
| str = append_string(str, " ", name); |
| return append_string(str, NULL, ";"); |
| } |
| |
| sign = field->flags & TEP_FIELD_IS_SIGNED; |
| |
| switch (field->size) { |
| case 1: |
| if (!sign) |
| type = "unsigned char"; |
| else |
| type = "char"; |
| break; |
| case 2: |
| if (sign) |
| type = "s16"; |
| else |
| type = "u16"; |
| break; |
| case 4: |
| if (sign) |
| type = "s32"; |
| else |
| type = "u32"; |
| break; |
| case 8: |
| if (sign) |
| type = "s64"; |
| else |
| type = "u64"; |
| break; |
| default: |
| errno = EBADF; |
| return NULL; |
| } |
| |
| str = strdup(type); |
| str = append_string(str, " ", name); |
| return append_string(str, NULL, ";"); |
| } |
| |
| static int add_var(char ***list, const char *name, const char *var, bool is_var) |
| { |
| char **new; |
| char *assign; |
| int ret; |
| |
| if (is_var) |
| ret = asprintf(&assign, "%s=$%s", name, var); |
| else |
| ret = asprintf(&assign, "%s=%s", name, var); |
| |
| if (ret < 0) |
| return -1; |
| |
| new = tracefs_list_add(*list, assign); |
| free(assign); |
| |
| if (!new) |
| return -1; |
| *list = new; |
| return 0; |
| } |
| |
| __hidden struct tracefs_synth * |
| synth_init_from(struct tep_handle *tep, const char *start_system, |
| const char *start_event_name) |
| { |
| struct tep_event *start_event; |
| struct tracefs_synth *synth; |
| |
| start_event = tep_find_event_by_name(tep, start_system, |
| start_event_name); |
| if (!start_event) { |
| errno = ENODEV; |
| return NULL; |
| } |
| |
| synth = calloc(1, sizeof(*synth)); |
| if (!synth) |
| return NULL; |
| |
| synth->start_event = start_event; |
| synth->next_action = &synth->actions; |
| |
| /* Hold onto a reference to this handler */ |
| tep_ref(tep); |
| synth->tep = tep; |
| |
| return synth; |
| } |
| |
| static int alloc_synthetic_event(struct tracefs_synth *synth) |
| { |
| char *format; |
| const char *field; |
| int i; |
| |
| format = strdup(""); |
| if (!format) |
| return -1; |
| |
| for (i = 0; synth->synthetic_fields && synth->synthetic_fields[i]; i++) { |
| field = synth->synthetic_fields[i]; |
| format = append_string(format, i ? " " : NULL, field); |
| } |
| |
| synth->dyn_event = dynevent_alloc(TRACEFS_DYNEVENT_SYNTH, SYNTHETIC_GROUP, |
| synth->name, NULL, format); |
| free(format); |
| |
| return synth->dyn_event ? 0 : -1; |
| } |
| |
| /* |
| * See if it is onmatch().trace(synth_event,...) or |
| * onmatch().synth_event(...) |
| */ |
| static bool has_new_format() |
| { |
| char *readme; |
| char *p; |
| int size; |
| |
| readme = tracefs_instance_file_read(NULL, "README", &size); |
| if (!readme) |
| return false; |
| |
| p = strstr(readme, "trace(<synthetic_event>,param list)"); |
| free(readme); |
| |
| return p != NULL; |
| } |
| |
| /** |
| * tracefs_synth_alloc - create a new tracefs_synth instance |
| * @tep: The tep handle that holds the events to work on |
| * @name: The name of the synthetic event being created |
| * @start_system: The name of the system of the start event (can be NULL) |
| * @start_event_name: The name of the start event |
| * @end_system: The name of the system of the end event (can be NULL) |
| * @end_event_name: The name of the end event |
| * @start_match_field: The name of the field in start event to match @end_match_field |
| * @end_match_field: The name of the field in end event to match @start_match_field |
| * @match_name: Name to call the fields that match (can be NULL) |
| * |
| * Creates a tracefs_synth instance that has the minimum requirements to |
| * create a synthetic event. |
| * |
| * @name is will be the name of the synthetic event that this can create. |
| * |
| * The start event is found with @start_system and @start_event_name. If |
| * @start_system is NULL, then the first event with @start_event_name will |
| * be used. |
| * |
| * The end event is found with @end_system and @end_event_name. If |
| * @end_system is NULL, then the first event with @end_event_name will |
| * be used. |
| * |
| * The @start_match_field is the field in the start event that will be used |
| * to match the @end_match_field of the end event. |
| * |
| * If @match_name is given, then the field that matched the start and |
| * end events will be passed an a field to the sythetic event with this |
| * as the field name. |
| * |
| * Returns an allocated tracefs_synth descriptor on success and NULL |
| * on error, with the following set in errno. |
| * |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be |
| * ENODEV - could not find an event or field |
| * EBADE - The start and end fields are not compatible to match |
| * |
| * Note, this does not modify the system. That is, the synthetic |
| * event on the system is not created. That needs to be done with |
| * tracefs_synth_create(). |
| */ |
| struct tracefs_synth *tracefs_synth_alloc(struct tep_handle *tep, |
| const char *name, |
| const char *start_system, |
| const char *start_event_name, |
| const char *end_system, |
| const char *end_event_name, |
| const char *start_match_field, |
| const char *end_match_field, |
| const char *match_name) |
| { |
| struct tep_event *end_event; |
| struct tracefs_synth *synth; |
| int ret = 0; |
| |
| if (!tep || !name || !start_event_name || !end_event_name || |
| !start_match_field || !end_match_field) { |
| errno = EINVAL; |
| return NULL; |
| } |
| |
| synth = synth_init_from(tep, start_system, start_event_name); |
| if (!synth) |
| return NULL; |
| |
| end_event = tep_find_event_by_name(tep, end_system, |
| end_event_name); |
| if (!end_event) { |
| tep_unref(tep); |
| errno = ENODEV; |
| return NULL; |
| } |
| |
| synth->end_event = end_event; |
| |
| synth->name = strdup(name); |
| |
| ret = tracefs_synth_add_match_field(synth, start_match_field, |
| end_match_field, match_name); |
| |
| if (!synth->name || !synth->start_keys || !synth->end_keys || ret) { |
| tracefs_synth_free(synth); |
| synth = NULL; |
| } else |
| synth->new_format = has_new_format(); |
| |
| return synth; |
| } |
| |
| static int add_synth_fields(struct tracefs_synth *synth, |
| const struct tep_format_field *field, |
| const char *name, const char *var) |
| { |
| char **list; |
| char *str; |
| int ret; |
| |
| str = add_synth_field(field, name ? : field->name); |
| if (!str) |
| return -1; |
| |
| if (!name) |
| name = var; |
| |
| list = tracefs_list_add(synth->synthetic_fields, str); |
| free(str); |
| if (!list) |
| return -1; |
| synth->synthetic_fields = list; |
| |
| ret = asprintf(&str, "$%s", var ? : name); |
| if (ret < 0) { |
| trace_list_pop(synth->synthetic_fields); |
| return -1; |
| } |
| |
| list = tracefs_list_add(synth->synthetic_args, str); |
| free(str); |
| if (!list) { |
| trace_list_pop(synth->synthetic_fields); |
| return -1; |
| } |
| |
| synth->synthetic_args = list; |
| |
| return 0; |
| } |
| |
| /** |
| * tracefs_synth_add_match_field - add another key to match events |
| * @synth: The tracefs_synth descriptor |
| * @start_match_field: The field of the start event to match the end event |
| * @end_match_field: The field of the end event to match the start event |
| * @name: The name to show in the synthetic event (NULL is allowed) |
| * |
| * This will add another set of keys to use for a match between |
| * the start event and the end event. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be |
| * ENODEV - could not find a field |
| * EBADE - The start and end fields are not compatible to match |
| */ |
| int tracefs_synth_add_match_field(struct tracefs_synth *synth, |
| const char *start_match_field, |
| const char *end_match_field, |
| const char *name) |
| { |
| const struct tep_format_field *key_field; |
| char **list; |
| int ret; |
| |
| if (!synth || !start_match_field || !end_match_field) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!verify_event_fields(synth->start_event, synth->end_event, |
| start_match_field, end_match_field, |
| &key_field)) |
| return -1; |
| |
| list = tracefs_list_add(synth->start_keys, start_match_field); |
| if (!list) |
| return -1; |
| |
| synth->start_keys = list; |
| |
| list = tracefs_list_add(synth->end_keys, end_match_field); |
| if (!list) { |
| trace_list_pop(synth->start_keys); |
| return -1; |
| } |
| synth->end_keys = list; |
| |
| if (!name) |
| return 0; |
| |
| ret = add_var(&synth->end_vars, name, end_match_field, false); |
| |
| if (ret < 0) |
| goto pop_lists; |
| |
| ret = add_synth_fields(synth, key_field, name, NULL); |
| if (ret < 0) |
| goto pop_lists; |
| |
| return 0; |
| |
| pop_lists: |
| trace_list_pop(synth->start_keys); |
| trace_list_pop(synth->end_keys); |
| return -1; |
| } |
| |
| static unsigned int make_rand(void) |
| { |
| struct timeval tv; |
| unsigned long seed; |
| |
| gettimeofday(&tv, NULL); |
| seed = (tv.tv_sec + tv.tv_usec) + getpid(); |
| |
| /* taken from the rand(3) man page */ |
| seed = seed * 1103515245 + 12345; |
| return((unsigned)(seed/65536) % 32768); |
| } |
| |
| static char *new_name(struct tracefs_synth *synth, const char *name) |
| { |
| int cnt = synth->arg_cnt + 1; |
| char *arg; |
| int ret; |
| |
| /* Create a unique argument name */ |
| if (!synth->arg_name[0]) { |
| /* make_rand() returns at most 32768 (total 13 bytes in use) */ |
| sprintf(synth->arg_name, "%u", make_rand()); |
| } |
| ret = asprintf(&arg, "__%s_%s_%d", name, synth->arg_name, cnt); |
| if (ret < 0) |
| return NULL; |
| |
| synth->arg_cnt = cnt; |
| return arg; |
| } |
| |
| static struct name_hash *find_name(struct tracefs_synth *synth, const char *name) |
| { |
| unsigned int key = quick_hash(name); |
| struct name_hash *hash = synth->name_hash[key]; |
| |
| for (; hash; hash = hash->next) { |
| if (!strcmp(hash->name, name)) |
| return hash; |
| } |
| return NULL; |
| } |
| |
| static const char *hash_name(struct tracefs_synth *synth, const char *name) |
| { |
| struct name_hash *hash; |
| int key; |
| |
| hash = find_name(synth, name); |
| if (hash) |
| return hash->hash; |
| |
| hash = malloc(sizeof(*hash)); |
| if (!hash) |
| return name; |
| |
| hash->hash = new_name(synth, name); |
| if (!hash->hash) { |
| free(hash); |
| return name; |
| } |
| |
| key = quick_hash(name); |
| hash->next = synth->name_hash[key]; |
| synth->name_hash[key] = hash; |
| |
| hash->name = strdup(name); |
| if (!hash->name) { |
| free(hash->hash); |
| free(hash); |
| return name; |
| } |
| return hash->hash; |
| } |
| |
| static char *new_arg(struct tracefs_synth *synth) |
| { |
| return new_name(synth, "arg"); |
| } |
| |
| /** |
| * tracefs_synth_add_compare_field - add a comparison between start and end |
| * @synth: The tracefs_synth descriptor |
| * @start_compare_field: The field of the start event to compare to the end |
| * @end_compare_field: The field of the end event to compare to the start |
| * @calc - How to go about the comparing the fields. |
| * @name: The name to show in the synthetic event (must NOT be NULL) |
| * |
| * This will add a way to compare two different fields between the |
| * start end end events. |
| * |
| * The comparing between events is decided by @calc: |
| * TRACEFS_SYNTH_DELTA_END - name = end - start |
| * TRACEFS_SYNTH_DELTA_START - name = start - end |
| * TRACEFS_SYNTH_ADD - name = end + start |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be |
| * ENODEV - could not find a field |
| * EBADE - The start and end fields are not compatible to compare |
| */ |
| int tracefs_synth_add_compare_field(struct tracefs_synth *synth, |
| const char *start_compare_field, |
| const char *end_compare_field, |
| enum tracefs_synth_calc calc, |
| const char *name) |
| { |
| const struct tep_format_field *start_field; |
| const char *hname; |
| char *start_arg; |
| char *compare; |
| int ret; |
| |
| /* Compare fields require a name */ |
| if (!name || !start_compare_field || !end_compare_field) { |
| errno = -EINVAL; |
| return -1; |
| } |
| |
| if (!verify_event_fields(synth->start_event, synth->end_event, |
| start_compare_field, end_compare_field, |
| &start_field)) |
| return -1; |
| |
| /* Calculations are not allowed on string */ |
| if (start_field->flags & (TEP_FIELD_IS_ARRAY | |
| TEP_FIELD_IS_DYNAMIC)) { |
| errno = -EINVAL; |
| return -1; |
| } |
| |
| start_arg = new_arg(synth); |
| if (!start_arg) |
| return -1; |
| |
| ret = add_var(&synth->start_vars, start_arg, start_compare_field, false); |
| if (ret < 0) { |
| free(start_arg); |
| return -1; |
| } |
| |
| ret = -1; |
| switch (calc) { |
| case TRACEFS_SYNTH_DELTA_END: |
| ret = asprintf(&compare, "%s-$%s", end_compare_field, |
| start_arg); |
| break; |
| case TRACEFS_SYNTH_DELTA_START: |
| ret = asprintf(&compare, "$%s-%s", start_arg, |
| end_compare_field); |
| break; |
| case TRACEFS_SYNTH_ADD: |
| ret = asprintf(&compare, "%s+$%s", end_compare_field, |
| start_arg); |
| break; |
| } |
| free(start_arg); |
| if (ret < 0) |
| return -1; |
| |
| hname = hash_name(synth, name); |
| ret = add_var(&synth->end_vars, hname, compare, false); |
| if (ret < 0) |
| goto out_free; |
| |
| ret = add_synth_fields(synth, start_field, name, hname); |
| if (ret < 0) |
| goto out_free; |
| |
| out_free: |
| free(compare); |
| |
| return ret ? -1 : 0; |
| } |
| |
| __hidden int synth_add_start_field(struct tracefs_synth *synth, |
| const char *start_field, |
| const char *name, |
| enum tracefs_hist_key_type type, int count) |
| { |
| const struct tep_format_field *field; |
| const char *var; |
| char *start_arg; |
| char **tmp; |
| int *types; |
| int len; |
| int ret; |
| |
| if (!synth || !start_field) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!name) |
| name = start_field; |
| |
| var = hash_name(synth, name); |
| |
| if (!trace_verify_event_field(synth->start_event, start_field, &field)) |
| return -1; |
| |
| start_arg = new_arg(synth); |
| if (!start_arg) |
| return -1; |
| |
| ret = add_var(&synth->start_vars, start_arg, start_field, false); |
| if (ret) |
| goto out_free; |
| |
| ret = add_var(&synth->end_vars, var, start_arg, true); |
| if (ret) |
| goto out_free; |
| |
| ret = add_synth_fields(synth, field, name, var); |
| if (ret) |
| goto out_free; |
| |
| tmp = tracefs_list_add(synth->start_selection, start_field); |
| if (!tmp) { |
| ret = -1; |
| goto out_free; |
| } |
| synth->start_selection = tmp; |
| |
| len = tracefs_list_size(tmp); |
| if (len <= 0) { /* ?? */ |
| ret = -1; |
| goto out_free; |
| } |
| |
| types = realloc(synth->start_type, sizeof(*types) * len); |
| if (!types) { |
| ret = -1; |
| goto out_free; |
| } |
| synth->start_type = types; |
| synth->start_type[len - 1] = type; |
| |
| out_free: |
| free(start_arg); |
| return ret; |
| } |
| |
| /** |
| * tracefs_synth_add_start_field - add a start field to save |
| * @synth: The tracefs_synth descriptor |
| * @start_field: The field of the start event to save |
| * @name: The name to show in the synthetic event (if NULL @start_field is used) |
| * |
| * This adds a field named by @start_field of the start event to |
| * record in the synthetic event. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be |
| * ENODEV - could not find a field |
| */ |
| int tracefs_synth_add_start_field(struct tracefs_synth *synth, |
| const char *start_field, |
| const char *name) |
| { |
| return synth_add_start_field(synth, start_field, name, 0, 0); |
| } |
| |
| /** |
| * tracefs_synth_add_end_field - add a end field to save |
| * @synth: The tracefs_synth descriptor |
| * @end_field: The field of the end event to save |
| * @name: The name to show in the synthetic event (if NULL @end_field is used) |
| * |
| * This adds a field named by @end_field of the start event to |
| * record in the synthetic event. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be |
| * ENODEV - could not find a field |
| */ |
| int tracefs_synth_add_end_field(struct tracefs_synth *synth, |
| const char *end_field, |
| const char *name) |
| { |
| const struct tep_format_field *field; |
| const char *hname = NULL; |
| char *tmp_var = NULL; |
| int ret; |
| |
| if (!synth || !end_field) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (name) { |
| if (strncmp(name, "__arg", 5) != 0) |
| hname = hash_name(synth, name); |
| else |
| hname = name; |
| } |
| |
| if (!name) |
| tmp_var = new_arg(synth); |
| |
| if (!trace_verify_event_field(synth->end_event, end_field, &field)) |
| return -1; |
| |
| ret = add_var(&synth->end_vars, name ? hname : tmp_var, end_field, false); |
| if (ret) |
| goto out; |
| |
| ret = add_synth_fields(synth, field, name, hname ? : tmp_var); |
| free(tmp_var); |
| out: |
| return ret; |
| } |
| |
| /** |
| * tracefs_synth_append_start_filter - create or append a filter |
| * @synth: The tracefs_synth descriptor |
| * @type: The type of element to add to the filter |
| * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare |
| * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val |
| * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be |
| * |
| * This will put together a filter string for the starting event |
| * of @synth. It check to make sure that what is added is correct compared |
| * to the filter that is already built. |
| * |
| * @type can be: |
| * TRACEFS_FILTER_COMPARE: See below |
| * TRACEFS_FILTER_AND: Append "&&" to the filter |
| * TRACEFS_FILTER_OR: Append "||" to the filter |
| * TRACEFS_FILTER_NOT: Append "!" to the filter |
| * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter |
| * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter |
| * |
| * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare, |
| * and @val are ignored. |
| * |
| * For @type == TRACEFS_FILTER_COMPARE. |
| * |
| * @field is the name of the field for the start event to compare. |
| * If it is not a field for the start event, this return an |
| * error. |
| * |
| * @compare can be one of: |
| * TRACEFS_COMPARE_EQ: Test @field == @val |
| * TRACEFS_COMPARE_NE: Test @field != @val |
| * TRACEFS_COMPARE_GT: Test @field > @val |
| * TRACEFS_COMPARE_GE: Test @field >= @val |
| * TRACEFS_COMPARE_LT: Test @field < @val |
| * TRACEFS_COMPARE_LE: Test @field <= @val |
| * TRACEFS_COMPARE_RE: Test @field ~ @val |
| * TRACEFS_COMPARE_AND: Test @field & @val |
| * |
| * If the @field is of type string, and @compare is not |
| * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE, |
| * then this will return an error. |
| * |
| * Various other checks are made, for instance, if more CLOSE_PARENs |
| * are added than existing OPEN_PARENs. Or if AND is added after an |
| * OPEN_PAREN or another AND or an OR or a NOT. |
| * |
| * Returns 0 on success and -1 on failure. |
| */ |
| int tracefs_synth_append_start_filter(struct tracefs_synth *synth, |
| enum tracefs_filter type, |
| const char *field, |
| enum tracefs_compare compare, |
| const char *val) |
| { |
| return trace_append_filter(&synth->start_filter, &synth->start_state, |
| &synth->start_parens, |
| synth->start_event, |
| type, field, compare, val); |
| } |
| |
| /** |
| * tracefs_synth_append_end_filter - create or append a filter |
| * @synth: The tracefs_synth descriptor |
| * @type: The type of element to add to the filter |
| * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare |
| * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val |
| * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be |
| * |
| * Performs the same thing as tracefs_synth_append_start_filter() but |
| * for the @synth end event. |
| */ |
| int tracefs_synth_append_end_filter(struct tracefs_synth *synth, |
| enum tracefs_filter type, |
| const char *field, |
| enum tracefs_compare compare, |
| const char *val) |
| { |
| return trace_append_filter(&synth->end_filter, &synth->end_state, |
| &synth->end_parens, |
| synth->end_event, |
| type, field, compare, val); |
| } |
| |
| static int test_max_var(struct tracefs_synth *synth, const char *var) |
| { |
| char **vars = synth->end_vars; |
| char *p; |
| int len; |
| int i; |
| |
| len = strlen(var); |
| |
| /* Make sure the var is defined for the end event */ |
| for (i = 0; vars[i]; i++) { |
| p = strchr(vars[i], '='); |
| if (!p) |
| continue; |
| if (p - vars[i] != len) |
| continue; |
| if (!strncmp(var, vars[i], len)) |
| return 0; |
| } |
| errno = ENODEV; |
| return -1; |
| } |
| |
| static struct action *create_action(enum tracefs_synth_handler type, |
| struct tracefs_synth *synth, |
| const char *var) |
| { |
| struct action *action; |
| int ret; |
| |
| switch (type) { |
| case TRACEFS_SYNTH_HANDLE_MAX: |
| case TRACEFS_SYNTH_HANDLE_CHANGE: |
| ret = test_max_var(synth, var); |
| if (ret < 0) |
| return NULL; |
| break; |
| default: |
| break; |
| } |
| |
| action = calloc(1, sizeof(*action)); |
| if (!action) |
| return NULL; |
| |
| if (var) { |
| ret = asprintf(&action->handle_field, "$%s", var); |
| if (!action->handle_field) { |
| free(action); |
| return NULL; |
| } |
| } |
| return action; |
| } |
| |
| static void add_action(struct tracefs_synth *synth, struct action *action) |
| { |
| *synth->next_action = action; |
| synth->next_action = &action->next; |
| } |
| |
| /** |
| * tracefs_synth_trace - Execute the trace option |
| * @synth: The tracefs_synth descriptor |
| * @type: The type of handler to attach the trace action with |
| * @field: The field for handlers onmax and onchange (ignored otherwise) |
| * |
| * Add the action 'trace' for handlers onmatch, onmax and onchange. |
| * |
| * Returns 0 on succes, -1 on error. |
| */ |
| int tracefs_synth_trace(struct tracefs_synth *synth, |
| enum tracefs_synth_handler type, const char *field) |
| { |
| struct action *action; |
| |
| if (!synth || (!field && (type != TRACEFS_SYNTH_HANDLE_MATCH))) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| action = create_action(type, synth, field); |
| if (!action) |
| return -1; |
| |
| action->type = ACTION_TRACE; |
| action->handler = type; |
| add_action(synth, action); |
| return 0; |
| } |
| |
| /** |
| * tracefs_synth_snapshot - create a snapshot command to the histogram |
| * @synth: The tracefs_synth descriptor |
| * @type: The type of handler to attach the snapshot action with |
| * @field: The field for handlers onmax and onchange |
| * |
| * Add the action to do a snapshot for handlers onmax and onchange. |
| * |
| * Returns 0 on succes, -1 on error. |
| */ |
| int tracefs_synth_snapshot(struct tracefs_synth *synth, |
| enum tracefs_synth_handler type, const char *field) |
| { |
| struct action *action; |
| |
| if (!synth || !field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| action = create_action(type, synth, field); |
| if (!action) |
| return -1; |
| |
| action->type = ACTION_SNAPSHOT; |
| action->handler = type; |
| add_action(synth, action); |
| return 0; |
| } |
| |
| /** |
| * tracefs_synth_save - create a save command to the histogram |
| * @synth: The tracefs_synth descriptor |
| * @type: The type of handler to attach the save action |
| * @max_field: The field for handlers onmax and onchange |
| * @fields: The fields to save for the end variable |
| * |
| * Add the action to save fields for handlers onmax and onchange. |
| * |
| * Returns 0 on succes, -1 on error. |
| */ |
| int tracefs_synth_save(struct tracefs_synth *synth, |
| enum tracefs_synth_handler type, const char *max_field, |
| char **fields) |
| { |
| struct action *action; |
| char *save; |
| int i; |
| |
| if (!synth || !max_field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| action = create_action(type, synth, max_field); |
| if (!action) |
| return -1; |
| |
| action->type = ACTION_SAVE; |
| action->handler = type; |
| *synth->next_action = action; |
| synth->next_action = &action->next; |
| |
| save = strdup(".save("); |
| if (!save) |
| goto error; |
| |
| for (i = 0; fields[i]; i++) { |
| char *delim = i ? "," : NULL; |
| |
| if (!trace_verify_event_field(synth->end_event, fields[i], NULL)) |
| goto error; |
| save = append_string(save, delim, fields[i]); |
| } |
| save = append_string(save, NULL, ")"); |
| if (!save) |
| goto error; |
| |
| action->save = save; |
| add_action(synth, action); |
| return 0; |
| error: |
| action_free(action); |
| free(save); |
| return -1; |
| return 0; |
| } |
| |
| static int remove_hist(struct tracefs_instance *instance, |
| struct tep_event *event, const char *hist) |
| { |
| char *str; |
| int ret; |
| |
| ret = asprintf(&str, "!%s", hist); |
| if (ret < 0) |
| return -1; |
| |
| ret = tracefs_event_file_append(instance, event->system, event->name, |
| "trigger", str); |
| free(str); |
| return ret < 0 ? -1 : 0; |
| } |
| |
| static char *create_hist(char **keys, char **vars) |
| { |
| char *hist = strdup("hist:keys="); |
| char *name; |
| int i; |
| |
| if (!hist) |
| return NULL; |
| |
| for (i = 0; keys[i]; i++) { |
| name = keys[i]; |
| if (i) |
| hist = append_string(hist, NULL, ","); |
| hist = append_string(hist, NULL, name); |
| } |
| |
| if (!vars) |
| return hist; |
| |
| hist = append_string(hist, NULL, ":"); |
| |
| for (i = 0; vars[i]; i++) { |
| name = vars[i]; |
| if (i) |
| hist = append_string(hist, NULL, ","); |
| hist = append_string(hist, NULL, name); |
| } |
| |
| return hist; |
| } |
| |
| static char *create_onmatch(char *hist, struct tracefs_synth *synth) |
| { |
| hist = append_string(hist, NULL, ":onmatch("); |
| hist = append_string(hist, NULL, synth->start_event->system); |
| hist = append_string(hist, NULL, "."); |
| hist = append_string(hist, NULL, synth->start_event->name); |
| return append_string(hist, NULL, ")"); |
| } |
| |
| static char *create_trace(char *hist, struct tracefs_synth *synth) |
| { |
| char *name; |
| int i; |
| |
| if (synth->new_format) { |
| hist = append_string(hist, NULL, ".trace("); |
| hist = append_string(hist, NULL, synth->name); |
| hist = append_string(hist, NULL, ","); |
| } else { |
| hist = append_string(hist, NULL, "."); |
| hist = append_string(hist, NULL, synth->name); |
| hist = append_string(hist, NULL, "("); |
| } |
| |
| for (i = 0; synth->synthetic_args && synth->synthetic_args[i]; i++) { |
| name = synth->synthetic_args[i]; |
| |
| if (i) |
| hist = append_string(hist, NULL, ","); |
| hist = append_string(hist, NULL, name); |
| } |
| |
| return append_string(hist, NULL, ")"); |
| } |
| |
| static char *create_max(char *hist, struct tracefs_synth *synth, char *field) |
| { |
| hist = append_string(hist, NULL, ":onmax("); |
| hist = append_string(hist, NULL, field); |
| return append_string(hist, NULL, ")"); |
| } |
| |
| static char *create_change(char *hist, struct tracefs_synth *synth, char *field) |
| { |
| hist = append_string(hist, NULL, ":onchange("); |
| hist = append_string(hist, NULL, field); |
| return append_string(hist, NULL, ")"); |
| } |
| |
| static char *create_actions(char *hist, struct tracefs_synth *synth) |
| { |
| struct action *action; |
| |
| if (!synth->actions) { |
| /* Default is "onmatch" and "trace" */ |
| hist = create_onmatch(hist, synth); |
| return create_trace(hist, synth); |
| } |
| |
| for (action = synth->actions; action; action = action->next) { |
| switch (action->handler) { |
| case TRACEFS_SYNTH_HANDLE_MATCH: |
| hist = create_onmatch(hist, synth); |
| break; |
| case TRACEFS_SYNTH_HANDLE_MAX: |
| hist = create_max(hist, synth, action->handle_field); |
| break; |
| case TRACEFS_SYNTH_HANDLE_CHANGE: |
| hist = create_change(hist, synth, action->handle_field); |
| break; |
| default: |
| continue; |
| } |
| switch (action->type) { |
| case ACTION_TRACE: |
| hist = create_trace(hist, synth); |
| break; |
| case ACTION_SNAPSHOT: |
| hist = append_string(hist, NULL, ".snapshot()"); |
| break; |
| case ACTION_SAVE: |
| hist = append_string(hist, NULL, action->save); |
| break; |
| default: |
| continue; |
| } |
| } |
| return hist; |
| } |
| |
| static char *create_end_hist(struct tracefs_synth *synth) |
| { |
| char *end_hist; |
| |
| end_hist = create_hist(synth->end_keys, synth->end_vars); |
| return create_actions(end_hist, synth); |
| } |
| |
| /* |
| * tracefs_synth_raw_fmt - show the raw format of a synthetic event |
| * @seq: A trace_seq to store the format string |
| * @synth: The synthetic event to read format from |
| * |
| * This shows the raw format that describes the synthetic event, including |
| * the format of the dynamic event and the start / end histograms. |
| * |
| * Returns 0 on succes -1 on error. |
| */ |
| int tracefs_synth_raw_fmt(struct trace_seq *seq, struct tracefs_synth *synth) |
| { |
| if (!synth->dyn_event) |
| return -1; |
| |
| trace_seq_printf(seq, "%s", synth->dyn_event->format); |
| trace_seq_printf(seq, "\n%s", synth->start_hist); |
| trace_seq_printf(seq, "\n%s", synth->end_hist); |
| |
| return 0; |
| } |
| |
| /* |
| * tracefs_synth_show_event - show the dynamic event used by a synth event |
| * @synth: The synthetic event to read format from |
| * |
| * This shows the raw format of the dynamic event used by the synthetic event. |
| * |
| * Returns format string owned by @synth on success, or NULL on error. |
| */ |
| const char *tracefs_synth_show_event(struct tracefs_synth *synth) |
| { |
| return synth->dyn_event ? synth->dyn_event->format : NULL; |
| } |
| |
| /* |
| * tracefs_synth_show_start_hist - show the start histogram used by a synth event |
| * @synth: The synthetic event to read format from |
| * |
| * This shows the raw format of the start histogram used by the synthetic event. |
| * |
| * Returns format string owned by @synth on success, or NULL on error. |
| */ |
| const char *tracefs_synth_show_start_hist(struct tracefs_synth *synth) |
| { |
| return synth->start_hist; |
| } |
| |
| /* |
| * tracefs_synth_show_end_hist - show the end histogram used by a synth event |
| * @synth: The synthetic event to read format from |
| * |
| * This shows the raw format of the end histogram used by the synthetic event. |
| * |
| * Returns format string owned by @synth on success, or NULL on error. |
| */ |
| const char *tracefs_synth_show_end_hist(struct tracefs_synth *synth) |
| { |
| return synth->end_hist; |
| } |
| |
| static char *append_filter(char *hist, char *filter, unsigned int parens) |
| { |
| int i; |
| |
| if (!filter) |
| return hist; |
| |
| hist = append_string(hist, NULL, " if "); |
| hist = append_string(hist, NULL, filter); |
| for (i = 0; i < parens; i++) |
| hist = append_string(hist, NULL, ")"); |
| return hist; |
| } |
| |
| static int verify_state(struct tracefs_synth *synth) |
| { |
| if (trace_test_state(synth->start_state) < 0 || |
| trace_test_state(synth->end_state) < 0) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * tracefs_synth_complete - tell if the tracefs_synth is complete or not |
| * @synth: The synthetic event to get the start hist from. |
| * |
| * Retruns true if the synthetic event @synth has both a start and |
| * end event (ie. a synthetic event, or just a histogram), and |
| * false otherwise. |
| */ |
| bool tracefs_synth_complete(struct tracefs_synth *synth) |
| { |
| return synth && synth->start_event && synth->end_event; |
| } |
| |
| /** |
| * tracefs_synth_get_start_hist - Return the histogram of the start event |
| * @synth: The synthetic event to get the start hist from. |
| * |
| * On success, returns a tracefs_hist descriptor that holds the |
| * histogram information of the start_event of the synthetic event |
| * structure. Returns NULL on failure. |
| */ |
| struct tracefs_hist * |
| tracefs_synth_get_start_hist(struct tracefs_synth *synth) |
| { |
| struct tracefs_hist *hist = NULL; |
| struct tep_handle *tep; |
| const char *system; |
| const char *event; |
| const char *key; |
| char **keys; |
| int *types; |
| int ret; |
| int i; |
| |
| if (!synth) { |
| errno = EINVAL; |
| return NULL; |
| } |
| |
| system = synth->start_event->system; |
| event = synth->start_event->name; |
| types = synth->start_type; |
| keys = synth->start_keys; |
| tep = synth->tep; |
| |
| if (!keys) |
| keys = synth->start_selection; |
| |
| if (!keys) |
| return NULL; |
| |
| for (i = 0; keys[i]; i++) { |
| int type = types ? types[i] : 0; |
| |
| if (type == HIST_COUNTER_TYPE) |
| continue; |
| |
| key = keys[i]; |
| |
| if (i) { |
| ret = tracefs_hist_add_key(hist, key, type); |
| if (ret < 0) { |
| tracefs_hist_free(hist); |
| return NULL; |
| } |
| } else { |
| hist = tracefs_hist_alloc(tep, system, event, |
| key, type); |
| if (!hist) |
| return NULL; |
| } |
| } |
| |
| if (!hist) |
| return NULL; |
| |
| for (i = 0; keys[i]; i++) { |
| int type = types ? types[i] : 0; |
| |
| if (type != HIST_COUNTER_TYPE) |
| continue; |
| |
| key = keys[i]; |
| |
| ret = tracefs_hist_add_value(hist, key); |
| if (ret < 0) { |
| tracefs_hist_free(hist); |
| return NULL; |
| } |
| } |
| |
| if (synth->start_filter) { |
| hist->filter = strdup(synth->start_filter); |
| if (!hist->filter) { |
| tracefs_hist_free(hist); |
| return NULL; |
| } |
| } |
| |
| return hist; |
| } |
| |
| /** |
| * tracefs_synth_create - creates the synthetic event on the system |
| * @synth: The tracefs_synth descriptor |
| * |
| * This creates the synthetic events. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be or a problem |
| * writing into the system. |
| */ |
| int tracefs_synth_create(struct tracefs_synth *synth) |
| { |
| int ret; |
| |
| if (!synth) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!synth->name || !synth->end_event) { |
| errno = EUNATCH; |
| return -1; |
| } |
| |
| if (verify_state(synth) < 0) |
| return -1; |
| |
| if (!synth->dyn_event && alloc_synthetic_event(synth)) |
| return -1; |
| if (tracefs_dynevent_create(synth->dyn_event)) |
| return -1; |
| |
| synth->start_hist = create_hist(synth->start_keys, synth->start_vars); |
| synth->start_hist = append_filter(synth->start_hist, synth->start_filter, |
| synth->start_parens); |
| if (!synth->start_hist) |
| goto remove_synthetic; |
| |
| synth->end_hist = create_end_hist(synth); |
| synth->end_hist = append_filter(synth->end_hist, synth->end_filter, |
| synth->end_parens); |
| if (!synth->end_hist) |
| goto remove_synthetic; |
| |
| ret = tracefs_event_file_append(synth->instance, synth->start_event->system, |
| synth->start_event->name, |
| "trigger", synth->start_hist); |
| if (ret < 0) |
| goto remove_synthetic; |
| |
| ret = tracefs_event_file_append(synth->instance, synth->end_event->system, |
| synth->end_event->name, |
| "trigger", synth->end_hist); |
| if (ret < 0) |
| goto remove_start_hist; |
| |
| return 0; |
| |
| remove_start_hist: |
| remove_hist(synth->instance, synth->start_event, synth->start_hist); |
| remove_synthetic: |
| tracefs_dynevent_destroy(synth->dyn_event, false); |
| return -1; |
| } |
| |
| /** |
| * tracefs_synth_destroy - delete the synthetic event from the system |
| * @synth: The tracefs_synth descriptor |
| * |
| * This will destroy a synthetic event created by tracefs_synth_create() |
| * with the same @synth. |
| * |
| * It will attempt to disable the synthetic event in its instance (top by default), |
| * but if other instances have it active, it is likely to fail, which will likely |
| * fail on all other parts of tearing down the synthetic event. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| * ENIVAL - a parameter is passed as NULL that should not be or a problem |
| * writing into the system. |
| */ |
| int tracefs_synth_destroy(struct tracefs_synth *synth) |
| { |
| char *hist; |
| int ret; |
| |
| if (!synth) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!synth->name || !synth->end_event) { |
| errno = EUNATCH; |
| return -1; |
| } |
| |
| /* Try to disable the event if possible */ |
| tracefs_event_disable(synth->instance, "synthetic", synth->name); |
| |
| hist = create_end_hist(synth); |
| hist = append_filter(hist, synth->end_filter, |
| synth->end_parens); |
| if (!hist) |
| return -1; |
| ret = remove_hist(synth->instance, synth->end_event, hist); |
| free(hist); |
| |
| hist = create_hist(synth->start_keys, synth->start_vars); |
| hist = append_filter(hist, synth->start_filter, |
| synth->start_parens); |
| if (!hist) |
| return -1; |
| |
| ret = remove_hist(synth->instance, synth->start_event, hist); |
| free(hist); |
| |
| ret = tracefs_dynevent_destroy(synth->dyn_event, true); |
| |
| return ret ? -1 : 0; |
| } |
| |
| /** |
| * tracefs_synth_echo_cmd - show the command lines to create the synthetic event |
| * @seq: The trace_seq to store the command lines in |
| * @synth: The tracefs_synth descriptor |
| * |
| * This will list the "echo" commands that are equivalent to what would |
| * be executed by the tracefs_synth_create() command. |
| * |
| * Returns 0 on succes and -1 on error. |
| * On error, errno is set to: |
| * ENOMEM - memory allocation failure. |
| */ |
| int tracefs_synth_echo_cmd(struct trace_seq *seq, |
| struct tracefs_synth *synth) |
| { |
| bool new_event = false; |
| char *hist = NULL; |
| char *path; |
| int ret = -1; |
| |
| if (!synth) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!synth->name || !synth->end_event) { |
| errno = EUNATCH; |
| return -1; |
| } |
| |
| if (!synth->dyn_event) { |
| if (alloc_synthetic_event(synth)) |
| return -1; |
| new_event = true; |
| } |
| |
| path = trace_find_tracing_dir(false); |
| if (!path) |
| goto out_free; |
| |
| trace_seq_printf(seq, "echo '%s%s%s %s' >> %s/%s\n", |
| synth->dyn_event->prefix, |
| strlen(synth->dyn_event->prefix) ? ":" : "", |
| synth->dyn_event->event, |
| synth->dyn_event->format, path, synth->dyn_event->trace_file); |
| |
| tracefs_put_tracing_file(path); |
| path = tracefs_instance_get_dir(synth->instance); |
| |
| hist = create_hist(synth->start_keys, synth->start_vars); |
| hist = append_filter(hist, synth->start_filter, |
| synth->start_parens); |
| if (!hist) |
| goto out_free; |
| |
| trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", |
| hist, path, synth->start_event->system, |
| synth->start_event->name); |
| free(hist); |
| hist = create_end_hist(synth); |
| hist = append_filter(hist, synth->end_filter, |
| synth->end_parens); |
| if (!hist) |
| goto out_free; |
| |
| trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", |
| hist, path, synth->end_event->system, |
| synth->end_event->name); |
| |
| ret = 0; |
| out_free: |
| free(hist); |
| tracefs_put_tracing_file(path); |
| if (new_event) { |
| tracefs_dynevent_free(synth->dyn_event); |
| synth->dyn_event = NULL; |
| } |
| return ret; |
| } |
| |
| /** |
| * tracefs_synth_get_event - return tep event representing the given synthetic event |
| * @tep: a handle to the trace event parser context that holds the events |
| * @synth: a synthetic event context, describing given synthetic event. |
| * |
| * Returns a pointer to a tep event describing the given synthetic event. The pointer |
| * is managed by the @tep handle and must not be freed. In case of an error, or in case |
| * the requested synthetic event is missing in the @tep handler - NULL is returned. |
| */ |
| struct tep_event * |
| tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth) |
| { |
| if (!tep || !synth || !synth->name) |
| return NULL; |
| |
| return get_tep_event(tep, SYNTHETIC_GROUP, synth->name); |
| } |