blob: 7fce9d4306c77cbbe08b5474c26bef4db3ae66a9 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/kernel/cpp/fidl.h>
#include <lib/inspect/cpp/hierarchy.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/sys/cpp/service_directory.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <iterator>
#include <optional>
#include <utility>
#include <perftest/results.h>
namespace {
constexpr const char* kTestSuiteName = "fuchsia.kernel.boot";
// Each kcounter names a time point. The corresponding "test result" names the
// interval between that time point and the previous one.
//
// **NOTE** Code in //zircon/kernel/top/handoff.cc and other places in the the
// kernel populate the "boot.timeline.*" kcounters with various time samples.
// This table is responsible for listing all of those sampling points in their
// intended chronological order and for giving appropriate names to each
// interval between two samples. (The total interval from boot.timeline.zbi
// until boot.timeline.init is also published as "KernelBootTotal", below.)
// When new sample points are added in the kernel, new entries should be made
// here. Take care in choosing the names for the intervals in this table, as
// these go into historical data collection under the "fuchsia.kernel.boot"
// test suite at https://chromeperf.appspot.com/report and changing these names
// can risk losing the correlation between historical data and new data.
constexpr std::pair<const char*, const char*> kTimelineSteps[] = {
{"boot.timeline.zbi", "KernelBootLoader"},
{"boot.timeline.physboot-setup", "KernelBootPhysSetup"},
{"boot.timeline.decompress-start", "KernelBootPhysZbiScan"},
{"boot.timeline.decompress-end", "KernelBootDecompression"},
{"boot.timeline.zbi-done", "KernelBootPhysZbiIngestion"},
{"boot.timeline.physboot-handoff", "KernelBootPhysHandoff"},
{"boot.timeline.virtual", "KernelBootPhysical"},
{"boot.timeline.threading", "KernelBootThreads"},
{"boot.timeline.userboot", "KernelBootUser"},
{"boot.timeline.init", "KernelBootComplete"},
};
constexpr const char* kHWStartupTime = "boot.timeline.hw";
// Return the property named `name` in the given node.
//
// Aborts if the name cannot be found or is of the wrong type.
int64_t GetIntValueOrDie(const inspect::NodeValue& node, const std::string& name) {
const std::vector<inspect::PropertyValue>& properties = node.properties();
auto it = std::find_if(properties.begin(), properties.end(),
[&name](const inspect::PropertyValue& m) { return m.name() == name; });
ZX_ASSERT_MSG(it != properties.end(), "Key '%s' not found", name.c_str());
ZX_ASSERT_MSG(it->Contains<inspect::IntPropertyValue>(),
"Property '%s' was expected to be an IntMetric, but found format %d.", name.c_str(),
static_cast<int>(it->format()));
return it->Get<inspect::IntPropertyValue>().value();
}
void WriteBootTimelineStats(perftest::ResultsSet& results, const inspect::Hierarchy& timeline) {
const inspect::NodeValue& node = timeline.node();
// The number of nodes we find starting with boot.timeline.* should be one
// more than the number of timeline steps we have. The missing step
// (boot.timeline.hw) is the time, on the target's "ticks" timeline at which
// we _think_ the hardware started up, if we may assume that the HW's
// reference clock started ticking from 0. This is the value we want to use
// as the initial value of last_step_ticks.
ZX_ASSERT(node.properties().size() == std::size(kTimelineSteps) + 1);
double ms_per_tick = 1000.0 / static_cast<double>(zx_ticks_per_second());
auto add_result = [ms_per_tick, &results](const char* result_name, zx_ticks_t before,
zx_ticks_t after) {
perftest::TestCaseResults* t = results.AddTestCase(kTestSuiteName, result_name, "milliseconds");
t->AppendValue(static_cast<double>(after - before) * ms_per_tick);
};
// Export the difference in time between each stage of the timeline.
std::optional<zx_ticks_t> first_step_ticks;
zx_ticks_t last_step_ticks = GetIntValueOrDie(node, kHWStartupTime);
for (auto [name, result_name] : kTimelineSteps) {
zx_ticks_t step_ticks = GetIntValueOrDie(node, name);
if (!first_step_ticks) {
first_step_ticks = step_ticks;
}
add_result(result_name, last_step_ticks, step_ticks);
last_step_ticks = step_ticks;
}
// Collect the soup-to-nuts interval from boot loader handoff to completion.
ZX_ASSERT(first_step_ticks);
ZX_ASSERT(last_step_ticks > *first_step_ticks);
add_result("KernelBootTotal", *first_step_ticks, last_step_ticks);
constexpr size_t kExpectedSteps = std::size(kTimelineSteps) + 1;
ZX_ASSERT_MSG(results.results()->size() == kExpectedSteps, "found %zu results from %zu steps",
results.results()->size(), std::size(kTimelineSteps));
}
// Add a test result recording the amount of free memory after kernel init.
void WriteBootMemoryStats(perftest::ResultsSet& results, const inspect::Hierarchy& memory_stats) {
int64_t value = GetIntValueOrDie(memory_stats.node(), "boot.memory.post_init_free_bytes");
perftest::TestCaseResults* t =
results.AddTestCase(kTestSuiteName, "KernelBootFreeMemoryAfterInit", "bytes");
t->AppendValue(static_cast<double>(value));
}
perftest::ResultsSet GetBootStatistics() {
fuchsia::kernel::CounterSyncPtr kcounter;
auto environment_services = ::sys::ServiceDirectory::CreateFromNamespace();
environment_services->Connect(kcounter.NewRequest());
fuchsia::mem::Buffer buffer;
zx_status_t status;
zx_status_t get_status = kcounter->GetInspectVmo(&status, &buffer);
ZX_ASSERT_MSG(get_status == ZX_OK, "GetInspectVmo: %s", zx_status_get_string(get_status));
ZX_ASSERT_MSG(status == ZX_OK, "GetInspectVmo yields status %s",
zx_status_get_string(get_status));
auto result = inspect::ReadFromVmo(buffer.vmo);
ZX_ASSERT_MSG(result.is_ok(), "ReadFromVmo failed");
auto root = result.take_value();
perftest::ResultsSet results;
// Export boot timeline stats.
const inspect::Hierarchy* timeline = root.GetByPath({"boot", "timeline"});
ZX_ASSERT_MSG(timeline, "boot.timeline not found");
WriteBootTimelineStats(results, *timeline);
// Export boot memory stats.
const inspect::Hierarchy* memory = root.GetByPath({"boot", "memory"});
ZX_ASSERT_MSG(memory, "boot.memory not found");
WriteBootMemoryStats(results, *memory);
return results;
}
} // namespace
int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s OUTFILE.json\n", argv[0]);
return 1;
}
const char* outfile = argv[1];
perftest::ResultsSet results = GetBootStatistics();
return results.WriteJSONFile(outfile) ? 0 : 1;
}