blob: 554fc749493d8aff1554cf29f3733ae5a00e2690 [file] [log] [blame]
// 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 <arch/ops.h>
#include <fbl/alloc_checker.h>
#include <fbl/mutex.h>
#include <kernel/auto_lock.h>
#include <kernel/cmdline.h>
#include <kernel/percpu.h>
#include <kernel/spinlock.h>
#include <kernel/timer.h>
#include <lib/console.h>
#include <lk/init.h>
#include <platform.h>
#include <stdlib.h>
#include <string.h>
#include "counters_private.h"
// kernel.ld uses this and fills in the descriptor table size after it and then
// places the sorted descriptor table after that (and then pads to page size),
// so as to fully populate the counters::DescriptorVmo layout.
__USED __SECTION(".kcounter.desc.header") static const uint64_t vmo_header[] = {
counters::DescriptorVmo::kMagic,
SMP_MAX_CPUS,
};
static_assert(sizeof(vmo_header) ==
offsetof(counters::DescriptorVmo, descriptor_table_size));
// This counter gets a constant value just as a sanity check.
KCOUNTER(magic, "kernel.counters.magic");
struct watched_counter_t {
list_node node;
const counters::Descriptor* 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 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 counters::Descriptor* upper_bound(
const char* val, const counters::Descriptor* first,
const counters::Descriptor* last) {
if (first >= last) {
return last;
}
const counters::Descriptor* 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 = CounterArena().CpuData(ix);
}
magic.Add(counters::DescriptorVmo::kMagic);
}
// Collapse values to only non-zero ones and sort.
void counters_clean_up_values(const uint64_t* values_in, uint64_t* values_out, size_t* count_out) {
assert(values_in != values_out);
*count_out = 0;
for (size_t i = 0; i < SMP_MAX_CPUS; ++i) {
if (values_in[i] > 0) {
values_out[(*count_out)++] = values_in[i];
}
}
qsort(values_out, *count_out, sizeof(uint64_t), [](const void* a, const void* b) {
return (*((uint64_t*)a) > *((uint64_t*)b)) - (*((uint64_t*)a) < *((uint64_t*)b));
});
}
static constexpr uint64_t DOT8_SHIFT = 8;
// This calculation tries to match what sheets.google.com uses for QUARTILE()
// and PERCENTAGE(), however this uses 56.8 rather than floating point.
// https://en.wikipedia.org/wiki/Percentile#The_linear_interpolation_between_closest_ranks_method
// This is just a linear interpolation between the items bracketing that
// percentage. The input value array must be sorted on entry to this function.
uint64_t counters_get_percentile(const uint64_t* values, size_t count, uint64_t percentage_dot8) {
assert(count >= 2);
uint64_t target_dot8 = (count - 1) * percentage_dot8;
uint64_t low_index = target_dot8 >> DOT8_SHIFT;
uint64_t high_index = low_index + 1;
uint64_t fraction_dot8 = target_dot8 & 0xff;
uint64_t delta = values[high_index] - values[low_index];
return ((values[low_index] << DOT8_SHIFT) + fraction_dot8 * delta);
}
bool counters_has_outlier(const uint64_t* values_in) {
uint64_t values[SMP_MAX_CPUS];
size_t count;
counters_clean_up_values(values_in, values, &count);
if (count < 2) {
return false;
}
// If there's a value that's an outlier per
// https://en.wikipedia.org/wiki/Outlier#Tukey's_fences, then we deem it
// worth outputting the per-core values. This is not perfect, but it is
// somewhat tricky to determine outliers for small data sets. We typically
// have something like 4 cores here, and e.g. calculating standard deviation
// is not useful for so few values.
const uint64_t q1_dot8 = counters_get_percentile(values, count, /*0.25*/ 64);
const uint64_t q3_dot8 = counters_get_percentile(values, count, /*0.75*/ 192);
const uint64_t k_dot8 = /*1.5*/ 384;
const uint64_t q_delta_dot8 = q3_dot8 - q1_dot8;
const int64_t low_dot8 =
q1_dot8 - static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
const int64_t high_dot8 =
q3_dot8 + static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
for (size_t i = 0; i < count; ++i) {
if ((static_cast<int64_t>(values[i]) << DOT8_SHIFT) < low_dot8 ||
(static_cast<int64_t>(values[i]) << DOT8_SHIFT) > high_dot8) {
return true;
}
}
return false;
}
static void dump_counter(const counters::Descriptor& desc, bool verbose) {
size_t counter_index = &desc - CounterDesc().begin();
uint64_t summary = 0;
uint64_t values[SMP_MAX_CPUS];
if (desc.type == counters::Type::kMax) {
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];
if (values[ix] > summary) {
summary = values[ix];
}
}
} else {
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];
summary += values[ix];
}
}
printf("[%.2zu] %s = %lu\n", counter_index, desc.name, summary);
if (summary == 0u) {
return;
}
// Print the per-core counts if verbose (-v) is set and it's not zero, or if
// a value for one of the cores indicates it's an outlier.
if (verbose || counters_has_outlier(values)) {
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(bool verbose) {
printf("%zu counters available:\n", CounterDesc().size());
for (const auto& desc : CounterDesc()) {
dump_counter(desc, verbose);
}
}
static int watcher_thread_fn(void* arg) {
bool verbose = static_cast<bool>(reinterpret_cast<uintptr_t>(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, verbose);
}
}
thread_sleep_relative(ZX_SEC(2));
}
}
static int view_counter(int argc, const cmd_args* argv) {
bool verbose = false;
if (argc == 3) {
if (strcmp(argv[1].str, "-v") == 0) {
verbose = true;
argc--;
argv++;
}
}
if (argc == 2) {
if (strcmp(argv[1].str, "all") == 0) {
dump_all_counters(verbose);
} else {
int num_results = 0;
auto name = argv[1].str;
auto desc = upper_bound(name,
CounterDesc().begin(), CounterDesc().end());
while (desc != CounterDesc().end()) {
if (!prefix_match(name, desc->name)) {
break;
}
dump_counter(*desc, verbose);
++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 [-v] <counter-name>\n"
"counters view [-v] <counter-prefix>\n"
"counters view [-v] all\n");
return 1;
}
return 0;
}
static int watch_counter(int argc, const cmd_args* argv) {
bool verbose = false;
if (argc == 3) {
if (strcmp(argv[1].str, "-v") == 0) {
verbose = true;
argc--;
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 = CounterDesc().size() - 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, CounterDesc().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,
reinterpret_cast<void*>(static_cast<uintptr_t>(verbose)), 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 [-v] <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 [-v] <name>\n"
" counters watch [-v] <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);