1 // Copyright 2017 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6
7 #include <lib/counters.h>
8
9 #include <arch/ops.h>
10 #include <fbl/alloc_checker.h>
11 #include <fbl/mutex.h>
12 #include <kernel/auto_lock.h>
13 #include <kernel/cmdline.h>
14 #include <kernel/percpu.h>
15 #include <kernel/spinlock.h>
16 #include <kernel/timer.h>
17 #include <lib/console.h>
18 #include <lk/init.h>
19 #include <platform.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "counters_private.h"
24
25 // The arena is allocated in kernel.ld linker script.
26 extern int64_t kcounters_arena[];
27
28 struct watched_counter_t {
29 list_node node;
30 const k_counter_desc* desc;
31 // TODO(cpu): add min, max.
32 };
33
34 static fbl::Mutex watcher_lock;
35 static list_node watcher_list = LIST_INITIAL_VALUE(watcher_list);
36 static thread_t* watcher_thread;
37
get_num_counters()38 static size_t get_num_counters() {
39 return kcountdesc_end - kcountdesc_begin;
40 }
41
prefix_match(const char * pre,const char * str)42 static bool prefix_match(const char* pre, const char* str) {
43 return strncmp(pre, str, strlen(pre)) == 0;
44 }
45
46 // Binary search the sorted counter descriptors.
47 // We rely on SORT_BY_NAME() in the linker script for this to work.
upper_bound(const char * val,const k_counter_desc * first,const k_counter_desc * last)48 static const k_counter_desc* upper_bound(
49 const char* val, const k_counter_desc* first, const k_counter_desc* last) {
50 if (first >= last) {
51 return last;
52 }
53
54 const k_counter_desc* it;
55 size_t step;
56 auto count = last - first;
57
58 while (count > 0) {
59 step = count / 2;
60 it = first + step;
61
62 if (strcmp(it->name, val) < 0) {
63 first = ++it;
64 count -= step + 1;
65 } else {
66 count = step;
67 }
68 }
69 return first;
70 }
71
counters_init(unsigned level)72 static void counters_init(unsigned level) {
73 // Wire the memory defined in the .bss section to the counters.
74 for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
75 percpu[ix].counters = &kcounters_arena[ix * get_num_counters()];
76 }
77 }
78
79 // Collapse values to only non-zero ones and sort.
counters_clean_up_values(const uint64_t * values_in,uint64_t * values_out,size_t * count_out)80 void counters_clean_up_values(const uint64_t* values_in, uint64_t* values_out, size_t* count_out) {
81 assert(values_in != values_out);
82
83 *count_out = 0;
84 for (size_t i = 0; i < SMP_MAX_CPUS; ++i) {
85 if (values_in[i] > 0) {
86 values_out[(*count_out)++] = values_in[i];
87 }
88 }
89
90 qsort(values_out, *count_out, sizeof(uint64_t), [](const void* a, const void* b) {
91 return (*((uint64_t*)a) > *((uint64_t*)b)) - (*((uint64_t*)a) < *((uint64_t*)b));
92 });
93 }
94
95 static constexpr uint64_t DOT8_SHIFT = 8;
96
97 // This calculation tries to match what sheets.google.com uses for QUARTILE()
98 // and PERCENTAGE(), however this uses 56.8 rather than floating point.
99 // https://en.wikipedia.org/wiki/Percentile#The_linear_interpolation_between_closest_ranks_method
100 // This is just a linear interpolation between the items bracketing that
101 // percentage. The input value array must be sorted on entry to this function.
counters_get_percentile(const uint64_t * values,size_t count,uint64_t percentage_dot8)102 uint64_t counters_get_percentile(const uint64_t* values, size_t count, uint64_t percentage_dot8) {
103 assert(count >= 2);
104
105 uint64_t target_dot8 = (count - 1) * percentage_dot8;
106 uint64_t low_index = target_dot8 >> DOT8_SHIFT;
107 uint64_t high_index = low_index + 1;
108 uint64_t fraction_dot8 = target_dot8 & 0xff;
109
110 uint64_t delta = values[high_index] - values[low_index];
111 return ((values[low_index] << DOT8_SHIFT) + fraction_dot8 * delta);
112 }
113
counters_has_outlier(const uint64_t * values_in)114 bool counters_has_outlier(const uint64_t* values_in) {
115 uint64_t values[SMP_MAX_CPUS];
116 size_t count;
117 counters_clean_up_values(values_in, values, &count);
118 if (count < 2) {
119 return false;
120 }
121
122 // If there's a value that's an outlier per
123 // https://en.wikipedia.org/wiki/Outlier#Tukey's_fences, then we deem it
124 // worth outputting the per-core values. This is not perfect, but it is
125 // somewhat tricky to determine outliers for small data sets. We typically
126 // have something like 4 cores here, and e.g. calculating standard deviation
127 // is not useful for so few values.
128 const uint64_t q1_dot8 = counters_get_percentile(values, count, /*0.25*/ 64);
129 const uint64_t q3_dot8 = counters_get_percentile(values, count, /*0.75*/ 192);
130 const uint64_t k_dot8 = /*1.5*/ 384;
131 const uint64_t q_delta_dot8 = q3_dot8 - q1_dot8;
132 const int64_t low_dot8 =
133 q1_dot8 - static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
134 const int64_t high_dot8 =
135 q3_dot8 + static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
136
137 for (size_t i = 0; i < count; ++i) {
138 if ((static_cast<int64_t>(values[i]) << DOT8_SHIFT) < low_dot8 ||
139 (static_cast<int64_t>(values[i]) << DOT8_SHIFT) > high_dot8) {
140 return true;
141 }
142 }
143
144 return false;
145 }
146
dump_counter(const k_counter_desc * desc,bool verbose)147 static void dump_counter(const k_counter_desc* desc, bool verbose) {
148 size_t counter_index = kcounter_index(desc);
149
150 uint64_t summary = 0;
151 uint64_t values[SMP_MAX_CPUS];
152
153 if (desc->type == k_counter_type::max) {
154 for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
155 // This value is not atomically consistent, therefore is just
156 // an approximation. TODO(cpu): for ARM this might need some magic.
157 values[ix] = percpu[ix].counters[counter_index];
158 if (values[ix] > summary) {
159 summary = values[ix];
160 }
161 }
162 } else {
163 for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
164 // This value is not atomically consistent, therefore is just
165 // an approximation. TODO(cpu): for ARM this might need some magic.
166 values[ix] = percpu[ix].counters[counter_index];
167 summary += values[ix];
168 }
169 }
170
171 printf("[%.2zu] %s = %lu\n", counter_index, desc->name, summary);
172 if (summary == 0u) {
173 return;
174 }
175
176 // Print the per-core counts if verbose (-v) is set and it's not zero, or if
177 // a value for one of the cores indicates it's an outlier.
178 if (verbose || counters_has_outlier(values)) {
179 printf(" ");
180 for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
181 if (values[ix] > 0) {
182 printf("[%zu:%lu]", ix, values[ix]);
183 }
184 }
185 printf("\n");
186 }
187 }
188
dump_all_counters(bool verbose)189 static void dump_all_counters(bool verbose) {
190 printf("%zu counters available:\n", get_num_counters());
191 for (auto it = kcountdesc_begin; it != kcountdesc_end; ++it) {
192 dump_counter(it, verbose);
193 }
194 }
195
watcher_thread_fn(void * arg)196 static int watcher_thread_fn(void* arg) {
197 bool verbose = static_cast<bool>(reinterpret_cast<uintptr_t>(arg));
198 while (true) {
199 {
200 fbl::AutoLock lock(&watcher_lock);
201 if (list_is_empty(&watcher_list)) {
202 watcher_thread = nullptr;
203 return 0;
204 }
205
206 watched_counter_t* wc;
207 list_for_every_entry (&watcher_list, wc, watched_counter_t, node) {
208 dump_counter(wc->desc, verbose);
209 }
210 }
211
212 thread_sleep_relative(ZX_SEC(2));
213 }
214 }
215
view_counter(int argc,const cmd_args * argv)216 static int view_counter(int argc, const cmd_args* argv) {
217 bool verbose = false;
218 if (argc == 3) {
219 if (strcmp(argv[1].str, "-v") == 0) {
220 verbose = true;
221 argc--;
222 argv++;
223 }
224 }
225
226 if (argc == 2) {
227 if (strcmp(argv[1].str, "all") == 0) {
228 dump_all_counters(verbose);
229 } else {
230 int num_results = 0;
231 auto name = argv[1].str;
232 auto desc = upper_bound(name, kcountdesc_begin, kcountdesc_end);
233 while (desc != kcountdesc_end) {
234 if (!prefix_match(name, desc->name)) {
235 break;
236 }
237 dump_counter(desc, verbose);
238 ++num_results;
239 ++desc;
240 }
241 if (num_results == 0) {
242 printf("counter '%s' not found, try all\n", name);
243 } else {
244 printf("%d counters found\n", num_results);
245 }
246 }
247 } else {
248 printf(
249 "counters view [-v] <counter-name>\n"
250 "counters view [-v] <counter-prefix>\n"
251 "counters view [-v] all\n");
252 return 1;
253 }
254
255 return 0;
256 }
257
watch_counter(int argc,const cmd_args * argv)258 static int watch_counter(int argc, const cmd_args* argv) {
259 bool verbose = false;
260 if (argc == 3) {
261 if (strcmp(argv[1].str, "-v") == 0) {
262 verbose = true;
263 argc--;
264 argv++;
265 }
266 }
267
268 if (argc == 2) {
269 if (strcmp(argv[1].str, "stop") == 0) {
270 fbl::AutoLock lock(&watcher_lock);
271 watched_counter_t* wc;
272 while ((wc = list_remove_head_type(
273 &watcher_list, watched_counter_t, node)) != nullptr) {
274 delete wc;
275 }
276 // The thread exits itself it there are no counters.
277 return 0;
278 }
279
280 size_t counter_id = argv[1].u;
281 auto range = get_num_counters() - 1;
282 if (counter_id > range) {
283 printf("counter id must be in the 0 to %zu range\n", range);
284 return 1;
285 } else if ((counter_id == 0) && (strlen(argv[1].str) > 1)) {
286 // Parsing a name as a number.
287 printf("counter ids are numbers\n");
288 return 1;
289 }
290
291 fbl::AllocChecker ac;
292 auto wc = new (&ac) watched_counter_t{
293 LIST_INITIAL_CLEARED_VALUE, &kcountdesc_begin[counter_id]};
294 if (!ac.check()) {
295 printf("no memory for counter\n");
296 return 1;
297 }
298
299 {
300 fbl::AutoLock lock(&watcher_lock);
301 list_add_head(&watcher_list, &wc->node);
302 if (watcher_thread == nullptr) {
303 watcher_thread = thread_create(
304 "counter-watcher", watcher_thread_fn,
305 reinterpret_cast<void*>(static_cast<uintptr_t>(verbose)), LOW_PRIORITY);
306 if (watcher_thread == nullptr) {
307 printf("no memory for watcher thread\n");
308 return 1;
309 }
310 thread_detach_and_resume(watcher_thread);
311 }
312 }
313
314 } else {
315 printf(
316 "counters watch [-v] <counter-id>\n"
317 "counters watch stop\n");
318 }
319
320 return 0;
321 }
322
cmd_counters(int argc,const cmd_args * argv,uint32_t flags)323 static int cmd_counters(int argc, const cmd_args* argv, uint32_t flags) {
324 if (argc > 1) {
325 if (strcmp(argv[1].str, "view") == 0) {
326 return view_counter(argc - 1, &argv[1]);
327 }
328 if (strcmp(argv[1].str, "watch") == 0) {
329 return watch_counter(argc - 1, &argv[1]);
330 }
331 }
332
333 printf(
334 "inspect system counters:\n"
335 " counters view [-v] <name>\n"
336 " counters watch [-v] <id>\n");
337 return 0;
338 }
339
340 LK_INIT_HOOK(kcounters, counters_init, LK_INIT_LEVEL_PLATFORM_EARLY);
341
342 STATIC_COMMAND_START
343 STATIC_COMMAND("counters", "view system counters", &cmd_counters)
344 STATIC_COMMAND_END(mem_tests);
345