mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-10-31 02:49:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			968 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			968 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 41f7872514c699137d4f48e8258f21fa5cf0b101 Mon Sep 17 00:00:00 2001
 | |
| From: Tom Zanussi <tom.zanussi@linux.intel.com>
 | |
| Date: Mon, 15 Jan 2018 20:51:56 -0600
 | |
| Subject: [PATCH 101/418] tracing: Add variable reference handling to hist
 | |
|  triggers
 | |
| 
 | |
| Add the necessary infrastructure to allow the variables defined on one
 | |
| event to be referenced in another.  This allows variables set by a
 | |
| previous event to be referenced and used in expressions combining the
 | |
| variable values saved by that previous event and the event fields of
 | |
| the current event.  For example, here's how a latency can be
 | |
| calculated and saved into yet another variable named 'wakeup_lat':
 | |
| 
 | |
|     # echo 'hist:keys=pid,prio:ts0=common_timestamp ...
 | |
|     # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp-$ts0 ...
 | |
| 
 | |
| In the first event, the event's timetamp is saved into the variable
 | |
| ts0.  In the next line, ts0 is subtracted from the second event's
 | |
| timestamp to produce the latency.
 | |
| 
 | |
| Further users of variable references will be described in subsequent
 | |
| patches, such as for instance how the 'wakeup_lat' variable above can
 | |
| be displayed in a latency histogram.
 | |
| 
 | |
| Link: http://lkml.kernel.org/r/b1d3e6975374e34d501ff417c20189c3f9b2c7b8.1516069914.git.tom.zanussi@linux.intel.com
 | |
| 
 | |
| Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com>
 | |
| Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
 | |
| (cherry picked from commit 434c1d5831194e72e6eb30d46534d75b5a985eb7)
 | |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
 | |
| ---
 | |
|  kernel/trace/trace.c                |   2 +
 | |
|  kernel/trace/trace.h                |   3 +
 | |
|  kernel/trace/trace_events_hist.c    | 661 +++++++++++++++++++++++++++-
 | |
|  kernel/trace/trace_events_trigger.c |   6 +
 | |
|  4 files changed, 656 insertions(+), 16 deletions(-)
 | |
| 
 | |
| diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
 | |
| index 9d199ebdc0cb..2610cd6280e1 100644
 | |
| --- a/kernel/trace/trace.c
 | |
| +++ b/kernel/trace/trace.c
 | |
| @@ -7788,6 +7788,7 @@ static int instance_mkdir(const char *name)
 | |
|  
 | |
|  	INIT_LIST_HEAD(&tr->systems);
 | |
|  	INIT_LIST_HEAD(&tr->events);
 | |
| +	INIT_LIST_HEAD(&tr->hist_vars);
 | |
|  
 | |
|  	if (allocate_trace_buffers(tr, trace_buf_size) < 0)
 | |
|  		goto out_free_tr;
 | |
| @@ -8538,6 +8539,7 @@ __init static int tracer_alloc_buffers(void)
 | |
|  
 | |
|  	INIT_LIST_HEAD(&global_trace.systems);
 | |
|  	INIT_LIST_HEAD(&global_trace.events);
 | |
| +	INIT_LIST_HEAD(&global_trace.hist_vars);
 | |
|  	list_add(&global_trace.list, &ftrace_trace_arrays);
 | |
|  
 | |
|  	apply_trace_boot_options();
 | |
| diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
 | |
| index 92936a6fc29e..5975d5f5c4bc 100644
 | |
| --- a/kernel/trace/trace.h
 | |
| +++ b/kernel/trace/trace.h
 | |
| @@ -274,6 +274,7 @@ struct trace_array {
 | |
|  	int			function_enabled;
 | |
|  #endif
 | |
|  	int			time_stamp_abs_ref;
 | |
| +	struct list_head	hist_vars;
 | |
|  };
 | |
|  
 | |
