1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
281dc9f0eSSteven Rostedt (Red Hat) #include <linux/delay.h>
381dc9f0eSSteven Rostedt (Red Hat) #include <linux/module.h>
481dc9f0eSSteven Rostedt (Red Hat) #include <linux/kthread.h>
581dc9f0eSSteven Rostedt (Red Hat) #include <linux/trace_clock.h>
681dc9f0eSSteven Rostedt (Red Hat)
781dc9f0eSSteven Rostedt (Red Hat) #define CREATE_TRACE_POINTS
881dc9f0eSSteven Rostedt (Red Hat) #include "trace_benchmark.h"
981dc9f0eSSteven Rostedt (Red Hat)
1081dc9f0eSSteven Rostedt (Red Hat) static struct task_struct *bm_event_thread;
1181dc9f0eSSteven Rostedt (Red Hat)
1281dc9f0eSSteven Rostedt (Red Hat) static char bm_str[BENCHMARK_EVENT_STRLEN] = "START";
1381dc9f0eSSteven Rostedt (Red Hat)
1481dc9f0eSSteven Rostedt (Red Hat) static u64 bm_total;
1581dc9f0eSSteven Rostedt (Red Hat) static u64 bm_totalsq;
1681dc9f0eSSteven Rostedt (Red Hat) static u64 bm_last;
1781dc9f0eSSteven Rostedt (Red Hat) static u64 bm_max;
1881dc9f0eSSteven Rostedt (Red Hat) static u64 bm_min;
1981dc9f0eSSteven Rostedt (Red Hat) static u64 bm_first;
2034839f5aSSteven Rostedt (Red Hat) static u64 bm_cnt;
2134839f5aSSteven Rostedt (Red Hat) static u64 bm_stddev;
2234839f5aSSteven Rostedt (Red Hat) static unsigned int bm_avg;
2334839f5aSSteven Rostedt (Red Hat) static unsigned int bm_std;
2481dc9f0eSSteven Rostedt (Red Hat)
259c1f6bb8SSteven Rostedt (Red Hat) static bool ok_to_run;
269c1f6bb8SSteven Rostedt (Red Hat)
2781dc9f0eSSteven Rostedt (Red Hat) /*
2881dc9f0eSSteven Rostedt (Red Hat) * This gets called in a loop recording the time it took to write
2981dc9f0eSSteven Rostedt (Red Hat) * the tracepoint. What it writes is the time statistics of the last
3081dc9f0eSSteven Rostedt (Red Hat) * tracepoint write. As there is nothing to write the first time
3181dc9f0eSSteven Rostedt (Red Hat) * it simply writes "START". As the first write is cold cache and
3281dc9f0eSSteven Rostedt (Red Hat) * the rest is hot, we save off that time in bm_first and it is
3381dc9f0eSSteven Rostedt (Red Hat) * reported as "first", which is shown in the second write to the
342b5894ccSQiujun Huang * tracepoint. The "first" field is written within the statics from
3581dc9f0eSSteven Rostedt (Red Hat) * then on but never changes.
3681dc9f0eSSteven Rostedt (Red Hat) */
trace_do_benchmark(void)3781dc9f0eSSteven Rostedt (Red Hat) static void trace_do_benchmark(void)
3881dc9f0eSSteven Rostedt (Red Hat) {
3981dc9f0eSSteven Rostedt (Red Hat) u64 start;
4081dc9f0eSSteven Rostedt (Red Hat) u64 stop;
4181dc9f0eSSteven Rostedt (Red Hat) u64 delta;
4272e2fe38SSteven Rostedt (Red Hat) u64 stddev;
4381dc9f0eSSteven Rostedt (Red Hat) u64 seed;
4481dc9f0eSSteven Rostedt (Red Hat) u64 last_seed;
4581dc9f0eSSteven Rostedt (Red Hat) unsigned int avg;
4681dc9f0eSSteven Rostedt (Red Hat) unsigned int std = 0;
4781dc9f0eSSteven Rostedt (Red Hat)
4881dc9f0eSSteven Rostedt (Red Hat) /* Only run if the tracepoint is actually active */
49bdb5d0f9SChunyan Zhang if (!trace_benchmark_event_enabled() || !tracing_is_on())
5081dc9f0eSSteven Rostedt (Red Hat) return;
5181dc9f0eSSteven Rostedt (Red Hat)
5281dc9f0eSSteven Rostedt (Red Hat) local_irq_disable();
5381dc9f0eSSteven Rostedt (Red Hat) start = trace_clock_local();
54b7b037ebSSteven Rostedt (Google) trace_benchmark_event(bm_str, bm_last);
5581dc9f0eSSteven Rostedt (Red Hat) stop = trace_clock_local();
5681dc9f0eSSteven Rostedt (Red Hat) local_irq_enable();
5781dc9f0eSSteven Rostedt (Red Hat)
5881dc9f0eSSteven Rostedt (Red Hat) bm_cnt++;
5981dc9f0eSSteven Rostedt (Red Hat)
6081dc9f0eSSteven Rostedt (Red Hat) delta = stop - start;
6181dc9f0eSSteven Rostedt (Red Hat)
6281dc9f0eSSteven Rostedt (Red Hat) /*
6381dc9f0eSSteven Rostedt (Red Hat) * The first read is cold cached, keep it separate from the
6481dc9f0eSSteven Rostedt (Red Hat) * other calculations.
6581dc9f0eSSteven Rostedt (Red Hat) */
6681dc9f0eSSteven Rostedt (Red Hat) if (bm_cnt == 1) {
6781dc9f0eSSteven Rostedt (Red Hat) bm_first = delta;
6881dc9f0eSSteven Rostedt (Red Hat) scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
6981dc9f0eSSteven Rostedt (Red Hat) "first=%llu [COLD CACHED]", bm_first);
7081dc9f0eSSteven Rostedt (Red Hat) return;
7181dc9f0eSSteven Rostedt (Red Hat) }
7281dc9f0eSSteven Rostedt (Red Hat)
7381dc9f0eSSteven Rostedt (Red Hat) bm_last = delta;
7481dc9f0eSSteven Rostedt (Red Hat)
7581dc9f0eSSteven Rostedt (Red Hat) if (delta > bm_max)
7681dc9f0eSSteven Rostedt (Red Hat) bm_max = delta;
7781dc9f0eSSteven Rostedt (Red Hat) if (!bm_min || delta < bm_min)
7881dc9f0eSSteven Rostedt (Red Hat) bm_min = delta;
7981dc9f0eSSteven Rostedt (Red Hat)
8034839f5aSSteven Rostedt (Red Hat) /*
8134839f5aSSteven Rostedt (Red Hat) * When bm_cnt is greater than UINT_MAX, it breaks the statistics
8234839f5aSSteven Rostedt (Red Hat) * accounting. Freeze the statistics when that happens.
8334839f5aSSteven Rostedt (Red Hat) * We should have enough data for the avg and stddev anyway.
8434839f5aSSteven Rostedt (Red Hat) */
8534839f5aSSteven Rostedt (Red Hat) if (bm_cnt > UINT_MAX) {
8634839f5aSSteven Rostedt (Red Hat) scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
8734839f5aSSteven Rostedt (Red Hat) "last=%llu first=%llu max=%llu min=%llu ** avg=%u std=%d std^2=%lld",
8834839f5aSSteven Rostedt (Red Hat) bm_last, bm_first, bm_max, bm_min, bm_avg, bm_std, bm_stddev);
8934839f5aSSteven Rostedt (Red Hat) return;
9034839f5aSSteven Rostedt (Red Hat) }
9134839f5aSSteven Rostedt (Red Hat)
9234839f5aSSteven Rostedt (Red Hat) bm_total += delta;
9334839f5aSSteven Rostedt (Red Hat) bm_totalsq += delta * delta;
9434839f5aSSteven Rostedt (Red Hat)
9581dc9f0eSSteven Rostedt (Red Hat) if (bm_cnt > 1) {
9681dc9f0eSSteven Rostedt (Red Hat) /*
9781dc9f0eSSteven Rostedt (Red Hat) * Apply Welford's method to calculate standard deviation:
9881dc9f0eSSteven Rostedt (Red Hat) * s^2 = 1 / (n * (n-1)) * (n * \Sum (x_i)^2 - (\Sum x_i)^2)
9981dc9f0eSSteven Rostedt (Red Hat) */
10081dc9f0eSSteven Rostedt (Red Hat) stddev = (u64)bm_cnt * bm_totalsq - bm_total * bm_total;
10134839f5aSSteven Rostedt (Red Hat) do_div(stddev, (u32)bm_cnt);
10234839f5aSSteven Rostedt (Red Hat) do_div(stddev, (u32)bm_cnt - 1);
10381dc9f0eSSteven Rostedt (Red Hat) } else
10481dc9f0eSSteven Rostedt (Red Hat) stddev = 0;
10581dc9f0eSSteven Rostedt (Red Hat)
10681dc9f0eSSteven Rostedt (Red Hat) delta = bm_total;
107*347bd7f0SThorsten Blum do_div(delta, (u32)bm_cnt);
10881dc9f0eSSteven Rostedt (Red Hat) avg = delta;
10981dc9f0eSSteven Rostedt (Red Hat)
11081dc9f0eSSteven Rostedt (Red Hat) if (stddev > 0) {
11181dc9f0eSSteven Rostedt (Red Hat) int i = 0;
11281dc9f0eSSteven Rostedt (Red Hat) /*
11381dc9f0eSSteven Rostedt (Red Hat) * stddev is the square of standard deviation but
1142b5894ccSQiujun Huang * we want the actually number. Use the average
11581dc9f0eSSteven Rostedt (Red Hat) * as our seed to find the std.
11681dc9f0eSSteven Rostedt (Red Hat) *
11781dc9f0eSSteven Rostedt (Red Hat) * The next try is:
11881dc9f0eSSteven Rostedt (Red Hat) * x = (x + N/x) / 2
11981dc9f0eSSteven Rostedt (Red Hat) *
12081dc9f0eSSteven Rostedt (Red Hat) * Where N is the squared number to find the square
12181dc9f0eSSteven Rostedt (Red Hat) * root of.
12281dc9f0eSSteven Rostedt (Red Hat) */
12381dc9f0eSSteven Rostedt (Red Hat) seed = avg;
12481dc9f0eSSteven Rostedt (Red Hat) do {
12581dc9f0eSSteven Rostedt (Red Hat) last_seed = seed;
12681dc9f0eSSteven Rostedt (Red Hat) seed = stddev;
12781dc9f0eSSteven Rostedt (Red Hat) if (!last_seed)
12881dc9f0eSSteven Rostedt (Red Hat) break;
129d6cb38e1SThorsten Blum seed = div64_u64(seed, last_seed);
13081dc9f0eSSteven Rostedt (Red Hat) seed += last_seed;
13181dc9f0eSSteven Rostedt (Red Hat) do_div(seed, 2);
13281dc9f0eSSteven Rostedt (Red Hat) } while (i++ < 10 && last_seed != seed);
13381dc9f0eSSteven Rostedt (Red Hat)
13481dc9f0eSSteven Rostedt (Red Hat) std = seed;
13581dc9f0eSSteven Rostedt (Red Hat) }
13681dc9f0eSSteven Rostedt (Red Hat)
13781dc9f0eSSteven Rostedt (Red Hat) scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
13881dc9f0eSSteven Rostedt (Red Hat) "last=%llu first=%llu max=%llu min=%llu avg=%u std=%d std^2=%lld",
13981dc9f0eSSteven Rostedt (Red Hat) bm_last, bm_first, bm_max, bm_min, avg, std, stddev);
14034839f5aSSteven Rostedt (Red Hat)
14134839f5aSSteven Rostedt (Red Hat) bm_std = std;
14234839f5aSSteven Rostedt (Red Hat) bm_avg = avg;
14334839f5aSSteven Rostedt (Red Hat) bm_stddev = stddev;
14481dc9f0eSSteven Rostedt (Red Hat) }
14581dc9f0eSSteven Rostedt (Red Hat)
benchmark_event_kthread(void * arg)14681dc9f0eSSteven Rostedt (Red Hat) static int benchmark_event_kthread(void *arg)
14781dc9f0eSSteven Rostedt (Red Hat) {
14881dc9f0eSSteven Rostedt (Red Hat) /* sleep a bit to make sure the tracepoint gets activated */
14981dc9f0eSSteven Rostedt (Red Hat) msleep(100);
15081dc9f0eSSteven Rostedt (Red Hat)
15181dc9f0eSSteven Rostedt (Red Hat) while (!kthread_should_stop()) {
15281dc9f0eSSteven Rostedt (Red Hat)
15381dc9f0eSSteven Rostedt (Red Hat) trace_do_benchmark();
15481dc9f0eSSteven Rostedt (Red Hat)
15581dc9f0eSSteven Rostedt (Red Hat) /*
156b980b117SSteven Rostedt (VMware) * We don't go to sleep, but let others run as well.
1572b5894ccSQiujun Huang * This is basically a "yield()" to let any task that
158b980b117SSteven Rostedt (VMware) * wants to run, schedule in, but if the CPU is idle,
159b980b117SSteven Rostedt (VMware) * we'll keep burning cycles.
160b980b117SSteven Rostedt (VMware) *
161cee43939SPaul E. McKenney * Note the tasks_rcu_qs() version of cond_resched() will
162b980b117SSteven Rostedt (VMware) * notify synchronize_rcu_tasks() that this thread has
163b980b117SSteven Rostedt (VMware) * passed a quiescent state for rcu_tasks. Otherwise
164b980b117SSteven Rostedt (VMware) * this thread will never voluntarily schedule which would
165b980b117SSteven Rostedt (VMware) * block synchronize_rcu_tasks() indefinitely.
16681dc9f0eSSteven Rostedt (Red Hat) */
167cee43939SPaul E. McKenney cond_resched_tasks_rcu_qs();
16881dc9f0eSSteven Rostedt (Red Hat) }
16981dc9f0eSSteven Rostedt (Red Hat)
17081dc9f0eSSteven Rostedt (Red Hat) return 0;
17181dc9f0eSSteven Rostedt (Red Hat) }
17281dc9f0eSSteven Rostedt (Red Hat)
17381dc9f0eSSteven Rostedt (Red Hat) /*
17481dc9f0eSSteven Rostedt (Red Hat) * When the benchmark tracepoint is enabled, it calls this
17581dc9f0eSSteven Rostedt (Red Hat) * function and the thread that calls the tracepoint is created.
17681dc9f0eSSteven Rostedt (Red Hat) */
trace_benchmark_reg(void)1778cf868afSSteven Rostedt (Red Hat) int trace_benchmark_reg(void)
17881dc9f0eSSteven Rostedt (Red Hat) {
1799c1f6bb8SSteven Rostedt (Red Hat) if (!ok_to_run) {
1803da2e1fdSKefeng Wang pr_warn("trace benchmark cannot be started via kernel command line\n");
1811dd349abSSteven Rostedt (Red Hat) return -EBUSY;
1821dd349abSSteven Rostedt (Red Hat) }
1831dd349abSSteven Rostedt (Red Hat)
18481dc9f0eSSteven Rostedt (Red Hat) bm_event_thread = kthread_run(benchmark_event_kthread,
18581dc9f0eSSteven Rostedt (Red Hat) NULL, "event_benchmark");
1868f0994bbSWei Yongjun if (IS_ERR(bm_event_thread)) {
1873da2e1fdSKefeng Wang pr_warn("trace benchmark failed to create kernel thread\n");
1888f0994bbSWei Yongjun return PTR_ERR(bm_event_thread);
1891dd349abSSteven Rostedt (Red Hat) }
1901dd349abSSteven Rostedt (Red Hat)
1918cf868afSSteven Rostedt (Red Hat) return 0;
19281dc9f0eSSteven Rostedt (Red Hat) }
19381dc9f0eSSteven Rostedt (Red Hat)
19481dc9f0eSSteven Rostedt (Red Hat) /*
19581dc9f0eSSteven Rostedt (Red Hat) * When the benchmark tracepoint is disabled, it calls this
19681dc9f0eSSteven Rostedt (Red Hat) * function and the thread that calls the tracepoint is deleted
19781dc9f0eSSteven Rostedt (Red Hat) * and all the numbers are reset.
19881dc9f0eSSteven Rostedt (Red Hat) */
trace_benchmark_unreg(void)19981dc9f0eSSteven Rostedt (Red Hat) void trace_benchmark_unreg(void)
20081dc9f0eSSteven Rostedt (Red Hat) {
20181dc9f0eSSteven Rostedt (Red Hat) if (!bm_event_thread)
20281dc9f0eSSteven Rostedt (Red Hat) return;
20381dc9f0eSSteven Rostedt (Red Hat)
20481dc9f0eSSteven Rostedt (Red Hat) kthread_stop(bm_event_thread);
2051dd349abSSteven Rostedt (Red Hat) bm_event_thread = NULL;
20681dc9f0eSSteven Rostedt (Red Hat)
20781dc9f0eSSteven Rostedt (Red Hat) strcpy(bm_str, "START");
20881dc9f0eSSteven Rostedt (Red Hat) bm_total = 0;
20981dc9f0eSSteven Rostedt (Red Hat) bm_totalsq = 0;
21081dc9f0eSSteven Rostedt (Red Hat) bm_last = 0;
21181dc9f0eSSteven Rostedt (Red Hat) bm_max = 0;
21281dc9f0eSSteven Rostedt (Red Hat) bm_min = 0;
21381dc9f0eSSteven Rostedt (Red Hat) bm_cnt = 0;
21434839f5aSSteven Rostedt (Red Hat) /* These don't need to be reset but reset them anyway */
21581dc9f0eSSteven Rostedt (Red Hat) bm_first = 0;
21634839f5aSSteven Rostedt (Red Hat) bm_std = 0;
21734839f5aSSteven Rostedt (Red Hat) bm_avg = 0;
21834839f5aSSteven Rostedt (Red Hat) bm_stddev = 0;
21981dc9f0eSSteven Rostedt (Red Hat) }
2209c1f6bb8SSteven Rostedt (Red Hat)
ok_to_run_trace_benchmark(void)2219c1f6bb8SSteven Rostedt (Red Hat) static __init int ok_to_run_trace_benchmark(void)
2229c1f6bb8SSteven Rostedt (Red Hat) {
2239c1f6bb8SSteven Rostedt (Red Hat) ok_to_run = true;
2249c1f6bb8SSteven Rostedt (Red Hat)
2259c1f6bb8SSteven Rostedt (Red Hat) return 0;
2269c1f6bb8SSteven Rostedt (Red Hat) }
2279c1f6bb8SSteven Rostedt (Red Hat)
2289c1f6bb8SSteven Rostedt (Red Hat) early_initcall(ok_to_run_trace_benchmark);
229