Message ID | 20210810204818.880714-2-rostedt@goodmis.org (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | libtracefs: APIs to read a trace event hist file | expand |
On 8/10/21 10:48 PM, Steven Rostedt wrote: > From: "Steven Rostedt (VMware)" <rostedt@goodmis.org> > > Add a function tracefs_hist_data_parse() that will take the content of a > trace event's hist data file, and parse it into a "tracefs_hist_data" > descriptor that can be used to read the raw data from the file. Steve, Is this the latest version? I am getting this when trying it (the patch 1/9): [root@f34 libtracefs]# make COMPILE FPIC tracefs-utils.o COMPILE FPIC tracefs-instance.o COMPILE FPIC tracefs-events.o COMPILE FPIC tracefs-tools.o COMPILE FPIC tracefs-marker.o COMPILE FPIC tracefs-kprobes.o COMPILE FPIC tracefs-hist.o COMPILE FPIC tracefs-filter.o COMPILE FPIC sqlhist-lex.o COMPILE FPIC sqlhist.tab.o COMPILE FPIC tracefs-sqlhist.o make[1]: *** No rule to make target 'hist.l', needed by 'hist-lex.c'. Stop. make: *** [Makefile:365: /root/libtracefs/lib/tracefs/libtracefs.so.1.3.dev] Error 2 -- Daniel > > Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org> > --- > include/tracefs.h | 7 + > src/Makefile | 7 + > src/tracefs-hist-data.c | 861 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 875 insertions(+) > create mode 100644 src/tracefs-hist-data.c > > diff --git a/include/tracefs.h b/include/tracefs.h > index 17020de0108a..6bd40d72cb25 100644 > --- a/include/tracefs.h > +++ b/include/tracefs.h > @@ -413,6 +413,13 @@ static inline int tracefs_hist_destroy(struct tracefs_instance *instance, > return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_DESTROY); > } > > +struct tracefs_hist_data; > + > +struct tracefs_hist_data *tracefs_hist_data_parse(const char *buffer, > + const char **next_buffer, > + char **err); > +void tracefs_hist_data_free(struct tracefs_hist_data *hdata); > + > struct tracefs_synth; > > /* > diff --git a/src/Makefile b/src/Makefile > index 9248efc5c7fd..1ab181416b82 100644 > --- a/src/Makefile > +++ b/src/Makefile > @@ -17,6 +17,10 @@ OBJS += sqlhist-lex.o > OBJS += sqlhist.tab.o > OBJS += tracefs-sqlhist.o > > +# Order matters for the the two below > +OBJS += hist-lex.o > +OBJS += tracefs-hist-data.o > + > OBJS := $(OBJS:%.o=$(bdir)/%.o) > DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d) > > @@ -45,6 +49,9 @@ sqlhist.tab.c: sqlhist.y sqlhist.tab.h > sqlhist-lex.c: sqlhist.l sqlhist.tab.c > flex -o $@ $< > > +hist-lex.c: hist.l > + flex -P hist_ -o $@ $< > + > $(bdir)/%.o: %.c > $(Q)$(call do_fpic_compile) > > diff --git a/src/tracefs-hist-data.c b/src/tracefs-hist-data.c > new file mode 100644 > index 000000000000..497ab9ce97b4 > --- /dev/null > +++ b/src/tracefs-hist-data.c > @@ -0,0 +1,861 @@ > +// 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 "tracefs.h" > +#include "tracefs-local.h" > + > +#define HIST_FILE "hist" > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <stdarg.h> > +#include <errno.h> > +#include <ctype.h> > +#include <unistd.h> > +#include <tracefs.h> > + > +#include "hist.h" > + > +#define offset_of(type, field) ((unsigned long )(&((type *)0)->field)) > +#define container_of(p, type, field) ((type *)((void *)(p) - offset_of(type, field))); > + > +extern int hist_lex_init_extra(void *data, void* ptr_yy_globals); > +extern int hist_lex_destroy(void *scanner); > + > +int hist_yyinput(void *extra, char *buf, int max) > +{ > + struct hist_data *data = extra; > + > + if (!data || !data->buffer) > + return -1; > + > + if (data->buffer_idx + max > data->buffer_size) > + max = data->buffer_size - data->buffer_idx; > + > + if (max) > + memcpy(buf, data->buffer + data->buffer_idx, max); > + > + data->buffer_idx += max; > + > + return max; > +} > + > +extern int hist_yylex(void *data, void *scanner); > + > +static char *name_token(enum yytokentype type) > +{ > + switch (type) { > + case YYEMPTY: > + return "YYEMPTY"; > + case YYEOF: > + return "YYEOF"; > + case YYerror: > + return "YYerror"; > + case YYUNDEF: > + return "YYUNDEF"; > + case NUMBER: > + return "NUMBER"; > + case HEX: > + return "HEX"; > + case NEWLINE: > + return "NEWLINE"; > + case STRING: > + return "STRING"; > + case KEY_TYPE: > + return "KEY_TYPE"; > + case KEY_VAL: > + return "KEY_VAL"; > + case START_RANGE: > + return "START_RANGE"; > + case RANGE_LINEAR: > + return "RANGE_LINEAR"; > + case RANGE_EXPONENT: > + return "RANGE_EXPONENT"; > + case RAW_VAL: > + return "RAW_VAL"; > + case STACKTRACE: > + return "STACKTRACE"; > + case STACK_ITEM: > + return "STACK_ITEM"; > + case STACK_MOD: > + return "STACK_MOD"; > + case VALUE: > + return "VALUE"; > + case TOTALS: > + return "TOTALS"; > + case HITS: > + return "HITS"; > + case ENTRIES: > + return "ENTRIES"; > + case DROPPED: > + return "DROPPED"; > + case COMMENT: > + return "COMMENT"; > + case COLON: > + return "COLON"; > + case COMMA: > + return "COMMA"; > + } > + return NULL; > +} > + > +enum tracefs_bucket_key_type { > + TRACEFS_BUCKET_KEY_UNDEF, > + TRACEFS_BUCKET_KEY_SINGLE, > + TRACEFS_BUCKET_KEY_RANGE, > +}; > + > +struct tracefs_hist_bucket_key_single { > + long long val; > + char *sym; > +}; > + > +struct tracefs_hist_bucket_key_range { > + long long start; > + long long end; > +}; > + > +struct tracefs_hist_bucket_key { > + struct tracefs_hist_bucket_key *next; > + enum tracefs_bucket_key_type type; > + union { > + struct tracefs_hist_bucket_key_single single; > + struct tracefs_hist_bucket_key_range range; > + }; > +}; > + > +struct tracefs_hist_bucket_val { > + struct tracefs_hist_bucket_val *next; > + long long val; > +}; > + > +struct tracefs_hist_bucket { > + struct tracefs_hist_bucket *next; > + struct tracefs_hist_bucket_key *keys; > + struct tracefs_hist_bucket_key **next_key; > + struct tracefs_hist_bucket_val *vals; > + struct tracefs_hist_bucket_val **next_val; > +}; > + > +struct tracefs_hist_data { > + char **key_names; > + char **value_names; > + struct tracefs_hist_bucket *buckets; > + struct tracefs_hist_bucket **next_bucket; > + unsigned long long hits; > + unsigned long long entries; > + unsigned long long dropped; > +}; > + > +static int do_comment(struct tracefs_hist_data *hdata, const char *comment) > +{ > + return 0; > +} > + > +static int do_key_type(struct tracefs_hist_data *hdata, const char *key) > +{ > + char **tmp; > + > + tmp = tracefs_list_add(hdata->key_names, key); > + if (!tmp) > + return -1; > + hdata->key_names = tmp; > + > + return 0; > +} > + > +static int do_value_type(struct tracefs_hist_data *hdata, const char *key) > +{ > + char **tmp; > + > + tmp = tracefs_list_add(hdata->value_names, key); > + if (!tmp) > + return -1; > + hdata->value_names = tmp; > + > + return 0; > +} > + > +static int start_new_row(struct tracefs_hist_data *hdata) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + > + bucket = calloc(1, sizeof(*bucket)); > + if (!bucket) > + return -1; > + > + key = calloc(1, sizeof(*key)); > + if (!key) { > + free(bucket); > + return -1; > + } > + > + bucket->keys = key; > + bucket->next_key = &key->next; > + > + bucket->next_val = &bucket->vals; > + > + *hdata->next_bucket = bucket; > + hdata->next_bucket = &bucket->next; > + return 0; > +} > + > +static int start_new_key(struct tracefs_hist_data *hdata) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + > + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); > + > + key = calloc(1, sizeof(*key)); > + if (!key) { > + free(bucket); > + return -1; > + } > + > + *bucket->next_key = key; > + bucket->next_key = &key->next; > + > + return 0; > +} > + > +static char *chomp(char *text) > +{ > + char *p; > + int len; > + > + while (isspace(*text)) > + text++; > + > + len = strlen(text); > + p = text + len - 1; > + while (p >= text && isspace(*p)) > + p--; > + > + p[1] = '\0'; > + > + return text; > +} > + > +static int __do_key_val(struct tracefs_hist_data *hdata, > + char *text, const char *delim, const char *end) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + struct tracefs_hist_bucket_key_single *k; > + char *val; > + int len; > + > + text = chomp(text); > + > + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); > + > + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); > + if (!key->type) > + key->type = TRACEFS_BUCKET_KEY_SINGLE; > + > + if (key->type != TRACEFS_BUCKET_KEY_SINGLE) > + return -1; > + > + k = &key->single; > + > + len = strlen(text); > + len += k->sym ? strlen(k->sym) + strlen(delim) : 0; > + if (end) > + len += strlen(end); > + > + val = realloc(k->sym, len + 1); > + if (!val) > + return -1; > + > + if (k->sym) > + strcat(val, delim); > + else > + val[0] = '\0'; > + > + strcat(val, text); > + if (end) > + strcat(val, end); > + > + k->sym = val; > + > + return 0; > +} > + > +static int do_key_val(struct tracefs_hist_data *hdata, char *text) > +{ > + return __do_key_val(hdata, text, " ", NULL); > +} > + > +static int do_key_stack(struct tracefs_hist_data *hdata, char *text) > +{ > + return __do_key_val(hdata, text, "\n", NULL); > +} > + > +static int do_key_stack_mod(struct tracefs_hist_data *hdata, char *text) > +{ > + return __do_key_val(hdata, text, " [", "]"); > +} > + > +static int do_key_raw(struct tracefs_hist_data *hdata, char *text) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + struct tracefs_hist_bucket_key_single *k; > + > + text = chomp(text); > + > + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); > + > + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); > + if (key->type != TRACEFS_BUCKET_KEY_SINGLE) > + return -1; > + > + k = &key->single; > + > + if (k->val) > + return -1; > + > + k->val = strtoll(text, NULL, 0); > + > + return 0; > +} > + > +static int do_key_range(struct tracefs_hist_data *hdata, long long start, > + long long end) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + struct tracefs_hist_bucket_key_range *k; > + > + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); > + > + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); > + > + if (!key->type) > + key->type = TRACEFS_BUCKET_KEY_RANGE; > + > + if (key->type != TRACEFS_BUCKET_KEY_RANGE) > + return -1; > + > + k = &key->range; > + > + k->start = start; > + k->end = end; > + > + return 0; > +} > + > +static int do_value_num(struct tracefs_hist_data *hdata, long long num) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_val *val; > + > + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); > + val = calloc(1, sizeof(*val)); > + if (!val) > + return -1; > + > + val->val = num; > + > + *bucket->next_val = val; > + bucket->next_val = &val->next; > + > + return 0; > +} > + > +static long long expo(unsigned int e, long long exp) > +{ > + long long ret; > + > + if (exp < 0) > + exp = 0; > + > + if (e == 2) > + return 1LL << exp; > + > + ret = 1; > + for (; exp > 0; exp--) > + ret *= e; > + return e; > +} > + > +enum hist_state { > + HIST_START, > + HIST_KEYS_START, > + HIST_KEYS, > + HIST_KEY_VALS, > + HIST_RANGE, > + HIST_VALUES, > + HIST_NEXT_KEY, > + HIST_STACK, > + HIST_ENTRIES, > + HIST_DROPPED, > + HIST_END, > +}; > + > +static const char *find_buffer_line(const char *buffer, int line_no) > +{ > + int line = 0; > + int i; > + > + for (i = 0; buffer[i]; i++) { > + if (buffer[i] == '\n') { > + line++; > + if (line >= line_no) { > + i++; > + break; > + } > + } > + } > + return buffer + i; > +} > + > +static void print_line(struct trace_seq *seq, struct hist_data *data) > +{ > + const char *buffer = data->buffer; > + int i; > + > + buffer = find_buffer_line(buffer, data->line_no); > + > + for (i = 0; buffer[i]; i++) { > + if (buffer[i] == '\n') > + break; > + } > + > + trace_seq_printf(seq, "%.*s (line:%d idx:%d)\n", i, buffer, > + data->line_no, data->line_idx); > + trace_seq_printf(seq, "%*s\n", data->line_idx, "^"); > +} > + > +static void print_error(struct hist_data *data, char **err, > + enum hist_state state, enum yytokentype type) > +{ > + struct trace_seq seq; > + char *tname; > + > + if (!err) > + return; > + > + trace_seq_init(&seq); > + > + print_line(&seq, data); > + > + trace_seq_printf(&seq, "Error in "); > + switch (state) { > + case HIST_START: > + trace_seq_printf(&seq, "HIST_START"); > + break; > + case HIST_KEYS_START: > + trace_seq_printf(&seq, "HIST_KEYS_START"); > + break; > + case HIST_KEYS: > + trace_seq_printf(&seq, "HIST_KEYS"); > + break; > + case HIST_KEY_VALS: > + trace_seq_printf(&seq, "HIST_KEY_VALS"); > + break; > + case HIST_RANGE: > + trace_seq_printf(&seq, "HIST_RANGE"); > + break; > + case HIST_VALUES: > + trace_seq_printf(&seq, "HIST_VALUES"); > + break; > + case HIST_NEXT_KEY: > + trace_seq_printf(&seq, "HIST_NEXT_KEY"); > + case HIST_STACK: > + trace_seq_printf(&seq, "HIST_STACK"); > + break; > + case HIST_ENTRIES: > + trace_seq_printf(&seq, "HIST_ENTRIES"); > + break; > + case HIST_DROPPED: > + trace_seq_printf(&seq, "HIST_DROPPED"); > + break; > + case HIST_END: > + trace_seq_printf(&seq, "HIST_END"); > + break; > + } > + trace_seq_printf(&seq, " with token "); > + tname = name_token(type); > + if (tname) > + trace_seq_printf(&seq, "%s", tname); > + else > + trace_seq_printf(&seq, "(unknown %d)", type); > + > + trace_seq_printf(&seq, " last token %s\n", data->text); > + trace_seq_terminate(&seq); > + if (seq.buffer) > + *err = seq.buffer; > + seq.buffer = NULL; > + trace_seq_destroy(&seq); > +} > + > +static void update_next(const char **next_buffer, struct hist_data *data) > +{ > + if (!next_buffer) > + return; > + > + *next_buffer = find_buffer_line(data->buffer, data->line_no - 1); > +} > + > +/** > + * tracefs_hist_data_free - free a created hist data descriptor > + * @hdata: The tracefs_hist_data descriptor to free. > + * > + * Frees the data allocated by tracefs_hist_data_parse(). > + */ > +void tracefs_hist_data_free(struct tracefs_hist_data *hdata) > +{ > + struct tracefs_hist_bucket *bucket; > + struct tracefs_hist_bucket_key *key; > + struct tracefs_hist_bucket_val *val; > + > + if (!hdata) > + return; > + > + tracefs_list_free(hdata->key_names); > + tracefs_list_free(hdata->value_names); > + > + while ((bucket = hdata->buckets)) { > + hdata->buckets = bucket->next; > + while ((key = bucket->keys)) { > + bucket->keys = key->next; > + switch (key->type) { > + case TRACEFS_BUCKET_KEY_SINGLE: > + free(key->single.sym); > + break; > + default: > + break; > + } > + free(key); > + } > + while ((val = bucket->vals)) { > + bucket->vals = val->next; > + free(val); > + } > + free(bucket); > + } > + > + free(hdata); > +} > + > +/* Used for debugging in gdb */ > +static void breakpoint(char *text) > +{ > +} > + > +/** > + * tracefs_hist_data_parse - parse a hist file of a trace event > + * @buffer: The buffer containing the hist file content > + * @next_buffer: If not NULL will point to the next hist in the buffer > + * @err: If not NULL, will load the error message on error > + * > + * Reads and parses the content of a "hist" file of a trace event. > + * It will return a descriptor that can be used to read the content and > + * create a histogram table. > + * > + * Because "hist" files may contain more than one histogram, and this > + * function will only parse one of the histograms, if there are more > + * than one histogram in the buffer, and @next_buffer is not NULL, then > + * it will return the location of the next histogram in @next_buffer. > + * > + * If there's an error in the parsing, then @err will contain an error > + * message about what went wrong. > + * > + * Returns a desrciptor of a histogram representing the hist file content. > + * NULL on error. > + * The descriptor must be freed with tracefs_hist_data_free(). > + */ > +struct tracefs_hist_data * > +tracefs_hist_data_parse(const char *buffer, const char **next_buffer, char **err) > +{ > + struct tracefs_hist_data *hdata; > + struct hist_data data; > + enum hist_state state = 0; > + long long start_range, end_range; > + bool first = false; > + unsigned int e; > + int buffer_size; > + bool done = false; > + char *text; > + enum yytokentype type; > + int ret; > + > + if (!buffer) > + return NULL; > + > + hdata = calloc(1, sizeof(*hdata)); > + if (!hdata) > + return NULL; > + > + hdata->next_bucket = &hdata->buckets; > + > + memset(&data, 0, sizeof(data)); > + > + buffer_size = strlen(buffer); > + data.buffer = buffer; > + data.buffer_size = buffer_size; > + data.text = malloc(buffer_size); > + if (!data.text) { > + free(hdata); > + perror("text"); > + exit(-1); > + } > + > + ret = hist_lex_init_extra(&data, &data.scanner); > + if (ret < 0) { > + perror("ylex_init"); > + return NULL; > + } > + while (!done) { > + type = hist_yylex(&data, data.scanner); > + if (type < 0) > + break; > + text = data.text; > + breakpoint(text); > + switch (state) { > + case HIST_START: > + switch (type) { > + case COMMENT: > + first = true; > + ret = do_comment(hdata, text); > + if (ret < 0) > + goto error; > + break; > + case KEY_TYPE: > + goto key_type; > + case STACKTRACE: > + goto stacktrace; > + default: > + goto error; > + } > + break; > + case HIST_KEYS_START: > + switch (type) { > + case KEY_TYPE: > + key_type: > + if (first) { > + ret = do_key_type(hdata, text); > + if (ret < 0) > + goto error; > + } > + ret = start_new_row(hdata); > + state = HIST_KEY_VALS; > + break; > + case STACKTRACE: > + stacktrace: > + if (first) { > + ret = do_key_type(hdata, "stacktrace"); > + if (ret < 0) > + goto error; > + } > + ret = start_new_row(hdata); > + state = HIST_STACK; > + break; > + case HITS: > + hdata->hits = strtoll(text, NULL, 0); > + state = HIST_ENTRIES; > + break; > + default: > + goto error; > + } > + break; > + case HIST_KEYS: > + switch (type) { > + case KEY_TYPE: > + if (first) { > + ret = do_key_type(hdata, text); > + if (ret < 0) > + goto error; > + } > + ret = start_new_key(hdata); > + state = HIST_KEY_VALS; > + break; > + case STACKTRACE: > + if (first) { > + ret = do_key_type(hdata, "stacktrace"); > + if (ret < 0) > + goto error; > + } > + ret = start_new_key(hdata); > + state = HIST_STACK; > + break; > + case NEWLINE: > + break; > + case COLON: > + state = HIST_VALUES; > + break; > + default: > + goto error; > + } > + break; > + case HIST_NEXT_KEY: > + switch (type) { > + case COLON: > + state = HIST_VALUES; > + break; > + case COMMA: > + state = HIST_KEYS; > + break; > + default: > + goto error; > + } > + break; > + case HIST_KEY_VALS: > + switch (type) { > + case NEWLINE: > + continue; > + case START_RANGE: > + start_range = strtoll(text, NULL, 0); > + state = HIST_RANGE; > + break; > + case KEY_VAL: > + ret = do_key_val(hdata, text); > + if (ret < 0) > + goto error; > + break; > + case RAW_VAL: > + ret = do_key_raw(hdata, text); > + if (ret < 0) > + goto error; > + state = HIST_NEXT_KEY; > + break; > + case COLON: > + state = HIST_VALUES; > + break; > + case COMMA: > + state = HIST_KEYS; > + break; > + default: > + goto error; > + } > + break; > + case HIST_STACK: > + switch (type) { > + case NEWLINE: > + break; > + case STACK_ITEM: > + ret = do_key_stack(hdata, text); > + if (ret < 0) > + goto error; > + break; > + case STACK_MOD: > + ret = do_key_stack_mod(hdata, text); > + if (ret < 0) > + goto error; > + break; > + case COLON: > + state = HIST_VALUES; > + break; > + case COMMA: > + state = HIST_KEYS; > + break; > + default: > + goto error; > + } > + break; > + case HIST_RANGE: > + switch (type) { > + case RANGE_LINEAR: > + do_key_range(hdata, start_range, > + strtoll(text, NULL, 0)); > + break; > + case RANGE_EXPONENT: > + end_range = strtoll(text, NULL, 0); > + e = (unsigned int)start_range; > + start_range = expo(e, end_range - 1); > + end_range = expo(e, end_range); > + do_key_range(hdata, start_range, end_range); > + break; > + default: > + goto error; > + } > + state = HIST_KEYS; > + break; > + case HIST_VALUES: > + switch (type) { > + case VALUE: > + if (first) { > + ret = do_value_type(hdata, text); > + if (ret < 0) > + goto error; > + } > + break; > + case NUMBER: > + ret = do_value_num(hdata, strtoll(text, NULL, 0)); > + if (ret < 0) > + goto error; > + break; > + case NEWLINE: > + state = HIST_KEYS_START; > + first = false; > + break; > + default: > + goto error; > + } > + break; > + case HIST_ENTRIES: > + switch (type) { > + case ENTRIES: > + hdata->entries = strtoll(text, NULL, 0); > + state = HIST_DROPPED; > + break; > + default: > + goto error; > + } > + break; > + case HIST_DROPPED: > + switch (type) { > + case DROPPED: > + hdata->dropped = strtoll(text, NULL, 0); > + state = HIST_END; > + break; > + default: > + goto error; > + } > + break; > + case HIST_END: > + done = true; > + switch (type) { > + case COMMENT: > + update_next(next_buffer, &data); > + break; > + case YYEOF: > + /* Fall through */ > + default: > + /* Do at end, as next_buffer may point to buffer*/ > + if (next_buffer) > + *next_buffer = NULL; > + break; > + } > + break; > + } > + } > + > + hist_lex_destroy(data.scanner); > + free(data.text); > + > + return hdata; > + error: > + print_error(&data, err, state, type); > + hist_lex_destroy(data.scanner); > + free(data.text); > + tracefs_hist_data_free(hdata); > + return NULL; > +} >
diff --git a/include/tracefs.h b/include/tracefs.h index 17020de0108a..6bd40d72cb25 100644 --- a/include/tracefs.h +++ b/include/tracefs.h @@ -413,6 +413,13 @@ static inline int tracefs_hist_destroy(struct tracefs_instance *instance, return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_DESTROY); } +struct tracefs_hist_data; + +struct tracefs_hist_data *tracefs_hist_data_parse(const char *buffer, + const char **next_buffer, + char **err); +void tracefs_hist_data_free(struct tracefs_hist_data *hdata); + struct tracefs_synth; /* diff --git a/src/Makefile b/src/Makefile index 9248efc5c7fd..1ab181416b82 100644 --- a/src/Makefile +++ b/src/Makefile @@ -17,6 +17,10 @@ OBJS += sqlhist-lex.o OBJS += sqlhist.tab.o OBJS += tracefs-sqlhist.o +# Order matters for the the two below +OBJS += hist-lex.o +OBJS += tracefs-hist-data.o + OBJS := $(OBJS:%.o=$(bdir)/%.o) DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d) @@ -45,6 +49,9 @@ sqlhist.tab.c: sqlhist.y sqlhist.tab.h sqlhist-lex.c: sqlhist.l sqlhist.tab.c flex -o $@ $< +hist-lex.c: hist.l + flex -P hist_ -o $@ $< + $(bdir)/%.o: %.c $(Q)$(call do_fpic_compile) diff --git a/src/tracefs-hist-data.c b/src/tracefs-hist-data.c new file mode 100644 index 000000000000..497ab9ce97b4 --- /dev/null +++ b/src/tracefs-hist-data.c @@ -0,0 +1,861 @@ +// 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 "tracefs.h" +#include "tracefs-local.h" + +#define HIST_FILE "hist" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#include <tracefs.h> + +#include "hist.h" + +#define offset_of(type, field) ((unsigned long )(&((type *)0)->field)) +#define container_of(p, type, field) ((type *)((void *)(p) - offset_of(type, field))); + +extern int hist_lex_init_extra(void *data, void* ptr_yy_globals); +extern int hist_lex_destroy(void *scanner); + +int hist_yyinput(void *extra, char *buf, int max) +{ + struct hist_data *data = extra; + + if (!data || !data->buffer) + return -1; + + if (data->buffer_idx + max > data->buffer_size) + max = data->buffer_size - data->buffer_idx; + + if (max) + memcpy(buf, data->buffer + data->buffer_idx, max); + + data->buffer_idx += max; + + return max; +} + +extern int hist_yylex(void *data, void *scanner); + +static char *name_token(enum yytokentype type) +{ + switch (type) { + case YYEMPTY: + return "YYEMPTY"; + case YYEOF: + return "YYEOF"; + case YYerror: + return "YYerror"; + case YYUNDEF: + return "YYUNDEF"; + case NUMBER: + return "NUMBER"; + case HEX: + return "HEX"; + case NEWLINE: + return "NEWLINE"; + case STRING: + return "STRING"; + case KEY_TYPE: + return "KEY_TYPE"; + case KEY_VAL: + return "KEY_VAL"; + case START_RANGE: + return "START_RANGE"; + case RANGE_LINEAR: + return "RANGE_LINEAR"; + case RANGE_EXPONENT: + return "RANGE_EXPONENT"; + case RAW_VAL: + return "RAW_VAL"; + case STACKTRACE: + return "STACKTRACE"; + case STACK_ITEM: + return "STACK_ITEM"; + case STACK_MOD: + return "STACK_MOD"; + case VALUE: + return "VALUE"; + case TOTALS: + return "TOTALS"; + case HITS: + return "HITS"; + case ENTRIES: + return "ENTRIES"; + case DROPPED: + return "DROPPED"; + case COMMENT: + return "COMMENT"; + case COLON: + return "COLON"; + case COMMA: + return "COMMA"; + } + return NULL; +} + +enum tracefs_bucket_key_type { + TRACEFS_BUCKET_KEY_UNDEF, + TRACEFS_BUCKET_KEY_SINGLE, + TRACEFS_BUCKET_KEY_RANGE, +}; + +struct tracefs_hist_bucket_key_single { + long long val; + char *sym; +}; + +struct tracefs_hist_bucket_key_range { + long long start; + long long end; +}; + +struct tracefs_hist_bucket_key { + struct tracefs_hist_bucket_key *next; + enum tracefs_bucket_key_type type; + union { + struct tracefs_hist_bucket_key_single single; + struct tracefs_hist_bucket_key_range range; + }; +}; + +struct tracefs_hist_bucket_val { + struct tracefs_hist_bucket_val *next; + long long val; +}; + +struct tracefs_hist_bucket { + struct tracefs_hist_bucket *next; + struct tracefs_hist_bucket_key *keys; + struct tracefs_hist_bucket_key **next_key; + struct tracefs_hist_bucket_val *vals; + struct tracefs_hist_bucket_val **next_val; +}; + +struct tracefs_hist_data { + char **key_names; + char **value_names; + struct tracefs_hist_bucket *buckets; + struct tracefs_hist_bucket **next_bucket; + unsigned long long hits; + unsigned long long entries; + unsigned long long dropped; +}; + +static int do_comment(struct tracefs_hist_data *hdata, const char *comment) +{ + return 0; +} + +static int do_key_type(struct tracefs_hist_data *hdata, const char *key) +{ + char **tmp; + + tmp = tracefs_list_add(hdata->key_names, key); + if (!tmp) + return -1; + hdata->key_names = tmp; + + return 0; +} + +static int do_value_type(struct tracefs_hist_data *hdata, const char *key) +{ + char **tmp; + + tmp = tracefs_list_add(hdata->value_names, key); + if (!tmp) + return -1; + hdata->value_names = tmp; + + return 0; +} + +static int start_new_row(struct tracefs_hist_data *hdata) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + + bucket = calloc(1, sizeof(*bucket)); + if (!bucket) + return -1; + + key = calloc(1, sizeof(*key)); + if (!key) { + free(bucket); + return -1; + } + + bucket->keys = key; + bucket->next_key = &key->next; + + bucket->next_val = &bucket->vals; + + *hdata->next_bucket = bucket; + hdata->next_bucket = &bucket->next; + return 0; +} + +static int start_new_key(struct tracefs_hist_data *hdata) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); + + key = calloc(1, sizeof(*key)); + if (!key) { + free(bucket); + return -1; + } + + *bucket->next_key = key; + bucket->next_key = &key->next; + + return 0; +} + +static char *chomp(char *text) +{ + char *p; + int len; + + while (isspace(*text)) + text++; + + len = strlen(text); + p = text + len - 1; + while (p >= text && isspace(*p)) + p--; + + p[1] = '\0'; + + return text; +} + +static int __do_key_val(struct tracefs_hist_data *hdata, + char *text, const char *delim, const char *end) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + struct tracefs_hist_bucket_key_single *k; + char *val; + int len; + + text = chomp(text); + + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); + + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); + if (!key->type) + key->type = TRACEFS_BUCKET_KEY_SINGLE; + + if (key->type != TRACEFS_BUCKET_KEY_SINGLE) + return -1; + + k = &key->single; + + len = strlen(text); + len += k->sym ? strlen(k->sym) + strlen(delim) : 0; + if (end) + len += strlen(end); + + val = realloc(k->sym, len + 1); + if (!val) + return -1; + + if (k->sym) + strcat(val, delim); + else + val[0] = '\0'; + + strcat(val, text); + if (end) + strcat(val, end); + + k->sym = val; + + return 0; +} + +static int do_key_val(struct tracefs_hist_data *hdata, char *text) +{ + return __do_key_val(hdata, text, " ", NULL); +} + +static int do_key_stack(struct tracefs_hist_data *hdata, char *text) +{ + return __do_key_val(hdata, text, "\n", NULL); +} + +static int do_key_stack_mod(struct tracefs_hist_data *hdata, char *text) +{ + return __do_key_val(hdata, text, " [", "]"); +} + +static int do_key_raw(struct tracefs_hist_data *hdata, char *text) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + struct tracefs_hist_bucket_key_single *k; + + text = chomp(text); + + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); + + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); + if (key->type != TRACEFS_BUCKET_KEY_SINGLE) + return -1; + + k = &key->single; + + if (k->val) + return -1; + + k->val = strtoll(text, NULL, 0); + + return 0; +} + +static int do_key_range(struct tracefs_hist_data *hdata, long long start, + long long end) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + struct tracefs_hist_bucket_key_range *k; + + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); + + key = container_of(bucket->next_key, struct tracefs_hist_bucket_key, next); + + if (!key->type) + key->type = TRACEFS_BUCKET_KEY_RANGE; + + if (key->type != TRACEFS_BUCKET_KEY_RANGE) + return -1; + + k = &key->range; + + k->start = start; + k->end = end; + + return 0; +} + +static int do_value_num(struct tracefs_hist_data *hdata, long long num) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_val *val; + + bucket = container_of(hdata->next_bucket, struct tracefs_hist_bucket, next); + val = calloc(1, sizeof(*val)); + if (!val) + return -1; + + val->val = num; + + *bucket->next_val = val; + bucket->next_val = &val->next; + + return 0; +} + +static long long expo(unsigned int e, long long exp) +{ + long long ret; + + if (exp < 0) + exp = 0; + + if (e == 2) + return 1LL << exp; + + ret = 1; + for (; exp > 0; exp--) + ret *= e; + return e; +} + +enum hist_state { + HIST_START, + HIST_KEYS_START, + HIST_KEYS, + HIST_KEY_VALS, + HIST_RANGE, + HIST_VALUES, + HIST_NEXT_KEY, + HIST_STACK, + HIST_ENTRIES, + HIST_DROPPED, + HIST_END, +}; + +static const char *find_buffer_line(const char *buffer, int line_no) +{ + int line = 0; + int i; + + for (i = 0; buffer[i]; i++) { + if (buffer[i] == '\n') { + line++; + if (line >= line_no) { + i++; + break; + } + } + } + return buffer + i; +} + +static void print_line(struct trace_seq *seq, struct hist_data *data) +{ + const char *buffer = data->buffer; + int i; + + buffer = find_buffer_line(buffer, data->line_no); + + for (i = 0; buffer[i]; i++) { + if (buffer[i] == '\n') + break; + } + + trace_seq_printf(seq, "%.*s (line:%d idx:%d)\n", i, buffer, + data->line_no, data->line_idx); + trace_seq_printf(seq, "%*s\n", data->line_idx, "^"); +} + +static void print_error(struct hist_data *data, char **err, + enum hist_state state, enum yytokentype type) +{ + struct trace_seq seq; + char *tname; + + if (!err) + return; + + trace_seq_init(&seq); + + print_line(&seq, data); + + trace_seq_printf(&seq, "Error in "); + switch (state) { + case HIST_START: + trace_seq_printf(&seq, "HIST_START"); + break; + case HIST_KEYS_START: + trace_seq_printf(&seq, "HIST_KEYS_START"); + break; + case HIST_KEYS: + trace_seq_printf(&seq, "HIST_KEYS"); + break; + case HIST_KEY_VALS: + trace_seq_printf(&seq, "HIST_KEY_VALS"); + break; + case HIST_RANGE: + trace_seq_printf(&seq, "HIST_RANGE"); + break; + case HIST_VALUES: + trace_seq_printf(&seq, "HIST_VALUES"); + break; + case HIST_NEXT_KEY: + trace_seq_printf(&seq, "HIST_NEXT_KEY"); + case HIST_STACK: + trace_seq_printf(&seq, "HIST_STACK"); + break; + case HIST_ENTRIES: + trace_seq_printf(&seq, "HIST_ENTRIES"); + break; + case HIST_DROPPED: + trace_seq_printf(&seq, "HIST_DROPPED"); + break; + case HIST_END: + trace_seq_printf(&seq, "HIST_END"); + break; + } + trace_seq_printf(&seq, " with token "); + tname = name_token(type); + if (tname) + trace_seq_printf(&seq, "%s", tname); + else + trace_seq_printf(&seq, "(unknown %d)", type); + + trace_seq_printf(&seq, " last token %s\n", data->text); + trace_seq_terminate(&seq); + if (seq.buffer) + *err = seq.buffer; + seq.buffer = NULL; + trace_seq_destroy(&seq); +} + +static void update_next(const char **next_buffer, struct hist_data *data) +{ + if (!next_buffer) + return; + + *next_buffer = find_buffer_line(data->buffer, data->line_no - 1); +} + +/** + * tracefs_hist_data_free - free a created hist data descriptor + * @hdata: The tracefs_hist_data descriptor to free. + * + * Frees the data allocated by tracefs_hist_data_parse(). + */ +void tracefs_hist_data_free(struct tracefs_hist_data *hdata) +{ + struct tracefs_hist_bucket *bucket; + struct tracefs_hist_bucket_key *key; + struct tracefs_hist_bucket_val *val; + + if (!hdata) + return; + + tracefs_list_free(hdata->key_names); + tracefs_list_free(hdata->value_names); + + while ((bucket = hdata->buckets)) { + hdata->buckets = bucket->next; + while ((key = bucket->keys)) { + bucket->keys = key->next; + switch (key->type) { + case TRACEFS_BUCKET_KEY_SINGLE: + free(key->single.sym); + break; + default: + break; + } + free(key); + } + while ((val = bucket->vals)) { + bucket->vals = val->next; + free(val); + } + free(bucket); + } + + free(hdata); +} + +/* Used for debugging in gdb */ +static void breakpoint(char *text) +{ +} + +/** + * tracefs_hist_data_parse - parse a hist file of a trace event + * @buffer: The buffer containing the hist file content + * @next_buffer: If not NULL will point to the next hist in the buffer + * @err: If not NULL, will load the error message on error + * + * Reads and parses the content of a "hist" file of a trace event. + * It will return a descriptor that can be used to read the content and + * create a histogram table. + * + * Because "hist" files may contain more than one histogram, and this + * function will only parse one of the histograms, if there are more + * than one histogram in the buffer, and @next_buffer is not NULL, then + * it will return the location of the next histogram in @next_buffer. + * + * If there's an error in the parsing, then @err will contain an error + * message about what went wrong. + * + * Returns a desrciptor of a histogram representing the hist file content. + * NULL on error. + * The descriptor must be freed with tracefs_hist_data_free(). + */ +struct tracefs_hist_data * +tracefs_hist_data_parse(const char *buffer, const char **next_buffer, char **err) +{ + struct tracefs_hist_data *hdata; + struct hist_data data; + enum hist_state state = 0; + long long start_range, end_range; + bool first = false; + unsigned int e; + int buffer_size; + bool done = false; + char *text; + enum yytokentype type; + int ret; + + if (!buffer) + return NULL; + + hdata = calloc(1, sizeof(*hdata)); + if (!hdata) + return NULL; + + hdata->next_bucket = &hdata->buckets; + + memset(&data, 0, sizeof(data)); + + buffer_size = strlen(buffer); + data.buffer = buffer; + data.buffer_size = buffer_size; + data.text = malloc(buffer_size); + if (!data.text) { + free(hdata); + perror("text"); + exit(-1); + } + + ret = hist_lex_init_extra(&data, &data.scanner); + if (ret < 0) { + perror("ylex_init"); + return NULL; + } + while (!done) { + type = hist_yylex(&data, data.scanner); + if (type < 0) + break; + text = data.text; + breakpoint(text); + switch (state) { + case HIST_START: + switch (type) { + case COMMENT: + first = true; + ret = do_comment(hdata, text); + if (ret < 0) + goto error; + break; + case KEY_TYPE: + goto key_type; + case STACKTRACE: + goto stacktrace; + default: + goto error; + } + break; + case HIST_KEYS_START: + switch (type) { + case KEY_TYPE: + key_type: + if (first) { + ret = do_key_type(hdata, text); + if (ret < 0) + goto error; + } + ret = start_new_row(hdata); + state = HIST_KEY_VALS; + break; + case STACKTRACE: + stacktrace: + if (first) { + ret = do_key_type(hdata, "stacktrace"); + if (ret < 0) + goto error; + } + ret = start_new_row(hdata); + state = HIST_STACK; + break; + case HITS: + hdata->hits = strtoll(text, NULL, 0); + state = HIST_ENTRIES; + break; + default: + goto error; + } + break; + case HIST_KEYS: + switch (type) { + case KEY_TYPE: + if (first) { + ret = do_key_type(hdata, text); + if (ret < 0) + goto error; + } + ret = start_new_key(hdata); + state = HIST_KEY_VALS; + break; + case STACKTRACE: + if (first) { + ret = do_key_type(hdata, "stacktrace"); + if (ret < 0) + goto error; + } + ret = start_new_key(hdata); + state = HIST_STACK; + break; + case NEWLINE: + break; + case COLON: + state = HIST_VALUES; + break; + default: + goto error; + } + break; + case HIST_NEXT_KEY: + switch (type) { + case COLON: + state = HIST_VALUES; + break; + case COMMA: + state = HIST_KEYS; + break; + default: + goto error; + } + break; + case HIST_KEY_VALS: + switch (type) { + case NEWLINE: + continue; + case START_RANGE: + start_range = strtoll(text, NULL, 0); + state = HIST_RANGE; + break; + case KEY_VAL: + ret = do_key_val(hdata, text); + if (ret < 0) + goto error; + break; + case RAW_VAL: + ret = do_key_raw(hdata, text); + if (ret < 0) + goto error; + state = HIST_NEXT_KEY; + break; + case COLON: + state = HIST_VALUES; + break; + case COMMA: + state = HIST_KEYS; + break; + default: + goto error; + } + break; + case HIST_STACK: + switch (type) { + case NEWLINE: + break; + case STACK_ITEM: + ret = do_key_stack(hdata, text); + if (ret < 0) + goto error; + break; + case STACK_MOD: + ret = do_key_stack_mod(hdata, text); + if (ret < 0) + goto error; + break; + case COLON: + state = HIST_VALUES; + break; + case COMMA: + state = HIST_KEYS; + break; + default: + goto error; + } + break; + case HIST_RANGE: + switch (type) { + case RANGE_LINEAR: + do_key_range(hdata, start_range, + strtoll(text, NULL, 0)); + break; + case RANGE_EXPONENT: + end_range = strtoll(text, NULL, 0); + e = (unsigned int)start_range; + start_range = expo(e, end_range - 1); + end_range = expo(e, end_range); + do_key_range(hdata, start_range, end_range); + break; + default: + goto error; + } + state = HIST_KEYS; + break; + case HIST_VALUES: + switch (type) { + case VALUE: + if (first) { + ret = do_value_type(hdata, text); + if (ret < 0) + goto error; + } + break; + case NUMBER: + ret = do_value_num(hdata, strtoll(text, NULL, 0)); + if (ret < 0) + goto error; + break; + case NEWLINE: + state = HIST_KEYS_START; + first = false; + break; + default: + goto error; + } + break; + case HIST_ENTRIES: + switch (type) { + case ENTRIES: + hdata->entries = strtoll(text, NULL, 0); + state = HIST_DROPPED; + break; + default: + goto error; + } + break; + case HIST_DROPPED: + switch (type) { + case DROPPED: + hdata->dropped = strtoll(text, NULL, 0); + state = HIST_END; + break; + default: + goto error; + } + break; + case HIST_END: + done = true; + switch (type) { + case COMMENT: + update_next(next_buffer, &data); + break; + case YYEOF: + /* Fall through */ + default: + /* Do at end, as next_buffer may point to buffer*/ + if (next_buffer) + *next_buffer = NULL; + break; + } + break; + } + } + + hist_lex_destroy(data.scanner); + free(data.text); + + return hdata; + error: + print_error(&data, err, state, type); + hist_lex_destroy(data.scanner); + free(data.text); + tracefs_hist_data_free(hdata); + return NULL; +}