mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-18 22:14:16 +00:00
tracing fixes for v6.14:
Function graph accounting fixes:
- Fix the manage ops hashes
The function graph registers a "manager ops" and "sub-ops" to ftrace.
The manager ops does not have any callback but calls the sub-ops
callbacks. The manage ops hashes (what is used to tell ftrace what
functions to attach to) is built on the sub-ops it manages.
There was an error in the way it built the hash. An empty hash means to
attach to all functions. When the manager ops had one sub-ops it properly
copied its hash. But when the manager ops had more than one sub-ops, it
went into a loop to make a set of all functions it needed to add to the
hash. If any of the subops hashes was empty, that would mean to attach
to all functions. The error was that the first iteration of the loop
passed in an empty hash to start with in order to add the other hashes.
That starting hash was mistaken as to attach to all functions. This made
the manage ops attach to all functions whenever it had two or more
sub-ops, even if each sub-op was attached to only a single function.
- Do not add duplicate entries to the manager ops hash
If two or more subops hashes trace the same function, an entry for that
function will be added to the manager ops for each subops. This causes
waste and extra overhead.
Fprobe accounting fixes:
- Remove last function from fprobe hash
Fprobes has a ftrace hash to manage which functions an fprobe is attached
to. It also has a counter of how many fprobes are attached. When the last
fprobe is removed, it unregisters the fprobe from ftrace but does not
remove the functions the last fprobe was attached to from the hash. This
leaves the old functions attached. When a new fprobe is added, the fprobe
infrastructure attaches to not only the functions of the new fprobe, but
also to the functions of the last fprobe.
- Fix accounting of the fprobe counter
When a fprobe is added, it updates a counter. If the counter goes from
zero to one, it attaches its ops to ftrace. When an fprobe is removed, the
counter is decremented. If the counter goes from 1 to zero, it removes the
fprobes ops from ftrace. There was an issue where if two fprobes trace the
same function, the addition of each fprobe would increment the counter.
But when removing the first of the fprobes, it would notice that another
fprobe is still attached to one of its functions no it does not remove
the functions from the ftrace ops. But it also did not decrement the
counter. When the last fprobe is removed, the counter is still one. This
leaves the fprobes callback still registered with ftrace and it being
called by the functions defined by the fprobes ops hash. Worse yet,
because all the functions from the fprobe ops hash have been removed, that
tells ftrace that it wants to trace all functions. Thus, this puts the
state of the system where every function is calling the fprobe callback
handler (which does nothing as there are no registered fprobes), but this
causes a good 13% slow down of the entire system.
Other updates:
- Add a selftest to test the above issues to prevent regressions.
- Fix preempt count accounting in function tracing
Better recursion protection was added to function tracing which added
another layer of preempt disable. As the preempt_count gets traced in the
event, it needs to subtract the amount of preempt disabling the tracer
does to record what the preempt_count was when the trace was triggered.
- Fix memory leak in output of set_event
A variable is passed by the seq_file functions in the location that is
set by the return of the next() function. The start() function allocates
it and the stop() function frees it. But when the last item is found, the
next() returns NULL which leaks the data that was allocated in start().
The m->private is used for something else, so have next() free the data
when it returns NULL, as stop() will then just receive NULL in that case.
-----BEGIN PGP SIGNATURE-----
iIoEABYIADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCZ7j6ARQccm9zdGVkdEBn
b29kbWlzLm9yZwAKCRAp5XQQmuv6quFxAQDrO8tjYbhLqg/LMOQyzwn/EF3Jx9ub
87961mA0rKTkYwEAhPNzTZ6GwKyKc4ny/R338KgNY69wWnOK6k/BTxCRmwk=
=TOah
-----END PGP SIGNATURE-----
Merge tag 'ftrace-v6.14-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull tracing fixes from Steven Rostedt:
"Function graph accounting fixes:
- Fix the manage ops hashes
The function graph registers a "manager ops" and "sub-ops" to
ftrace. The manager ops does not have any callback but calls the
sub-ops callbacks. The manage ops hashes (what is used to tell
ftrace what functions to attach to) is built on the sub-ops it
manages.
There was an error in the way it built the hash. An empty hash
means to attach to all functions. When the manager ops had one
sub-ops it properly copied its hash. But when the manager ops had
more than one sub-ops, it went into a loop to make a set of all
functions it needed to add to the hash. If any of the subops hashes
was empty, that would mean to attach to all functions. The error
was that the first iteration of the loop passed in an empty hash to
start with in order to add the other hashes. That starting hash was
mistaken as to attach to all functions. This made the manage ops
attach to all functions whenever it had two or more sub-ops, even
if each sub-op was attached to only a single function.
- Do not add duplicate entries to the manager ops hash
If two or more subops hashes trace the same function, an entry for
that function will be added to the manager ops for each subops.
This causes waste and extra overhead.
Fprobe accounting fixes:
- Remove last function from fprobe hash
Fprobes has a ftrace hash to manage which functions an fprobe is
attached to. It also has a counter of how many fprobes are
attached. When the last fprobe is removed, it unregisters the
fprobe from ftrace but does not remove the functions the last
fprobe was attached to from the hash. This leaves the old functions
attached. When a new fprobe is added, the fprobe infrastructure
attaches to not only the functions of the new fprobe, but also to
the functions of the last fprobe.
- Fix accounting of the fprobe counter
When a fprobe is added, it updates a counter. If the counter goes
from zero to one, it attaches its ops to ftrace. When an fprobe is
removed, the counter is decremented. If the counter goes from 1 to
zero, it removes the fprobes ops from ftrace.
There was an issue where if two fprobes trace the same function,
the addition of each fprobe would increment the counter. But when
removing the first of the fprobes, it would notice that another
fprobe is still attached to one of its functions no it does not
remove the functions from the ftrace ops.
But it also did not decrement the counter, so when the last fprobe
is removed, the counter is still one. This leaves the fprobes
callback still registered with ftrace and it being called by the
functions defined by the fprobes ops hash. Worse yet, because all
the functions from the fprobe ops hash have been removed, that
tells ftrace that it wants to trace all functions.
Thus, this puts the state of the system where every function is
calling the fprobe callback handler (which does nothing as there
are no registered fprobes), but this causes a good 13% slow down of
the entire system.
Other updates:
- Add a selftest to test the above issues to prevent regressions.
- Fix preempt count accounting in function tracing
Better recursion protection was added to function tracing which
added another layer of preempt disable. As the preempt_count gets
traced in the event, it needs to subtract the amount of preempt
disabling the tracer does to record what the preempt_count was when
the trace was triggered.
- Fix memory leak in output of set_event
A variable is passed by the seq_file functions in the location that
is set by the return of the next() function. The start() function
allocates it and the stop() function frees it. But when the last
item is found, the next() returns NULL which leaks the data that
was allocated in start(). The m->private is used for something
else, so have next() free the data when it returns NULL, as stop()
will then just receive NULL in that case"
* tag 'ftrace-v6.14-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
tracing: Fix memory leak when reading set_event file
ftrace: Correct preemption accounting for function tracing.
selftests/ftrace: Update fprobe test to check enabled_functions file
fprobe: Fix accounting of when to unregister from function graph
fprobe: Always unregister fgraph function from ops
ftrace: Do not add duplicate entries in subops manager ops
ftrace: Fix accounting of adding subops to a manager ops
This commit is contained in:
commit
b8c8c1414f
5 changed files with 95 additions and 24 deletions
|
|
@ -403,13 +403,12 @@ static void fprobe_graph_remove_ips(unsigned long *addrs, int num)
|
||||||
lockdep_assert_held(&fprobe_mutex);
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
fprobe_graph_active--;
|
fprobe_graph_active--;
|
||||||
if (!fprobe_graph_active) {
|
/* Q: should we unregister it ? */
|
||||||
/* Q: should we unregister it ? */
|
if (!fprobe_graph_active)
|
||||||
unregister_ftrace_graph(&fprobe_graph_ops);
|
unregister_ftrace_graph(&fprobe_graph_ops);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 1, 0);
|
if (num)
|
||||||
|
ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int symbols_cmp(const void *a, const void *b)
|
static int symbols_cmp(const void *a, const void *b)
|
||||||
|
|
@ -679,8 +678,7 @@ int unregister_fprobe(struct fprobe *fp)
|
||||||
}
|
}
|
||||||
del_fprobe_hash(fp);
|
del_fprobe_hash(fp);
|
||||||
|
|
||||||
if (count)
|
fprobe_graph_remove_ips(addrs, count);
|
||||||
fprobe_graph_remove_ips(addrs, count);
|
|
||||||
|
|
||||||
kfree_rcu(hlist_array, rcu);
|
kfree_rcu(hlist_array, rcu);
|
||||||
fp->hlist_array = NULL;
|
fp->hlist_array = NULL;
|
||||||
|
|
|
||||||
|
|
@ -3220,15 +3220,22 @@ static struct ftrace_hash *copy_hash(struct ftrace_hash *src)
|
||||||
* The filter_hash updates uses just the append_hash() function
|
* The filter_hash updates uses just the append_hash() function
|
||||||
* and the notrace_hash does not.
|
* and the notrace_hash does not.
|
||||||
*/
|
*/
|
||||||
static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
|
static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash,
|
||||||
|
int size_bits)
|
||||||
{
|
{
|
||||||
struct ftrace_func_entry *entry;
|
struct ftrace_func_entry *entry;
|
||||||
int size;
|
int size;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* An empty hash does everything */
|
if (*hash) {
|
||||||
if (ftrace_hash_empty(*hash))
|
/* An empty hash does everything */
|
||||||
return 0;
|
if (ftrace_hash_empty(*hash))
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
*hash = alloc_ftrace_hash(size_bits);
|
||||||
|
if (!*hash)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
/* If new_hash has everything make hash have everything */
|
/* If new_hash has everything make hash have everything */
|
||||||
if (ftrace_hash_empty(new_hash)) {
|
if (ftrace_hash_empty(new_hash)) {
|
||||||
|
|
@ -3292,16 +3299,18 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has
|
||||||
/* Return a new hash that has a union of all @ops->filter_hash entries */
|
/* Return a new hash that has a union of all @ops->filter_hash entries */
|
||||||
static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
|
static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
|
||||||
{
|
{
|
||||||
struct ftrace_hash *new_hash;
|
struct ftrace_hash *new_hash = NULL;
|
||||||
struct ftrace_ops *subops;
|
struct ftrace_ops *subops;
|
||||||
|
int size_bits;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
new_hash = alloc_ftrace_hash(ops->func_hash->filter_hash->size_bits);
|
if (ops->func_hash->filter_hash)
|
||||||
if (!new_hash)
|
size_bits = ops->func_hash->filter_hash->size_bits;
|
||||||
return NULL;
|
else
|
||||||
|
size_bits = FTRACE_HASH_DEFAULT_BITS;
|
||||||
|
|
||||||
list_for_each_entry(subops, &ops->subop_list, list) {
|
list_for_each_entry(subops, &ops->subop_list, list) {
|
||||||
ret = append_hash(&new_hash, subops->func_hash->filter_hash);
|
ret = append_hash(&new_hash, subops->func_hash->filter_hash, size_bits);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
free_ftrace_hash(new_hash);
|
free_ftrace_hash(new_hash);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -3310,7 +3319,8 @@ static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
|
||||||
if (ftrace_hash_empty(new_hash))
|
if (ftrace_hash_empty(new_hash))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return new_hash;
|
/* Can't return NULL as that means this failed */
|
||||||
|
return new_hash ? : EMPTY_HASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make @ops trace evenything except what all its subops do not trace */
|
/* Make @ops trace evenything except what all its subops do not trace */
|
||||||
|
|
@ -3505,7 +3515,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int
|
||||||
filter_hash = alloc_and_copy_ftrace_hash(size_bits, ops->func_hash->filter_hash);
|
filter_hash = alloc_and_copy_ftrace_hash(size_bits, ops->func_hash->filter_hash);
|
||||||
if (!filter_hash)
|
if (!filter_hash)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
ret = append_hash(&filter_hash, subops->func_hash->filter_hash);
|
ret = append_hash(&filter_hash, subops->func_hash->filter_hash,
|
||||||
|
size_bits);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
free_ftrace_hash(filter_hash);
|
free_ftrace_hash(filter_hash);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -5707,6 +5718,9 @@ __ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
free_hash_entry(hash, entry);
|
free_hash_entry(hash, entry);
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if (__ftrace_lookup_ip(hash, ip) != NULL) {
|
||||||
|
/* Already exists */
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = add_hash_entry(hash, ip);
|
entry = add_hash_entry(hash, ip);
|
||||||
|
|
|
||||||
|
|
@ -1591,6 +1591,13 @@ s_next(struct seq_file *m, void *v, loff_t *pos)
|
||||||
return iter;
|
return iter;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The iter is allocated in s_start() and passed via the 'v'
|
||||||
|
* parameter. To stop the iterator, NULL must be returned. But
|
||||||
|
* the return value is what the 'v' parameter in s_stop() receives
|
||||||
|
* and frees. Free iter here as it will no longer be used.
|
||||||
|
*/
|
||||||
|
kfree(iter);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1667,9 +1674,9 @@ static int s_show(struct seq_file *m, void *v)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void s_stop(struct seq_file *m, void *p)
|
static void s_stop(struct seq_file *m, void *v)
|
||||||
{
|
{
|
||||||
kfree(p);
|
kfree(v);
|
||||||
t_stop(m, NULL);
|
t_stop(m, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ function_trace_call(unsigned long ip, unsigned long parent_ip,
|
||||||
|
|
||||||
parent_ip = function_get_true_parent_ip(parent_ip, fregs);
|
parent_ip = function_get_true_parent_ip(parent_ip, fregs);
|
||||||
|
|
||||||
trace_ctx = tracing_gen_ctx();
|
trace_ctx = tracing_gen_ctx_dec();
|
||||||
|
|
||||||
data = this_cpu_ptr(tr->array_buffer.data);
|
data = this_cpu_ptr(tr->array_buffer.data);
|
||||||
if (!atomic_read(&data->disabled))
|
if (!atomic_read(&data->disabled))
|
||||||
|
|
@ -321,7 +321,6 @@ function_no_repeats_trace_call(unsigned long ip, unsigned long parent_ip,
|
||||||
struct trace_array *tr = op->private;
|
struct trace_array *tr = op->private;
|
||||||
struct trace_array_cpu *data;
|
struct trace_array_cpu *data;
|
||||||
unsigned int trace_ctx;
|
unsigned int trace_ctx;
|
||||||
unsigned long flags;
|
|
||||||
int bit;
|
int bit;
|
||||||
|
|
||||||
if (unlikely(!tr->function_enabled))
|
if (unlikely(!tr->function_enabled))
|
||||||
|
|
@ -347,8 +346,7 @@ function_no_repeats_trace_call(unsigned long ip, unsigned long parent_ip,
|
||||||
if (is_repeat_check(tr, last_info, ip, parent_ip))
|
if (is_repeat_check(tr, last_info, ip, parent_ip))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
local_save_flags(flags);
|
trace_ctx = tracing_gen_ctx_dec();
|
||||||
trace_ctx = tracing_gen_ctx_flags(flags);
|
|
||||||
process_repeats(tr, ip, parent_ip, last_info, trace_ctx);
|
process_repeats(tr, ip, parent_ip, last_info, trace_ctx);
|
||||||
|
|
||||||
trace_function(tr, ip, parent_ip, trace_ctx);
|
trace_function(tr, ip, parent_ip, trace_ctx);
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,38 @@ echo 0 > events/enable
|
||||||
echo > dynamic_events
|
echo > dynamic_events
|
||||||
|
|
||||||
PLACE=$FUNCTION_FORK
|
PLACE=$FUNCTION_FORK
|
||||||
|
PLACE2="kmem_cache_free"
|
||||||
|
PLACE3="schedule_timeout"
|
||||||
|
|
||||||
echo "f:myevent1 $PLACE" >> dynamic_events
|
echo "f:myevent1 $PLACE" >> dynamic_events
|
||||||
|
|
||||||
|
# Make sure the event is attached and is the only one
|
||||||
|
grep -q $PLACE enabled_functions
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 1 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
echo "f:myevent2 $PLACE%return" >> dynamic_events
|
echo "f:myevent2 $PLACE%return" >> dynamic_events
|
||||||
|
|
||||||
|
# It should till be the only attached function
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 1 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
# add another event
|
||||||
|
echo "f:myevent3 $PLACE2" >> dynamic_events
|
||||||
|
|
||||||
|
grep -q $PLACE2 enabled_functions
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 2 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
grep -q myevent1 dynamic_events
|
grep -q myevent1 dynamic_events
|
||||||
grep -q myevent2 dynamic_events
|
grep -q myevent2 dynamic_events
|
||||||
|
grep -q myevent3 dynamic_events
|
||||||
test -d events/fprobes/myevent1
|
test -d events/fprobes/myevent1
|
||||||
test -d events/fprobes/myevent2
|
test -d events/fprobes/myevent2
|
||||||
|
|
||||||
|
|
@ -21,6 +47,34 @@ echo "-:myevent2" >> dynamic_events
|
||||||
grep -q myevent1 dynamic_events
|
grep -q myevent1 dynamic_events
|
||||||
! grep -q myevent2 dynamic_events
|
! grep -q myevent2 dynamic_events
|
||||||
|
|
||||||
|
# should still have 2 left
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 2 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
echo > dynamic_events
|
echo > dynamic_events
|
||||||
|
|
||||||
|
# Should have none left
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 0 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "f:myevent4 $PLACE" >> dynamic_events
|
||||||
|
|
||||||
|
# Should only have one enabled
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 1 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo > dynamic_events
|
||||||
|
|
||||||
|
# Should have none left
|
||||||
|
cnt=`cat enabled_functions | wc -l`
|
||||||
|
if [ $cnt -ne 0 ]; then
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
|
||||||
clear_trace
|
clear_trace
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue