1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Benchmark scanning sysfs files for PMU information. |
4 | * |
5 | * Copyright 2023 Google LLC. |
6 | */ |
7 | #include <stdio.h> |
8 | #include "bench.h" |
9 | #include "util/debug.h" |
10 | #include "util/pmu.h" |
11 | #include "util/pmus.h" |
12 | #include "util/stat.h" |
13 | #include <linux/atomic.h> |
14 | #include <linux/err.h> |
15 | #include <linux/time64.h> |
16 | #include <subcmd/parse-options.h> |
17 | |
18 | static unsigned int iterations = 100; |
19 | |
20 | struct pmu_scan_result { |
21 | char *name; |
22 | int nr_aliases; |
23 | int nr_formats; |
24 | int nr_caps; |
25 | bool is_core; |
26 | }; |
27 | |
28 | static const struct option options[] = { |
29 | OPT_UINTEGER('i', "iterations" , &iterations, |
30 | "Number of iterations used to compute average" ), |
31 | OPT_END() |
32 | }; |
33 | |
34 | static const char *const bench_usage[] = { |
35 | "perf bench internals pmu-scan <options>" , |
36 | NULL |
37 | }; |
38 | |
39 | static int nr_pmus; |
40 | static struct pmu_scan_result *results; |
41 | |
42 | static int save_result(void) |
43 | { |
44 | struct perf_pmu *pmu = NULL; |
45 | struct list_head *list; |
46 | struct pmu_scan_result *r; |
47 | |
48 | while ((pmu = perf_pmus__scan(pmu)) != NULL) { |
49 | r = realloc(results, (nr_pmus + 1) * sizeof(*r)); |
50 | if (r == NULL) |
51 | return -ENOMEM; |
52 | |
53 | results = r; |
54 | r = results + nr_pmus; |
55 | |
56 | r->name = strdup(pmu->name); |
57 | r->is_core = pmu->is_core; |
58 | r->nr_caps = pmu->nr_caps; |
59 | |
60 | r->nr_aliases = perf_pmu__num_events(pmu); |
61 | |
62 | r->nr_formats = 0; |
63 | list_for_each(list, &pmu->format) |
64 | r->nr_formats++; |
65 | |
66 | pr_debug("pmu[%d] name=%s, nr_caps=%d, nr_aliases=%d, nr_formats=%d\n" , |
67 | nr_pmus, r->name, r->nr_caps, r->nr_aliases, r->nr_formats); |
68 | nr_pmus++; |
69 | } |
70 | |
71 | perf_pmus__destroy(); |
72 | return 0; |
73 | } |
74 | |
75 | static int check_result(bool core_only) |
76 | { |
77 | struct pmu_scan_result *r; |
78 | struct perf_pmu *pmu; |
79 | struct list_head *list; |
80 | int nr; |
81 | |
82 | for (int i = 0; i < nr_pmus; i++) { |
83 | r = &results[i]; |
84 | if (core_only && !r->is_core) |
85 | continue; |
86 | |
87 | pmu = perf_pmus__find(r->name); |
88 | if (pmu == NULL) { |
89 | pr_err("Cannot find PMU %s\n" , r->name); |
90 | return -1; |
91 | } |
92 | |
93 | if (pmu->nr_caps != (u32)r->nr_caps) { |
94 | pr_err("Unmatched number of event caps in %s: expect %d vs got %d\n" , |
95 | pmu->name, r->nr_caps, pmu->nr_caps); |
96 | return -1; |
97 | } |
98 | |
99 | nr = perf_pmu__num_events(pmu); |
100 | if (nr != r->nr_aliases) { |
101 | pr_err("Unmatched number of event aliases in %s: expect %d vs got %d\n" , |
102 | pmu->name, r->nr_aliases, nr); |
103 | return -1; |
104 | } |
105 | |
106 | nr = 0; |
107 | list_for_each(list, &pmu->format) |
108 | nr++; |
109 | if (nr != r->nr_formats) { |
110 | pr_err("Unmatched number of event formats in %s: expect %d vs got %d\n" , |
111 | pmu->name, r->nr_formats, nr); |
112 | return -1; |
113 | } |
114 | } |
115 | return 0; |
116 | } |
117 | |
118 | static void delete_result(void) |
119 | { |
120 | for (int i = 0; i < nr_pmus; i++) |
121 | free(results[i].name); |
122 | free(results); |
123 | |
124 | results = NULL; |
125 | nr_pmus = 0; |
126 | } |
127 | |
128 | static int run_pmu_scan(void) |
129 | { |
130 | struct stats stats; |
131 | struct timeval start, end, diff; |
132 | double time_average, time_stddev; |
133 | u64 runtime_us; |
134 | int ret; |
135 | |
136 | init_stats(&stats); |
137 | pr_info("Computing performance of sysfs PMU event scan for %u times\n" , |
138 | iterations); |
139 | |
140 | if (save_result() < 0) { |
141 | pr_err("Failed to initialize PMU scan result\n" ); |
142 | return -1; |
143 | } |
144 | |
145 | for (int j = 0; j < 2; j++) { |
146 | bool core_only = (j == 0); |
147 | |
148 | for (unsigned int i = 0; i < iterations; i++) { |
149 | gettimeofday(&start, NULL); |
150 | if (core_only) |
151 | perf_pmus__scan_core(NULL); |
152 | else |
153 | perf_pmus__scan(NULL); |
154 | gettimeofday(&end, NULL); |
155 | timersub(&end, &start, &diff); |
156 | runtime_us = diff.tv_sec * USEC_PER_SEC + diff.tv_usec; |
157 | update_stats(&stats, runtime_us); |
158 | |
159 | ret = check_result(core_only); |
160 | perf_pmus__destroy(); |
161 | if (ret < 0) |
162 | break; |
163 | } |
164 | time_average = avg_stats(&stats); |
165 | time_stddev = stddev_stats(&stats); |
166 | pr_info(" Average%s PMU scanning took: %.3f usec (+- %.3f usec)\n" , |
167 | core_only ? " core" : "" , time_average, time_stddev); |
168 | } |
169 | delete_result(); |
170 | return 0; |
171 | } |
172 | |
173 | int bench_pmu_scan(int argc, const char **argv) |
174 | { |
175 | int err = 0; |
176 | |
177 | argc = parse_options(argc, argv, options, bench_usage, 0); |
178 | if (argc) { |
179 | usage_with_options(bench_usage, options); |
180 | exit(EXIT_FAILURE); |
181 | } |
182 | |
183 | err = run_pmu_scan(); |
184 | |
185 | return err; |
186 | } |
187 | |