LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: Steven Rostedt <rostedt@goodmis.org>
To: linux-trace-devel@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, Tom Zanussi <zanussi@kernel.org>,
	Daniel Bristot de Oliveira <bristot@redhat.com>,
	Masami Hiramatsu <mhiramat@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>,
	linux-rt-users <linux-rt-users@vger.kernel.org>,
	Clark Williams <williams@redhat.com>,
	"Steven Rostedt (VMware)" <rostedt@goodmis.org>,
	Lukas Bulwahn <lukas.bulwahn@gmail.com>,
	Daniel Black <daniel@linux.ibm.com>
Subject: [PATCH v3 01/22] libtracefs: Added new API tracefs_sql()
Date: Tue,  3 Aug 2021 13:05:45 -0400	[thread overview]
Message-ID: <20210803170606.694085-2-rostedt@goodmis.org> (raw)
In-Reply-To: <20210803170606.694085-1-rostedt@goodmis.org>

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 | 756 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1072 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

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..5f2729325c1c
--- /dev/null
+++ b/src/tracefs-sqlhist.c
@@ -0,0 +1,756 @@
+// 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_name;
+	struct tep_event	*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 test_event_exists(struct tep_handle *tep,
+			     struct expr *expr, struct tep_event **pevent)
+{
+	struct field *field = &expr->field;
+	const char *system = field->system;
+	const char *event = field->event_name;
+
+	if (!field->event)
+		field->event = tep_find_event_by_name(tep, system, event);
+	if (pevent)
+		*pevent = field->event;
+	return field->event != NULL ? 0 : -1;
+}
+
+static int test_field_exists(struct expr *expr)
+{
+	struct field *field = &expr->field;
+	struct tep_format_field *tfield;
+	char *field_name;
+	const char *p;
+
+	if (!field->event)
+		return -1;
+
+	/* The field could have a conversion */
+	p = strchr(field->field, '.');
+	if (p)
+		field_name = strndup(field->field, p - field->field);
+	else
+		field_name = strdup(field->field);
+
+	if (!field_name)
+		return -1;
+
+	if (!strcmp(field_name, TRACEFS_TIMESTAMP) ||
+	    !strcmp(field->field, TRACEFS_TIMESTAMP_USECS))
+		tfield = (void *)1L;
+	else
+		tfield = tep_find_any_field(field->event, field_name);
+	free(field_name);
+
+	return tfield != NULL ? 0 : -1;
+}
+
+static int update_vars(struct tep_handle *tep,
+		       struct sql_table *table,
+		       struct expr *expr)
+{
+	struct sqlhist_bison *sb = table->sb;
+	struct field *event_field = &expr->field;
+	struct tep_event *event;
+	struct field *field;
+	const char *label;
+	const char *raw = event_field->raw;
+	const char *event_name;
+	const char *system;
+	const char *p;
+	int label_len = 0, event_len, system_len;
+
+	p = strchr(raw, '.');
+	if (p) {
+		char *str;
+
+		str = strndup(raw, p - raw);
+		if (!str)
+			return -1;
+		event_field->system = store_str(sb, str);
+		free(str);
+		if (!event_field->system)
+			return -1;
+		p++;
+	} else {
+		p = raw;
+	}
+
+	event_field->event_name = store_str(sb, p);
+	if (!event_field->event_name)
+		return -1;
+
+	if (test_event_exists(tep, expr, &event))
+		return -1;
+
+	if (!event_field->system)
+		event_field->system = store_str(sb, event->system);
+
+	if (!event_field->system)
+		return -1;
+
+	label = event_field->label;
+	if (label)
+		label_len = strlen(label);
+
+	system = event_field->system;
+	system_len = strlen(system);
+
+	event_name = event_field->event_name;
+	event_len = strlen(event_name);
+
+	for_each_field(expr, field, table) {
+		int len;
+
+		field = &expr->field;
+
+		if (field->event)
+			continue;
+
+		raw = field->raw;
+
+		/*
+		 * The field could be:
+		 *     system.event.field...
+		 *     event.field...
+		 *     label.field...
+		 * We check label first.
+		 */
+
+		len = label_len;
+		if (label && !strncmp(raw, label, len) &&
+		    raw[len] == '.') {
+			/* Label matches and takes precedence */
+			goto found;
+		}
+
+		if (!strncmp(raw, system, system_len) &&
+		    raw[system_len] == '.') {
+			raw += system_len + 1;
+			/* Check the event portion next */
+		}
+
+		len = event_len;
+		if (strncmp(raw, event_name, len) ||
+		    raw[len] != '.') {
+			/* Does not match */
+			continue;
+		}
+ found:
+		field->system = system;
+		field->event_name = event_name;
+		field->event = event;
+		field->field = raw + len + 1;
+
+		if (!strcmp(field->field, "TIMESTAMP"))
+			field->field = store_str(sb, TRACEFS_TIMESTAMP);
+		if (!strcmp(field->field, "TIMESTAMP_USECS"))
+			field->field = store_str(sb, TRACEFS_TIMESTAMP_USECS);
+		if (test_field_exists(expr))
+			return -1;
+	}
+
+	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_name == 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(tep, table, table->to);
+	if (ret < 0)
+		return NULL;
+
+	ret = update_vars(tep, table, table->from);
+	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_name;
+
+	end_system = table->to->field.system;
+	end_event = table->to->field.event_name;
+
+	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_name == 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;
+}
-- 
2.30.2


  reply	other threads:[~2021-08-03 17:07 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-03 17:05 [PATCH v3 00/22] libtracefs: Introducing tracefs_sql() to create synthetice events with an SQL line Steven Rostedt
2021-08-03 17:05 ` Steven Rostedt [this message]
2021-08-03 17:05 ` [PATCH v3 02/22] tracefs: Add unit tests for tracefs_sql() Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 03/22] libtracefs: Add comparing start and end fields in tracefs_sql() Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 04/22] libtracefs: Add unit test to test tracefs_sql() compare Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 05/22] libtracefs: Add filtering for start and end events in tracefs_sql() Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 06/22] libtracefs: Add unit test to test tracefs_sql() where clause Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 07/22] libtracefs: Make sqlhist parser reentrant Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 08/22] libtracefs: Make parser unique to libtracefs Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 09/22] libtracefs: Add line number and index to expr structure Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 10/22] libtracefs: Add error message when match fields are not FROM and JOIN events Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 11/22] libtracefs: Add error message when match or init fails from bad events Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 12/22] libtracefs; Add error message for bad selections to SQL sequence Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 13/22] libtracefs: Add error message when compare fields fail Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 14/22] libtracefs: Add error message for grouping events in SQL filter Steven Rostedt
2021-08-03 17:05 ` [PATCH v3 15/22] libtracefs: Add error message for bad filters in SQL statement Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 16/22] libtracefs: Add error message when calculation has no label Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 17/22] libtracefs: Add man page for tracefs_sql() Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 18/22] libtracefs: Add Makefile rule to create sqlhist Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 19/22] libtracefs: Allow for simple SQL statements to create a histogram Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 20/22] libtracefs: Allow trace_sql() to take keywords for fields with backslash Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 21/22] libtracefs: Add CAST() syntax to SQL parsing for histogram types Steven Rostedt
2021-08-03 17:06 ` [PATCH v3 22/22] libtracefs: Add CAST(x AS _COUNTER_) syntax to create values in histograms Steven Rostedt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210803170606.694085-2-rostedt@goodmis.org \
    --to=rostedt@goodmis.org \
    --cc=bristot@redhat.com \
    --cc=daniel@linux.ibm.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rt-users@vger.kernel.org \
    --cc=linux-trace-devel@vger.kernel.org \
    --cc=lukas.bulwahn@gmail.com \
    --cc=mhiramat@kernel.org \
    --cc=namhyung@kernel.org \
    --cc=williams@redhat.com \
    --cc=zanussi@kernel.org \
    --subject='Re: [PATCH v3 01/22] libtracefs: Added new API tracefs_sql()' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).