diff options
Diffstat (limited to 'src/tracefs-sqlhist.c')
-rw-r--r-- | src/tracefs-sqlhist.c | 1653 |
1 files changed, 1653 insertions, 0 deletions
diff --git a/src/tracefs-sqlhist.c b/src/tracefs-sqlhist.c new file mode 100644 index 0000000..3f571b7 --- /dev/null +++ b/src/tracefs-sqlhist.c @@ -0,0 +1,1653 @@ +// 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 <stdarg.h> +#include <ctype.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" +#include "sqlhist-parse.h" + +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, +}; + +enum field_type { + FIELD_NONE, + FIELD_FROM, + FIELD_TO, +}; + +#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; + const char *type; + enum field_type ftype; +}; + +struct filter { + enum filter_type type; + struct expr *lval; + struct expr *rval; +}; + +struct match { + struct match *next; + struct expr *lval; + struct expr *rval; +}; + +struct compare { + enum compare_type type; + struct expr *lval; + struct expr *rval; + const char *name; +}; + +enum expr_type +{ + EXPR_NUMBER, + EXPR_STRING, + EXPR_FIELD, + EXPR_FILTER, + EXPR_COMPARE, +}; + +struct expr { + struct expr *free_list; + struct expr *next; + enum expr_type type; + int line; + int idx; + union { + struct field field; + struct filter filter; + struct compare compare; + 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 expr *where; + struct expr **next_where; + struct match *matches; + struct match **next_match; + struct expr *selections; + struct expr **next_selection; +}; + +__hidden int my_yyinput(void *extra, char *buf, int max) +{ + struct sqlhist_bison *sb = extra; + + 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) { + tracefs_warning("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 void parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, text, fmt, ap); + va_end(ap); +} + +__hidden 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 void *add_cast(struct sqlhist_bison *sb, + void *data, const char *type) +{ + struct expr *expr = data; + struct field *field = &expr->field; + + field->type = type; + return expr; +} + +__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: + expr->field.label = name; + break; + case EXPR_COMPARE: + expr->compare.name = name; + break; + case EXPR_NUMBER: + case EXPR_STRING: + case EXPR_FILTER: + 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; + if (label && strcmp(label, field->label) != 0) + continue; + 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(struct sqlhist_bison *sb, + 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; + expr->line = sb->line_no; + expr->idx = sb->line_idx; + + switch (type) { + case EXPR_FIELD: return &expr->field; + case EXPR_COMPARE: return &expr->compare; + case EXPR_NUMBER: return &expr->number; + case EXPR_STRING: return &expr->string; + case EXPR_FILTER: return &expr->filter; + } + + return NULL; +} + +#define __create_expr(var, type, ENUM, expr) \ + do { \ + var = (type *)create_expr(sb, EXPR_##ENUM, expr); \ + } while(0) + +#define create_field(var, expr) \ + __create_expr(var, struct field, FIELD, expr) + +#define create_filter(var, expr) \ + __create_expr(var, struct filter, FILTER, expr) + +#define create_compare(var, expr) \ + __create_expr(var, struct compare, COMPARE, expr) + +#define create_string(var, expr) \ + __create_expr(var, const char *, STRING, expr) + +#define create_number(var, expr) \ + __create_expr(var, long, NUMBER, 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 void *add_filter(struct sqlhist_bison *sb, + void *A, void *B, enum filter_type op) +{ + struct filter *filter; + struct expr *expr; + + create_filter(filter, &expr); + + filter->lval = A; + filter->rval = B; + + filter->type = op; + + 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 void *add_compare(struct sqlhist_bison *sb, + void *A, void *B, enum compare_type type) +{ + struct compare *compare; + struct expr *expr; + + create_compare(compare, &expr); + + compare = &expr->compare; + compare->lval = A; + compare->rval = B; + compare->type = type; + + return expr; +} + +__hidden int add_where(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + struct sql_table *table = sb->table; + + if (expr->type != EXPR_FILTER) + return -1; + + *table->next_where = expr; + table->next_where = &expr->next; + + if (expr->next) + return -1; + + 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 void *add_string(struct sqlhist_bison *sb, const char *str) +{ + struct expr *expr; + const char **str_p; + + create_string(str_p, &expr); + *str_p = str; + return expr; +} + +__hidden void *add_number(struct sqlhist_bison *sb, long val) +{ + struct expr *expr; + long *num; + + create_number(num, &expr); + *num = val; + return expr; +} + +__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_where = &table->where; + table->next_match = &table->matches; + table->next_selection = &table->selections; + + return 0; +} + +static int test_event_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + 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; + + if (field->event) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, "event not found\n"); + return -1; +} + +static int test_field_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + struct tep_format_field *tfield; + char *field_name; + const char *p; + + if (!field->event) { + if (test_event_exists(tep, sb, expr, NULL)) + 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); + + if (!tfield && (!strcmp(field->field, "COMM") || !strcmp(field->field, "comm"))) + tfield = (void *)1L; + + if (tfield) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' not part of event %s\n", + field->field, field->event_name); + return -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; + enum field_type ftype = FIELD_NONE; + 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; + + if (expr == table->to) + ftype = FIELD_TO; + else if (expr == table->from) + ftype = FIELD_FROM; + + 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, sb, 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; + field->ftype = ftype; + + 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(tep, sb, expr)) + return -1; + } + + return 0; +} + +/* + * Called when there's a FROM but no JOIN(to), which means that the + * selections can be fields and not mention the event itself. + */ +static int update_fields(struct tep_handle *tep, + struct sql_table *table, + struct expr *expr) +{ + struct field *event_field = &expr->field; + struct sqlhist_bison *sb = table->sb; + struct tep_format_field *tfield; + struct tep_event *event; + struct field *field; + const char *p; + int len; + + /* First update fields with aliases an such and add event */ + update_vars(tep, table, expr); + + /* + * If event is not found, the creation of the synth will + * add a proper error, so return "success". + */ + if (!event_field->event) + return 0; + + event = event_field->event; + + for_each_field(expr, field, table) { + const char *field_name; + + field = &expr->field; + + if (field->event) + continue; + + field_name = field->raw; + + p = strchr(field_name, '.'); + if (p) { + len = p - field_name; + p = strndup(field_name, len); + if (!p) + return -1; + field_name = store_str(sb, p); + if (!field_name) + return -1; + free((char *)p); + } + + tfield = tep_find_any_field(event, field_name); + /* Let it error properly later */ + if (!tfield) + continue; + + field->system = event_field->system; + field->event_name = event_field->event_name; + field->event = event; + field->field = field_name; + } + + return 0; +} + +static int match_error(struct sqlhist_bison *sb, struct match *match, + struct field *lmatch, struct field *rmatch) +{ + struct field *lval = &match->lval->field; + struct field *rval = &match->rval->field; + struct field *field; + struct expr *expr; + + if (lval->system != lmatch->system || + lval->event != lmatch->event) { + expr = match->lval; + field = lval; + } else { + expr = match->rval; + field = rval; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "'%s' and '%s' must be a field for each event: '%s' and '%s'\n", + lval->raw, rval->raw, sb->table->to->field.raw, + sb->table->from->field.raw); + + return -1; +} + +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 match_error(table->sb, match, from, to); + } else { + if ((rval->system != from->system) || + (rval->event != from->event) || + (lval->system != to->system) || + (lval->event != to->event)) + return match_error(table->sb, match, to, from); + } + 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 int build_compare(struct tracefs_synth *synth, + const char *system, const char *event, + struct compare *compare) +{ + const char *start_field; + const char *end_field; + struct field *lval, *rval; + enum tracefs_synth_calc calc; + int ret; + + if (!compare->name) + return -1; + + lval = &compare->lval->field; + rval = &compare->rval->field; + + if (lval->system == system && + lval->event_name == event) { + start_field = lval->field; + end_field = rval->field; + calc = TRACEFS_SYNTH_DELTA_START; + } else { + start_field = rval->field; + end_field = lval->field; + calc = TRACEFS_SYNTH_DELTA_END; + } + + if (compare->type == COMPARE_ADD) + calc = TRACEFS_SYNTH_ADD; + + ret = tracefs_synth_add_compare_field(synth, start_field, + end_field, calc, + compare->name); + return ret; +} + +static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr, + const char *event) +{ + struct field *field = &expr->field; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "event '%s' can not be grouped or '||' together with '%s'\n" + "All filters between '&&' must be for the same event\n", + field->event, event); + return -1; +} + +static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + if (filter->type == FILTER_OR || + filter->type == FILTER_AND) { + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + } + if (filter->type == FILTER_GROUP || + filter->type == FILTER_NOT_GROUP) { + return do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + } + + /* + * system and event will be NULL until we find the left most + * node. Then assign it, and compare on the way back up. + */ + if (!*system && !*event) { + *system = filter->lval->field.system; + *event = filter->lval->field.event_name; + *ftype = filter->lval->field.ftype; + return 0; + } + + if (filter->lval->field.system != *system || + filter->lval->field.event_name != *event) + return verify_filter_error(sb, filter->lval, *event); + + return 0; +} + +static int verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + break; + default: + return do_verify_filter(sb, filter, system, event, ftype); + } + + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + default: + return 0; + } +} + +static int test_field_exists(struct tep_handle *tep, struct sqlhist_bison *sb, + struct expr *expr); + +static void filter_compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + + switch (errno) { + case ENODEV: + case EBADE: + break; + case EINVAL: + parse_error(sb, field->raw, "Invalid compare\n"); + break; + default: + parse_error(sb, field->raw, "System error?\n"); + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, expr)) + return; + if (test_field_exists(tep, sb, expr)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' is not compatible to be compared with the given value\n", + field->field); +} + +static void filter_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct filter *filter = &expr->filter; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + switch (filter->type) { + case FILTER_NOT_GROUP: + case FILTER_GROUP: + case FILTER_OR: + case FILTER_AND: + break; + default: + filter_compare_error(tep, sb, filter->lval); + return; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, "", "Problem with filter entry?\n"); +} + +static int build_filter(struct tep_handle *tep, struct sqlhist_bison *sb, + struct tracefs_synth *synth, + bool start, struct expr *expr, bool *started) +{ + int (*append_filter)(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); + struct filter *filter = &expr->filter; + enum tracefs_compare cmp; + const char *val; + int and_or = TRACEFS_FILTER_AND; + char num[64]; + int ret; + + if (start) + append_filter = tracefs_synth_append_start_filter; + else + append_filter = tracefs_synth_append_end_filter; + + if (started && *started) { + ret = append_filter(synth, and_or, NULL, 0, NULL); + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + } + + switch (filter->type) { + case FILTER_NOT_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_NOT, + NULL, 0, NULL); + if (ret < 0) + goto out; + /* Fall through */ + case FILTER_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + if (ret < 0) + goto out; + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + goto out; + + case FILTER_OR: + and_or = TRACEFS_FILTER_OR; + /* Fall through */ + case FILTER_AND: + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, and_or, NULL, 0, NULL); + + if (ret) + goto out; + ret = build_filter(tep, sb, synth, start, filter->rval, NULL); + goto out; + default: + break; + } + + switch (filter->rval->type) { + case EXPR_NUMBER: + sprintf(num, "%ld", filter->rval->number); + val = num; + break; + case EXPR_STRING: + val = filter->rval->string; + break; + default: + break; + } + + switch (filter->type) { + case FILTER_EQ: cmp = TRACEFS_COMPARE_EQ; break; + case FILTER_NE: cmp = TRACEFS_COMPARE_NE; break; + case FILTER_LE: cmp = TRACEFS_COMPARE_LE; break; + case FILTER_LT: cmp = TRACEFS_COMPARE_LT; break; + case FILTER_GE: cmp = TRACEFS_COMPARE_GE; break; + case FILTER_GT: cmp = TRACEFS_COMPARE_GT; break; + case FILTER_BIN_AND: cmp = TRACEFS_COMPARE_AND; break; + case FILTER_STR_CMP: cmp = TRACEFS_COMPARE_RE; break; + default: + tracefs_warning("Error invalid filter type '%d'", filter->type); + return ERANGE; + } + + ret = append_filter(synth, TRACEFS_FILTER_COMPARE, + filter->lval->field.field, cmp, val); + + if (ret) + filter_error(tep, sb, expr); + out: + if (!ret && started) { + if (*started) + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + *started = true; + } + return ret; +} + +static void *field_match_error(struct tep_handle *tep, struct sqlhist_bison *sb, + struct match *match) +{ + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, match->lval)) + return NULL; + if (test_field_exists(tep, sb, match->rval)) + return NULL; + return NULL; + } + + /* fields exist, but values are not compatible */ + sb->line_no = match->lval->line; + sb->line_idx = match->lval->idx; + + parse_error(sb, match->lval->field.raw, + "Field '%s' is not compatible to match field '%s'\n", + match->lval->field.raw, match->rval->field.raw); + return NULL; +} + +static void *synth_init_error(struct tep_handle *tep, struct sql_table *table) +{ + struct sqlhist_bison *sb = table->sb; + struct match *match = table->matches; + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV could mean that start or end events do not exist */ + if (errno == ENODEV) { + if (test_event_exists(tep, sb, table->from, NULL)) + return NULL; + if (test_event_exists(tep, sb, table->to, NULL)) + return NULL; + } + + return field_match_error(tep, sb, match); +} + +static void selection_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + /* We just care about event not existing */ + if (errno != ENODEV) + return; + + test_field_exists(tep, sb, expr); +} + +static void compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + if (!compare->name) { + sb->line_no = expr->line; + sb->line_idx = expr->idx + strlen("no name"); + + parse_error(sb, "no name", + "Field calculations must be labeled 'AS name'\n"); + } + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, compare->lval)) + return; + if (test_field_exists(tep, sb, compare->rval)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "'%s' is not compatible to compare with '%s'\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void compare_no_to_error(struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "Simple SQL (without JOIN/ON) do not allow comparisons\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr, + const char *from_event, const char *event) +{ + while (expr) { + switch (expr->filter.type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + expr = expr->filter.lval; + continue; + default: + break; + } + break; + } + sb->line_no = expr->filter.lval->line; + sb->line_idx = expr->filter.lval->idx; + + parse_error(sb, expr->filter.lval->field.raw, + "Event '%s' does not match FROM event '%s'\n", + event, from_event); +} + +static int verify_field_type(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr, int *cnt) +{ + struct field *field = &expr->field; + struct tep_event *event; + struct tep_format_field *tfield; + char *type; + int ret; + int i; + + if (!field->type) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + event = tep_find_event_by_name(tep, field->system, field->event_name); + if (!event) { + parse_error(sb, field->raw, + "Event '%s' not found\n", + field->event_name ? : "(null)"); + return -1; + } + + tfield = tep_find_any_field(event, field->field); + if (!tfield) { + parse_error(sb, field->raw, + "Field '%s' not part of event '%s'\n", + field->field ? : "(null)", field->event); + return -1; + } + + type = strdup(field->type); + if (!type) + return -1; + + if (!strcmp(type, TRACEFS_HIST_COUNTER) || + !strcmp(type, "_COUNTER_")) { + ret = HIST_COUNTER_TYPE; + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) { + parse_error(sb, field->raw, + "'%s' is a string, and counters may only be used with numbers\n"); + ret = -1; + } + goto out; + } + + for (i = 0; type[i]; i++) + type[i] = tolower(type[i]); + + if (!strcmp(type, "hex")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_HEX; + } else if (!strcmp(type, "sym")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM; + } else if (!strcmp(type, "sym-offset")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM_OFFSET; + } else if (!strcmp(type, "syscall")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYSCALL; + } else if (!strcmp(type, "execname") || + !strcmp(type, "comm")) { + ret = TRACEFS_HIST_KEY_EXECNAME; + if (strcmp(field->field, "common_pid")) { + parse_error(sb, field->raw, + "'%s' is only allowed for common_pid\n", + type); + ret = -1; + } + } else if (!strcmp(type, "log") || + !strcmp(type, "log2")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_LOG; + } else if (!strncmp(type, "buckets", 7)) { + if (type[7] != '=' || !isdigit(type[8])) { + parse_error(sb, field->raw, + "buckets type must have '=[number]' after it\n"); + ret = -1; + goto out; + } + *cnt = atoi(&type[8]); + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_BUCKETS; + } else { + parse_error(sb, field->raw, + "Cast of '%s' to unknown type '%s'\n", + field->raw, type); + ret = -1; + } + out: + free(type); + return ret; + fail_type: + parse_error(sb, field->raw, + "Field '%s' cast to '%s' but is of type %s\n", + field->field, type, tfield->flags & TEP_FIELD_IS_STRING ? + "string" : "array"); + free(type); + return -1; +} + +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; + bool started_start = false; + bool started_end = false; + bool non_val = false; + int ret; + + if (!table->from) + return NULL; + + /* This could be a simple SQL statement to only build a histogram */ + if (!table->to) { + ret = update_fields(tep, table, table->from); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + synth = synth_init_from(tep, start_system, start_event); + if (!synth) + return synth_init_error(tep, table); + goto hist_only; + } + + ret = update_vars(tep, table, table->from); + if (ret < 0) + return NULL; + + ret = update_vars(tep, table, table->to); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + match = table->matches; + if (!match) + return NULL; + + ret = test_match(table, match); + if (ret < 0) + return NULL; + + 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_alloc(tep, name, start_system, + start_event, end_system, end_event, + start_match, end_match, NULL); + if (!synth) + return synth_init_error(tep, table); + + 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) { + field_match_error(tep, table->sb, match); + goto free; + } + } + + hist_only: + /* table->to may be NULL here */ + + for (expr = table->selections; expr; expr = expr->next) { + if (expr->type == EXPR_FIELD) { + ret = -1; + field = &expr->field; + if (field->ftype != FIELD_TO && + field->system == start_system && + field->event_name == start_event) { + int type; + int cnt = 0; + type = verify_field_type(tep, table->sb, expr, &cnt); + if (type < 0) + goto free; + if (type != HIST_COUNTER_TYPE) + non_val = true; + ret = synth_add_start_field(synth, + field->field, field->label, + type, cnt); + } else if (table->to) { + ret = tracefs_synth_add_end_field(synth, + field->field, field->label); + } + if (ret < 0) { + selection_error(tep, table->sb, expr); + goto free; + } + continue; + } + + if (!table->to) { + compare_no_to_error(table->sb, expr); + goto free; + } + + if (expr->type != EXPR_COMPARE) + goto free; + + ret = build_compare(synth, start_system, end_system, + &expr->compare); + if (ret < 0) { + compare_error(tep, table->sb, expr); + goto free; + } + } + + if (!non_val && !table->to) { + table->sb->line_no = 0; + table->sb->line_idx = 10; + parse_error(table->sb, "CAST", + "Not all SELECT items can be of type _COUNTER_\n"); + goto free; + } + + for (expr = table->where; expr; expr = expr->next) { + const char *filter_system = NULL; + const char *filter_event = NULL; + enum field_type ftype = FIELD_NONE; + bool *started; + bool start; + + ret = verify_filter(table->sb, &expr->filter, &filter_system, + &filter_event, &ftype); + if (ret < 0) + goto free; + + start = filter_system == start_system && + filter_event == start_event && + ftype != FIELD_TO; + + if (start) + started = &started_start; + else if (!table->to) { + where_no_to_error(table->sb, expr, start_event, + filter_event); + goto free; + } else + started = &started_end; + + ret = build_filter(tep, table->sb, synth, start, expr, started); + if (ret < 0) + 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->free_list; + 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 tracefs_synth *synth = NULL; + struct sqlhist_bison sb; + int ret; + + if (!tep || !sql_buffer) { + errno = EINVAL; + return NULL; + } + + memset(&sb, 0, sizeof(sb)); + + sb.buffer = sql_buffer; + sb.buffer_size = strlen(sql_buffer); + sb.buffer_idx = 0; + + ret = yylex_init_extra(&sb, &sb.scanner); + if (ret < 0) { + yylex_destroy(sb.scanner); + return NULL; + } + + ret = tracefs_parse(&sb); + yylex_destroy(sb.scanner); + + 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; +} |