diff mbox series

[v2,2/5] tracing: Add division and multiplication support for hist triggers

Message ID 20211020013153.4106001-3-kaleshsingh@google.com (mailing list archive)
State New
Headers show
Series tracing: Extend histogram triggers expression parsing | expand

Commit Message

Kalesh Singh Oct. 20, 2021, 1:31 a.m. UTC
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(-)

Comments

Steven Rostedt Oct. 20, 2021, 2:27 a.m. UTC | #1
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
Kalesh Singh Oct. 20, 2021, 2:54 p.m. UTC | #2
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
Steven Rostedt Oct. 20, 2021, 3:13 p.m. UTC | #3
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
Kalesh Singh Oct. 20, 2021, 3:23 p.m. UTC | #4
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
diff mbox series

Patch

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;