On 8/10/21 10:48 PM, Steven Rostedt wrote: > From: "Steven Rostedt (VMware)" <rostedt@xxxxxxxxxxx> > > 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@xxxxxxxxxxx> > --- > 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@xxxxxxxxxxx> > + * > + * Updates: > + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@xxxxxxxxx> > + * > + */ > +#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; > +} >