| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // SPDX-License-Identifier: GPL-2.0-only
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/ftrace.h>
- #include <linux/ktime.h>
- #include <linux/module.h>
- #include <asm/barrier.h>
- /*
- * Arbitrary large value chosen to be sufficiently large to minimize noise but
- * sufficiently small to complete quickly.
- */
- static unsigned int nr_function_calls = 100000;
- module_param(nr_function_calls, uint, 0);
- MODULE_PARM_DESC(nr_function_calls, "How many times to call the relevant tracee");
- /*
- * The number of ops associated with a call site affects whether a tracer can
- * be called directly or whether it's necessary to go via the list func, which
- * can be significantly more expensive.
- */
- static unsigned int nr_ops_relevant = 1;
- module_param(nr_ops_relevant, uint, 0);
- MODULE_PARM_DESC(nr_ops_relevant, "How many ftrace_ops to associate with the relevant tracee");
- /*
- * On architectures where all call sites share the same trampoline, having
- * tracers enabled for distinct functions can force the use of the list func
- * and incur overhead for all call sites.
- */
- static unsigned int nr_ops_irrelevant;
- module_param(nr_ops_irrelevant, uint, 0);
- MODULE_PARM_DESC(nr_ops_irrelevant, "How many ftrace_ops to associate with the irrelevant tracee");
- /*
- * On architectures with DYNAMIC_FTRACE_WITH_REGS, saving the full pt_regs can
- * be more expensive than only saving the minimal necessary regs.
- */
- static bool save_regs;
- module_param(save_regs, bool, 0);
- MODULE_PARM_DESC(save_regs, "Register ops with FTRACE_OPS_FL_SAVE_REGS (save all registers in the trampoline)");
- static bool assist_recursion;
- module_param(assist_recursion, bool, 0);
- MODULE_PARM_DESC(assist_reursion, "Register ops with FTRACE_OPS_FL_RECURSION");
- static bool assist_rcu;
- module_param(assist_rcu, bool, 0);
- MODULE_PARM_DESC(assist_reursion, "Register ops with FTRACE_OPS_FL_RCU");
- /*
- * By default, a trivial tracer is used which immediately returns to mimimize
- * overhead. Sometimes a consistency check using a more expensive tracer is
- * desireable.
- */
- static bool check_count;
- module_param(check_count, bool, 0);
- MODULE_PARM_DESC(check_count, "Check that tracers are called the expected number of times\n");
- /*
- * Usually it's not interesting to leave the ops registered after the test
- * runs, but sometimes it can be useful to leave them registered so that they
- * can be inspected through the tracefs 'enabled_functions' file.
- */
- static bool persist;
- module_param(persist, bool, 0);
- MODULE_PARM_DESC(persist, "Successfully load module and leave ftrace ops registered after test completes\n");
- /*
- * Marked as noinline to ensure that an out-of-line traceable copy is
- * generated by the compiler.
- *
- * The barrier() ensures the compiler won't elide calls by determining there
- * are no side-effects.
- */
- static noinline void tracee_relevant(void)
- {
- barrier();
- }
- /*
- * Marked as noinline to ensure that an out-of-line traceable copy is
- * generated by the compiler.
- *
- * The barrier() ensures the compiler won't elide calls by determining there
- * are no side-effects.
- */
- static noinline void tracee_irrelevant(void)
- {
- barrier();
- }
- struct sample_ops {
- struct ftrace_ops ops;
- unsigned int count;
- };
- static void ops_func_nop(unsigned long ip, unsigned long parent_ip,
- struct ftrace_ops *op,
- struct ftrace_regs *fregs)
- {
- /* do nothing */
- }
- static void ops_func_count(unsigned long ip, unsigned long parent_ip,
- struct ftrace_ops *op,
- struct ftrace_regs *fregs)
- {
- struct sample_ops *self;
- self = container_of(op, struct sample_ops, ops);
- self->count++;
- }
- static struct sample_ops *ops_relevant;
- static struct sample_ops *ops_irrelevant;
- static struct sample_ops *ops_alloc_init(void *tracee, ftrace_func_t func,
- unsigned long flags, int nr)
- {
- struct sample_ops *ops;
- ops = kcalloc(nr, sizeof(*ops), GFP_KERNEL);
- if (WARN_ON_ONCE(!ops))
- return NULL;
- for (unsigned int i = 0; i < nr; i++) {
- ops[i].ops.func = func;
- ops[i].ops.flags = flags;
- WARN_ON_ONCE(ftrace_set_filter_ip(&ops[i].ops, (unsigned long)tracee, 0, 0));
- WARN_ON_ONCE(register_ftrace_function(&ops[i].ops));
- }
- return ops;
- }
- static void ops_destroy(struct sample_ops *ops, int nr)
- {
- if (!ops)
- return;
- for (unsigned int i = 0; i < nr; i++) {
- WARN_ON_ONCE(unregister_ftrace_function(&ops[i].ops));
- ftrace_free_filter(&ops[i].ops);
- }
- kfree(ops);
- }
- static void ops_check(struct sample_ops *ops, int nr,
- unsigned int expected_count)
- {
- if (!ops || !check_count)
- return;
- for (unsigned int i = 0; i < nr; i++) {
- if (ops->count == expected_count)
- continue;
- pr_warn("Counter called %u times (expected %u)\n",
- ops->count, expected_count);
- }
- }
- static ftrace_func_t tracer_relevant = ops_func_nop;
- static ftrace_func_t tracer_irrelevant = ops_func_nop;
- static int __init ftrace_ops_sample_init(void)
- {
- unsigned long flags = 0;
- ktime_t start, end;
- u64 period;
- if (!IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && save_regs) {
- pr_info("this kernel does not support saving registers\n");
- save_regs = false;
- } else if (save_regs) {
- flags |= FTRACE_OPS_FL_SAVE_REGS;
- }
- if (assist_recursion)
- flags |= FTRACE_OPS_FL_RECURSION;
- if (assist_rcu)
- flags |= FTRACE_OPS_FL_RCU;
- if (check_count) {
- tracer_relevant = ops_func_count;
- tracer_irrelevant = ops_func_count;
- }
- pr_info("registering:\n"
- " relevant ops: %u\n"
- " tracee: %ps\n"
- " tracer: %ps\n"
- " irrelevant ops: %u\n"
- " tracee: %ps\n"
- " tracer: %ps\n"
- " saving registers: %s\n"
- " assist recursion: %s\n"
- " assist RCU: %s\n",
- nr_ops_relevant, tracee_relevant, tracer_relevant,
- nr_ops_irrelevant, tracee_irrelevant, tracer_irrelevant,
- save_regs ? "YES" : "NO",
- assist_recursion ? "YES" : "NO",
- assist_rcu ? "YES" : "NO");
- ops_relevant = ops_alloc_init(tracee_relevant, tracer_relevant,
- flags, nr_ops_relevant);
- ops_irrelevant = ops_alloc_init(tracee_irrelevant, tracer_irrelevant,
- flags, nr_ops_irrelevant);
- start = ktime_get();
- for (unsigned int i = 0; i < nr_function_calls; i++)
- tracee_relevant();
- end = ktime_get();
- ops_check(ops_relevant, nr_ops_relevant, nr_function_calls);
- ops_check(ops_irrelevant, nr_ops_irrelevant, 0);
- period = ktime_to_ns(ktime_sub(end, start));
- pr_info("Attempted %u calls to %ps in %lluns (%lluns / call)\n",
- nr_function_calls, tracee_relevant,
- period, div_u64(period, nr_function_calls));
- if (persist)
- return 0;
- ops_destroy(ops_relevant, nr_ops_relevant);
- ops_destroy(ops_irrelevant, nr_ops_irrelevant);
- /*
- * The benchmark completed sucessfully, but there's no reason to keep
- * the module around. Return an error do the user doesn't have to
- * manually unload the module.
- */
- return -EINVAL;
- }
- module_init(ftrace_ops_sample_init);
- static void __exit ftrace_ops_sample_exit(void)
- {
- ops_destroy(ops_relevant, nr_ops_relevant);
- ops_destroy(ops_irrelevant, nr_ops_irrelevant);
- }
- module_exit(ftrace_ops_sample_exit);
- MODULE_AUTHOR("Mark Rutland");
- MODULE_DESCRIPTION("Example of using custom ftrace_ops");
- MODULE_LICENSE("GPL");
|