| // 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 "cpu_stress.h" |
| |
| #include <lib/zx/clock.h> |
| #include <lib/zx/time.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <atomic> |
| #include <string> |
| #include <thread> |
| #include <utility> |
| |
| #include "args.h" |
| #include "cpu_stressor.h" |
| #include "cpu_workloads.h" |
| #include "status.h" |
| #include "temperature_sensor.h" |
| #include "util.h" |
| |
| namespace hwstress { |
| |
| namespace { |
| |
| void RunWorkload(StatusLine* status, ProfileManager* profile_manager, TemperatureSensor* sensor, |
| const CpuWorkload& workload, std::vector<uint32_t> cores_to_test, |
| zx::duration duration, double utilization) { |
| // Start a workload. |
| CpuStressor stressor{std::move(cores_to_test), workload.work, utilization, profile_manager}; |
| stressor.Start(); |
| |
| // Update the status line until the test is finished. |
| zx::time start_time = zx::clock::get_monotonic(); |
| zx::time end_time = start_time + duration; |
| std::optional<double> temperature; |
| while (zx::clock::get_monotonic() < end_time) { |
| // Sleep for 250ms or the finish time, whichever is sooner. |
| zx::time next_update = zx::deadline_after(zx::msec(250)); |
| zx::nanosleep(std::min(end_time, next_update)); |
| |
| // Update the status line. |
| temperature = sensor->ReadCelcius(); |
| zx::duration time_running = zx::clock::get_monotonic() - start_time; |
| status->Set(" %02ld:%02ld:%02ld || Current test: %s || System temperature: %s", |
| time_running.to_hours(), time_running.to_mins() % 60, time_running.to_secs() % 60, |
| workload.name.c_str(), TemperatureToString(temperature).c_str()); |
| } |
| stressor.Stop(); |
| |
| // Log final temperature |
| status->Set(""); |
| status->Log("* CpuWorkload %s complete after %0.2fs: final temp: %s\n", workload.name.c_str(), |
| DurationToSecs(duration), TemperatureToString(temperature).c_str()); |
| } |
| |
| // Get a list of workloads to run. |
| std::optional<std::vector<CpuWorkload>> GetWorkloads(const CommandLineArgs& args) { |
| // Fetch all workloads. |
| std::vector<CpuWorkload> workloads = GetCpuWorkloads(); |
| |
| // If no specific workload was used, run them all. |
| if (args.cpu_workload.empty()) { |
| return workloads; |
| } |
| |
| // Otherwise, find the named workload. |
| for (const CpuWorkload& workload : workloads) { |
| if (workload.name == args.cpu_workload) { |
| return std::vector<CpuWorkload>{workload}; |
| } |
| } |
| |
| // Invalid workload. |
| fprintf(stderr, |
| "Invalid workload name: '%s'.\n\n" |
| "Valid workload names are:\n", |
| args.cpu_workload.c_str()); |
| for (const CpuWorkload& workload : workloads) { |
| fprintf(stderr, " %s\n", workload.name.c_str()); |
| } |
| |
| return std::nullopt; |
| } |
| |
| } // namespace |
| |
| bool StressCpu(StatusLine* status, const CommandLineArgs& args, zx::duration duration, |
| TemperatureSensor* temperature_sensor) { |
| // Calculate finish time. |
| zx::time start_time = zx::clock::get_monotonic(); |
| zx::time finish_time = start_time + duration; |
| |
| std::string cores_to_test("Testing CPU(s):"); |
| for (auto core : args.cores_to_test.cores) { |
| cores_to_test += " " + std::to_string(core); |
| } |
| status->Log("%s", cores_to_test.c_str()); |
| |
| // Create a profile manager. |
| std::unique_ptr<ProfileManager> profile_manager = ProfileManager::CreateFromEnvironment(); |
| if (profile_manager == nullptr) { |
| status->Log("Error: could not create profile manager."); |
| return false; |
| } |
| |
| // Print start banner. |
| if (finish_time == zx::time::infinite()) { |
| status->Log("Exercising CPU until stopped...\n"); |
| } else { |
| status->Log("Exercising CPU for %0.2f seconds...\n", DurationToSecs(duration)); |
| } |
| |
| // Get workloads. |
| std::optional<std::vector<CpuWorkload>> maybe_workloads = GetWorkloads(args); |
| if (!maybe_workloads.has_value()) { |
| return false; |
| } |
| std::vector<CpuWorkload> workloads = std::move(maybe_workloads).value(); |
| |
| // Get initial time per test. |
| // |
| // Our strategy is to run through the tests multiple times, doubling the |
| // runtime each time. This allows us to catch obvious faults detected by |
| // a particular test quickly, while later on moving to a "burn in" mode. It |
| // also has the added benefit that if our process is terminated at an |
| // arbitrary point, no one test will have run for more than twice as long as |
| // any other test. |
| // |
| // When the user has passed in a fixed test duration, we additionally want all |
| // tests to have an equal run time. We thus choose an initial test time such |
| // that after runtime doubling is applied will cause the test end time to |
| // coincide with the end of a full round of tests. |
| zx::duration time_per_test; |
| if (finish_time == zx::time::infinite()) { |
| time_per_test = zx::msec(100); |
| } else { |
| // After running through K tests N times, doubling the test time after |
| // each, and starting with an initial test time of D, we will have run for: |
| // |
| // D * K * (2**(N + 1) - 1) |
| // |
| // we select the largest such D such that: |
| // |
| // 1. Above equation evenly divides the total desired test duration; and |
| // 2. "D" is no larger than 100 msec. |
| uint64_t n = 1; |
| do { |
| time_per_test = duration / (workloads.size() * ((1ul << n) - 1)); |
| n++; |
| } while (time_per_test > zx::msec(100) && n < 63); |
| } |
| |
| // Run the workloads. |
| uint64_t iteration = 1; |
| do { |
| status->Log("Iteration %ld: %0.2fs per test.", iteration++, DurationToSecs(time_per_test)); |
| for (const auto& workload : workloads) { |
| RunWorkload(status, profile_manager.get(), temperature_sensor, workload, |
| args.cores_to_test.cores, time_per_test, |
| /*utilization=*/(args.utilization_percent / 100.0)); |
| } |
| time_per_test *= 2; |
| } while (zx::clock::get_monotonic() < finish_time); |
| |
| status->Log("Complete.\n"); |
| return true; |
| } |
| |
| } // namespace hwstress |