|  | // 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; | 
|  | } |