|  enum {
 | |
| @@ -1550,6 +1551,8 @@ extern void pause_named_trigger(struct event_trigger_data *data);
 | |
|  extern void unpause_named_trigger(struct event_trigger_data *data);
 | |
|  extern void set_named_trigger_data(struct event_trigger_data *data,
 | |
|  				   struct event_trigger_data *named_data);
 | |
| +extern struct event_trigger_data *
 | |
| +get_named_trigger_data(struct event_trigger_data *data);
 | |
|  extern int register_event_command(struct event_command *cmd);
 | |
|  extern int unregister_event_command(struct event_command *cmd);
 | |
|  extern int register_trigger_hist_enable_disable_cmds(void);
 | |
| diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
 | |
| index 6e60cb794dcd..2bc1f76915ef 100644
 | |
| --- a/kernel/trace/trace_events_hist.c
 | |
| +++ b/kernel/trace/trace_events_hist.c
 | |
| @@ -59,7 +59,12 @@ struct hist_field {
 | |
|  	struct hist_trigger_data	*hist_data;
 | |
|  	struct hist_var			var;
 | |
|  	enum field_op_id		operator;
 | |
| +	char				*system;
 | |
| +	char				*event_name;
 | |
|  	char				*name;
 | |
| +	unsigned int			var_idx;
 | |
| +	unsigned int			var_ref_idx;
 | |
| +	bool                            read_once;
 | |
|  };
 | |
|  
 | |
|  static u64 hist_field_none(struct hist_field *field,
 | |
| @@ -214,6 +219,7 @@ enum hist_field_flags {
 | |
|  	HIST_FIELD_FL_TIMESTAMP_USECS	= 1 << 11,
 | |
|  	HIST_FIELD_FL_VAR		= 1 << 12,
 | |
|  	HIST_FIELD_FL_EXPR		= 1 << 13,
 | |
| +	HIST_FIELD_FL_VAR_REF		= 1 << 14,
 | |
|  };
 | |
|  
 | |
|  struct var_defs {
 | |
| @@ -253,6 +259,8 @@ struct hist_trigger_data {
 | |
|  	struct tracing_map		*map;
 | |
|  	bool				enable_timestamps;
 | |
|  	bool				remove;
 | |
| +	struct hist_field               *var_refs[TRACING_MAP_VARS_MAX];
 | |
| +	unsigned int			n_var_refs;
 | |
|  };
 | |
|  
 | |
|  static u64 hist_field_timestamp(struct hist_field *hist_field,
 | |
| @@ -271,6 +279,214 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,
 | |
|  	return ts;
 | |
|  }
 | |
|  
 | |
| +struct hist_var_data {
 | |
| +	struct list_head list;
 | |
| +	struct hist_trigger_data *hist_data;
 | |
| +};
 | |
| +
 | |
| +static struct hist_field *
 | |
| +check_field_for_var_ref(struct hist_field *hist_field,
 | |
| +			struct hist_trigger_data *var_data,
 | |
| +			unsigned int var_idx)
 | |
| +{
 | |
| +	struct hist_field *found = NULL;
 | |
| +
 | |
| +	if (hist_field && hist_field->flags & HIST_FIELD_FL_VAR_REF) {
 | |
| +		if (hist_field->var.idx == var_idx &&
 | |
| +		    hist_field->var.hist_data == var_data) {
 | |
| +			found = hist_field;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *
 | |
| +check_field_for_var_refs(struct hist_trigger_data *hist_data,
 | |
| +			 struct hist_field *hist_field,
 | |
| +			 struct hist_trigger_data *var_data,
 | |
| +			 unsigned int var_idx,
 | |
| +			 unsigned int level)
 | |
| +{
 | |
| +	struct hist_field *found = NULL;
 | |
| +	unsigned int i;
 | |
| +
 | |
| +	if (level > 3)
 | |
| +		return found;
 | |
| +
 | |
| +	if (!hist_field)
 | |
| +		return found;
 | |
| +
 | |
| +	found = check_field_for_var_ref(hist_field, var_data, var_idx);
 | |
| +	if (found)
 | |
| +		return found;
 | |
| +
 | |
| +	for (i = 0; i < HIST_FIELD_OPERANDS_MAX; i++) {
 | |
| +		struct hist_field *operand;
 | |
| +
 | |
| +		operand = hist_field->operands[i];
 | |
| +		found = check_field_for_var_refs(hist_data, operand, var_data,
 | |
| +						 var_idx, level + 1);
 | |
| +		if (found)
 | |
| +			return found;
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *find_var_ref(struct hist_trigger_data *hist_data,
 | |
| +				       struct hist_trigger_data *var_data,
 | |
| +				       unsigned int var_idx)
 | |
| +{
 | |
| +	struct hist_field *hist_field, *found = NULL;
 | |
| +	unsigned int i;
 | |
| +
 | |
| +	for_each_hist_field(i, hist_data) {
 | |
| +		hist_field = hist_data->fields[i];
 | |
| +		found = check_field_for_var_refs(hist_data, hist_field,
 | |
| +						 var_data, var_idx, 0);
 | |
| +		if (found)
 | |
| +			return found;
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *find_any_var_ref(struct hist_trigger_data *hist_data,
 | |
| +					   unsigned int var_idx)
 | |
| +{
 | |
| +	struct trace_array *tr = hist_data->event_file->tr;
 | |
| +	struct hist_field *found = NULL;
 | |
| +	struct hist_var_data *var_data;
 | |
| +
 | |
| +	list_for_each_entry(var_data, &tr->hist_vars, list) {
 | |
| +		if (var_data->hist_data == hist_data)
 | |
| +			continue;
 | |
| +		found = find_var_ref(var_data->hist_data, hist_data, var_idx);
 | |
| +		if (found)
 | |
| +			break;
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static bool check_var_refs(struct hist_trigger_data *hist_data)
 | |
| +{
 | |
| +	struct hist_field *field;
 | |
| +	bool found = false;
 | |
| +	int i;
 | |
| +
 | |
| +	for_each_hist_field(i, hist_data) {
 | |
| +		field = hist_data->fields[i];
 | |
| +		if (field && field->flags & HIST_FIELD_FL_VAR) {
 | |
| +			if (find_any_var_ref(hist_data, field->var.idx)) {
 | |
| +				found = true;
 | |
| +				break;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static struct hist_var_data *find_hist_vars(struct hist_trigger_data *hist_data)
 | |
| +{
 | |
| +	struct trace_array *tr = hist_data->event_file->tr;
 | |
| +	struct hist_var_data *var_data, *found = NULL;
 | |
| +
 | |
| +	list_for_each_entry(var_data, &tr->hist_vars, list) {
 | |
| +		if (var_data->hist_data == hist_data) {
 | |
| +			found = var_data;
 | |
| +			break;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static bool field_has_hist_vars(struct hist_field *hist_field,
 | |
| +				unsigned int level)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	if (level > 3)
 | |
| +		return false;
 | |
| +
 | |
| +	if (!hist_field)
 | |
| +		return false;
 | |
| +
 | |
| +	if (hist_field->flags & HIST_FIELD_FL_VAR ||
 | |
| +	    hist_field->flags & HIST_FIELD_FL_VAR_REF)
 | |
| +		return true;
 | |
| +
 | |
| +	for (i = 0; i < HIST_FIELD_OPERANDS_MAX; i++) {
 | |
| +		struct hist_field *operand;
 | |
| +
 | |
| +		operand = hist_field->operands[i];
 | |
| +		if (field_has_hist_vars(operand, level + 1))
 | |
| +			return true;
 | |
| +	}
 | |
| +
 | |
| +	return false;
 | |
| +}
 | |
| +
 | |
| +static bool has_hist_vars(struct hist_trigger_data *hist_data)
 | |
| +{
 | |
| +	struct hist_field *hist_field;
 | |
| +	int i;
 | |
| +
 | |
| +	for_each_hist_field(i, hist_data) {
 | |
| +		hist_field = hist_data->fields[i];
 | |
| +		if (field_has_hist_vars(hist_field, 0))
 | |
| +			return true;
 | |
| +	}
 | |
| +
 | |
| +	return false;
 | |
| +}
 | |
| +
 | |
| +static int save_hist_vars(struct hist_trigger_data *hist_data)
 | |
| +{
 | |
| +	struct trace_array *tr = hist_data->event_file->tr;
 | |
| +	struct hist_var_data *var_data;
 | |
| +
 | |
| +	var_data = find_hist_vars(hist_data);
 | |
| +	if (var_data)
 | |
| +		return 0;
 | |
| +
 | |
| +	if (trace_array_get(tr) < 0)
 | |
| +		return -ENODEV;
 | |
| +
 | |
| +	var_data = kzalloc(sizeof(*var_data), GFP_KERNEL);
 | |
| +	if (!var_data) {
 | |
| +		trace_array_put(tr);
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	var_data->hist_data = hist_data;
 | |
| +	list_add(&var_data->list, &tr->hist_vars);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void remove_hist_vars(struct hist_trigger_data *hist_data)
 | |
| +{
 | |
| +	struct trace_array *tr = hist_data->event_file->tr;
 | |
| +	struct hist_var_data *var_data;
 | |
| +
 | |
| +	var_data = find_hist_vars(hist_data);
 | |
| +	if (!var_data)
 | |
| +		return;
 | |
| +
 | |
| +	if (WARN_ON(check_var_refs(hist_data)))
 | |
| +		return;
 | |
| +
 | |
| +	list_del(&var_data->list);
 | |
| +
 | |
| +	kfree(var_data);
 | |
| +
 | |
| +	trace_array_put(tr);
 | |
| +}
 | |
| +
 | |
|  static struct hist_field *find_var_field(struct hist_trigger_data *hist_data,
 | |
|  					 const char *var_name)
 | |
|  {
 | |
| @@ -313,10 +529,137 @@ static struct hist_field *find_var(struct hist_trigger_data *hist_data,
 | |
|  	return NULL;
 | |
|  }
 | |
|  
 | |
| +static struct trace_event_file *find_var_file(struct trace_array *tr,
 | |
| +					      char *system,
 | |
| +					      char *event_name,
 | |
| +					      char *var_name)
 | |
| +{
 | |
| +	struct hist_trigger_data *var_hist_data;
 | |
| +	struct hist_var_data *var_data;
 | |
| +	struct trace_event_file *file, *found = NULL;
 | |
| +
 | |
| +	if (system)
 | |
| +		return find_event_file(tr, system, event_name);
 | |
| +
 | |
| +	list_for_each_entry(var_data, &tr->hist_vars, list) {
 | |
| +		var_hist_data = var_data->hist_data;
 | |
| +		file = var_hist_data->event_file;
 | |
| +		if (file == found)
 | |
| +			continue;
 | |
| +
 | |
| +		if (find_var_field(var_hist_data, var_name)) {
 | |
| +			if (found)
 | |
| +				return NULL;
 | |
| +
 | |
| +			found = file;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return found;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *find_file_var(struct trace_event_file *file,
 | |
| +					const char *var_name)
 | |
| +{
 | |
| +	struct hist_trigger_data *test_data;
 | |
| +	struct event_trigger_data *test;
 | |
| +	struct hist_field *hist_field;
 | |
| +
 | |
| +	list_for_each_entry_rcu(test, &file->triggers, list) {
 | |
| +		if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
 | |
| +			test_data = test->private_data;
 | |
| +			hist_field = find_var_field(test_data, var_name);
 | |
| +			if (hist_field)
 | |
| +				return hist_field;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *find_event_var(struct hist_trigger_data *hist_data,
 | |
| +					 char *system,
 | |
| +					 char *event_name,
 | |
| +					 char *var_name)
 | |
| +{
 | |
| +	struct trace_array *tr = hist_data->event_file->tr;
 | |
| +	struct hist_field *hist_field = NULL;
 | |
| +	struct trace_event_file *file;
 | |
| +
 | |
| +	file = find_var_file(tr, system, event_name, var_name);
 | |
| +	if (!file)
 | |
| +		return NULL;
 | |
| +
 | |
| +	hist_field = find_file_var(file, var_name);
 | |
| +
 | |
| +	return hist_field;
 | |
| +}
 | |
| +
 | |
|  struct hist_elt_data {
 | |
|  	char *comm;
 | |
| +	u64 *var_ref_vals;
 | |
|  };
 | |
|  
 | |
| +static u64 hist_field_var_ref(struct hist_field *hist_field,
 | |
| +			      struct tracing_map_elt *elt,
 | |
| +			      struct ring_buffer_event *rbe,
 | |
| +			      void *event)
 | |
| +{
 | |
| +	struct hist_elt_data *elt_data;
 | |
| +	u64 var_val = 0;
 | |
| +
 | |
| +	elt_data = elt->private_data;
 | |
| +	var_val = elt_data->var_ref_vals[hist_field->var_ref_idx];
 | |
| +
 | |
| +	return var_val;
 | |
| +}
 | |
| +
 | |
| +static bool resolve_var_refs(struct hist_trigger_data *hist_data, void *key,
 | |
| +			     u64 *var_ref_vals, bool self)
 | |
| +{
 | |
| +	struct hist_trigger_data *var_data;
 | |
| +	struct tracing_map_elt *var_elt;
 | |
| +	struct hist_field *hist_field;
 | |
| +	unsigned int i, var_idx;
 | |
| +	bool resolved = true;
 | |
| +	u64 var_val = 0;
 | |
| +
 | |
| +	for (i = 0; i < hist_data->n_var_refs; i++) {
 | |
| +		hist_field = hist_data->var_refs[i];
 | |
| +		var_idx = hist_field->var.idx;
 | |
| +		var_data = hist_field->var.hist_data;
 | |
| +
 | |
| +		if (var_data == NULL) {
 | |
| +			resolved = false;
 | |
| +			break;
 | |
| +		}
 | |
| +
 | |
| +		if ((self && var_data != hist_data) ||
 | |
| +		    (!self && var_data == hist_data))
 | |
| +			continue;
 | |
| +
 | |
| +		var_elt = tracing_map_lookup(var_data->map, key);
 | |
| +		if (!var_elt) {
 | |
| +			resolved = false;
 | |
| +			break;
 | |
| +		}
 | |
| +
 | |
| +		if (!tracing_map_var_set(var_elt, var_idx)) {
 | |
| +			resolved = false;
 | |
| +			break;
 | |
| +		}
 | |
| +
 | |
| +		if (self || !hist_field->read_once)
 | |
| +			var_val = tracing_map_read_var(var_elt, var_idx);
 | |
| +		else
 | |
| +			var_val = tracing_map_read_var_once(var_elt, var_idx);
 | |
| +
 | |
| +		var_ref_vals[i] = var_val;
 | |
| +	}
 | |
| +
 | |
| +	return resolved;
 | |
| +}
 | |
| +
 | |
|  static const char *hist_field_name(struct hist_field *field,
 | |
|  				   unsigned int level)
 | |
|  {
 | |
| @@ -331,8 +674,20 @@ static const char *hist_field_name(struct hist_field *field,
 | |
|  		field_name = hist_field_name(field->operands[0], ++level);
 | |
|  	else if (field->flags & HIST_FIELD_FL_TIMESTAMP)
 | |
|  		field_name = "common_timestamp";
 | |
| -	else if (field->flags & HIST_FIELD_FL_EXPR)
 | |
| -		field_name = field->name;
 | |
| +	else if (field->flags & HIST_FIELD_FL_EXPR ||
 | |
| +		 field->flags & HIST_FIELD_FL_VAR_REF) {
 | |
| +		if (field->system) {
 | |
| +			static char full_name[MAX_FILTER_STR_VAL];
 | |
| +
 | |
| +			strcat(full_name, field->system);
 | |
| +			strcat(full_name, ".");
 | |
| +			strcat(full_name, field->event_name);
 | |
| +			strcat(full_name, ".");
 | |
| +			strcat(full_name, field->name);
 | |
| +			field_name = full_name;
 | |
| +		} else
 | |
| +			field_name = field->name;
 | |
| +	}
 | |
|  
 | |
|  	if (field_name == NULL)
 | |
|  		field_name = "";
 | |
| @@ -612,6 +967,9 @@ static const char *get_hist_field_flags(struct hist_field *hist_field)
 | |
|  
 | |
|  static void expr_field_str(struct hist_field *field, char *expr)
 | |
|  {
 | |
| +	if (field->flags & HIST_FIELD_FL_VAR_REF)
 | |
| +		strcat(expr, "$");
 | |
| +
 | |
|  	strcat(expr, hist_field_name(field, 0));
 | |
|  
 | |
|  	if (field->flags) {
 | |
| @@ -742,6 +1100,11 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,
 | |
|  	if (flags & HIST_FIELD_FL_EXPR)
 | |
|  		goto out; /* caller will populate */
 | |
|  
 | |
| +	if (flags & HIST_FIELD_FL_VAR_REF) {
 | |
| +		hist_field->fn = hist_field_var_ref;
 | |
| +		goto out;
 | |
| +	}
 | |
| +
 | |
|  	if (flags & HIST_FIELD_FL_HITCOUNT) {
 | |
|  		hist_field->fn = hist_field_counter;
 | |
|  		hist_field->size = sizeof(u64);
 | |
| @@ -835,6 +1198,144 @@ static void destroy_hist_fields(struct hist_trigger_data *hist_data)
 | |
|  	}
 | |
|  }
 | |
|  
 | |
| +static int init_var_ref(struct hist_field *ref_field,
 | |
| +			struct hist_field *var_field,
 | |
| +			char *system, char *event_name)
 | |
| +{
 | |
| +	int err = 0;
 | |
| +
 | |
| +	ref_field->var.idx = var_field->var.idx;
 | |
| +	ref_field->var.hist_data = var_field->hist_data;
 | |
| +	ref_field->size = var_field->size;
 | |
| +	ref_field->is_signed = var_field->is_signed;
 | |
| +	ref_field->flags |= var_field->flags &
 | |
| +		(HIST_FIELD_FL_TIMESTAMP | HIST_FIELD_FL_TIMESTAMP_USECS);
 | |
| +
 | |
| +	if (system) {
 | |
| +		ref_field->system = kstrdup(system, GFP_KERNEL);
 | |
| +		if (!ref_field->system)
 | |
| +			return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	if (event_name) {
 | |
| +		ref_field->event_name = kstrdup(event_name, GFP_KERNEL);
 | |
| +		if (!ref_field->event_name) {
 | |
| +			err = -ENOMEM;
 | |
| +			goto free;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	ref_field->name = kstrdup(var_field->var.name, GFP_KERNEL);
 | |
| +	if (!ref_field->name) {
 | |
| +		err = -ENOMEM;
 | |
| +		goto free;
 | |
| +	}
 | |
| +
 | |
| +	ref_field->type = kstrdup(var_field->type, GFP_KERNEL);
 | |
| +	if (!ref_field->type) {
 | |
| +		err = -ENOMEM;
 | |
| +		goto free;
 | |
| +	}
 | |
| + out:
 | |
| +	return err;
 | |
| + free:
 | |
| +	kfree(ref_field->system);
 | |
| +	kfree(ref_field->event_name);
 | |
| +	kfree(ref_field->name);
 | |
| +
 | |
| +	goto out;
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *create_var_ref(struct hist_field *var_field,
 | |
| +					 char *system, char *event_name)
 | |
| +{
 | |
| +	unsigned long flags = HIST_FIELD_FL_VAR_REF;
 | |
| +	struct hist_field *ref_field;
 | |
| +
 | |
| +	ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL);
 | |
| +	if (ref_field) {
 | |
| +		if (init_var_ref(ref_field, var_field, system, event_name)) {
 | |
| +			destroy_hist_field(ref_field, 0);
 | |
| +			return NULL;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return ref_field;
 | |
| +}
 | |
| +
 | |
| +static bool is_var_ref(char *var_name)
 | |
| +{
 | |
| +	if (!var_name || strlen(var_name) < 2 || var_name[0] != '$')
 | |
| +		return false;
 | |
| +
 | |
| +	return true;
 | |
| +}
 | |
| +
 | |
| +static char *field_name_from_var(struct hist_trigger_data *hist_data,
 | |
| +				 char *var_name)
 | |
| +{
 | |
| +	char *name, *field;
 | |
| +	unsigned int i;
 | |
| +
 | |
| +	for (i = 0; i < hist_data->attrs->var_defs.n_vars; i++) {
 | |
| +		name = hist_data->attrs->var_defs.name[i];
 | |
| +
 | |
| +		if (strcmp(var_name, name) == 0) {
 | |
| +			field = hist_data->attrs->var_defs.expr[i];
 | |
| +			if (contains_operator(field) || is_var_ref(field))
 | |
| +				continue;
 | |
| +			return field;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static char *local_field_var_ref(struct hist_trigger_data *hist_data,
 | |
| +				 char *system, char *event_name,
 | |
| +				 char *var_name)
 | |
| +{
 | |
| +	struct trace_event_call *call;
 | |
| +
 | |
| +	if (system && event_name) {
 | |
| +		call = hist_data->event_file->event_call;
 | |
| +
 | |
| +		if (strcmp(system, call->class->system) != 0)
 | |
| +			return NULL;
 | |
| +
 | |
| +		if (strcmp(event_name, trace_event_name(call)) != 0)
 | |
| +			return NULL;
 | |
| +	}
 | |
| +
 | |
| +	if (!!system != !!event_name)
 | |
| +		return NULL;
 | |
| +
 | |
| +	if (!is_var_ref(var_name))
 | |
| +		return NULL;
 | |
| +
 | |
| +	var_name++;
 | |
| +
 | |
| +	return field_name_from_var(hist_data, var_name);
 | |
| +}
 | |
| +
 | |
| +static struct hist_field *parse_var_ref(struct hist_trigger_data *hist_data,
 | |
| +					char *system, char *event_name,
 | |
| +					char *var_name)
 | |
| +{
 | |
| +	struct hist_field *var_field = NULL, *ref_field = NULL;
 | |
| +
 | |
| +	if (!is_var_ref(var_name))
 | |
| +		return NULL;
 | |
| +
 | |
| +	var_name++;
 | |
| +
 | |
| +	var_field = find_event_var(hist_data, system, event_name, var_name);
 | |
| +	if (var_field)
 | |
| +		ref_field = create_var_ref(var_field, system, event_name);
 | |
| +
 | |
| +	return ref_field;
 | |
| +}
 | |
| +
 | |
|  static struct ftrace_event_field *
 | |
|  parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
 | |
|  	    char *field_str, unsigned long *flags)
 | |
| @@ -891,10 +1392,40 @@ 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)
 | |
|  {
 | |
| +	char *s, *ref_system = NULL, *ref_event = NULL, *ref_var = str;
 | |
|  	struct ftrace_event_field *field = NULL;
 | |
|  	struct hist_field *hist_field = NULL;
 | |
|  	int ret = 0;
 | |
|  
 | |
| +	s = strchr(str, '.');
 | |
| +	if (s) {
 | |
| +		s = strchr(++s, '.');
 | |
| +		if (s) {
 | |
| +			ref_system = strsep(&str, ".");
 | |
| +			if (!str) {
 | |
| +				ret = -EINVAL;
 | |
| +				goto out;
 | |
| +			}
 | |
| +			ref_event = strsep(&str, ".");
 | |
| +			if (!str) {
 | |
| +				ret = -EINVAL;
 | |
| +				goto out;
 | |
| +			}
 | |
| +			ref_var = str;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	s = local_field_var_ref(hist_data, ref_system, ref_event, ref_var);
 | |
| +	if (!s) {
 | |
| +		hist_field = parse_var_ref(hist_data, ref_system, ref_event, ref_var);
 | |
| +		if (hist_field) {
 | |
| +			hist_data->var_refs[hist_data->n_var_refs] = hist_field;
 | |
| +			hist_field->var_ref_idx = hist_data->n_var_refs++;
 | |
| +			return hist_field;
 | |
| +		}
 | |
| +	} else
 | |
| +		str = s;
 | |
| +
 | |
|  	field = parse_field(hist_data, file, str, flags);
 | |
|  	if (IS_ERR(field)) {
 | |
|  		ret = PTR_ERR(field);
 | |
| @@ -1066,6 +1597,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
 | |
|  		goto free;
 | |
|  	}
 | |
|  
 | |
| +	operand1->read_once = true;
 | |
| +	operand2->read_once = true;
 | |
| +
 | |
|  	expr->operands[0] = operand1;
 | |
|  	expr->operands[1] = operand2;
 | |
|  	expr->operator = field_op;
 | |
| @@ -1238,6 +1772,12 @@ static int create_key_field(struct hist_trigger_data *hist_data,
 | |
|  			goto out;
 | |
|  		}
 | |
|  
 | |
| +		if (hist_field->flags & HIST_FIELD_FL_VAR_REF) {
 | |
| +			destroy_hist_field(hist_field, 0);
 | |
| +			ret = -EINVAL;
 | |
| +			goto out;
 | |
| +		}
 | |
| +
 | |
|  		key_size = hist_field->size;
 | |
|  	}
 | |
|  
 | |
| @@ -1576,6 +2116,7 @@ create_hist_data(unsigned int map_bits,
 | |
|  
 | |
|  	hist_data->attrs = attrs;
 | |
|  	hist_data->remove = remove;
 | |
| +	hist_data->event_file = file;
 | |
|  
 | |
|  	ret = create_hist_fields(hist_data, file);
 | |
|  	if (ret)
 | |
| @@ -1598,12 +2139,6 @@ create_hist_data(unsigned int map_bits,
 | |
|  	ret = create_tracing_map_fields(hist_data);
 | |
|  	if (ret)
 | |
|  		goto free;
 | |
| -
 | |
| -	ret = tracing_map_init(hist_data->map);
 | |
| -	if (ret)
 | |
| -		goto free;
 | |
| -
 | |
| -	hist_data->event_file = file;
 | |
|   out:
 | |
|  	return hist_data;
 | |
|   free:
 | |
| @@ -1618,12 +2153,17 @@ create_hist_data(unsigned int map_bits,
 | |
|  
 | |
|  static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,
 | |
|  				    struct tracing_map_elt *elt, void *rec,
 | |
| -				    struct ring_buffer_event *rbe)
 | |
| +				    struct ring_buffer_event *rbe,
 | |
| +				    u64 *var_ref_vals)
 | |
|  {
 | |
| +	struct hist_elt_data *elt_data;
 | |
|  	struct hist_field *hist_field;
 | |
|  	unsigned int i, var_idx;
 | |
|  	u64 hist_val;
 | |
|  
 | |
| +	elt_data = elt->private_data;
 | |
| +	elt_data->var_ref_vals = var_ref_vals;
 | |
| +
 | |
|  	for_each_hist_val_field(i, hist_data) {
 | |
|  		hist_field = hist_data->fields[i];
 | |
|  		hist_val = hist_field->fn(hist_field, elt, rbe, rec);
 | |
| @@ -1675,6 +2215,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec,
 | |
|  	struct hist_trigger_data *hist_data = data->private_data;
 | |
|  	bool use_compound_key = (hist_data->n_keys > 1);
 | |
|  	unsigned long entries[HIST_STACKTRACE_DEPTH];
 | |
| +	u64 var_ref_vals[TRACING_MAP_VARS_MAX];
 | |
|  	char compound_key[HIST_KEY_SIZE_MAX];
 | |
|  	struct tracing_map_elt *elt = NULL;
 | |
|  	struct stack_trace stacktrace;
 | |
| @@ -1714,9 +2255,15 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec,
 | |
|  	if (use_compound_key)
 | |
|  		key = compound_key;
 | |
|  
 | |
| +	if (hist_data->n_var_refs &&
 | |
| +	    !resolve_var_refs(hist_data, key, var_ref_vals, false))
 | |
| +		return;
 | |
| +
 | |
|  	elt = tracing_map_insert(hist_data->map, key);
 | |
| -	if (elt)
 | |
| -		hist_trigger_elt_update(hist_data, elt, rec, rbe);
 | |
| +	if (!elt)
 | |
| +		return;
 | |
| +
 | |
| +	hist_trigger_elt_update(hist_data, elt, rec, rbe, var_ref_vals);
 | |
|  }
 | |
|  
 | |
|  static void hist_trigger_stacktrace_print(struct seq_file *m,
 | |
| @@ -1933,8 +2480,11 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)
 | |
|  
 | |
|  	if (hist_field->flags & HIST_FIELD_FL_TIMESTAMP)
 | |
|  		seq_puts(m, "common_timestamp");
 | |
| -	else if (field_name)
 | |
| +	else if (field_name) {
 | |
| +		if (hist_field->flags & HIST_FIELD_FL_VAR_REF)
 | |
| +			seq_putc(m, '$');
 | |
|  		seq_printf(m, "%s", field_name);
 | |
| +	}
 | |
|  
 | |
|  	if (hist_field->flags) {
 | |
|  		const char *flags_str = get_hist_field_flags(hist_field);
 | |
| @@ -2074,7 +2624,11 @@ static void event_hist_trigger_free(struct event_trigger_ops *ops,
 | |
|  	if (!data->ref) {
 | |
|  		if (data->name)
 | |
|  			del_named_trigger(data);
 | |
| +
 | |
|  		trigger_data_free(data);
 | |
| +
 | |
| +		remove_hist_vars(hist_data);
 | |
| +
 | |
|  		destroy_hist_data(hist_data);
 | |
|  	}
 | |
|  }
 | |
| @@ -2287,23 +2841,55 @@ 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);
 | |
| + out:
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int hist_trigger_enable(struct event_trigger_data *data,
 | |
| +			       struct trace_event_file *file)
 | |
| +{
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	list_add_tail_rcu(&data->list, &file->triggers);
 | |
| +
 | |
| +	update_cond_flag(file);
 | |
|  
 | |
|  	if (trace_event_trigger_enable_disable(file, 1) < 0) {
 | |
|  		list_del_rcu(&data->list);
 | |
|  		update_cond_flag(file);
 | |
|  		ret--;
 | |
|  	}
 | |
| - out:
 | |
| +
 | |
|  	return ret;
 | |
|  }
 | |
|  
 | |
| +static bool hist_trigger_check_refs(struct event_trigger_data *data,
 | |
| +				    struct trace_event_file *file)
 | |
| +{
 | |
| +	struct hist_trigger_data *hist_data = data->private_data;
 | |
| +	struct event_trigger_data *test, *named_data = NULL;
 | |
| +
 | |
| +	if (hist_data->attrs->name)
 | |
| +		named_data = find_named_trigger(hist_data->attrs->name);
 | |
| +
 | |
| +	list_for_each_entry_rcu(test, &file->triggers, list) {
 | |
| +		if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
 | |
| +			if (!hist_trigger_match(data, test, named_data, false))
 | |
| +				continue;
 | |
| +			hist_data = test->private_data;
 | |
| +			if (check_var_refs(hist_data))
 | |
| +				return true;
 | |
| +			break;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return false;
 | |
| +}
 | |
| +
 | |
|  static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,
 | |
|  				    struct event_trigger_data *data,
 | |
|  				    struct trace_event_file *file)
 | |
| @@ -2336,11 +2922,30 @@ static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,
 | |
|  	}
 | |
|  }
 | |
|  
 | |
| +static bool hist_file_check_refs(struct trace_event_file *file)
 | |
| +{
 | |
| +	struct hist_trigger_data *hist_data;
 | |
| +	struct event_trigger_data *test;
 | |
| +
 | |
| +	list_for_each_entry_rcu(test, &file->triggers, list) {
 | |
| +		if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
 | |
| +			hist_data = test->private_data;
 | |
| +			if (check_var_refs(hist_data))
 | |
| +				return true;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return false;
 | |
| +}
 | |
| +
 | |
|  static void hist_unreg_all(struct trace_event_file *file)
 | |
|  {
 | |
|  	struct event_trigger_data *test, *n;
 | |
|  	struct hist_trigger_data *hist_data;
 | |
|  
 | |
| +	if (hist_file_check_refs(file))
 | |
| +		return;
 | |
| +
 | |
|  	list_for_each_entry_safe(test, n, &file->triggers, list) {
 | |
|  		if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
 | |
|  			hist_data = test->private_data;
 | |
| @@ -2416,6 +3021,11 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,
 | |
|  	}
 | |
|  
 | |
|  	if (remove) {
 | |
| +		if (hist_trigger_check_refs(trigger_data, file)) {
 | |
| +			ret = -EBUSY;
 | |
| +			goto out_free;
 | |
| +		}
 | |
| +
 | |
|  		cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file);
 | |
|  		ret = 0;
 | |
|  		goto out_free;
 | |
| @@ -2433,14 +3043,33 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,
 | |
|  		goto out_free;
 | |
|  	} else if (ret < 0)
 | |
|  		goto out_free;
 | |
| +
 | |
| +	if (get_named_trigger_data(trigger_data))
 | |
| +		goto enable;
 | |
| +
 | |
| +	if (has_hist_vars(hist_data))
 | |
| +		save_hist_vars(hist_data);
 | |
| +
 | |
| +	ret = tracing_map_init(hist_data->map);
 | |
| +	if (ret)
 | |
| +		goto out_unreg;
 | |
| +enable:
 | |
| +	ret = hist_trigger_enable(trigger_data, file);
 | |
| +	if (ret)
 | |
| +		goto out_unreg;
 | |
| +
 | |
|  	/* Just return zero, not the number of registered triggers */
 | |
|  	ret = 0;
 | |
|   out:
 | |
|  	return ret;
 | |
| + out_unreg:
 | |
| +	cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file);
 | |
|   out_free:
 | |
|  	if (cmd_ops->set_filter)
 | |
|  		cmd_ops->set_filter(NULL, trigger_data, NULL);
 | |
|  
 | |
| +	remove_hist_vars(hist_data);
 | |
| +
 | |
|  	kfree(trigger_data);
 | |
|  
 | |
|  	destroy_hist_data(hist_data);
 | |
| diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c
 | |
| index 9dd8b9ce2594..5d41bc2aad1c 100644
 | |
| --- a/kernel/trace/trace_events_trigger.c
 | |
| +++ b/kernel/trace/trace_events_trigger.c
 | |
| @@ -911,6 +911,12 @@ void set_named_trigger_data(struct event_trigger_data *data,
 | |
|  	data->named_data = named_data;
 | |
|  }
 | |
|  
 | |
| +struct event_trigger_data *
 | |
| +get_named_trigger_data(struct event_trigger_data *data)
 | |
| +{
 | |
| +	return data->named_data;
 | |
| +}
 | |
| +
 | |
|  static void
 | |
|  traceon_trigger(struct event_trigger_data *data, void *rec,
 | |
|  		struct ring_buffer_event *event)
 | |
| -- 
 | |
| 2.17.1
 | |
| 
 |