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