Hi all,
The v1 of this series was posted at: https://lore.kernel.org/r/20210915195306.612966-1-kaleshsingh@google.com/
The cover letter here is mostly identical to that in v1, with a correction to how the results were obtained. ---
The frequency of the rss_stat trace event is known to be of the same magnitude as that of the sched_switch event on Android devices. This can cause flooding of the trace buffer with rss_stat traces leading to a decreased trace buffer capacity and loss of data.
If it is not necessary to monitor very small changes in rss (as is the case in Android) then the rss_stat tracepoint can be throttled to only emit the event once there is a large enough change in the rss size. The original patch that introduced the rss_stat tracepoint also proposed a fixed throttling mechanism that only emits the rss_stat event when the rss size crosses a 512KB boundary. It was concluded that more generic support for this type of filtering/throttling was need, so that it can be applied to any trace event. [1]
From the discussion in [1], histogram triggers seemed the most likely
candidate to support this type of throttling. For instance to achieve the same throttling as was proposed in [1]:
(1) Create a histogram variable to save the 512KB bucket of the rss size (2) Use the onchange handler to generate a synthetic event when the rss size bucket changes.
The only missing pieces to support such a hist trigger are: (1) Support for setting a hist variable to a specific value -- to set the bucket size / granularity. (2) Support for division arithmetic operation -- to determine the corresponding bucket for an rss size.
This series extends histogram trigger expressions to: (1) Allow assigning numeric literals to hist variable (eg. x=1234) and using literals directly in expressions (eg. x=size/1234) (2) Support division and multiplication in hist expressions. (eg. a=$x/$y*z); and (3) Fixes expression parsing for non-associative operators: subtraction and division. (eg. 8-4-2 should be 2 not 6)
The rss_stat event can then be throttled using histogram triggers as below:
# Create a synthetic event to monitor instead of the high frequency # rss_stat event echo 'rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size' >> tracing/synthetic_events
# Create a hist trigger that emits the synthetic rss_stat_throttled # event only when the rss size crosses a 512KB boundary. echo 'hist:keys=mm_id,member:bucket=size/0x80000:onchange($bucket) .rss_stat_throttled(mm_id,curr,member,size)' >> events/kmem/rss_stat/trigger
------ Test Results ------ Histograms can also be used to evaluate the effectiveness of this throttling by noting the Total Hits on each trigger:
echo 'hist:keys=common_pid' >> events/sched/sched_switch/trigger echo 'hist:keys=common_pid' >> events/kmem/rss_stat/trigger echo 'hist:keys=common_pid' >> events/synthetic/rss_stat_throttled/trigger
Allowing the above example (512KB granularity) run for 5 minutes on an arm64 device with 5.10 kernel:
sched_switch : total hits = 147153 rss_stat : total hits = 38863 rss_stat_throttled: total hits = 2409
The synthetic rss_stat_throttled event is ~16x less frequent than the rss_stat event when using a 512KB granularity.
The results are more pronounced when rss size is changing at a higher rate in small increments. For instance the following results were obtained by recording the hits on the above events for a run of Android's lmkd_unit_test [2], which continually forks processes that map anonymous memory until there is an oom kill:
sched_switch : total hits = 148832 rss_stat : total hits = 4754802 rss_stat_throttled: total hits = 96214
In this stress test, the synthetic rss_stat_throttled event is ~50x less frequent than the rss_stat event when using a 512KB granularity.
[1] https://lore.kernel.org/lkml/20190903200905.198642-1-joel@joelfernandes.org/ [2] https://cs.android.com/android/platform/superproject/+/master:system/memory/...
Kalesh Singh (5): tracing: Add support for creating hist trigger variables from literal tracing: Add division and multiplication support for hist triggers tracing: Fix operator precedence for hist triggers expression tracing/selftests: Add tests for hist trigger expression parsing tracing/histogram: Document expression arithmetic and constants
Documentation/trace/histogram.rst | 14 + kernel/trace/trace_events_hist.c | 318 +++++++++++++++--- .../testing/selftests/ftrace/test.d/functions | 4 +- .../trigger/trigger-hist-expressions.tc | 74 ++++ 4 files changed, 358 insertions(+), 52 deletions(-) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-expressions.tc
base-commit: d9abdee5fd5abffd0e763e52fbfa3116de167822
Currently hist trigger expressions don't support the use of numeric literals: e.g. echo 'hist:keys=common_pid:x=$y-1234' --> is not valid expression syntax
Having the ability to use numeric constants in hist triggers supports a wider range of expressions for creating variables.
Add support for creating trace event histogram variables from numeric literals.
e.g. echo 'hist:keys=common_pid:x=1234,y=size-1024' >> event/trigger
A negative numeric constant is created, using unary minus operator (parentheses are required).
e.g. echo 'hist:keys=common_pid:z=-(2)' >> event/trigger
Constants can be used with division/multiplication (added in the next patch in this series) to implement granularity filters for frequent trace events. For instance we can limit emitting the rss_stat trace event to when there is a 512KB cross over in the rss size:
# Create a synthetic event to monitor instead of the high frequency # rss_stat event echo 'rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size' >> tracing/synthetic_events
# Create a hist trigger that emits the synthetic rss_stat_throttled # event only when the rss size crosses a 512KB boundary. echo 'hist:keys=keys=mm_id,member:bucket=size/0x80000:onchange($bucket) .rss_stat_throttled(mm_id,curr,member,size)' >> events/kmem/rss_stat/trigger
A use case for using constants with addition/subtraction is not yet known, but for completeness the use of constants are supported for all operators.
Signed-off-by: Kalesh Singh kaleshsingh@google.com Reviewed-by: Namhyung Kim namhyung@kernel.org ---
Changes in v2: - Add description of use case for constants in arithmetic operations, per Steven Rostedt - Add Namhyung's Reviewed-by
kernel/trace/trace_events_hist.c | 80 +++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index f01e442716e2..8563a2d51f65 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -66,7 +66,9 @@ C(EMPTY_SORT_FIELD, "Empty sort field"), \ C(TOO_MANY_SORT_FIELDS, "Too many sort fields (Max = 2)"), \ C(INVALID_SORT_FIELD, "Sort field must be a key or a val"), \ - C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), + C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), \ + C(TOO_MANY_CONSTS, "Too many constants defined"), \ + C(EXPECT_NUMBER, "Expecting numeric literal"),
#undef C #define C(a, b) HIST_ERR_##a @@ -89,6 +91,8 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, #define HIST_FIELD_OPERANDS_MAX 2 #define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) #define HIST_ACTIONS_MAX 8 +#define HIST_CONST_MAX 4 +#define HIST_CONST_DIGITS_MAX 21
enum field_op_id { FIELD_OP_NONE, @@ -152,6 +156,9 @@ struct hist_field { bool read_once;
unsigned int var_str_idx; + + /* Numeric literals are represented as u64 */ + u64 constant; };
static u64 hist_field_none(struct hist_field *field, @@ -163,6 +170,15 @@ static u64 hist_field_none(struct hist_field *field, return 0; }
+static u64 hist_field_const(struct hist_field *field, + struct tracing_map_elt *elt, + struct trace_buffer *buffer, + struct ring_buffer_event *rbe, + void *event) +{ + return field->constant; +} + static u64 hist_field_counter(struct hist_field *field, struct tracing_map_elt *elt, struct trace_buffer *buffer, @@ -341,6 +357,7 @@ enum hist_field_flags { HIST_FIELD_FL_CPU = 1 << 15, HIST_FIELD_FL_ALIAS = 1 << 16, HIST_FIELD_FL_BUCKET = 1 << 17, + HIST_FIELD_FL_CONST = 1 << 18, };
struct var_defs { @@ -410,6 +427,7 @@ struct hist_trigger_data { struct field_var *save_vars[SYNTH_FIELDS_MAX]; unsigned int n_save_vars; unsigned int n_save_var_str; + unsigned int n_constants; };
struct action_data; @@ -1516,6 +1534,12 @@ static void expr_field_str(struct hist_field *field, char *expr) { if (field->flags & HIST_FIELD_FL_VAR_REF) strcat(expr, "$"); + else if (field->flags & HIST_FIELD_FL_CONST) { + char str[HIST_CONST_DIGITS_MAX]; + + snprintf(str, HIST_CONST_DIGITS_MAX, "%llu", field->constant); + strcat(expr, str); + }
strcat(expr, hist_field_name(field, 0));
@@ -1689,6 +1713,15 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, goto out; }
+ if (flags & HIST_FIELD_FL_CONST) { + hist_field->fn = hist_field_const; + hist_field->size = sizeof(u64); + hist_field->type = kstrdup("u64", GFP_KERNEL); + if (!hist_field->type) + goto free; + goto out; + } + if (flags & HIST_FIELD_FL_STACKTRACE) { hist_field->fn = hist_field_none; goto out; @@ -2090,6 +2123,35 @@ static struct hist_field *create_alias(struct hist_trigger_data *hist_data, return alias; }
+static struct hist_field *parse_const(struct hist_trigger_data *hist_data, + char *str, char *var_name, + unsigned long *flags) +{ + struct trace_array *tr = hist_data->event_file->tr; + struct hist_field *field = NULL; + u64 constant; + + if (hist_data->n_constants >= HIST_CONST_MAX) { + hist_err(tr, HIST_ERR_TOO_MANY_CONSTS, errpos(str)); + return NULL; + } + + if (kstrtoull(str, 0, &constant)) { + hist_err(tr, HIST_ERR_EXPECT_NUMBER, errpos(str)); + return NULL; + } + + *flags |= HIST_FIELD_FL_CONST; + field = create_hist_field(hist_data, NULL, *flags, var_name); + if (!field) + return NULL; + + field->constant = constant; + hist_data->n_constants++; + + return field; +} + static struct hist_field *parse_atom(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long *flags, char *var_name) @@ -2100,6 +2162,15 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data, unsigned long buckets = 0; int ret = 0;
+ if (isdigit(str[0])) { + hist_field = parse_const(hist_data, str, var_name, flags); + if (!hist_field) { + ret = -EINVAL; + goto out; + } + return hist_field; + } + s = strchr(str, '.'); if (s) { s = strchr(++s, '.'); @@ -4950,6 +5021,8 @@ static void hist_field_debug_show_flags(struct seq_file *m,
if (flags & HIST_FIELD_FL_ALIAS) seq_puts(m, " HIST_FIELD_FL_ALIAS\n"); + else if (flags & HIST_FIELD_FL_CONST) + seq_puts(m, " HIST_FIELD_FL_CONST\n"); }
static int hist_field_debug_show(struct seq_file *m, @@ -4971,6 +5044,9 @@ static int hist_field_debug_show(struct seq_file *m, field->var.idx); }
+ if (field->flags & HIST_FIELD_FL_CONST) + seq_printf(m, " constant: %llu\n", field->constant); + if (field->flags & HIST_FIELD_FL_ALIAS) seq_printf(m, " var_ref_idx (into hist_data->var_refs[]): %u\n", field->var_ref_idx); @@ -5213,6 +5289,8 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)
if (hist_field->flags & HIST_FIELD_FL_CPU) seq_puts(m, "common_cpu"); + else if (hist_field->flags & HIST_FIELD_FL_CONST) + seq_printf(m, "%llu", hist_field->constant); else if (field_name) { if (hist_field->flags & HIST_FIELD_FL_VAR_REF || hist_field->flags & HIST_FIELD_FL_ALIAS)
On Tue, 19 Oct 2021 18:31:38 -0700 Kalesh Singh kaleshsingh@google.com wrote:
@@ -89,6 +91,8 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, #define HIST_FIELD_OPERANDS_MAX 2 #define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) #define HIST_ACTIONS_MAX 8 +#define HIST_CONST_MAX 4
BTW, why is there a limit to the number of constants?
-- Steve
+#define HIST_CONST_DIGITS_MAX 21
On Wed, Oct 20, 2021 at 8:32 AM Steven Rostedt rostedt@goodmis.org wrote:
On Tue, 19 Oct 2021 18:31:38 -0700 Kalesh Singh kaleshsingh@google.com wrote:
@@ -89,6 +91,8 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, #define HIST_FIELD_OPERANDS_MAX 2 #define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) #define HIST_ACTIONS_MAX 8 +#define HIST_CONST_MAX 4
BTW, why is there a limit to the number of constants?
The motivation was to limit the number of hist_fields that can be created for constants. These are also indirectly limited by the max number of subexpressions (3) and the max number of vars/keys that can be defined, so I don't feel strongly about keeping it.
Thanks, Kalesh
-- Steve
+#define HIST_CONST_DIGITS_MAX 21
On Wed, 20 Oct 2021 08:55:23 -0700 Kalesh Singh kaleshsingh@google.com wrote:
BTW, why is there a limit to the number of constants?
The motivation was to limit the number of hist_fields that can be created for constants. These are also indirectly limited by the max number of subexpressions (3) and the max number of vars/keys that can be defined, so I don't feel strongly about keeping it.
Yeah. I see the limiting factor is the number of expressions and such. And as described in my other email, if you have two constants for a single operand, you can easily combine them into one.
-- Steve
Adds basic support for division and multiplication operations for hist trigger variable expressions.
For simplicity this patch only supports, division and multiplication for a single operation expression (e.g. x=$a/$b), as currently expressions are always evaluated right to left. This can lead to some incorrect results:
e.g. echo 'hist:keys=common_pid:x=8-4-2' >> event/trigger
8-4-2 should evaluate to 2 i.e. (8-4)-2 but currently x evaluate to 6 i.e. 8-(4-2)
Multiplication and division in sub-expressions will work correctly, once correct operator precedence support is added (See next patch in this series).
For the undefined case of division by 0, the histogram expression evaluates to (u64)(-1). Since this cannot be detected when the expression is created, it is the responsibility of the user to be aware and account for this possibility.
Examples: echo 'hist:keys=common_pid:a=8,b=4,x=$a/$b' \ >> event/trigger
echo 'hist:keys=common_pid:y=5*$b' \ >> event/trigger
Signed-off-by: Kalesh Singh kaleshsingh@google.com ---
Changes in v2: - Use div64 helper in hist_field_div() to avoid faults on x86 32-bit machines, per Steven Rostedt
kernel/trace/trace_events_hist.c | 72 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8563a2d51f65..9415ee65acc0 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -99,6 +99,8 @@ enum field_op_id { FIELD_OP_PLUS, FIELD_OP_MINUS, FIELD_OP_UNARY_MINUS, + FIELD_OP_DIV, + FIELD_OP_MULT, };
/* @@ -287,6 +289,40 @@ static u64 hist_field_minus(struct hist_field *hist_field, return val1 - val2; }
+static u64 hist_field_div(struct hist_field *hist_field, + struct tracing_map_elt *elt, + struct trace_buffer *buffer, + struct ring_buffer_event *rbe, + void *event) +{ + struct hist_field *operand1 = hist_field->operands[0]; + struct hist_field *operand2 = hist_field->operands[1]; + + u64 val1 = operand1->fn(operand1, elt, buffer, rbe, event); + u64 val2 = operand2->fn(operand2, elt, buffer, rbe, event); + + /* Return -1 for the undefined case */ + if (!val2) + return -1; + + return div64_u64(val1, val2); +} + +static u64 hist_field_mult(struct hist_field *hist_field, + struct tracing_map_elt *elt, + struct trace_buffer *buffer, + struct ring_buffer_event *rbe, + void *event) +{ + struct hist_field *operand1 = hist_field->operands[0]; + struct hist_field *operand2 = hist_field->operands[1]; + + u64 val1 = operand1->fn(operand1, elt, buffer, rbe, event); + u64 val2 = operand2->fn(operand2, elt, buffer, rbe, event); + + return val1 * val2; +} + static u64 hist_field_unary_minus(struct hist_field *hist_field, struct tracing_map_elt *elt, struct trace_buffer *buffer, @@ -1595,6 +1631,12 @@ static char *expr_str(struct hist_field *field, unsigned int level) case FIELD_OP_PLUS: strcat(expr, "+"); break; + case FIELD_OP_DIV: + strcat(expr, "/"); + break; + case FIELD_OP_MULT: + strcat(expr, "*"); + break; default: kfree(expr); return NULL; @@ -1610,7 +1652,7 @@ static int contains_operator(char *str) enum field_op_id field_op = FIELD_OP_NONE; char *op;
- op = strpbrk(str, "+-"); + op = strpbrk(str, "+-/*"); if (!op) return FIELD_OP_NONE;
@@ -1631,6 +1673,12 @@ static int contains_operator(char *str) case '+': field_op = FIELD_OP_PLUS; break; + case '/': + field_op = FIELD_OP_DIV; + break; + case '*': + field_op = FIELD_OP_MULT; + break; default: break; } @@ -2370,10 +2418,26 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, case FIELD_OP_PLUS: sep = "+"; break; + case FIELD_OP_DIV: + sep = "/"; + break; + case FIELD_OP_MULT: + sep = "*"; + break; default: goto free; }
+ /* + * Multiplication and division are only supported in single operator + * expressions, since the expression is always evaluated from right + * to left. + */ + if ((field_op == FIELD_OP_DIV || field_op == FIELD_OP_MULT) && level > 0) { + hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); + return ERR_PTR(-EINVAL); + } + operand1_str = strsep(&str, sep); if (!operand1_str || !str) goto free; @@ -2445,6 +2509,12 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, case FIELD_OP_PLUS: expr->fn = hist_field_plus; break; + case FIELD_OP_DIV: + expr->fn = hist_field_div; + break; + case FIELD_OP_MULT: + expr->fn = hist_field_mult; + break; default: ret = -EINVAL; goto free;
On Tue, 19 Oct 2021 18:31:39 -0700 Kalesh Singh kaleshsingh@google.com wrote:
+static u64 hist_field_div(struct hist_field *hist_field,
struct tracing_map_elt *elt,
struct trace_buffer *buffer,
struct ring_buffer_event *rbe,
void *event)
+{
- struct hist_field *operand1 = hist_field->operands[0];
- struct hist_field *operand2 = hist_field->operands[1];
- u64 val1 = operand1->fn(operand1, elt, buffer, rbe, event);
- u64 val2 = operand2->fn(operand2, elt, buffer, rbe, event);
- /* Return -1 for the undefined case */
- if (!val2)
return -1;
- return div64_u64(val1, val2);
+}
I wonder if you should add a shift operator as well?
I mean, if for some reason you want to divide by a power of two, then why us the division. Especially if this is on a 32 bit machine.
Of course, the parsing could detect that. If the divisor is a constant. Or we could even optimize the above with:
if (!val2) return -1;
if (!(val2 & (val2 - 1)) return val1 >> __ffs64(val2);
Which should be faster than a divide, and even if it isn't a power of two, the subtract and & should be in the noise compared to the divide.
Note, the above can be added to this. I'm not suggesting changing this patch.
-- Steve
On Tue, Oct 19, 2021 at 7:28 PM Steven Rostedt rostedt@goodmis.org wrote:
On Tue, 19 Oct 2021 18:31:39 -0700 Kalesh Singh kaleshsingh@google.com wrote:
+static u64 hist_field_div(struct hist_field *hist_field,
struct tracing_map_elt *elt,
struct trace_buffer *buffer,
struct ring_buffer_event *rbe,
void *event)
+{
struct hist_field *operand1 = hist_field->operands[0];
struct hist_field *operand2 = hist_field->operands[1];
u64 val1 = operand1->fn(operand1, elt, buffer, rbe, event);
u64 val2 = operand2->fn(operand2, elt, buffer, rbe, event);
/* Return -1 for the undefined case */
if (!val2)
return -1;
return div64_u64(val1, val2);
+}
I wonder if you should add a shift operator as well?
I mean, if for some reason you want to divide by a power of two, then why us the division. Especially if this is on a 32 bit machine.
Of course, the parsing could detect that. If the divisor is a constant. Or we could even optimize the above with:
if (!val2) return -1; if (!(val2 & (val2 - 1)) return val1 >> __ffs64(val2);
Which should be faster than a divide, and even if it isn't a power of two, the subtract and & should be in the noise compared to the divide.
Note, the above can be added to this. I'm not suggesting changing this patch.
Is it worth adding something like this for the multiplication case as well?
- Kalesh
-- Steve
On Wed, 20 Oct 2021 07:54:59 -0700 Kalesh Singh kaleshsingh@google.com wrote:
Is it worth adding something like this for the multiplication case as well?
No, multiplication is a pretty fast operation, and the added branches to test would cause more overhead than what you would save. But, division is a very slow operation, and I believe that even with the extra branches it would still help.
If we do this, it should be a separate patch anyway, where we can actual do measurements to see if there was an improvement, and revert if not.
-- Steve
On Wed, Oct 20, 2021 at 8:13 AM Steven Rostedt rostedt@goodmis.org wrote:
On Wed, 20 Oct 2021 07:54:59 -0700 Kalesh Singh kaleshsingh@google.com wrote:
Is it worth adding something like this for the multiplication case as well?
No, multiplication is a pretty fast operation, and the added branches to test would cause more overhead than what you would save. But, division is a very slow operation, and I believe that even with the extra branches it would still help.
If we do this, it should be a separate patch anyway, where we can actual do measurements to see if there was an improvement, and revert if not.
Sounds good. Thanks for the clarification Steve.
- Kalesh
-- Steve
The current histogram expression evaluation logic evaluates the expression from right to left. This can lead to incorrect results if the operations are not associative (as is the case for subtraction and, the now added, division operators). e.g. 16-8-4-2 should be 2 not 10 --> 16-8-4-2 = ((16-8)-4)-2 64/8/4/2 should be 1 not 16 --> 64/8/4/2 = ((64/8)/4)/2
Division and multiplication are currently limited to single operation expression due to operator precedence support not yet implemented.
Rework the expression parsing to support the correct evaluation of expressions containing operators of different precedences; and fix the associativity error by evaluating expressions with operators of the same precedence from left to right.
Examples: (1) echo 'hist:keys=common_pid:a=8,b=4,c=2,d=1,w=$a-$b-$c-$d' \ >> event/trigger (2) echo 'hist:keys=common_pid:x=$a/$b/3/2' >> event/trigger (3) echo 'hist:keys=common_pid:y=$a+10/$c*1024' >> event/trigger (4) echo 'hist:keys=common_pid:z=$a/$b+$c*$d' >> event/trigger
Signed-off-by: Kalesh Singh kaleshsingh@google.com Reviewed-by: Namhyung Kim namhyung@kernel.org ---
Changed in v2: - Add Namhyung's Reviewed-by
kernel/trace/trace_events_hist.c | 210 ++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 70 deletions(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 9415ee65acc0..9205cfe120e8 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -68,7 +68,9 @@ C(INVALID_SORT_FIELD, "Sort field must be a key or a val"), \ C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), \ C(TOO_MANY_CONSTS, "Too many constants defined"), \ - C(EXPECT_NUMBER, "Expecting numeric literal"), + C(EXPECT_NUMBER, "Expecting numeric literal"), \ + C(UNARY_MINUS_SUBEXPR, "Unary minus not supported in sub-expressions"), \ + C(SYM_OFFSET_SUBEXPR, ".sym-offset not supported in sub-expressions"),
#undef C #define C(a, b) HIST_ERR_##a @@ -1647,40 +1649,96 @@ static char *expr_str(struct hist_field *field, unsigned int level) return expr; }
-static int contains_operator(char *str) +/* + * If field_op != FIELD_OP_NONE, *sep points to the root operator + * of the expression tree to be evaluated. + */ +static int contains_operator(char *str, char **sep) { enum field_op_id field_op = FIELD_OP_NONE; - char *op; + char *minus_op, *plus_op, *div_op, *mult_op; + + + /* + * Report the last occurrence of the operators first, so that the + * expression is evaluated left to right. This is important since + * subtraction and division are not associative. + * + * e.g + * 64/8/4/2 is 1, i.e 64/8/4/2 = ((64/8)/4)/2 + * 14-7-5-2 is 0, i.e 14-7-5-2 = ((14-7)-5)-2 + */
- op = strpbrk(str, "+-/*"); - if (!op) - return FIELD_OP_NONE; + /* + * First, find lower precedence addition and subtraction + * since the expression will be evaluated recursively. + */ + minus_op = strrchr(str, '-'); + if (minus_op) { + /* Unfortunately, the modifier ".sym-offset" can confuse things. */ + if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11)) + goto out;
- switch (*op) { - case '-': /* - * Unfortunately, the modifier ".sym-offset" - * can confuse things. + * Unary minus is not supported in sub-expressions. If + * present, it is always the next root operator. */ - if (op - str >= 4 && !strncmp(op - 4, ".sym-offset", 11)) - return FIELD_OP_NONE; - - if (*str == '-') + if (minus_op == str) { field_op = FIELD_OP_UNARY_MINUS; - else - field_op = FIELD_OP_MINUS; - break; - case '+': - field_op = FIELD_OP_PLUS; - break; - case '/': + goto out; + } + + field_op = FIELD_OP_MINUS; + } + + plus_op = strrchr(str, '+'); + if (plus_op || minus_op) { + /* + * For operators of the same precedence use to rightmost as the + * root, so that the expression is evaluated left to right. + */ + if (plus_op > minus_op) + field_op = FIELD_OP_PLUS; + goto out; + } + + /* + * Multiplication and division have higher precedence than addition and + * subtraction. + */ + div_op = strrchr(str, '/'); + if (div_op) field_op = FIELD_OP_DIV; - break; - case '*': + + mult_op = strrchr(str, '*'); + /* + * For operators of the same precedence use to rightmost as the + * root, so that the expression is evaluated left to right. + */ + if (mult_op > div_op) field_op = FIELD_OP_MULT; - break; - default: - break; + +out: + if (sep) { + switch (field_op) { + case FIELD_OP_UNARY_MINUS: + case FIELD_OP_MINUS: + *sep = minus_op; + break; + case FIELD_OP_PLUS: + *sep = plus_op; + break; + case FIELD_OP_DIV: + *sep = div_op; + break; + case FIELD_OP_MULT: + *sep = mult_op; + break; + case FIELD_OP_NONE: + default: + *sep = NULL; + break; + } }
return field_op; @@ -2006,7 +2064,7 @@ static char *field_name_from_var(struct hist_trigger_data *hist_data,
if (strcmp(var_name, name) == 0) { field = hist_data->attrs->var_defs.expr[i]; - if (contains_operator(field) || is_var_ref(field)) + if (contains_operator(field, NULL) || is_var_ref(field)) continue; return field; } @@ -2275,21 +2333,24 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags, - char *var_name, unsigned int level); + char *var_name, unsigned int *n_subexprs);
static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags, - char *var_name, unsigned int level) + char *var_name, unsigned int *n_subexprs) { struct hist_field *operand1, *expr = NULL; unsigned long operand_flags; int ret = 0; char *s;
+ /* Unary minus operator, increment n_subexprs */ + ++*n_subexprs; + /* we support only -(xxx) i.e. explicit parens required */
- if (level > 3) { + if (*n_subexprs > 3) { hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); ret = -EINVAL; goto free; @@ -2306,8 +2367,16 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, }
s = strrchr(str, ')'); - if (s) + if (s) { + /* unary minus not supported in sub-expressions */ + if (*(s+1) != '\0') { + hist_err(file->tr, HIST_ERR_UNARY_MINUS_SUBEXPR, + errpos(str)); + ret = -EINVAL; + goto free; + } *s = '\0'; + } else { ret = -EINVAL; /* no closing ')' */ goto free; @@ -2321,7 +2390,7 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, }
operand_flags = 0; - operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level); + operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand1)) { ret = PTR_ERR(operand1); goto free; @@ -2391,60 +2460,61 @@ static int check_expr_operands(struct trace_array *tr, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags, - char *var_name, unsigned int level) + char *var_name, unsigned int *n_subexprs) { struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL; unsigned long operand_flags; int field_op, ret = -EINVAL; char *sep, *operand1_str;
- if (level > 3) { + if (*n_subexprs > 3) { hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); return ERR_PTR(-EINVAL); }
- field_op = contains_operator(str); + /* + * ".sym-offset" in expressions has no effect on their evaluation, + * but can confuse operator parsing. + */ + if (*n_subexprs == 0) { + sep = strstr(str, ".sym-offset"); + if (sep) { + *sep = '\0'; + if (strpbrk(str, "+-/*") || strpbrk(sep + 11, "+-/*")) { + *sep = '.'; + hist_err(file->tr, HIST_ERR_SYM_OFFSET_SUBEXPR, + errpos(sep)); + return ERR_PTR(-EINVAL); + } + *sep = '.'; + } + } + + field_op = contains_operator(str, &sep);
if (field_op == FIELD_OP_NONE) return parse_atom(hist_data, file, str, &flags, var_name);
if (field_op == FIELD_OP_UNARY_MINUS) - return parse_unary(hist_data, file, str, flags, var_name, ++level); + return parse_unary(hist_data, file, str, flags, var_name, n_subexprs);
- switch (field_op) { - case FIELD_OP_MINUS: - sep = "-"; - break; - case FIELD_OP_PLUS: - sep = "+"; - break; - case FIELD_OP_DIV: - sep = "/"; - break; - case FIELD_OP_MULT: - sep = "*"; - break; - default: - goto free; - } + /* Binary operator found, increment n_subexprs */ + ++*n_subexprs;
- /* - * Multiplication and division are only supported in single operator - * expressions, since the expression is always evaluated from right - * to left. - */ - if ((field_op == FIELD_OP_DIV || field_op == FIELD_OP_MULT) && level > 0) { - hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); - return ERR_PTR(-EINVAL); - } + /* Split the expression string at the root operator */ + if (!sep) + goto free; + *sep = '\0'; + operand1_str = str; + str = sep+1;
- operand1_str = strsep(&str, sep); if (!operand1_str || !str) goto free;
operand_flags = 0; - operand1 = parse_atom(hist_data, file, operand1_str, - &operand_flags, NULL); + + /* LHS of string is an expression e.g. a+b in a+b+c */ + operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand1)) { ret = PTR_ERR(operand1); operand1 = NULL; @@ -2456,9 +2526,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, goto free; }
- /* rest of string could be another expression e.g. b+c in a+b+c */ + /* RHS of string is another expression e.g. c in a+b+c */ operand_flags = 0; - operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level); + operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand2)) { ret = PTR_ERR(operand2); operand2 = NULL; @@ -3892,9 +3962,9 @@ static int __create_val_field(struct hist_trigger_data *hist_data, unsigned long flags) { struct hist_field *hist_field; - int ret = 0; + int ret = 0, n_subexprs = 0;
- hist_field = parse_expr(hist_data, file, field_str, flags, var_name, 0); + hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs); if (IS_ERR(hist_field)) { ret = PTR_ERR(hist_field); goto out; @@ -4035,7 +4105,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, struct hist_field *hist_field = NULL; unsigned long flags = 0; unsigned int key_size; - int ret = 0; + int ret = 0, n_subexprs = 0;
if (WARN_ON(key_idx >= HIST_FIELDS_MAX)) return -EINVAL; @@ -4048,7 +4118,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, hist_field = create_hist_field(hist_data, NULL, flags, NULL); } else { hist_field = parse_expr(hist_data, file, field_str, flags, - NULL, 0); + NULL, &n_subexprs); if (IS_ERR(hist_field)) { ret = PTR_ERR(hist_field); goto out;
On Tue, 19 Oct 2021 18:31:40 -0700 Kalesh Singh kaleshsingh@google.com wrote:
- minus_op = strrchr(str, '-');
- if (minus_op) {
/* Unfortunately, the modifier ".sym-offset" can confuse things. */
if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11))
goto out;
I was thinking about this, and perhaps we can add this later, but we could just replace all ".sym-offset" with ".symXoffset" after receiving it from the user. Then it won't be an issue during prasing.
-- Steve
On Wed, Oct 20, 2021 at 7:28 AM Steven Rostedt rostedt@goodmis.org wrote:
On Tue, 19 Oct 2021 18:31:40 -0700 Kalesh Singh kaleshsingh@google.com wrote:
minus_op = strrchr(str, '-');
if (minus_op) {
/* Unfortunately, the modifier ".sym-offset" can confuse things. */
if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11))
goto out;
I was thinking about this, and perhaps we can add this later, but we could just replace all ".sym-offset" with ".symXoffset" after receiving it from the user. Then it won't be an issue during prasing.
That's a good idea. It would clean things up a bit and avoid bailing out if the user has a sym-offest in an expression string. I can send a separate patch for this.
-- Steve
On Wed, 20 Oct 2021 08:06:26 -0700 Kalesh Singh kaleshsingh@google.com wrote:
That's a good idea. It would clean things up a bit and avoid bailing out if the user has a sym-offest in an expression string. I can send a separate patch for this.
+1
-- Steve
On Tue, 19 Oct 2021 18:31:40 -0700 Kalesh Singh kaleshsingh@google.com wrote:
@@ -2391,60 +2460,61 @@ static int check_expr_operands(struct trace_array *tr, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags,
char *var_name, unsigned int level)
char *var_name, unsigned int *n_subexprs)
{ struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL; unsigned long operand_flags; int field_op, ret = -EINVAL; char *sep, *operand1_str;
- if (level > 3) {
- if (*n_subexprs > 3) {
Why limit the sub expressions, and not just keep the limit of the level of recursion. We allow 3 levels of recursion, but we could have more than 3 sub expressions.
If we have: a * b + c / d - e * f / h
It would break down into: - + / * / * h a b c d e f
Which I believe is 6 "sub expressions", but never goes more than three deep in recursion:
"a * b + c / d - e * f / h"
Step 1:
op = "-" operand1 = "a * b + c / d" operand2 = "e * f / h"
Process operand1: (recursion level 1)
op = "+" operand1a = "a * b" operand2a = "c / d"
Process operand1a: (recursion level 2)
op = "*" operand1b = "a" operand2b = "b"
return;
Process operand1b: (recursion level 2)
op = "/" operand1b = "c" operand2b = "d"
return;
return;
Process operand2: (recursion level 1)
op = "/" operand1c = "e * f" operand2c = "h"
Process operand1c: (recursion level 2)
op = "*" operand1c = "e" operand2c = "f"
return;
return;
- /* LHS of string is an expression e.g. a+b in a+b+c */
- operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand1)) { ret = PTR_ERR(operand1); operand1 = NULL;
I wonder if we should look for optimizations, in case of operand1 and operand2 are both constants?
Just perform the function, and convert it into a constant as well.
-- Steve
On Wed, Oct 20, 2021 at 8:48 AM Steven Rostedt rostedt@goodmis.org wrote:
On Tue, 19 Oct 2021 18:31:40 -0700 Kalesh Singh kaleshsingh@google.com wrote:
@@ -2391,60 +2460,61 @@ static int check_expr_operands(struct trace_array *tr, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags,
char *var_name, unsigned int level)
char *var_name, unsigned int *n_subexprs)
{ struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL; unsigned long operand_flags; int field_op, ret = -EINVAL; char *sep, *operand1_str;
if (level > 3) {
if (*n_subexprs > 3) {
Why limit the sub expressions, and not just keep the limit of the level of recursion. We allow 3 levels of recursion, but we could have more than 3 sub expressions.
The main reason for this is that it's predictable behavior form the user's perspective. Before this patch the recursion always walked down a single branch so limiting by level worked out the same as limiting by sub expressions and is in line with the error the user would see ("Too many sub-expressions (3 max)"). Now that we take multiple paths in the recursion, using the level to reflect the number of sub-expressions would lead to only seeing the error in some of the cases (Sometimes we allow 4, 5, 6 sub-expressions depending on how balanced the tree is, and sometimes we error out on 4 - when the tree is list-like). Limiting by sub-expression keeps this consistent (always error out if we have more than 3 sub-expressions) and is in line with the previous behavior.
- Kalesh
If we have: a * b + c / d - e * f / h
It would break down into: - + /
/ * h
a b c d e f
Which I believe is 6 "sub expressions", but never goes more than three deep in recursion:
"a * b + c / d - e * f / h"
Step 1:
op = "-" operand1 = "a * b + c / d" operand2 = "e * f / h"
Process operand1: (recursion level 1)
op = "+" operand1a = "a * b" operand2a = "c / d"
Process operand1a: (recursion level 2)
op = "*" operand1b = "a" operand2b = "b"
return;
Process operand1b: (recursion level 2)
op = "/" operand1b = "c" operand2b = "d"
return;
return;
Process operand2: (recursion level 1)
op = "/" operand1c = "e * f" operand2c = "h"
Process operand1c: (recursion level 2)
op = "*" operand1c = "e" operand2c = "f"
return;
return;
/* LHS of string is an expression e.g. a+b in a+b+c */
operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand1)) { ret = PTR_ERR(operand1); operand1 = NULL;
I wonder if we should look for optimizations, in case of operand1 and operand2 are both constants?
Just perform the function, and convert it into a constant as well.
-- Steve
On Wed, 20 Oct 2021 09:11:27 -0700 Kalesh Singh kaleshsingh@google.com wrote:
The main reason for this is that it's predictable behavior form the user's perspective. Before this patch the recursion always walked down a single branch so limiting by level worked out the same as limiting by sub expressions and is in line with the error the user would see ("Too many sub-expressions (3 max)"). Now that we take multiple paths in the recursion, using the level to reflect the number of sub-expressions would lead to only seeing the error in some of the cases (Sometimes we allow 4, 5, 6 sub-expressions depending on how balanced the tree is, and sometimes we error out on 4 - when the tree is list-like). Limiting by sub-expression keeps this consistent (always error out if we have more than 3 sub-expressions) and is in line with the previous behavior.
I'm fine with that. If we want to improve this in the future then fine. We can always extend, as that doesn't break user API.
So we can keep it as is.
-- Steve
On Wed, Oct 20, 2021 at 8:48 AM Steven Rostedt rostedt@goodmis.org wrote:
On Tue, 19 Oct 2021 18:31:40 -0700 Kalesh Singh kaleshsingh@google.com wrote:
@@ -2391,60 +2460,61 @@ static int check_expr_operands(struct trace_array *tr, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, struct trace_event_file *file, char *str, unsigned long flags,
char *var_name, unsigned int level)
char *var_name, unsigned int *n_subexprs)
{ struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL; unsigned long operand_flags; int field_op, ret = -EINVAL; char *sep, *operand1_str;
if (level > 3) {
if (*n_subexprs > 3) {
Why limit the sub expressions, and not just keep the limit of the level of recursion. We allow 3 levels of recursion, but we could have more than 3 sub expressions.
If we have: a * b + c / d - e * f / h
It would break down into: - + /
/ * h
a b c d e f
Which I believe is 6 "sub expressions", but never goes more than three deep in recursion:
"a * b + c / d - e * f / h"
Step 1:
op = "-" operand1 = "a * b + c / d" operand2 = "e * f / h"
Process operand1: (recursion level 1)
op = "+" operand1a = "a * b" operand2a = "c / d"
Process operand1a: (recursion level 2)
op = "*" operand1b = "a" operand2b = "b"
return;
Process operand1b: (recursion level 2)
op = "/" operand1b = "c" operand2b = "d"
return;
return;
Process operand2: (recursion level 1)
op = "/" operand1c = "e * f" operand2c = "h"
Process operand1c: (recursion level 2)
op = "*" operand1c = "e" operand2c = "f"
return;
return;
/* LHS of string is an expression e.g. a+b in a+b+c */
operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs); if (IS_ERR(operand1)) { ret = PTR_ERR(operand1); operand1 = NULL;
I wonder if we should look for optimizations, in case of operand1 and operand2 are both constants?
Just perform the function, and convert it into a constant as well.
I think we achieve something like this by propagating up the HIST_FIELD_FL_CONST flag. Thanks for the suggestions. I'll update this in the next version.
Thanks, Kalesh
-- Steve
Add tests for the parsing of hist trigger expressions; and to validate expression evaluation.
Signed-off-by: Kalesh Singh kaleshsingh@google.com Reviewed-by: Namhyung Kim namhyung@kernel.org ---
Changes in v2: - Add Namhyung's Reviewed-by - Update comment to clarify err_pos in "Too many subexpressions" test
.../testing/selftests/ftrace/test.d/functions | 4 +- .../trigger/trigger-hist-expressions.tc | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-expressions.tc
diff --git a/tools/testing/selftests/ftrace/test.d/functions b/tools/testing/selftests/ftrace/test.d/functions index 000fd05e84b1..1855a63559ad 100644 --- a/tools/testing/selftests/ftrace/test.d/functions +++ b/tools/testing/selftests/ftrace/test.d/functions @@ -16,13 +16,13 @@ reset_tracer() { # reset the current tracer
reset_trigger_file() { # remove action triggers first - grep -H ':on[^:]*(' $@ | + grep -H ':on[^:]*(' $@ | tac | while read line; do cmd=`echo $line | cut -f2- -d: | cut -f1 -d"["` file=`echo $line | cut -f1 -d:` echo "!$cmd" >> $file done - grep -Hv ^# $@ | + grep -Hv ^# $@ | tac | while read line; do cmd=`echo $line | cut -f2- -d: | cut -f1 -d"["` file=`echo $line | cut -f1 -d:` diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-expressions.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-expressions.tc new file mode 100644 index 000000000000..45399fce3483 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-expressions.tc @@ -0,0 +1,74 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test histogram expression parsing +# requires: set_event events/sched/sched_process_fork/trigger events/sched/sched_process_fork/hist error_log + + +fail() { #msg + echo $1 + exit_fail +} + +get_hist_var() { #var_name hist_path + hist_output=`grep -m1 "$1: " $2` + hitcount=`echo $hist_output | awk '{ for (i=1; i<=NF; ++i) { if ($i ~ "hitcount:") print $(i+1)} }'` + var_sum=`echo $hist_output | awk '{ for (i=1; i<=NF; ++i) { if ($i ~ "'$1':") print $(i+1)} }'` + var_val=$(( var_sum / hitcount )) + echo $var_val +} + +test_hist_expr() { # test_name expression expected_val + echo "Test hist trigger expressions - $1" + + echo "hist:keys=common_pid:x=$2" > events/sched/sched_process_fork/trigger + echo 'hist:keys=common_pid:vals=$x' >> events/sched/sched_process_fork/trigger + for i in `seq 1 10` ; do ( echo "forked" > /dev/null); done + + actual=`get_hist_var x events/sched/sched_process_fork/hist` + + if [ $actual != $3 ]; then + fail "Failed hist trigger expression evaluation: Expression: $2 Expected: $3, Actual: $actual" + fi + + reset_trigger +} + +check_error() { # test_name command-with-error-pos-by-^ + echo "Test hist trigger expressions - $1" + ftrace_errlog_check 'hist:sched:sched_process_fork' "$2" 'events/sched/sched_process_fork/trigger' + reset_trigger +} + +test_hist_expr "Variable assignment" "123" "123" + +test_hist_expr "Subtraction not associative" "16-8-4-2" "2" + +test_hist_expr "Division not associative" "64/8/4/2" "1" + +test_hist_expr "Same precedence operators (+,-) evaluated left to right" "16-8+4+2" "14" + +test_hist_expr "Same precedence operators (*,/) evaluated left to right" "4*3/2*2" "12" + +test_hist_expr "Multiplication evaluated before addition/subtraction" "4+3*2-2" "8" + +test_hist_expr "Division evaluated before addition/subtraction" "4+6/2-2" "5" + +# Division by zero returns -1 +test_hist_expr "Handles division by zero" "3/0" "-1" + +# err pos for "too many subexpressions" is dependent on where +# the last subexpression was detected. This can vary depending +# on how the expression tree was generated. +check_error "Too many subexpressions" 'hist:keys=common_pid:x=32+^10*3/20-4' +check_error "Too many subexpressions" 'hist:keys=common_pid:x=^1+2+3+4+5' + +check_error "Unary minus not supported in subexpression" 'hist:keys=common_pid:x=-(^1)+2' + +check_error "'.sym-offset' modifier not supported in subexpression" \ + 'hist:keys=common_pid:x=1+common_pid^.sym-offset' +check_error "'.sym-offset' modifier not supported in subexpression" \ + 'hist:keys=common_pid:x=common_pid^.sym-offset-2' +check_error "'.sym-offset' modifier not supported in subexpression" \ + 'hist:keys=common_pid:x=4/common_pid^.sym-offset*3' + +exit 0
Histogram expressions now support division, and multiplication in addition to the already supported subtraction and addition operators.
Numeric constants can also be used in a hist trigger expressions or assigned to a variable and used by refernce in an expression.
Signed-off-by: Kalesh Singh kaleshsingh@google.com Reviewed-by: Namhyung Kim namhyung@kernel.org --- Changes in v2: - Add Namhyung's Reviewed-by
Documentation/trace/histogram.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/Documentation/trace/histogram.rst b/Documentation/trace/histogram.rst index 533415644c54..e12699abaee8 100644 --- a/Documentation/trace/histogram.rst +++ b/Documentation/trace/histogram.rst @@ -1763,6 +1763,20 @@ using the same key and variable from yet another event::
# echo 'hist:key=pid:wakeupswitch_lat=$wakeup_lat+$switchtime_lat ...' >> event3/trigger
+Expressions support the use of addition, subtraction, multiplication and +division operators (+-*/). + +Note that division by zero always returns -1. + +Numeric constants can also be used directly in an expression:: + + # echo 'hist:keys=next_pid:timestamp_secs=common_timestamp/1000000 ...' >> event/trigger + +or assigned to a variable and referenced in a subsequent expression:: + + # echo 'hist:keys=next_pid:us_per_sec=1000000 ...' >> event/trigger + # echo 'hist:keys=next_pid:timestamp_secs=common_timestamp/$us_per_sec ...' >> event/trigger + 2.2.2 Synthetic Events ----------------------
linux-kselftest-mirror@lists.linaro.org