blob: 139abab4b4d157635d474c639ed92ffd709bdf33 [file] [log] [blame]
// Copyright 2019 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 <getopt.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <ratio>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include "action.h"
#include "object.h"
#include "tracing.h"
#include "utility.h"
#include "worker.h"
#include "workload.h"
using std::chrono_literals::operator""ms;
using std::chrono_literals::operator""s;
constexpr char kShortOptions[] = "hfiltv::";
// clang-format off
constexpr struct option kLongOptions[] = {
{ "help", no_argument, nullptr, 'h' },
{ "file", required_argument, nullptr, 'f' },
{ "interval", required_argument, nullptr, 'i' },
{ "list", no_argument, nullptr, 'l' },
{ "terse", no_argument, nullptr, 't' },
{ "verbose", no_argument, nullptr, 'v' },
{ nullptr, 0, nullptr, 0 }
};
// clang-format on
constexpr char kDefaultWorkloadPath[] = "/pkg/data/default.json";
constexpr auto kDefaultWorkloadInterval = 10s;
void PrintUsage(const char* program_name) {
printf(
"Usage: %s [-hfltv] [--help] [--file <PATH>] [--interval <INTERVAL>] [--list] [--terse] "
"[--verbose]\n"
"Executes a synthetic workload and reports benchmarks.\n"
"With --help or -h, display this help and exit.\n"
"With --file <PATH> or -f <PATH>, execute the workload file given by PATH.\n"
"With --interval <INTERVAL> or -i <INTERVAL>, run workload for <INTERVAL> time.\n"
"With --list or -l, list workload files included in this package.\n"
"With --terse or -t, show simplified output.\n"
"With --verbose or -v, show verbose output.\n"
"\n"
"The default workload file is: %s\n"
"The default workload interval is %llu seconds, unless specified in the\n"
"workload config or using --interval.\n",
program_name, kDefaultWorkloadPath, kDefaultWorkloadInterval.count());
}
int main(int argc, char** argv) {
std::optional<std::chrono::nanoseconds> default_interval;
std::string workload_path = kDefaultWorkloadPath;
bool help_flag = false;
bool verbose_flag = false;
int opt;
while ((opt = getopt_long(argc, argv, kShortOptions, kLongOptions, nullptr)) != -1) {
switch (opt) {
case 'h':
help_flag = true;
break;
case 'v':
verbose_flag = true;
break;
case 'i':
default_interval = ParseDurationString(optarg);
break;
case 'f':
workload_path = optarg;
break;
default:
PrintUsage(argv[0]);
return 1;
}
}
if (help_flag) {
PrintUsage(argv[0]);
return 0;
}
std::cout << "Loading workload config from: " << workload_path << std::endl;
Workload workload = Workload::Load(workload_path);
if (workload.priority().has_value()) {
auto profile = GetProfile(workload.priority().value());
const auto status = zx::thread::self()->set_profile(*profile, 0);
FX_CHECK(status == ZX_OK) << "Failed to set the priority of the main thread!";
}
std::vector<std::thread> threads;
std::vector<std::unique_ptr<Worker>> workers;
for (auto& worker_config : workload.workers()) {
auto [thread, worker] = Worker::Create(std::move(worker_config));
threads.emplace_back(std::move(thread));
workers.emplace_back(std::move(worker));
}
// Setup to sample CPU stats.
const size_t cpu_count = ReadCpuCount();
std::vector<zx_info_cpu_stats_t> cpu_stats_start(cpu_count);
std::vector<zx_info_cpu_stats_t> cpu_stats_end(cpu_count);
std::cout << "Waiting for workers to start up..." << std::endl;
Worker::WaitForAllReady(threads.size());
Tracing tracing;
if (workload.tracing().has_value()) {
tracing.Rewind();
tracing.Start(workload.tracing().value().group_mask);
std::cout << "Tracing started." << std::endl;
}
std::cout << "Kicking off workload..." << std::endl;
Worker::StartAll();
ReadCpuStats(cpu_stats_start.data(), cpu_stats_start.size());
const std::chrono::nanoseconds interval_ns =
default_interval.has_value() ? default_interval.value()
: workload.interval().has_value() ? workload.interval().value()
: kDefaultWorkloadInterval;
const auto interval_s = double_seconds{interval_ns}.count();
std::cout << "Waiting for " << interval_s << " s..." << std::endl;
std::this_thread::sleep_for(interval_ns);
ReadCpuStats(cpu_stats_end.data(), cpu_stats_end.size());
std::cout << "Terminating workload..." << std::endl;
Worker::TerminateAll();
if (workload.tracing().has_value()) {
tracing.Stop();
std::cout << "Tracing stopped." << std::endl;
}
for (auto& thread : threads) {
thread.join();
}
threads.clear();
std::cout << "CPU Stats:" << std::endl;
for (size_t i = 0; i < cpu_stats_start.size(); i++) {
const std::chrono::nanoseconds idle_time_ns{cpu_stats_end[i].idle_time -
cpu_stats_start[i].idle_time};
const auto idle_time_s = double_seconds{idle_time_ns}.count();
const auto active_time_s = interval_s - idle_time_s;
std::cout << " CPU " << i << ":" << std::endl;
std::cout << " Average Utilization: " << active_time_s << " s ("
<< (active_time_s * 100.0 / interval_s) << "%)" << std::endl;
}
struct GroupStats {
size_t count{0};
uint64_t iterations{0};
std::chrono::nanoseconds runtime{0};
uint64_t AverageIterations() const { return iterations / count; }
double_seconds AverageRuntime() const { return double_seconds{runtime / count}; }
};
std::map<std::string, GroupStats> group_stats;
for (auto& worker : workers) {
if (verbose_flag) {
worker->Dump();
}
// Add an entry for each unique group.
auto [iter, okay] = group_stats.emplace(worker->group(), GroupStats{});
// Accumulate group stats.
iter->second.count++;
iter->second.iterations += worker->spin_iterations();
iter->second.runtime += worker->total_runtime();
}
workers.clear();
std::cout << "Group stats:" << std::endl;
for (auto& group : group_stats) {
const auto& group_name = group.first;
const auto thread_count = group.second.count;
const auto average_iterations = group.second.AverageIterations();
const auto average_runtime = group.second.AverageRuntime().count();
std::cout << "Group: " << group_name << std::endl;
std::cout << " Threads: " << thread_count << std::endl;
std::cout << " Average Iterations: " << average_iterations << " per thread ("
<< (average_iterations * thread_count / cpu_count) << " per cpu)" << std::endl;
std::cout << " Average Runtime: " << average_runtime << " s/thread ("
<< (average_runtime * static_cast<double>(thread_count) /
static_cast<double>(cpu_count))
<< " s/cpu)" << std::endl;
}
using MapItem = decltype(group_stats)::value_type;
std::vector<std::reference_wrapper<MapItem>> group_list{group_stats.begin(), group_stats.end()};
std::sort(group_list.begin(), group_list.end(),
[](const MapItem& a, const MapItem& b) { return a.second.runtime > b.second.runtime; });
std::cout << "Relative stats:" << std::endl;
for (auto i = group_list.cbegin(); i != group_list.cend(); ++i) {
for (auto j = i + 1; j != group_list.cend(); ++j) {
const MapItem& group_a = *i;
const MapItem& group_b = *j;
std::cout << "Group " << group_a.first << " vs " << group_b.first << std::endl;
const auto runtime_a = group_a.second.AverageRuntime();
const auto runtime_b = group_b.second.AverageRuntime();
std::cout << " Relative Runtime: "
<< 100.0 * (runtime_a - runtime_b) / (runtime_a + runtime_b) << " %" << std::endl;
}
}
if (workload.tracing().has_value()) {
TracingConfig config = workload.tracing().value();
if (config.filepath.has_value()) {
std::string tracing_filepath = config.filepath.value();
std::ofstream human_readable_file;
human_readable_file.open(tracing_filepath);
if (human_readable_file) {
std::cout << "Traces being saved in " << tracing_filepath << "..." << std::endl;
if (!tracing.WriteHumanReadable(human_readable_file))
FX_LOGS(ERROR) << "Writing human readable file failed.";
} else {
FX_LOGS(ERROR) << "Failed to open " << tracing_filepath << ".";
}
}
if (config.trace_string_ref.has_value()) {
std::vector<Tracing::DurationStats> duration_stats;
std::map<uint64_t, Tracing::QueuingStats> queuing_stats;
if (!tracing.PopulateDurationStats(config.trace_string_ref.value(), &duration_stats,
&queuing_stats)) {
FX_LOGS(ERROR) << "Provided string ref not found.";
} else if (duration_stats.size() != 0 && queuing_stats.size() != 0) {
uint64_t total_wall_duration_ns{0};
uint64_t total_queuing_time_ns{0};
for (auto& event : duration_stats) {
total_wall_duration_ns += event.wall_duration_ns;
}
for (auto& event : queuing_stats) {
total_queuing_time_ns += event.second.queuing_time_ns;
}
std::cout << "Tracing stats:" << std::endl;
std::cout << " Average Wall Duration: "
<< double_nanoseconds{total_wall_duration_ns}.count() /
static_cast<double>(duration_stats.size())
<< " ns." << std::endl;
std::cout << " Average Queuing Time: "
<< double_nanoseconds{total_queuing_time_ns}.count() /
static_cast<double>(queuing_stats.size())
<< " ns." << std::endl;
} else {
FX_LOGS(WARNING) << "No events found that match provided string ref.";
}
}
}
std::cout << "Done!" << std::endl;
return 0;
}