a8e4e88083
For a metric like: EVENT1 if #smt_on else EVENT2 currently EVENT1 and EVENT2 will be measured and then when the metric is reported EVENT1 or EVENT2 will be printed depending on the value from smt_on() during the expr parsing. Computing both events is unnecessary and can lead to multiplexing as discussed in this thread: https://lore.kernel.org/lkml/20201110100346.2527031-1-irogers@google.com/ If the input is constant to certain operators like: IDS1 if CONST else IDS2 then the result will be either IDS1 or IDS2 depending on CONST (which may be evaluated from an entire expression), and so IDS1 or IDS2 may be discarded avoiding events from being programmed. Signed-off-by: Ian Rogers <irogers@google.com> Tested-by: John Garry <john.garry@huawei.com> Acked-by: Jiri Olsa <jolsa@redhat.com> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jin Yao <yao.jin@linux.intel.com> Cc: Kajol Jain <kjain@linux.ibm.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Paul Clarke <pc@us.ibm.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Sandeep Dasgupta <sdasgup@google.com> Cc: Stephane Eranian <eranian@google.com> Link: https://lore.kernel.org/r/20210923074616.674826-13-irogers@google.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
281 lines
6.2 KiB
Plaintext
281 lines
6.2 KiB
Plaintext
/* Simple expression parser */
|
|
%{
|
|
#define YYDEBUG 1
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include "util/debug.h"
|
|
#include "smt.h"
|
|
#define IN_EXPR_Y 1
|
|
#include "expr.h"
|
|
%}
|
|
|
|
%define api.pure full
|
|
|
|
%parse-param { double *final_val }
|
|
%parse-param { struct expr_parse_ctx *ctx }
|
|
%parse-param { bool compute_ids }
|
|
%parse-param {void *scanner}
|
|
%lex-param {void* scanner}
|
|
|
|
%union {
|
|
double num;
|
|
char *str;
|
|
struct ids {
|
|
/*
|
|
* When creating ids, holds the working set of event ids. NULL
|
|
* implies the set is empty.
|
|
*/
|
|
struct hashmap *ids;
|
|
/*
|
|
* The metric value. When not creating ids this is the value
|
|
* read from a counter, a constant or some computed value. When
|
|
* creating ids the value is either a constant or BOTTOM. NAN is
|
|
* used as the special BOTTOM value, representing a "set of all
|
|
* values" case.
|
|
*/
|
|
double val;
|
|
} ids;
|
|
}
|
|
|
|
%token ID NUMBER MIN MAX IF ELSE SMT_ON D_RATIO EXPR_ERROR
|
|
%left MIN MAX IF
|
|
%left '|'
|
|
%left '^'
|
|
%left '&'
|
|
%left '<' '>'
|
|
%left '-' '+'
|
|
%left '*' '/' '%'
|
|
%left NEG NOT
|
|
%type <num> NUMBER
|
|
%type <str> ID
|
|
%destructor { free ($$); } <str>
|
|
%type <ids> expr if_expr
|
|
%destructor { ids__free($$.ids); } <ids>
|
|
|
|
%{
|
|
static void expr_error(double *final_val __maybe_unused,
|
|
struct expr_parse_ctx *ctx __maybe_unused,
|
|
bool compute_ids __maybe_unused,
|
|
void *scanner,
|
|
const char *s)
|
|
{
|
|
pr_debug("%s\n", s);
|
|
}
|
|
|
|
/*
|
|
* During compute ids, the special "bottom" value uses NAN to represent the set
|
|
* of all values. NAN is selected as it isn't a useful constant value.
|
|
*/
|
|
#define BOTTOM NAN
|
|
|
|
/* During computing ids, does val represent a constant (non-BOTTOM) value? */
|
|
static bool is_const(double val)
|
|
{
|
|
return isfinite(val);
|
|
}
|
|
|
|
static struct ids union_expr(struct ids ids1, struct ids ids2)
|
|
{
|
|
struct ids result = {
|
|
.val = BOTTOM,
|
|
.ids = ids__union(ids1.ids, ids2.ids),
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* If we're not computing ids or $1 and $3 are constants, compute the new
|
|
* constant value using OP. Its invariant that there are no ids. If computing
|
|
* ids for non-constants union the set of IDs that must be computed.
|
|
*/
|
|
#define BINARY_LONG_OP(RESULT, OP, LHS, RHS) \
|
|
if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \
|
|
assert(LHS.ids == NULL); \
|
|
assert(RHS.ids == NULL); \
|
|
RESULT.val = (long)LHS.val OP (long)RHS.val; \
|
|
RESULT.ids = NULL; \
|
|
} else { \
|
|
RESULT = union_expr(LHS, RHS); \
|
|
}
|
|
|
|
#define BINARY_OP(RESULT, OP, LHS, RHS) \
|
|
if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \
|
|
assert(LHS.ids == NULL); \
|
|
assert(RHS.ids == NULL); \
|
|
RESULT.val = LHS.val OP RHS.val; \
|
|
RESULT.ids = NULL; \
|
|
} else { \
|
|
RESULT = union_expr(LHS, RHS); \
|
|
}
|
|
|
|
%}
|
|
%%
|
|
|
|
start: if_expr
|
|
{
|
|
if (compute_ids)
|
|
ctx->ids = ids__union($1.ids, ctx->ids);
|
|
|
|
if (final_val)
|
|
*final_val = $1.val;
|
|
}
|
|
;
|
|
|
|
if_expr: expr IF expr ELSE expr
|
|
{
|
|
if (fpclassify($3.val) == FP_ZERO) {
|
|
/*
|
|
* The IF expression evaluated to 0 so treat as false, take the
|
|
* ELSE and discard everything else.
|
|
*/
|
|
$$.val = $5.val;
|
|
$$.ids = $5.ids;
|
|
ids__free($1.ids);
|
|
ids__free($3.ids);
|
|
} else if (!compute_ids || is_const($3.val)) {
|
|
/*
|
|
* If ids aren't computed then treat the expression as true. If
|
|
* ids are being computed and the IF expr is a non-zero
|
|
* constant, then also evaluate the true case.
|
|
*/
|
|
$$.val = $1.val;
|
|
$$.ids = $1.ids;
|
|
ids__free($3.ids);
|
|
ids__free($5.ids);
|
|
} else {
|
|
/*
|
|
* Value is either the LHS or RHS and we need the IF expression
|
|
* to compute it.
|
|
*/
|
|
$$ = union_expr($1, union_expr($3, $5));
|
|
}
|
|
}
|
|
| expr
|
|
;
|
|
|
|
expr: NUMBER
|
|
{
|
|
$$.val = $1;
|
|
$$.ids = NULL;
|
|
}
|
|
| ID
|
|
{
|
|
if (!compute_ids) {
|
|
/*
|
|
* Compute the event's value from ID. If the ID isn't known then
|
|
* it isn't used to compute the formula so set to NAN.
|
|
*/
|
|
struct expr_id_data *data;
|
|
|
|
$$.val = NAN;
|
|
if (expr__resolve_id(ctx, $1, &data) == 0)
|
|
$$.val = expr_id_data__value(data);
|
|
|
|
$$.ids = NULL;
|
|
free($1);
|
|
} else {
|
|
/*
|
|
* Set the value to BOTTOM to show that any value is possible
|
|
* when the event is computed. Create a set of just the ID.
|
|
*/
|
|
$$.val = BOTTOM;
|
|
$$.ids = ids__new();
|
|
if (!$$.ids || ids__insert($$.ids, $1, ctx->parent))
|
|
YYABORT;
|
|
}
|
|
}
|
|
| expr '|' expr { BINARY_LONG_OP($$, |, $1, $3); }
|
|
| expr '&' expr { BINARY_LONG_OP($$, &, $1, $3); }
|
|
| expr '^' expr { BINARY_LONG_OP($$, ^, $1, $3); }
|
|
| expr '<' expr { BINARY_OP($$, <, $1, $3); }
|
|
| expr '>' expr { BINARY_OP($$, >, $1, $3); }
|
|
| expr '+' expr { BINARY_OP($$, +, $1, $3); }
|
|
| expr '-' expr { BINARY_OP($$, -, $1, $3); }
|
|
| expr '*' expr { BINARY_OP($$, *, $1, $3); }
|
|
| expr '/' expr
|
|
{
|
|
if (fpclassify($3.val) == FP_ZERO) {
|
|
pr_debug("division by zero\n");
|
|
YYABORT;
|
|
} else if (!compute_ids || (is_const($1.val) && is_const($3.val))) {
|
|
assert($1.ids == NULL);
|
|
assert($3.ids == NULL);
|
|
$$.val = $1.val / $3.val;
|
|
$$.ids = NULL;
|
|
} else {
|
|
/* LHS and/or RHS need computing from event IDs so union. */
|
|
$$ = union_expr($1, $3);
|
|
}
|
|
}
|
|
| expr '%' expr
|
|
{
|
|
if (fpclassify($3.val) == FP_ZERO) {
|
|
pr_debug("division by zero\n");
|
|
YYABORT;
|
|
} else if (!compute_ids || (is_const($1.val) && is_const($3.val))) {
|
|
assert($1.ids == NULL);
|
|
assert($3.ids == NULL);
|
|
$$.val = (long)$1.val % (long)$3.val;
|
|
$$.ids = NULL;
|
|
} else {
|
|
/* LHS and/or RHS need computing from event IDs so union. */
|
|
$$ = union_expr($1, $3);
|
|
}
|
|
}
|
|
| D_RATIO '(' expr ',' expr ')'
|
|
{
|
|
if (fpclassify($5.val) == FP_ZERO) {
|
|
/*
|
|
* Division by constant zero always yields zero and no events
|
|
* are necessary.
|
|
*/
|
|
assert($5.ids == NULL);
|
|
$$.val = 0.0;
|
|
$$.ids = NULL;
|
|
ids__free($3.ids);
|
|
} else if (!compute_ids || (is_const($3.val) && is_const($5.val))) {
|
|
assert($3.ids == NULL);
|
|
assert($5.ids == NULL);
|
|
$$.val = $3.val / $5.val;
|
|
$$.ids = NULL;
|
|
} else {
|
|
/* LHS and/or RHS need computing from event IDs so union. */
|
|
$$ = union_expr($3, $5);
|
|
}
|
|
}
|
|
| '-' expr %prec NEG
|
|
{
|
|
$$.val = -$2.val;
|
|
$$.ids = $2.ids;
|
|
}
|
|
| '(' if_expr ')'
|
|
{
|
|
$$ = $2;
|
|
}
|
|
| MIN '(' expr ',' expr ')'
|
|
{
|
|
if (!compute_ids) {
|
|
$$.val = $3.val < $5.val ? $3.val : $5.val;
|
|
$$.ids = NULL;
|
|
} else {
|
|
$$ = union_expr($3, $5);
|
|
}
|
|
}
|
|
| MAX '(' expr ',' expr ')'
|
|
{
|
|
if (!compute_ids) {
|
|
$$.val = $3.val > $5.val ? $3.val : $5.val;
|
|
$$.ids = NULL;
|
|
} else {
|
|
$$ = union_expr($3, $5);
|
|
}
|
|
}
|
|
| SMT_ON
|
|
{
|
|
$$.val = smt_on() > 0 ? 1.0 : 0.0;
|
|
$$.ids = NULL;
|
|
}
|
|
;
|
|
|
|
%%
|