diff mbox series

[v2,01/21] libtracefs: Added new API tracefs_sql()

Message ID 20210803042347.679499-2-rostedt@goodmis.org
State New
Headers show
Series [v2,01/21] libtracefs: Added new API tracefs_sql() | expand

Commit Message

Steven Rostedt Aug. 3, 2021, 4:23 a.m. UTC
From: "Steven Rostedt (VMware)" <rostedt@goodmis.org>

This adds the API tracefs_sql() that takes a tep_handle handler, a name,
and a SQL string and parses it to produce a tracefs_synth synthetic event
handler.

Currently it only supports simple SQL of the type:

  SELECT start.common_pid AS pid, end.common_timestamp.usecs AS usecs
    FROM sched_waking AS start JOIN sched_switch AS end
    ON start.pid = end.next_pid

Special thanks to:

  Lukas Bulwahn for first introducing the idea at RT Summit on the Summit
    Spring of 2019.

  Daniel Black for sorting out the syntax of mapping the synthetic
    event creations into SQL statements at Linux Plumbers, Fall of 2019.

Cc: Lukas Bulwahn <lukas.bulwahn@gmail.com>
Cc: Daniel Black <daniel@linux.ibm.com>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
---
 include/tracefs.h     |   3 +
 src/Makefile          |  13 +
 src/sqlhist-parse.h   |  69 +++++
 src/sqlhist.l         |  88 ++++++
 src/sqlhist.y         | 143 +++++++++
 src/tracefs-sqlhist.c | 691 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1007 insertions(+)
 create mode 100644 src/sqlhist-parse.h
 create mode 100644 src/sqlhist.l
 create mode 100644 src/sqlhist.y
 create mode 100644 src/tracefs-sqlhist.c

Comments

Lukas Bulwahn Aug. 3, 2021, 7:27 a.m. UTC | #1
On Tue, Aug 3, 2021 at 6:23 AM Steven Rostedt <rostedt@goodmis.org> wrote:
>
> From: "Steven Rostedt (VMware)" <rostedt@goodmis.org>
>
> This adds the API tracefs_sql() that takes a tep_handle handler, a name,
> and a SQL string and parses it to produce a tracefs_synth synthetic event
> handler.
>
> Currently it only supports simple SQL of the type:
>
>   SELECT start.common_pid AS pid, end.common_timestamp.usecs AS usecs
>     FROM sched_waking AS start JOIN sched_switch AS end
>     ON start.pid = end.next_pid
>
> Special thanks to:
>
>   Lukas Bulwahn for first introducing the idea at RT Summit on the Summit
>     Spring of 2019.

Thanks, Steven, for putting this idea to life.

I hope that the tracing users appreciate this new declarative
interface using SQL and it makes tracing events easier comprehensible
to a larger community of users.

Lukas
diff mbox series

Patch

diff --git a/include/tracefs.h b/include/tracefs.h
index 55f82392b004..219adba4b0ce 100644
--- a/include/tracefs.h
+++ b/include/tracefs.h
@@ -477,4 +477,7 @@  void tracefs_synth_free(struct tracefs_synth *synth);
 int tracefs_synth_show(struct trace_seq *seq, struct tracefs_instance *instance,
 		       struct tracefs_synth *synth);
 
+struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name,
+				  const char *sql_buffer, char **err);
+
 #endif /* _TRACE_FS_H */
diff --git a/src/Makefile b/src/Makefile
index 767af49034ee..9248efc5c7fd 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -12,6 +12,11 @@  OBJS += tracefs-kprobes.o
 OBJS += tracefs-hist.o
 OBJS += tracefs-filter.o
 
+# Order matters for the the three below
+OBJS += sqlhist-lex.o
+OBJS += sqlhist.tab.o
+OBJS += tracefs-sqlhist.o
+
 OBJS := $(OBJS:%.o=$(bdir)/%.o)
 DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
 
@@ -32,6 +37,14 @@  $(LIBTRACEFS_SHARED_SO): $(LIBTRACEFS_SHARED_VERSION)
 
 libtracefs.so: $(LIBTRACEFS_SHARED_SO)
 
+# bison will create both sqlhist.tab.c and sqlhist.tab.h
+sqlhist.tab.h:
+sqlhist.tab.c: sqlhist.y sqlhist.tab.h
+	bison --debug -v --report-file=bison.report -d -o $@ $<
+
+sqlhist-lex.c: sqlhist.l sqlhist.tab.c
+	flex -o $@ $<
+
 $(bdir)/%.o: %.c
 	$(Q)$(call do_fpic_compile)
 
