diff mbox series

[1/9] tracefs: Add API tracefs_hist_data_parse()

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

Commit Message

Steven Rostedt Aug. 10, 2021, 8:48 p.m. UTC
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.

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

Comments

Daniel Bristot de Oliveira Sept. 10, 2021, 4:54 p.m. UTC | #1
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 mbox series

Patch

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;
+}