tracing/filters: Enable filtering a cpumask field by another cpumask
The recently introduced ipi_send_cpumask trace event contains a cpumask
field, but it currently cannot be used in filter expressions.
Make event filtering aware of cpumask fields, and allow these to be
filtered by a user-provided cpumask.
The user-provided cpumask is to be given in cpulist format and wrapped as:
"CPUS{$cpulist}". The use of curly braces instead of parentheses is to
prevent predicate_parse() from parsing the contents of CPUS{...} as a
full-fledged predicate subexpression.
This enables e.g.:
$ trace-cmd record -e 'ipi_send_cpumask' -f 'cpumask & CPUS{2,4,6,8-32}'
Link: https://lkml.kernel.org/r/20230707172155.70873-3-vschneid@redhat.com
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Juri Lelli <juri.lelli@redhat.com>
Cc: Daniel Bristot de Oliveira <bristot@redhat.com>
Cc: Marcelo Tosatti <mtosatti@redhat.com>
Cc: Leonardo Bras <leobras@redhat.com>
Cc: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Valentin Schneider <vschneid@redhat.com>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
			
			
This commit is contained in:
		
				
					committed by
					
						 Steven Rostedt (Google)
						Steven Rostedt (Google)
					
				
			
			
				
	
			
			
			
						parent
						
							cfb58e278c
						
					
				
				
					commit
					39f7c41c90
				
			| @@ -808,6 +808,7 @@ enum { | ||||
| 	FILTER_RDYN_STRING, | ||||
| 	FILTER_PTR_STRING, | ||||
| 	FILTER_TRACE_FN, | ||||
| 	FILTER_CPUMASK, | ||||
| 	FILTER_COMM, | ||||
| 	FILTER_CPU, | ||||
| 	FILTER_STACKTRACE, | ||||
|   | ||||
| @@ -64,6 +64,7 @@ enum filter_pred_fn { | ||||
| 	FILTER_PRED_FN_PCHAR_USER, | ||||
| 	FILTER_PRED_FN_PCHAR, | ||||
| 	FILTER_PRED_FN_CPU, | ||||
| 	FILTER_PRED_FN_CPUMASK, | ||||
| 	FILTER_PRED_FN_FUNCTION, | ||||
| 	FILTER_PRED_FN_, | ||||
| 	FILTER_PRED_TEST_VISITED, | ||||
| @@ -71,6 +72,7 @@ enum filter_pred_fn { | ||||
| 
 | ||||
| struct filter_pred { | ||||
| 	struct regex		*regex; | ||||
| 	struct cpumask          *mask; | ||||
| 	unsigned short		*ops; | ||||
| 	struct ftrace_event_field *field; | ||||
| 	u64			val; | ||||
| @@ -94,6 +96,8 @@ struct filter_pred { | ||||
| 	C(TOO_MANY_OPEN,	"Too many '('"),			\ | ||||
| 	C(TOO_MANY_CLOSE,	"Too few '('"),				\ | ||||
| 	C(MISSING_QUOTE,	"Missing matching quote"),		\ | ||||
| 	C(MISSING_BRACE_OPEN,   "Missing '{'"),				\ | ||||
| 	C(MISSING_BRACE_CLOSE,  "Missing '}'"),				\ | ||||
| 	C(OPERAND_TOO_LONG,	"Operand too long"),			\ | ||||
| 	C(EXPECT_STRING,	"Expecting string field"),		\ | ||||
| 	C(EXPECT_DIGIT,		"Expecting numeric field"),		\ | ||||
| @@ -103,6 +107,7 @@ struct filter_pred { | ||||
| 	C(BAD_SUBSYS_FILTER,	"Couldn't find or set field in one of a subsystem's events"), \ | ||||
| 	C(TOO_MANY_PREDS,	"Too many terms in predicate expression"), \ | ||||
| 	C(INVALID_FILTER,	"Meaningless filter expression"),	\ | ||||
| 	C(INVALID_CPULIST,	"Invalid cpulist"),	\ | ||||
| 	C(IP_FIELD_ONLY,	"Only 'ip' field is supported for function trace"), \ | ||||
| 	C(INVALID_VALUE,	"Invalid value (did you forget quotes)?"), \ | ||||
| 	C(NO_FUNCTION,		"Function not found"),			\ | ||||
| @@ -190,6 +195,7 @@ static void free_predicate(struct filter_pred *pred) | ||||
| { | ||||
| 	if (pred) { | ||||
| 		kfree(pred->regex); | ||||
| 		kfree(pred->mask); | ||||
| 		kfree(pred); | ||||
| 	} | ||||
| } | ||||
| @@ -877,6 +883,26 @@ static int filter_pred_cpu(struct filter_pred *pred, void *event) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* Filter predicate for cpumask field vs user-provided cpumask */ | ||||
| static int filter_pred_cpumask(struct filter_pred *pred, void *event) | ||||
| { | ||||
| 	u32 item = *(u32 *)(event + pred->offset); | ||||
| 	int loc = item & 0xffff; | ||||
| 	const struct cpumask *mask = (event + loc); | ||||
| 	const struct cpumask *cmp = pred->mask; | ||||
| 
 | ||||
| 	switch (pred->op) { | ||||
| 	case OP_EQ: | ||||
| 		return cpumask_equal(mask, cmp); | ||||
| 	case OP_NE: | ||||
| 		return !cpumask_equal(mask, cmp); | ||||
| 	case OP_BAND: | ||||
| 		return cpumask_intersects(mask, cmp); | ||||
| 	default: | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* Filter predicate for COMM. */ | ||||
| static int filter_pred_comm(struct filter_pred *pred, void *event) | ||||
| { | ||||
| @@ -1244,8 +1270,12 @@ static void filter_free_subsystem_filters(struct trace_subsystem_dir *dir, | ||||
| 
 | ||||
| int filter_assign_type(const char *type) | ||||
| { | ||||
| 	if (strstr(type, "__data_loc") && strstr(type, "char")) | ||||
| 		return FILTER_DYN_STRING; | ||||
| 	if (strstr(type, "__data_loc")) { | ||||
| 		if (strstr(type, "char")) | ||||
| 			return FILTER_DYN_STRING; | ||||
| 		if (strstr(type, "cpumask_t")) | ||||
| 			return FILTER_CPUMASK; | ||||
| 		} | ||||
| 
 | ||||
| 	if (strstr(type, "__rel_loc") && strstr(type, "char")) | ||||
| 		return FILTER_RDYN_STRING; | ||||
| @@ -1357,6 +1387,8 @@ static int filter_pred_fn_call(struct filter_pred *pred, void *event) | ||||
| 		return filter_pred_pchar(pred, event); | ||||
| 	case FILTER_PRED_FN_CPU: | ||||
| 		return filter_pred_cpu(pred, event); | ||||
| 	case FILTER_PRED_FN_CPUMASK: | ||||
| 		return filter_pred_cpumask(pred, event); | ||||
| 	case FILTER_PRED_FN_FUNCTION: | ||||
| 		return filter_pred_function(pred, event); | ||||
| 	case FILTER_PRED_TEST_VISITED: | ||||
| @@ -1568,6 +1600,67 @@ static int parse_pred(const char *str, void *data, | ||||
| 		strncpy(pred->regex->pattern, str + s, len); | ||||
| 		pred->regex->pattern[len] = 0; | ||||
| 
 | ||||
| 	} else if (!strncmp(str + i, "CPUS", 4)) { | ||||
| 		unsigned int maskstart; | ||||
| 		char *tmp; | ||||
| 
 | ||||
| 		switch (field->filter_type) { | ||||
| 		case FILTER_CPUMASK: | ||||
| 			break; | ||||
| 		default: | ||||
| 			parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 
 | ||||
| 		switch (op) { | ||||
| 		case OP_EQ: | ||||
| 		case OP_NE: | ||||
| 		case OP_BAND: | ||||
| 			break; | ||||
| 		default: | ||||
| 			parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 
 | ||||
| 		/* Skip CPUS */ | ||||
| 		i += 4; | ||||
| 		if (str[i++] != '{') { | ||||
| 			parse_error(pe, FILT_ERR_MISSING_BRACE_OPEN, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 		maskstart = i; | ||||
| 
 | ||||
| 		/* Walk the cpulist until closing } */ | ||||
| 		for (; str[i] && str[i] != '}'; i++); | ||||
| 		if (str[i] != '}') { | ||||
| 			parse_error(pe, FILT_ERR_MISSING_BRACE_CLOSE, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 
 | ||||
| 		if (maskstart == i) { | ||||
| 			parse_error(pe, FILT_ERR_INVALID_CPULIST, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 
 | ||||
| 		/* Copy the cpulist between { and } */ | ||||
| 		tmp = kmalloc((i - maskstart) + 1, GFP_KERNEL); | ||||
| 		strscpy(tmp, str + maskstart, (i - maskstart) + 1); | ||||
| 
 | ||||
| 		pred->mask = kzalloc(cpumask_size(), GFP_KERNEL); | ||||
| 		if (!pred->mask) | ||||
| 			goto err_mem; | ||||
| 
 | ||||
| 		/* Now parse it */ | ||||
| 		if (cpulist_parse(tmp, pred->mask)) { | ||||
| 			parse_error(pe, FILT_ERR_INVALID_CPULIST, pos + i); | ||||
| 			goto err_free; | ||||
| 		} | ||||
| 
 | ||||
| 		/* Move along */ | ||||
| 		i++; | ||||
| 		if (field->filter_type == FILTER_CPUMASK) | ||||
| 			pred->fn_num = FILTER_PRED_FN_CPUMASK; | ||||
| 
 | ||||
| 	/* This is either a string, or an integer */ | ||||
| 	} else if (str[i] == '\'' || str[i] == '"') { | ||||
| 		char q = str[i]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user