diff --git a/src/sqlhist-parse.h b/src/sqlhist-parse.h
new file mode 100644
index 000000000000..aa5232eea451
--- /dev/null
+++ b/src/sqlhist-parse.h
@@ -0,0 +1,69 @@ 
+#ifndef __SQLHIST_PARSE_H
+#define __SQLHIST_PARSE_H
+
+#include <stdarg.h>
+#include <tracefs.h>
+
+struct str_hash;
+#define HASH_BITS 10
+
+struct sql_table;
+
+struct sqlhist_bison {
+	const char		*buffer;
+	size_t			buffer_size;
+	size_t			buffer_idx;
+	int			line_no;
+	int			line_idx;
+	struct sql_table	*table;
+	char			*parse_error_str;
+	struct str_hash         *str_hash[1 << HASH_BITS];
+};
+
+extern struct sqlhist_bison *sb;
+
+#include "sqlhist.tab.h"
+
+enum filter_type {
+	FILTER_GROUP,
+	FILTER_NOT_GROUP,
+	FILTER_EQ,
+	FILTER_NE,
+	FILTER_LE,
+	FILTER_LT,
+	FILTER_GE,
+	FILTER_GT,
+	FILTER_BIN_AND,
+	FILTER_STR_CMP,
+	FILTER_AND,
+	FILTER_OR,
+};
+
+enum compare_type {
+	COMPARE_GROUP,
+	COMPARE_ADD,
+	COMPARE_SUB,
+	COMPARE_MUL,
+	COMPARE_DIV,
+	COMPARE_BIN_AND,
+	COMPARE_BIN_OR,
+	COMPARE_AND,
+	COMPARE_OR,
+};
+
+char * store_str(struct sqlhist_bison *sb, const char *str);
+
+int table_start(struct sqlhist_bison *sb);
+
+void *add_field(struct sqlhist_bison *sb, const char *field, const char *label);
+
+int add_match(struct sqlhist_bison *sb, void *A, void *B);
+
+int add_selection(struct sqlhist_bison *sb, void *item, const char *label);
+int add_from(struct sqlhist_bison *sb, void *item);
+int add_to(struct sqlhist_bison *sb, void *item);
+
+extern void sql_parse_error(struct sqlhist_bison *sb, const char *text,
+			    const char *fmt, va_list ap);
+
+#endif
diff --git a/src/sqlhist.l b/src/sqlhist.l
new file mode 100644
index 000000000000..5dfd17517c6e
--- /dev/null
+++ b/src/sqlhist.l
@@ -0,0 +1,88 @@ 
+%{
+/* code here */
+
+#include <stdarg.h>
+#include "sqlhist-parse.h"
+
+extern int my_yyinput(char *buf, int max);
+
+#undef YY_INPUT
+#define YY_INPUT(b, r, m) ({r = my_yyinput(b, m);})
+
+#define YY_NO_INPUT
+#define YY_NO_UNPUT
+
+#define YY_EXTRA_TYPE struct sqlhist_bison *
+
+#define HANDLE_COLUMN do { sb->line_idx += strlen(yytext); } while (0)
+
+%}
+
+%option caseless
+
+field		[a-z_][a-z0-9_\.]*
+qstring		\"[^\"]*\"
+hexnum		0x[0-9a-f]+
+number		[0-9a-f]+
+
+%%
+
+select { HANDLE_COLUMN; return SELECT; }
+as { HANDLE_COLUMN; return AS; }
+from { HANDLE_COLUMN; return FROM; }
+join { HANDLE_COLUMN; return JOIN; }
+on { HANDLE_COLUMN; return ON; }
+
+{qstring} {
+	HANDLE_COLUMN;
+	yylval.string = store_str(sb, yytext);
+	return STRING;
+}
+
+{field} {
+	HANDLE_COLUMN;
+	yylval.string = store_str(sb, yytext);
+	return FIELD;
+}
+
+{hexnum} {
+	HANDLE_COLUMN;
+	yylval.number = strtol(yytext, NULL, 0);
+	return NUMBER;
+}
+
+{number} {
+	HANDLE_COLUMN;
+	yylval.number = strtol(yytext, NULL, 0);
+	return NUMBER;
+}
+
+\!= { HANDLE_COLUMN; return NEQ; }
+\<= { HANDLE_COLUMN; return LE; }
+\>= { HANDLE_COLUMN; return GE; }
+== { HANDLE_COLUMN; return EQ; }
+&& { HANDLE_COLUMN; return AND; }
+"||" { HANDLE_COLUMN; return OR; }
+[<>&~] { HANDLE_COLUMN; return yytext[0]; }
+
+[\!()\-\+\*/,=] { HANDLE_COLUMN; return yytext[0]; }
+
+[ \t] { HANDLE_COLUMN; }
+\n { sb->line_idx = 0; sb->line_no++; }
+
+. { HANDLE_COLUMN; return PARSE_ERROR; }
+%%
+
+int yywrap(void)
+{
+	return 1;
+}
+
+void yyerror(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	sql_parse_error(sb, yytext, fmt, ap);
+	va_end(ap);
+}
diff --git a/src/sqlhist.y b/src/sqlhist.y
new file mode 100644
index 000000000000..ecb0a0ed44b3
--- /dev/null
+++ b/src/sqlhist.y
@@ -0,0 +1,143 @@ 
+%{
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sqlhist-parse.h"
+
+extern int yylex(void);
+extern void yyerror(char *fmt, ...);
+
+#define CHECK_RETURN_PTR(x)					\
+	do {							\
+		if (!(x)) {					\
+			printf("FAILED MEMORY: %s\n", #x);	\
+			return -ENOMEM;				\
+		}						\
+	} while (0)
+
+#define CHECK_RETURN_VAL(x)					\
+	do {							\
+		if ((x) < 0) {					\
+			printf("FAILED MEMORY: %s\n", #x);	\
+			return -ENOMEM;				\
+		}						\
+	} while (0)
+
+%}
+
+%union {
+	int	s32;
+	char	*string;
+	long	number;
+	void	*expr;
+}
+
+%token AS SELECT FROM JOIN ON PARSE_ERROR
+%token <number> NUMBER
+%token <string> STRING
+%token <string> FIELD
+%token <string> LE GE EQ NEQ AND OR
+
+%left '+' '-'
+%left '*' '/'
+%left '<' '>'
+%left AND OR
+
+%type <string> name label
+
+%type <expr>  selection_expr field item named_field join_clause
+
+%%
+
+start :
+   select_statement
+ ;
+
+label : AS name { CHECK_RETURN_PTR($$ = store_str(sb, $2)); }
+ | name { CHECK_RETURN_PTR($$ = store_str(sb, $1)); }
+ ;
+
+select : SELECT  { table_start(sb); }
+  ;
+
+select_statement :
+    select selection_list table_exp
+  ;
+
+selection_list :
+   selection
+ | selection ',' selection_list
+ ;
+
+selection :
+    selection_expr
+				{
+					CHECK_RETURN_VAL(add_selection(sb, $1, NULL));
+				}
+  | selection_expr label
+				{
+					CHECK_RETURN_VAL(add_selection(sb, $1, $2));
+				}
+  ;
+
+selection_expr :
+   field
+ | '(' field ')'		{  $$ = $2; }
+ ;
+
+item :
+   named_field
+ | field
+ ;
+
+field :
+   FIELD	{ $$ = add_field(sb, $1, NULL); CHECK_RETURN_PTR($$); }
+ ;
+
+named_field :
+   FIELD label { $$ = add_field(sb, $1, $2); CHECK_RETURN_PTR($$); }
+ ;
+
+name :
+   FIELD
+ ;
+
+table_exp :
+   from_clause join_clause
+ ;
+
+from_clause :
+   FROM item		{ CHECK_RETURN_VAL(add_from(sb, $2)); }
+
+/*
+ * Select from a from clause confuses the variable parsing.
+ * disable it for now.
+
+   | FROM '(' select_statement ')' label
+				{
+					from_table_end($5);
+					$$ = store_printf("FROM (%s) AS %s", $3, $5);
+				}
+*/
+ ;
+
+join_clause :
+  JOIN item ON match_clause	{ add_to(sb, $2); }
+ ;
+
+match :
+   item '=' item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); }
+ | item EQ item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); }
+
+ ;
+
+match_clause :
+   match
+ | match ',' match_clause
+ ;
+
+%%
+
+
diff --git a/src/tracefs-sqlhist.c b/src/tracefs-sqlhist.c
new file mode 100644
index 000000000000..7ec0c11b902e
--- /dev/null
+++ b/src/tracefs-sqlhist.c
@@ -0,0 +1,691 @@ 
+// 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 <trace-seq.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "tracefs.h"
+#include "tracefs-local.h"
+#include "sqlhist-parse.h"
+
+struct sqlhist_bison *sb;
+
+extern int yylex_init(void* ptr_yy_globals);
+extern int yylex_init_extra(struct sqlhist_bison *sb, void* ptr_yy_globals);
+extern int yylex_destroy (void * yyscanner );
+
+struct str_hash {
+	struct str_hash		*next;
+	char			*str;
+};
+
+enum alias_type {
+	ALIAS_EVENT,
+	ALIAS_FIELD,
+};
+
+#define for_each_field(expr, field, table) \
+	for (expr = (table)->fields; expr; expr = (field)->next)
+
+struct field {
+	struct expr		*next;	/* private link list */
+	const char		*system;
+	const char		*event;
+	const char		*raw;
+	const char		*label;
+	const char		*field;
+};
+
+struct match {
+	struct match		*next;
+	struct expr		*lval;
+	struct expr		*rval;
+};
+
+enum expr_type
+{
+	EXPR_NUMBER,
+	EXPR_STRING,
+	EXPR_FIELD,
+};
+
+struct expr {
+	struct expr		*free_list;
+	struct expr		*next;
+	enum expr_type		type;
+	union {
+		struct field	field;
+		const char	*string;
+		long		number;
+	};
+};
+
+struct sql_table {
+	struct sqlhist_bison	*sb;
+	const char		*name;
+	struct expr		*exprs;
+	struct expr		*fields;
+	struct expr		*from;
+	struct expr		*to;
+	struct match		*matches;
+	struct match		**next_match;
+	struct expr		*selections;
+	struct expr		**next_selection;
+};
+
+__hidden int my_yyinput(char *buf, int max)
+{
+	if (!sb || !sb->buffer)
+		return -1;
+
+	if (sb->buffer_idx + max > sb->buffer_size)
+		max = sb->buffer_size - sb->buffer_idx;
+
+	if (max)
+		memcpy(buf, sb->buffer + sb->buffer_idx, max);
+
+	sb->buffer_idx += max;
+
+	return max;
+}
+
+__hidden void sql_parse_error(struct sqlhist_bison *sb, const char *text,
+			      const char *fmt, va_list ap)
+{
+	const char *buffer = sb->buffer;
+	struct trace_seq s;
+	int line = sb->line_no;
+	int idx = sb->line_idx - strlen(text);
+	int i;
+
+	if (!buffer)
+		return;
+
+	trace_seq_init(&s);
+	if (!s.buffer) {
+		fprintf(stderr, "Error allocating internal buffer\n");
+		return;
+	}
+
+	for (i = 0; line && buffer[i]; i++) {
+		if (buffer[i] == '\n')
+			line--;
+	}
+	for (; buffer[i] && buffer[i] != '\n'; i++)
+		trace_seq_putc(&s, buffer[i]);
+	trace_seq_putc(&s, '\n');
+	for (i = idx; i > 0; i--)
+		trace_seq_putc(&s, ' ');
+	trace_seq_puts(&s, "^\n");
+	trace_seq_printf(&s, "ERROR: '%s'\n", text);
+	trace_seq_vprintf(&s, fmt, ap);
+
+	trace_seq_terminate(&s);
+
+	sb->parse_error_str = strdup(s.buffer);
+	trace_seq_destroy(&s);
+}
+
+static inline unsigned int quick_hash(const char *str)
+{
+	unsigned int val = 0;
+	int len = strlen(str);
+
+	for (; len >= 4; str += 4, len -= 4) {
+		val += str[0];
+		val += str[1] << 8;
+		val += str[2] << 16;
+		val += str[3] << 24;
+	}
+	for (; len > 0; str++, len--)
+		val += str[0] << (len * 8);
+
+        val *= 2654435761;
+
+        return val & ((1 << HASH_BITS) - 1);
+}
+
+
+static struct str_hash *find_string(struct sqlhist_bison *sb, const char *str)
+{
+	unsigned int key = quick_hash(str);
+	struct str_hash *hash = sb->str_hash[key];
+
+	for (; hash; hash = hash->next) {
+		if (!strcmp(hash->str, str))
+			return hash;
+	}
+	return NULL;
+}
+
+/*
+ * If @str is found, then return the hash string.
+ * This lets store_str() know to free str.
+ */
+static char **add_hash(struct sqlhist_bison *sb, const char *str)
+{
+	struct str_hash *hash;
+	unsigned int key;
+
+	if ((hash = find_string(sb, str))) {
+		return &hash->str;
+	}
+
+	hash = malloc(sizeof(*hash));
+	if (!hash)
+		return NULL;
+	key = quick_hash(str);
+	hash->next = sb->str_hash[key];
+	sb->str_hash[key] = hash;
+	hash->str = NULL;
+	return &hash->str;
+}
+
+__hidden char *store_str(struct sqlhist_bison *sb, const char *str)
+{
+	char **pstr = add_hash(sb, str);
+
+	if (!pstr)
+		return NULL;
+
+	if (!(*pstr))
+		*pstr = strdup(str);
+
+	return *pstr;
+}
+
+__hidden int add_selection(struct sqlhist_bison *sb, void *select,
+			   const char *name)
+{
+	struct sql_table *table = sb->table;
+	struct expr *expr = select;
+
+	switch (expr->type) {
+	case EXPR_FIELD:
+		break;
+	case EXPR_NUMBER:
+	case EXPR_STRING:
+	default:
+		return -1;
+	}
+
+	if (expr->next)
+		return -1;
+
+	*table->next_selection = expr;
+	table->next_selection = &expr->next;
+
+	return 0;
+}
+
+static struct expr *find_field(struct sqlhist_bison *sb,
+				const char *raw, const char *label)
+{
+	struct field *field;
+	struct expr *expr;
+
+	for_each_field(expr, field, sb->table) {
+		field = &expr->field;
+
+		if (!strcmp(field->raw, raw)) {
+			if (label && !field->label)
+				field->label = label;
+			return expr;
+		}
+
+		if (label && !strcmp(field->raw, label)) {
+			if (!field->label) {
+				field->label = label;
+				field->raw = raw;
+			}
+			return expr;
+		}
+
+		if (!field->label)
+			continue;
+
+		if (!strcmp(field->label, raw))
+			return expr;
+
+		if (label && !strcmp(field->label, label))
+			return expr;
+	}
+	return NULL;
+}
+
+static void *create_expr(enum expr_type type, struct expr **expr_p)
+{
+	struct expr *expr;
+
+	expr = calloc(1, sizeof(*expr));
+	if (!expr)
+		return NULL;
+
+	if (expr_p)
+		*expr_p = expr;
+
+	expr->free_list = sb->table->exprs;
+	sb->table->exprs = expr;
+
+	expr->type = type;
+
+	switch (type) {
+	case EXPR_FIELD:	return &expr->field;
+	case EXPR_NUMBER:	return &expr->number;
+	case EXPR_STRING:	return &expr->string;
+	}
+
+	return NULL;
+}
+
+#define __create_expr(var, type, ENUM, expr)			\
+	do {							\
+		var = (type *)create_expr(EXPR_##ENUM, expr);	\
+	} while(0)
+
+#define create_field(var, expr)				\
+	__create_expr(var, struct field, FIELD, expr)
+
+__hidden void *add_field(struct sqlhist_bison *sb,
+			 const char *field_name, const char *label)
+{
+	struct sql_table *table = sb->table;
+	struct expr *expr;
+	struct field *field;
+
+	expr = find_field(sb, field_name, label);
+	if (expr)
+		return expr;
+
+	create_field(field, &expr);
+
+	field->next = table->fields;
+	table->fields = expr;
+
+	field->raw = field_name;
+	field->label = label;
+
+	return expr;
+}
+
+__hidden int add_match(struct sqlhist_bison *sb, void *A, void *B)
+{
+	struct sql_table *table = sb->table;
+	struct match *match;
+
+	match = calloc(1, sizeof(*match));
+	if (!match)
+		return -1;
+
+	match->lval = A;
+	match->rval = B;
+
+	*table->next_match = match;
+	table->next_match = &match->next;
+
+	return 0;
+}
+
+__hidden int add_from(struct sqlhist_bison *sb, void *item)
+{
+	struct expr *expr = item;
+
+	if (expr->type != EXPR_FIELD)
+		return -1;
+
+	sb->table->from = expr;
+
+	return 0;
+}
+
+__hidden int add_to(struct sqlhist_bison *sb, void *item)
+{
+	struct expr *expr = item;
+
+	if (expr->type != EXPR_FIELD)
+		return -1;
+
+	sb->table->to = expr;
+
+	return 0;
+}
+
+__hidden int table_start(struct sqlhist_bison *sb)
+{
+	struct sql_table *table;
+
+	table = calloc(1, sizeof(*table));
+	if (!table)
+		return -ENOMEM;
+
+	table->sb = sb;
+	sb->table = table;
+
+	table->next_match = &table->matches;
+	table->next_selection = &table->selections;
+
+	return 0;
+}
+
+static int update_vars(struct sql_table *table, struct field *event)
+{
+	struct sqlhist_bison *sb = table->sb;
+	struct expr *expr;
+	struct field *field;
+	const char *label;
+	const char *p, *r;
+	char *system;
+	int len;
+
+	p = strchr(event->raw, '.');
+	if (p) {
+		system = strndup(event->raw, p - event->raw);
+		if (!system)
+			return -1;
+		event->system = store_str(sb, system);
+		free(system);
+		if (!event->system)
+			return -1;
+		p++;
+	} else {
+		p = event->raw;
+	}
+
+	event->event = store_str(sb, p);
+	if (!event->event)
+		return -1;
+
+	if (!event->label)
+		event->label = event->event;
+
+	label = event->label;
+	len = strlen(label);
+
+	for_each_field(expr, field, table) {
+		field = &expr->field;
+
+		if (field->event)
+			continue;
+
+		p = strchr(field->raw, '.');
+		if (p) {
+			/* Does this field have a system */
+			r = strchr(p + 1, '.');
+			if (r) {
+				/* This has a system, and is not a alias */
+				system = strndup(field->raw, p - field->raw);
+				if (!system)
+					return -1;
+				field->system = store_str(sb, system);
+				free(system);
+				if (!field->system)
+					return -1;
+
+				/* save the event as well */
+				p++;
+				system = strndup(p, r - p);
+				if (!system)
+					return -1;
+				field->event = store_str(sb, system);
+				free(system);
+				if (!field->event)
+					return -1;
+				r++;
+				field->field = store_str(sb, r);
+				goto check_timestamps;
+			}
+		}
+
+		if (strncmp(field->raw, label, len))
+			continue;
+
+		if (field->raw[len] != '.')
+			continue;
+
+		field->system = event->system;
+		field->event = event->event;
+		field->field = field->raw + len + 1;
+ check_timestamps:
+		if (!strcmp(field->field, "TIMESTAMP"))
+			field->field = store_str(sb, TRACEFS_TIMESTAMP);
+		else if (!strcmp(field->field, "TIMESTAMP_USECS"))
+			field->field = store_str(sb, TRACEFS_TIMESTAMP_USECS);
+	}
+
+	return 0;
+}
+
+static int test_match(struct sql_table *table, struct match *match)
+{
+	struct field *lval, *rval;
+	struct field *to, *from;
+
+	if (!match->lval || !match->rval)
+		return -1;
+
+	if (match->lval->type != EXPR_FIELD || match->rval->type != EXPR_FIELD)
+		return -1;
+
+	to = &table->to->field;
+	from = &table->from->field;
+
+	lval = &match->lval->field;
+	rval = &match->rval->field;
+
+	/*
+	 * Note, strings are stored in the string store, so all
+	 * duplicate strings are the same value, and we can use
+	 * normal "==" and "!=" instead of strcmp().
+	 *
+	 * Either lval == to and rval == from
+	 * or lval == from and rval == to.
+	 */
+	if ((lval->system != to->system) ||
+	    (lval->event != to->event)) {
+		if ((rval->system != to->system) ||
+		    (rval->event != to->event) ||
+		    (lval->system != from->system) ||
+		    (lval->event != from->event))
+			return -1;
+	} else {
+		if ((rval->system != from->system) ||
+		    (rval->event != from->event) ||
+		    (lval->system != to->system) ||
+		    (lval->event != to->event))
+			return -1;
+	}
+	return 0;
+}
+
+static void assign_match(const char *system, const char *event,
+			 struct match *match,
+			 const char **start_match, const char **end_match)
+{
+	struct field *lval, *rval;
+
+	lval = &match->lval->field;
+	rval = &match->rval->field;
+
+	if (lval->system == system &&
+	    lval->event == event) {
+		*start_match = lval->field;
+		*end_match = rval->field;
+	} else {
+		*start_match = rval->field;
+		*end_match = lval->field;
+	}
+}
+
+static struct tracefs_synth *build_synth(struct tep_handle *tep,
+					 const char *name,
+					 struct sql_table *table)
+{
+	struct tracefs_synth *synth;
+	struct field *field;
+	struct match *match;
+	struct expr *expr;
+	const char *start_system;
+	const char *start_event;
+	const char *end_system;
+	const char *end_event;
+	const char *start_match;
+	const char *end_match;
+	int ret;
+
+	if (!table->to || !table->from)
+		return NULL;
+
+	ret = update_vars(table, &table->to->field);
+	if (ret < 0)
+		return NULL;
+
+	ret = update_vars(table, &table->from->field);
+	if (ret < 0)
+		return NULL;
+
+	match = table->matches;
+	if (!match)
+		return NULL;
+
+	ret = test_match(table, match);
+	if (ret < 0)
+		return NULL;
+
+	start_system = table->from->field.system;
+	start_event = table->from->field.event;
+
+	end_system = table->to->field.system;
+	end_event = table->to->field.event;
+
+	assign_match(start_system, start_event, match,
+		     &start_match, &end_match);
+
+	synth = tracefs_synth_init(tep, name, start_system,
+				   start_event, end_system, end_event,
+				   start_match, end_match, NULL);
+	if (!synth)
+		return NULL;
+
+	for (match = match->next; match; match = match->next) {
+		ret = test_match(table, match);
+		if (ret < 0)
+			goto free;
+
+		assign_match(start_system, start_event, match,
+			     &start_match, &end_match);
+
+		ret = tracefs_synth_add_match_field(synth,
+						    start_match,
+						    end_match, NULL);
+		if (ret < 0)
+			goto free;
+	}
+
+	for (expr = table->selections; expr; expr = expr->next) {
+		if (expr->type == EXPR_FIELD) {
+			field = &expr->field;
+			if (field->system == start_system &&
+			    field->event == start_event) {
+				ret = tracefs_synth_add_start_field(synth,
+						field->field, field->label);
+			} else {
+				ret = tracefs_synth_add_end_field(synth,
+						field->field, field->label);
+			}
+			if (ret < 0)
+				goto free;
+			continue;
+		}
+		goto free;
+	}
+
+	return synth;
+ free:
+	tracefs_synth_free(synth);
+	return NULL;
+}
+
+static void free_sql_table(struct sql_table *table)
+{
+	struct match *match;
+	struct expr *expr;
+
+	if (!table)
+		return;
+
+	while ((expr = table->exprs)) {
+		table->exprs = expr->next;
+		free(expr);
+	}
+
+	while ((match = table->matches)) {
+		table->matches = match->next;
+		free(match);
+	}
+
+	free(table);
+}
+
+static void free_str_hash(struct str_hash **hash)
+{
+	struct str_hash *item;
+	int i;
+
+	for (i = 0; i < 1 << HASH_BITS; i++) {
+		while ((item = hash[i])) {
+			hash[i] = item->next;
+			free(item->str);
+			free(item);
+		}
+	}
+}
+
+static void free_sb(struct sqlhist_bison *sb)
+{
+	free_sql_table(sb->table);
+	free_str_hash(sb->str_hash);
+	free(sb->parse_error_str);
+}
+
+struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name,
+				  const char *sql_buffer, char **err)
+{
+	struct sqlhist_bison local_sb;
+	struct tracefs_synth *synth = NULL;
+	int ret;
+
+	if (!tep || !sql_buffer) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	memset(&local_sb, 0, sizeof(local_sb));
+
+	local_sb.buffer = sql_buffer;
+	local_sb.buffer_size = strlen(sql_buffer);
+	local_sb.buffer_idx = 0;
+
+	sb = &local_sb;
+	ret = yyparse();
+
+	if (ret)
+		goto free;
+
+	synth = build_synth(tep, name, sb->table);
+
+ free:
+	if (!synth) {
+		if (sb->parse_error_str && err) {
+			*err = sb->parse_error_str;
+			sb->parse_error_str = NULL;
+		}
+	}
+	free_sb(sb);
+	return synth;
+}