[14/54] tracing: Add hist trigger timestamp support
diff mbox series

Message ID TY1PR01MB1692F0D1707729ADD0561D0896090@TY1PR01MB1692.jpnprd01.prod.outlook.com
State New
Headers show
Series
  • Untitled series #12751
Related show

Commit Message

Motai.Hirotaka@aj.MitsubishiElectric.co.jp Aug. 29, 2018, 12:17 p.m. UTC
Add support for a timestamp event field.  This is actually a 'pseudo-'
event field in that it behaves like it's part of the event record, but
is really part of the corresponding ring buffer event.

To make use of the timestamp field, users can specify
"common_timestamp" as a field name for any histogram.  Note that this
doesn't make much sense on its own either as either a key or value,
but needs to be supported even so, since follow-on patches will add
support for making use of this field in time deltas.  The
common_timestamp 'field' is not a bona fide event field - so you won't
find it in the event description - but rather it's a synthetic field
that can be used like a real field.

Note that the use of this field requires the ring buffer be put into
'absolute timestamp' mode, which saves the complete timestamp for each
event rather than an offset.  This mode will be enabled if and only if
a histogram makes use of the "common_timestamp" field.

Link: http://lkml.kernel.org/r/97afbd646ed146e26271f3458b4b33e16d7817c2.1516069914.git.tom.zanussi@linux.intel.com

Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com>
Signed-off-by: Baohong Liu <baohong.liu@intel.com>
[kasan use-after-free fix]
Signed-off-by: Vedang Patel <vedang.patel@intel.com>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
(cherry picked from commit ad42febe51ae0a2e875f507a37a6329277f75fdd)
Signed-off-by: Hirotaka MOTAI <Motai.Hirotaka@aj.MitsubishiElectric.co.jp>
---
 kernel/trace/trace_events_hist.c | 94 ++++++++++++++++++++++++--------
 1 file changed, 71 insertions(+), 23 deletions(-)

Patch
diff mbox series

diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index e4368bb7..a793f8c0 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -84,16 +84,22 @@  static u64 hist_field_log2(struct hist_field *hist_field, void *event,
 {
 	struct hist_field *operand = hist_field->operands[0];
 
 	u64 val = operand->fn(operand, event, rbe);
 
 	return (u64) ilog2(roundup_pow_of_two(val));
 }
 
+static u64 hist_field_timestamp(struct hist_field *hist_field, void *event,
+				struct ring_buffer_event *rbe)
+{
+	return ring_buffer_event_time_stamp(rbe);
+}
+
 #define DEFINE_HIST_FIELD_FN(type)					\
 	static u64 hist_field_##type(struct hist_field *hist_field,	\
 				     void *event,			\
 				     struct ring_buffer_event *rbe)	\
 {									\
 	type *addr = (type *)(event + hist_field->field->offset);	\
 									\
 	return (u64)(unsigned long)*addr;				\
@@ -130,16 +136,17 @@  enum hist_field_flags {
 	HIST_FIELD_FL_STRING		= 1 << 2,
 	HIST_FIELD_FL_HEX		= 1 << 3,
 	HIST_FIELD_FL_SYM		= 1 << 4,
 	HIST_FIELD_FL_SYM_OFFSET	= 1 << 5,
 	HIST_FIELD_FL_EXECNAME		= 1 << 6,
 	HIST_FIELD_FL_SYSCALL		= 1 << 7,
 	HIST_FIELD_FL_STACKTRACE	= 1 << 8,
 	HIST_FIELD_FL_LOG2		= 1 << 9,
+	HIST_FIELD_FL_TIMESTAMP		= 1 << 10,
 };
 
 struct hist_trigger_attrs {
 	char		*keys_str;
 	char		*vals_str;
 	char		*sort_key_str;
 	char		*name;
 	bool		pause;
@@ -154,30 +161,33 @@  struct hist_trigger_data {
 	unsigned int			n_keys;
 	unsigned int			n_fields;
 	unsigned int			key_size;
 	struct tracing_map_sort_key	sort_keys[TRACING_MAP_SORT_KEYS_MAX];
 	unsigned int			n_sort_keys;
 	struct trace_event_file		*event_file;
 	struct hist_trigger_attrs	*attrs;
 	struct tracing_map		*map;
+	bool				enable_timestamps;
 };
 
 static const char *hist_field_name(struct hist_field *field,
 				   unsigned int level)
 {
 	const char *field_name = "";
 
 	if (level > 1)
 		return field_name;
 
 	if (field->field)
 		field_name = field->field->name;
 	else if (field->flags & HIST_FIELD_FL_LOG2)
 		field_name = hist_field_name(field->operands[0], ++level);
+	else if (field->flags & HIST_FIELD_FL_TIMESTAMP)
+		field_name = "common_timestamp";
 
 	if (field_name == NULL)
 		field_name = "";
 
 	return field_name;
 }
 
 static hist_field_fn_t select_value_fn(int field_size, int field_is_signed)
@@ -435,16 +445,22 @@  static struct hist_field *create_hist_field(struct ftrace_event_field *field,
 	if (flags & HIST_FIELD_FL_LOG2) {
 		unsigned long fl = flags & ~HIST_FIELD_FL_LOG2;
 		hist_field->fn = hist_field_log2;
 		hist_field->operands[0] = create_hist_field(field, fl);
 		hist_field->size = hist_field->operands[0]->size;
 		goto out;
 	}
 
+	if (flags & HIST_FIELD_FL_TIMESTAMP) {
+		hist_field->fn = hist_field_timestamp;
+		hist_field->size = sizeof(u64);
+		goto out;
+	}
+
 	if (WARN_ON_ONCE(!field))
 		goto out;
 
 	if (is_string_field(field)) {
 		flags |= HIST_FIELD_FL_STRING;
 
 		if (field->filter_type == FILTER_STATIC_STRING)
 			hist_field->fn = hist_field_string;
@@ -512,20 +528,25 @@  static int create_val_field(struct hist_trigger_data *hist_data,
 		if (strcmp(field_str, "hex") == 0)
 			flags |= HIST_FIELD_FL_HEX;
 		else {
 			ret = -EINVAL;
 			goto out;
 		}
 	}
 
-	field = trace_find_event_field(file->event_call, field_name);
-	if (!field || !field->size) {
-		ret = -EINVAL;
-		goto out;
+	if (strcmp(field_name, "common_timestamp") == 0) {
+		flags |= HIST_FIELD_FL_TIMESTAMP;
+		hist_data->enable_timestamps = true;
+	} else {
+		field = trace_find_event_field(file->event_call, field_name);
+		if (!field || !field->size) {
+			ret = -EINVAL;
+			goto out;
+		}
 	}
 
 	hist_data->fields[val_idx] = create_hist_field(field, flags);
 	if (!hist_data->fields[val_idx]) {
 		ret = -ENOMEM;
 		goto out;
 	}
 
@@ -610,26 +631,32 @@  static int create_key_field(struct hist_trigger_data *hist_data,
 			else if (strcmp(field_str, "log2") == 0)
 				flags |= HIST_FIELD_FL_LOG2;
 			else {
 				ret = -EINVAL;
 				goto out;
 			}
 		}
 
-		field = trace_find_event_field(file->event_call, field_name);
-		if (!field || !field->size) {
-			ret = -EINVAL;
-			goto out;
-		}
+		if (strcmp(field_name, "common_timestamp") == 0) {
+			flags |= HIST_FIELD_FL_TIMESTAMP;
+			hist_data->enable_timestamps = true;
+			key_size = sizeof(u64);
+		} else {
+			field = trace_find_event_field(file->event_call, field_name);
+			if (!field || !field->size) {
+				ret = -EINVAL;
+				goto out;
+			}
 
-		if (is_string_field(field))
-			key_size = MAX_FILTER_STR_VAL;
-		else
-			key_size = field->size;
+			if (is_string_field(field))
+				key_size = MAX_FILTER_STR_VAL;
+			else
+				key_size = field->size;
+		}
 	}
 
 	hist_data->fields[key_idx] = create_hist_field(field, flags);
 	if (!hist_data->fields[key_idx]) {
 		ret = -ENOMEM;
 		goto out;
 	}
 
@@ -815,16 +842,19 @@  static int create_tracing_map_fields(struct hist_trigger_data *hist_data)
 		hist_field = hist_data->fields[i];
 		if (hist_field->flags & HIST_FIELD_FL_KEY) {
 			tracing_map_cmp_fn_t cmp_fn;
 
 			field = hist_field->field;
 
 			if (hist_field->flags & HIST_FIELD_FL_STACKTRACE)
 				cmp_fn = tracing_map_cmp_none;
+			else if (!field)
+				cmp_fn = tracing_map_cmp_num(hist_field->size,
+							     hist_field->is_signed);
 			else if (is_string_field(field))
 				cmp_fn = tracing_map_cmp_string;
 			else
 				cmp_fn = tracing_map_cmp_num(field->size,
 							     field->is_signed);
 			idx = tracing_map_add_key_field(map,
 							hist_field->offset,
 							cmp_fn);
@@ -1210,17 +1240,21 @@  static const char *get_hist_field_flags(struct hist_field *hist_field)
 
 	return flags_str;
 }
 
 static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)
 {
 	const char *field_name = hist_field_name(hist_field, 0);
 
-	seq_printf(m, "%s", field_name);
+	if (hist_field->flags & HIST_FIELD_FL_TIMESTAMP)
+		seq_puts(m, "common_timestamp");
+	else if (field_name)
+		seq_printf(m, "%s", field_name);
+
 	if (hist_field->flags) {
 		const char *flags_str = get_hist_field_flags(hist_field);
 
 		if (flags_str)
 			seq_printf(m, ".%s", flags_str);
 	}
 }
 
@@ -1261,37 +1295,35 @@  static int event_hist_trigger_print(struct seq_file *m,
 			hist_field_print(m, hist_data->fields[i]);
 		}
 	}
 
 	seq_puts(m, ":sort=");
 
 	for (i = 0; i < hist_data->n_sort_keys; i++) {
 		struct tracing_map_sort_key *sort_key;
+		unsigned int idx;
 
 		sort_key = &hist_data->sort_keys[i];
+		idx = sort_key->field_idx;
+
+		if (WARN_ON(idx >= TRACING_MAP_FIELDS_MAX))
+			return -EINVAL;
 
 		if (i > 0)
 			seq_puts(m, ",");
 
-		if (sort_key->field_idx == HITCOUNT_IDX)
+		if (idx == HITCOUNT_IDX)
 			seq_puts(m, "hitcount");
-		else {
-			unsigned int idx = sort_key->field_idx;
-
-			if (WARN_ON(idx >= TRACING_MAP_FIELDS_MAX))
-				return -EINVAL;
-
+		else
 			hist_field_print(m, hist_data->fields[idx]);
-		}
 
 		if (sort_key->descending)
 			seq_puts(m, ".descending");
 	}
-
 	seq_printf(m, ":size=%u", (1 << hist_data->map->map_bits));
 
 	if (data->filter_str)
 		seq_printf(m, " if %s", data->filter_str);
 
 	if (data->paused)
 		seq_puts(m, " [paused]");
 	else
@@ -1449,16 +1481,20 @@  static bool hist_trigger_match(struct event_trigger_data *data,
 		key_field_test = hist_data_test->fields[i];
 
 		if (key_field->flags != key_field_test->flags)
 			return false;
 		if (!compatible_field(key_field->field, key_field_test->field))
 			return false;
 		if (key_field->offset != key_field_test->offset)
 			return false;
+		if (key_field->size != key_field_test->size)
+			return false;
+		if (key_field->is_signed != key_field_test->is_signed)
+			return false;
 	}
 
 	for (i = 0; i < hist_data->n_sort_keys; i++) {
 		sort_key = &hist_data->sort_keys[i];
 		sort_key_test = &hist_data_test->sort_keys[i];
 
 		if (sort_key->field_idx != sort_key_test->field_idx ||
 		    sort_key->descending != sort_key_test->descending)
@@ -1531,16 +1567,19 @@  static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,
 			goto out;
 	}
 
 	list_add_rcu(&data->list, &file->triggers);
 	ret++;
 
 	update_cond_flag(file);
 
+	if (hist_data->enable_timestamps)
+		tracing_set_time_stamp_abs(file->tr, true);
+
 	if (trace_event_trigger_enable_disable(file, 1) < 0) {
 		list_del_rcu(&data->list);
 		update_cond_flag(file);
 		ret--;
 	}
  out:
 	return ret;
 }
@@ -1565,27 +1604,36 @@  static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,
 			trace_event_trigger_enable_disable(file, 0);
 			update_cond_flag(file);
 			break;
 		}
 	}
 
 	if (unregistered && test->ops->free)
 		test->ops->free(test->ops, test);
+
+	if (hist_data->enable_timestamps) {
+		if (unregistered)
+			tracing_set_time_stamp_abs(file->tr, false);
+	}
 }
 
 static void hist_unreg_all(struct trace_event_file *file)
 {
 	struct event_trigger_data *test, *n;
+	struct hist_trigger_data *hist_data;
 
 	list_for_each_entry_safe(test, n, &file->triggers, list) {
 		if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
+			hist_data = test->private_data;
 			list_del_rcu(&test->list);
 			trace_event_trigger_enable_disable(file, 0);
 			update_cond_flag(file);
+			if (hist_data->enable_timestamps)
+				tracing_set_time_stamp_abs(file->tr, false);
 			if (test->ops->free)
 				test->ops->free(test->ops, test);
 		}
 	}
 }
 
 static int event_hist_trigger_func(struct event_command *cmd_ops,
 				   struct trace_event_file *file,