Re: [PATCH v3 00/27] function_graph: Allow multiple users for function graph tracing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Masami,

This series passed all my tests, are you comfortable with me pushing
them to linux-next?

-- Steve


On Mon, 03 Jun 2024 15:07:04 -0400
Steven Rostedt <rostedt@xxxxxxxxxxx> wrote:

> This is a continuation of the function graph multi user code.
> I wrote a proof of concept back in 2019 of this code[1] and
> Masami started cleaning it up. I started from Masami's work v10
> that can be found here:
> 
>  https://lore.kernel.org/linux-trace-kernel/171509088006.162236.7227326999861366050.stgit@devnote2/
> 
> This is *only* the code that allows multiple users of function
> graph tracing. This is not the fprobe work that Masami is working
> to add on top of it. As Masami took my proof of concept, there
> was still several things I disliked about that code. Instead of
> having Masami clean it up even more, I decided to take over on just
> my code and change it up a bit.
> 
> Changes since v2: https://lore.kernel.org/linux-trace-kernel/20240602033744.563858532@xxxxxxxxxxx
> 
> - Added comments describing which hashes the append and intersect
>   functions were used for.
> 
> - Replaced checks of (NULL or EMPTY_HASH) with ftrace_hash_empty()
>   helper function.
> 
> - Added check at the end of intersect_hash() to convert the hash
>   to EMPTY hash if it doesn't have any functions.
> 
> - Renamed compare_ops() to ops_equal() and return boolean (inversed return
>   value).
> 
> - Broke out __ftrace_hash_move_and_update_ops() to use in both
>   ftrace_hash_move_and_update_ops() and ftrace_hash_move_and_update_subops().
> 
> Diff between last version at end of this email.
> 
> Masami Hiramatsu (Google) (3):
>       function_graph: Handle tail calls for stack unwinding
>       function_graph: Use a simple LRU for fgraph_array index number
>       ftrace: Add multiple fgraph storage selftest
> 
> Steven Rostedt (Google) (9):
>       ftrace: Add subops logic to allow one ops to manage many
>       ftrace: Allow subops filtering to be modified
>       function_graph: Add pid tracing back to function graph tracer
>       function_graph: Use for_each_set_bit() in __ftrace_return_to_handler()
>       function_graph: Use bitmask to loop on fgraph entry
>       function_graph: Use static_call and branch to optimize entry function
>       function_graph: Use static_call and branch to optimize return function
>       selftests/ftrace: Add function_graph tracer to func-filter-pid test
>       selftests/ftrace: Add fgraph-multi.tc test
> 
> Steven Rostedt (VMware) (15):
>       function_graph: Convert ret_stack to a series of longs
>       fgraph: Use BUILD_BUG_ON() to make sure we have structures divisible by long
>       function_graph: Add an array structure that will allow multiple callbacks
>       function_graph: Allow multiple users to attach to function graph
>       function_graph: Remove logic around ftrace_graph_entry and return
>       ftrace/function_graph: Pass fgraph_ops to function graph callbacks
>       ftrace: Allow function_graph tracer to be enabled in instances
>       ftrace: Allow ftrace startup flags to exist without dynamic ftrace
>       function_graph: Have the instances use their own ftrace_ops for filtering
>       function_graph: Add "task variables" per task for fgraph_ops
>       function_graph: Move set_graph_function tests to shadow stack global var
>       function_graph: Move graph depth stored data to shadow stack global var
>       function_graph: Move graph notrace bit to shadow stack global var
>       function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data()
>       function_graph: Add selftest for passing local variables
> 
> ----
>  include/linux/ftrace.h                             |   43 +-
>  include/linux/sched.h                              |    2 +-
>  include/linux/trace_recursion.h                    |   39 -
>  kernel/trace/fgraph.c                              | 1044 ++++++++++++++++----
>  kernel/trace/ftrace.c                              |  522 +++++++++-
>  kernel/trace/ftrace_internal.h                     |    5 +-
>  kernel/trace/trace.h                               |   94 +-
>  kernel/trace/trace_functions.c                     |    8 +
>  kernel/trace/trace_functions_graph.c               |   96 +-
>  kernel/trace/trace_irqsoff.c                       |   10 +-
>  kernel/trace/trace_sched_wakeup.c                  |   10 +-
>  kernel/trace/trace_selftest.c                      |  259 ++++-
>  .../selftests/ftrace/test.d/ftrace/fgraph-multi.tc |  103 ++
>  .../ftrace/test.d/ftrace/func-filter-pid.tc        |   27 +-
>  14 files changed, 1945 insertions(+), 317 deletions(-)
>  create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/fgraph-multi.tc
> 
> 
> diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
> index 41fabc6d30e4..da7e6abf48b4 100644
> --- a/kernel/trace/ftrace.c
> +++ b/kernel/trace/ftrace.c
> @@ -3170,7 +3170,7 @@ int ftrace_shutdown(struct ftrace_ops *ops, int command)
>  /* Simply make a copy of @src and return it */
>  static struct ftrace_hash *copy_hash(struct ftrace_hash *src)
>  {
> -	if (!src || src == EMPTY_HASH)
> +	if (ftrace_hash_empty(src))
>  		return EMPTY_HASH;
>  
>  	return alloc_and_copy_ftrace_hash(src->size_bits, src);
> @@ -3187,6 +3187,9 @@ static struct ftrace_hash *copy_hash(struct ftrace_hash *src)
>   *
>   *  Otherwise, go through all of @new_hash and add anything that @hash
>   *  doesn't already have, to @hash.
> + *
> + *  The filter_hash updates uses just the append_hash() function
> + *  and the notrace_hash does not.
>   */
>  static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
>  {
> @@ -3195,11 +3198,11 @@ static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
>  	int i;
>  
>  	/* An empty hash does everything */
> -	if (!*hash || *hash == EMPTY_HASH)
> +	if (ftrace_hash_empty(*hash))
>  		return 0;
>  
>  	/* If new_hash has everything make hash have everything */
> -	if (!new_hash || new_hash == EMPTY_HASH) {
> +	if (ftrace_hash_empty(new_hash)) {
>  		free_ftrace_hash(*hash);
>  		*hash = EMPTY_HASH;
>  		return 0;
> @@ -3217,7 +3220,12 @@ static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
>  	return 0;
>  }
>  
> -/* Add to @hash only those that are in both @new_hash1 and @new_hash2 */
> +/*
> + * Add to @hash only those that are in both @new_hash1 and @new_hash2
> + *
> + * The notrace_hash updates uses just the intersect_hash() function
> + * and the filter_hash does not.
> + */
>  static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash1,
>  			  struct ftrace_hash *new_hash2)
>  {
> @@ -3229,8 +3237,7 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has
>  	 * If new_hash1 or new_hash2 is the EMPTY_HASH then make the hash
>  	 * empty as well as empty for notrace means none are notraced.
>  	 */
> -	if (!new_hash1 || new_hash1 == EMPTY_HASH ||
> -	    !new_hash2 || new_hash2 == EMPTY_HASH) {
> +	if (ftrace_hash_empty(new_hash1) || ftrace_hash_empty(new_hash2)) {
>  		free_ftrace_hash(*hash);
>  		*hash = EMPTY_HASH;
>  		return 0;
> @@ -3245,6 +3252,11 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has
>  				return -ENOMEM;
>  		}
>  	}
> +	/* If nothing intersects, make it the empty set */
> +	if (ftrace_hash_empty(*hash)) {
> +		free_ftrace_hash(*hash);
> +		*hash = EMPTY_HASH;
> +	}
>  	return 0;
>  }
>  
> @@ -3266,7 +3278,7 @@ static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
>  			return NULL;
>  		}
>  		/* Nothing more to do if new_hash is empty */
> -		if (new_hash == EMPTY_HASH)
> +		if (ftrace_hash_empty(new_hash))
>  			break;
>  	}
>  	return new_hash;
> @@ -3300,59 +3312,76 @@ static struct ftrace_hash *intersect_hashes(struct ftrace_ops *ops)
>  			return NULL;
>  		}
>  		/* Nothing more to do if new_hash is empty */
> -		if (new_hash == EMPTY_HASH)
> +		if (ftrace_hash_empty(new_hash))
>  			break;
>  	}
>  	return new_hash;
>  }
>  
> -/* Returns 0 on equal or non-zero on non-equal */
> -static int compare_ops(struct ftrace_hash *A, struct ftrace_hash *B)
> +static bool ops_equal(struct ftrace_hash *A, struct ftrace_hash *B)
>  {
>  	struct ftrace_func_entry *entry;
>  	int size;
>  	int i;
>  
> -	if (!A || A == EMPTY_HASH)
> -		return !(!B || B == EMPTY_HASH);
> +	if (ftrace_hash_empty(A))
> +		return ftrace_hash_empty(B);
>  
> -	if (!B || B == EMPTY_HASH)
> -		return !(!A || A == EMPTY_HASH);
> +	if (ftrace_hash_empty(B))
> +		return ftrace_hash_empty(A);
>  
>  	if (A->count != B->count)
> -		return 1;
> +		return false;
>  
>  	size = 1 << A->size_bits;
>  	for (i = 0; i < size; i++) {
>  		hlist_for_each_entry(entry, &A->buckets[i], hlist) {
>  			if (!__ftrace_lookup_ip(B, entry->ip))
> -				return 1;
> +				return false;
>  		}
>  	}
>  
> -	return 0;
> +	return true;
>  }
>  
> -static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
> -					   struct ftrace_hash **orig_hash,
> -					   struct ftrace_hash *hash,
> -					   int enable);
> +static void ftrace_ops_update_code(struct ftrace_ops *ops,
> +				   struct ftrace_ops_hash *old_hash);
> +
> +static int __ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
> +					     struct ftrace_hash **orig_hash,
> +					     struct ftrace_hash *hash,
> +					     int enable)
> +{
> +	struct ftrace_ops_hash old_hash_ops;
> +	struct ftrace_hash *old_hash;
> +	int ret;
> +
> +	old_hash = *orig_hash;
> +	old_hash_ops.filter_hash = ops->func_hash->filter_hash;
> +	old_hash_ops.notrace_hash = ops->func_hash->notrace_hash;
> +	ret = ftrace_hash_move(ops, enable, orig_hash, hash);
> +	if (!ret) {
> +		ftrace_ops_update_code(ops, &old_hash_ops);
> +		free_ftrace_hash_rcu(old_hash);
> +	}
> +	return ret;
> +}
>  
>  static int ftrace_update_ops(struct ftrace_ops *ops, struct ftrace_hash *filter_hash,
>  			     struct ftrace_hash *notrace_hash)
>  {
>  	int ret;
>  
> -	if (compare_ops(filter_hash, ops->func_hash->filter_hash)) {
> -		ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->filter_hash,
> -						      filter_hash, 1);
> +	if (!ops_equal(filter_hash, ops->func_hash->filter_hash)) {
> +		ret = __ftrace_hash_move_and_update_ops(ops, &ops->func_hash->filter_hash,
> +							filter_hash, 1);
>  		if (ret < 0)
>  			return ret;
>  	}
>  
> -	if (compare_ops(notrace_hash, ops->func_hash->notrace_hash)) {
> -		ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->notrace_hash,
> -						      notrace_hash, 0);
> +	if (!ops_equal(notrace_hash, ops->func_hash->notrace_hash)) {
> +		ret = __ftrace_hash_move_and_update_ops(ops, &ops->func_hash->notrace_hash,
> +							notrace_hash, 0);
>  		if (ret < 0)
>  			return ret;
>  	}
> @@ -3438,8 +3467,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int
>  	 *   o If either notrace_hash is empty then the final stays empty
>  	 *      o Otherwise, the final is an intersection between the hashes
>  	 */
> -	if (ops->func_hash->filter_hash == EMPTY_HASH ||
> -	    subops->func_hash->filter_hash == EMPTY_HASH) {
> +	if (ftrace_hash_empty(ops->func_hash->filter_hash) ||
> +	    ftrace_hash_empty(subops->func_hash->filter_hash)) {
>  		filter_hash = EMPTY_HASH;
>  	} else {
>  		size_bits = max(ops->func_hash->filter_hash->size_bits,
> @@ -3454,8 +3483,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int
>  		}
>  	}
>  
> -	if (ops->func_hash->notrace_hash == EMPTY_HASH ||
> -	    subops->func_hash->notrace_hash == EMPTY_HASH) {
> +	if (ftrace_hash_empty(ops->func_hash->notrace_hash) ||
> +	    ftrace_hash_empty(subops->func_hash->notrace_hash)) {
>  		notrace_hash = EMPTY_HASH;
>  	} else {
>  		size_bits = max(ops->func_hash->filter_hash->size_bits,
> @@ -3591,7 +3620,7 @@ static int ftrace_hash_move_and_update_subops(struct ftrace_ops *subops,
>  	}
>  
>  	/* Move the hash over to the new hash */
> -	ret = ftrace_hash_move_and_update_ops(ops, orig_hash, new_hash, enable);
> +	ret = __ftrace_hash_move_and_update_ops(ops, orig_hash, new_hash, enable);
>  
>  	free_ftrace_hash(new_hash);
>  
> @@ -4822,11 +4851,6 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
>  					   struct ftrace_hash *hash,
>  					   int enable)
>  {
> -	struct ftrace_ops_hash old_hash_ops;
> -	struct ftrace_hash *old_hash;
> -	struct ftrace_ops *op;
> -	int ret;
> -
>  	if (ops->flags & FTRACE_OPS_FL_SUBOP)
>  		return ftrace_hash_move_and_update_subops(ops, orig_hash, hash, enable);
>  
> @@ -4838,6 +4862,8 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
>  	 * it will not affect subops that share it.
>  	 */
>  	if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) {
> +		struct ftrace_ops *op;
> +
>  		/* Check if any other manager subops maps to this hash */
>  		do_for_each_ftrace_op(op, ftrace_ops_list) {
>  			struct ftrace_ops *subops;
> @@ -4851,15 +4877,7 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
>  		} while_for_each_ftrace_op(op);
>  	}
>  
> -	old_hash = *orig_hash;
> -	old_hash_ops.filter_hash = ops->func_hash->filter_hash;
> -	old_hash_ops.notrace_hash = ops->func_hash->notrace_hash;
> -	ret = ftrace_hash_move(ops, enable, orig_hash, hash);
> -	if (!ret) {
> -		ftrace_ops_update_code(ops, &old_hash_ops);
> -		free_ftrace_hash_rcu(old_hash);
> -	}
> -	return ret;
> +	return __ftrace_hash_move_and_update_ops(ops, orig_hash, hash, enable);
>  }
>  
>  static bool module_exists(const char *module)





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux