blob: cf33c5e2aadd86697023670803d89a1d005ce611 [file] [log] [blame]
// Copyright 2018 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 <inttypes.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zircon-internal/device/cpu-trace/perf-mon.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <limits>
#include "garnet/lib/debugger_utils/util.h"
#include "garnet/lib/perfmon/controller.h"
#include "garnet/lib/perfmon/events.h"
#include "print_tallies.h"
#include "session_result_spec.h"
#include "session_spec.h"
#include "src/lib/files/file.h"
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/log_settings_command_line.h"
#include "src/lib/fxl/strings/split_string.h"
const char kUsageString[] =
"Usage: cpuperf [options]\n"
"\n"
"Options:\n"
" --spec-file=FILE Use the cpuperf specification data in FILE\n"
" --help Show this help message and exit\n"
" --list-events Print the list of supported events\n"
" --describe-event=EVENT Print a description of EVENT\n"
" Event is specified as group:name\n"
"\n"
"Logging options:\n"
" --quiet[=LEVEL] Set quietness level (opposite of verbose)\n"
" --verbose[=LEVEL] Set debug verbosity level\n"
" --log-file=FILE Write log output to FILE.\n"
"Quiet supersedes verbose if both are specified.\n"
"Defined log levels:\n"
"-n - verbosity level n\n"
" 0 - INFO - this is the default level\n"
" 1 - WARNING\n"
" 2 - ERROR\n"
" 3 - FATAL\n";
static void PrintUsageString(FILE* f) { fputs(kUsageString, f); }
static bool GetSessionSpecFromArgv(const fxl::CommandLine& cl, cpuperf::SessionSpec* out_spec) {
std::string arg;
if (cl.GetOptionValue("spec-file", &arg)) {
std::string content;
if (!files::ReadFileToString(arg, &content)) {
FX_LOGS(ERROR) << "Can't read spec file \"" << arg << "\"";
return false;
}
if (!cpuperf::DecodeSessionSpec(content, out_spec)) {
return false;
}
}
return true;
}
static void DescribeEvent(FILE* f, const perfmon::EventDetails* details) {
if (details->description[0] != '\0') {
fprintf(f, "%s: %s\n", details->name, details->description);
} else {
// Print some kind of description for consistency.
// The output in some sessions (e.g., emacs) gets colorized due to
// the presence of colons, and it's harder to read without consistency.
// Printing "missing description" will help encourage adding one. :-)
fprintf(f, "%s: <missing description>\n", details->name);
}
}
static void DescribeEvent(FILE* f, perfmon::ModelEventManager* model_event_manager,
const std::string& full_name) {
std::vector<std::string> parts =
fxl::SplitStringCopy(full_name, ":", fxl::kTrimWhitespace, fxl::kSplitWantAll);
if (parts.size() != 2) {
FX_LOGS(ERROR) << "Usage: cpuperf --describe-event=group:name";
exit(EXIT_FAILURE);
}
const perfmon::EventDetails* details;
if (!model_event_manager->LookupEventByName(parts[0].c_str(), parts[1].c_str(), &details)) {
FX_LOGS(ERROR) << "Unknown event: " << full_name;
exit(EXIT_FAILURE);
}
DescribeEvent(f, details);
}
static void PrintEventList(FILE* f, perfmon::ModelEventManager* model_event_manager) {
perfmon::ModelEventManager::GroupTable groups = model_event_manager->GetAllGroups();
for (auto& group : groups) {
std::sort(group.events.begin(), group.events.end(),
[](const perfmon::EventDetails*& a, const perfmon::EventDetails*& b) {
return strcmp(a->name, b->name) < 0;
});
fprintf(f, "\nGroup %s\n", group.group_name.c_str());
for (const auto& event : group.events) {
DescribeEvent(f, event);
}
}
}
static void SaveTrace(const cpuperf::SessionResultSpec& result_spec,
perfmon::Controller* controller, size_t iter) {
std::unique_ptr<perfmon::Reader> reader = controller->GetReader();
if (!reader) {
return;
}
FX_VLOGS(1) << "Saving results of iteration " << iter;
for (size_t trace = 0; trace < result_spec.num_traces; ++trace) {
if (reader->SetTrace(trace) != perfmon::ReaderStatus::kOk) {
// If we can't set the trace to this one it's unlikely we can continue.
return;
}
auto buffer = reinterpret_cast<const char*>(reader->GetCurrentTraceBuffer());
FX_DCHECK(buffer);
size_t size = reader->GetCurrentTraceSize();
FX_DCHECK(size > 0);
std::string output_file_path = result_spec.GetTraceFilePath(iter, trace);
if (!files::WriteFile(output_file_path, buffer, size)) {
FX_LOGS(ERROR) << "Error saving trace data to: " << output_file_path;
// If writing this one fails, it's unlikely we can continue.
return;
}
}
// Print a summary of this run.
// In tally mode this is noise, but if verbosity is on sure.
if (controller->config().GetMode() != perfmon::CollectionMode::kTally || FX_VLOG_IS_ON(1)) {
FX_VLOGS(1) << "Iteration " << iter << " summary";
for (size_t trace = 0; trace < result_spec.num_traces; ++trace) {
std::string path = result_spec.GetTraceFilePath(iter, trace);
uint64_t size;
if (files::GetFileSize(path, &size)) {
FX_VLOGS(1) << path << ": " << size << " bytes";
} else {
FX_VLOGS(1) << path << ": unknown size";
}
}
}
}
static bool RunSession(const cpuperf::SessionSpec& spec,
const perfmon::ModelEventManager* model_event_manager,
perfmon::Controller* controller) {
cpuperf::SessionResultSpec result_spec{spec.config_name, spec.model_name, spec.num_iterations,
controller->num_traces(), spec.output_path_prefix};
for (size_t iter = 0; iter < spec.num_iterations; ++iter) {
if (!controller->Start()) {
return false;
}
zx::nanosleep(zx::deadline_after(spec.duration));
controller->Stop();
// Save the trace, even if printing results, for testing purposes.
if (result_spec.save_results()) {
SaveTrace(result_spec, controller, iter);
}
if (controller->config().GetMode() == perfmon::CollectionMode::kTally) {
PrintTallyResults(stdout, spec, result_spec, model_event_manager, controller);
}
}
if (result_spec.save_results()) {
if (!cpuperf::WriteSessionResultSpec(spec.session_result_spec_path, result_spec)) {
return false;
}
}
return true;
}
static bool GetBufferSizeInPages(uint32_t size_in_mb, uint32_t* out_num_pages) {
const uint64_t kPagesPerMb = 1024 * 1024 / perfmon::Controller::kPageSize;
const uint64_t kMaxSizeInMb = std::numeric_limits<uint32_t>::max() / kPagesPerMb;
if (size_in_mb > kMaxSizeInMb) {
return false;
}
*out_num_pages = size_in_mb * kPagesPerMb;
return true;
}
int main(int argc, char* argv[]) {
fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv);
if (!fxl::SetLogSettingsFromCommandLine(cl))
return EXIT_FAILURE;
if (cl.HasOption("help", nullptr)) {
PrintUsageString(stdout);
return EXIT_SUCCESS;
}
if (cl.HasOption("list-events", nullptr) || cl.HasOption("describe-event", nullptr)) {
// For list-events and describe-event, just support the default model
// for now.
std::unique_ptr<perfmon::ModelEventManager> model_event_manager =
perfmon::ModelEventManager::Create(perfmon::GetDefaultModelName());
FX_CHECK(model_event_manager);
if (cl.HasOption("list-events", nullptr)) {
PrintEventList(stdout, model_event_manager.get());
return EXIT_SUCCESS;
}
std::string arg;
if (cl.GetOptionValue("describe-event", &arg)) {
DescribeEvent(stdout, model_event_manager.get(), arg);
return EXIT_SUCCESS;
}
}
// TODO(dje): dump-arch option
// TODO(dje): Command line options for parts of the spec.
cpuperf::SessionSpec spec;
if (!GetSessionSpecFromArgv(cl, &spec)) {
return EXIT_FAILURE;
}
if (spec.perfmon_config.GetEventCount() == 0) {
FX_LOGS(ERROR) << "No events specified";
return EXIT_FAILURE;
}
// The provided buffer size is in MB, the controller takes the buffer size
// in pages.
uint32_t buffer_size_in_pages;
if (!GetBufferSizeInPages(spec.buffer_size_in_mb, &buffer_size_in_pages)) {
FX_LOGS(ERROR) << "Buffer size too large";
exit(EXIT_FAILURE);
}
std::unique_ptr<perfmon::Controller> controller;
if (!perfmon::Controller::Create(buffer_size_in_pages, spec.perfmon_config, &controller)) {
return EXIT_FAILURE;
}
FX_LOGS(INFO) << "cpuperf control program starting";
FX_LOGS(INFO) << spec.num_iterations << " iteration(s), " << spec.duration.to_secs()
<< " second(s) per iteration";
bool success = RunSession(spec, spec.model_event_manager.get(), controller.get());
if (!success) {
FX_LOGS(INFO) << "cpuperf exiting with error";
return EXIT_FAILURE;
}
FX_LOGS(INFO) << "cpuperf control program exiting";
return EXIT_SUCCESS;
}