| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/counters.h> |
| |
| #include <string.h> |
| |
| #include <arch/ops.h> |
| #include <platform.h> |
| |
| #include <kernel/auto_lock.h> |
| #include <kernel/cmdline.h> |
| #include <kernel/timer.h> |
| #include <kernel/percpu.h> |
| #include <kernel/spinlock.h> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/mutex.h> |
| |
| #include <lk/init.h> |
| |
| #include <lib/console.h> |
| |
| // The arena is allocated in kernel.ld linker script. |
| extern int64_t kcounters_arena[]; |
| |
| struct watched_counter_t { |
| list_node node; |
| const k_counter_desc* desc; |
| // TODO(cpu): add min, max. |
| }; |
| |
| static fbl::Mutex watcher_lock; |
| static list_node watcher_list = LIST_INITIAL_VALUE(watcher_list); |
| static thread_t* watcher_thread; |
| |
| static size_t get_num_counters() { |
| return kcountdesc_end - kcountdesc_begin; |
| } |
| |
| static bool prefix_match(const char *pre, const char *str) { |
| return strncmp(pre, str, strlen(pre)) == 0; |
| } |
| |
| // Binary search the sorted counter descriptors. |
| // We rely on SORT_BY_NAME() in the linker script for this to work. |
| static const k_counter_desc* upper_bound( |
| const char* val, const k_counter_desc* first, const k_counter_desc* last) { |
| if (first >= last) |
| return last; |
| |
| const k_counter_desc* it; |
| size_t step; |
| auto count = last - first; |
| |
| while (count > 0) { |
| step = count / 2; |
| it = first + step; |
| |
| if (strcmp(it->name, val) < 0) { |
| first = ++it; |
| count -= step + 1; |
| } else { |
| count = step; |
| } |
| } |
| return first; |
| } |
| |
| static void counters_init(unsigned level) { |
| // Wire the memory defined in the .bss section to the counters. |
| for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) { |
| percpu[ix].counters = &kcounters_arena[ix * get_num_counters()]; |
| } |
| } |
| |
| static void dump_counter(const k_counter_desc* desc) { |
| size_t counter_index = kcounter_index(desc); |
| |
| uint64_t sum = 0; |
| uint64_t values[SMP_MAX_CPUS]; |
| for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) { |
| // This value is not atomically consistent, therefore is just |
| // an approximation. TODO(cpu): for ARM this might need some magic. |
| values[ix] = percpu[ix].counters[counter_index]; |
| sum += values[ix]; |
| } |
| |
| printf("[%.2zu] %s = %lu\n", counter_index, desc->name, sum); |
| if (sum == 0u) |
| return; |
| |
| // Print the per-core counts when the sum is not zero. |
| printf(" "); |
| for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) { |
| if (values[ix] > 0) |
| printf("[%zu:%lu]", ix, values[ix]); |
| } |
| printf("\n"); |
| } |
| |
| static void dump_all_counters() { |
| printf("%zu counters available:\n", get_num_counters()); |
| for (auto it = kcountdesc_begin; it != kcountdesc_end; ++it) { |
| dump_counter(it); |
| } |
| } |
| |
| static int watcher_thread_fn(void* arg) { |
| while (true) { |
| { |
| fbl::AutoLock lock(&watcher_lock); |
| if (list_is_empty(&watcher_list)) { |
| watcher_thread = nullptr; |
| return 0; |
| } |
| |
| watched_counter_t* wc; |
| list_for_every_entry (&watcher_list, wc, watched_counter_t, node) { |
| dump_counter(wc->desc); |
| } |
| } |
| |
| thread_sleep_relative(ZX_SEC(2)); |
| } |
| } |
| |
| static int view_counter(int argc, const cmd_args* argv) { |
| if (argc == 2) { |
| if (strcmp(argv[1].str, "--all") == 0) { |
| dump_all_counters(); |
| } else { |
| int num_results = 0; |
| auto name = argv[1].str; |
| auto desc = upper_bound(name, kcountdesc_begin, kcountdesc_end); |
| while (desc != kcountdesc_end) { |
| if (!prefix_match(name, desc->name)) |
| break; |
| dump_counter(desc); |
| ++num_results; |
| ++desc; |
| } |
| if (num_results == 0) { |
| printf("counter '%s' not found, try --all\n", name); |
| } else { |
| printf("%d counters found\n", num_results); |
| } |
| } |
| } else { |
| printf( |
| "counters view <counter-name>\n" |
| "counters view <counter-prefix>\n" |
| "counters view --all\n" |
| ); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int watch_counter(int argc, const cmd_args* argv) { |
| if (argc == 2) { |
| if (strcmp(argv[1].str, "--stop") == 0) { |
| fbl::AutoLock lock(&watcher_lock); |
| watched_counter_t* wc; |
| while ((wc = list_remove_head_type( |
| &watcher_list, watched_counter_t, node)) != nullptr) { |
| delete wc; |
| } |
| // The thread exits itself it there are no counters. |
| return 0; |
| } |
| |
| size_t counter_id = argv[1].u; |
| auto range = get_num_counters() - 1; |
| if (counter_id > range) { |
| printf("counter id must be in the 0 to %zu range\n", range); |
| return 1; |
| } else if ((counter_id == 0) && (strlen(argv[1].str) > 1)) { |
| // Parsing a name as a number. |
| printf("counter ids are numbers\n"); |
| return 1; |
| } |
| |
| fbl::AllocChecker ac; |
| auto wc = new (&ac) watched_counter_t { |
| LIST_INITIAL_CLEARED_VALUE, &kcountdesc_begin[counter_id] }; |
| if (!ac.check()) { |
| printf("no memory for counter\n"); |
| return 1; |
| } |
| |
| { |
| fbl::AutoLock lock(&watcher_lock); |
| list_add_head(&watcher_list, &wc->node); |
| if (watcher_thread == nullptr) { |
| watcher_thread = thread_create( |
| "counter-watcher", watcher_thread_fn, nullptr, LOW_PRIORITY); |
| if (watcher_thread == nullptr) { |
| printf("no memory for watcher thread\n"); |
| return 1; |
| } |
| thread_detach_and_resume(watcher_thread); |
| } |
| } |
| |
| } else { |
| printf( |
| "counters watch <counter-id>\n" |
| "counters watch --stop\n" |
| ); |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_counters(int argc, const cmd_args* argv, uint32_t flags) { |
| if (argc > 1) { |
| if (strcmp(argv[1].str, "view") == 0) { |
| return view_counter(argc - 1, &argv[1]); |
| } |
| if (strcmp(argv[1].str, "watch") == 0) { |
| return watch_counter(argc - 1, &argv[1]); |
| } |
| } |
| |
| printf( |
| "inspect system counters:\n" |
| " counters view <name>\n" |
| " counters watch <id>\n" |
| ); |
| return 0; |
| } |
| |
| LK_INIT_HOOK(kcounters, counters_init, LK_INIT_LEVEL_PLATFORM_EARLY); |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("counters", "view system counters", &cmd_counters) |
| STATIC_COMMAND_END(mem_tests); |