blob: cf4ac7b7f1c400ce8f46de626a30521b0f7b96ba [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 "cpu_stressor.h"
#include <fuchsia/scheduler/cpp/fidl.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/zx/profile.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <atomic>
#include <thread>
#include <unordered_map>
#include <utility>
#include "cpu_workloads.h"
#include "lib/zx/time.h"
#include "profile_manager.h"
#include "status.h"
#include "temperature_sensor.h"
#include "util.h"
namespace hwstress {
zx::duration GetCurrentThreadCpuTime() {
zx_info_thread_stats stats = {};
zx::thread::self()->get_info(ZX_INFO_THREAD_STATS, &stats, sizeof(stats), nullptr, nullptr);
return zx::duration(stats.total_runtime);
}
zx::duration RequiredSleepForTargetUtilization(zx::duration cpu_time, zx::duration wall_time,
double utilization) {
double sleep_time = (DurationToSecs(cpu_time) / utilization) - DurationToSecs(wall_time);
// If we have been running under utilization, there is no need to sleep.
if (sleep_time <= 0) {
return zx::sec(0);
}
// Otherwise, sleep for for an amount of time that will make our utilization
// drop below the target.
return SecsToDuration(sleep_time);
}
void WorkIndicator::MaybeSleep() {
// Determine how long we need to sleep to reach "utilization", based on
// consumed CPU time and wall time.
zx::time now = zx::clock::get_monotonic();
zx::duration sleep_time =
RequiredSleepForTargetUtilization(/*cpu_time=*/GetCurrentThreadCpuTime(),
/*wall_time=*/(now - start_time_), utilization_);
// Sleep if we need to decrease our utilization.
//
// We sleep a tad longer than what we strictly need to. If we didn't, we would
// only be able to perform 1 more iteration of the workload before needing to
// sleep again.
//
// Sleeping a tad longer drops our utilization below the target value, and
// hence allows us to run longer after we wake up. The goal here is to reduce
// the number of sleeps (and hence context switches) overall, so we spend more
// time in the workload and less time in the kernel.
if (sleep_time > zx::sec(0)) {
zx::nanosleep(now + sleep_time + zx::msec(50));
}
}
void StopIndicator::Stop() { should_stop_.store(true, std::memory_order_release); }
CpuStressor::CpuStressor(std::vector<uint32_t> cores_to_test,
std::function<void(WorkIndicator)> workload, double utilization,
ProfileManager* profile_manager)
: cores_to_test_(std::move(cores_to_test)),
workload_(std::move(workload)),
utilization_(utilization),
profile_manager_(profile_manager) {}
CpuStressor::CpuStressor(std::vector<uint32_t> cores_to_test,
std::function<void()> looping_workload, double utilization,
ProfileManager* profile_manager)
: cores_to_test_(std::move(cores_to_test)),
workload_([f = std::move(looping_workload)](WorkIndicator indicator) {
do {
f();
} while (!indicator.ShouldStop());
}),
utilization_(utilization),
profile_manager_(profile_manager) {}
CpuStressor::~CpuStressor() { Stop(); }
void CpuStressor::Start() {
ZX_ASSERT(workers_.empty());
// Start the workers.
for (uint32_t core : cores_to_test_) {
auto worker = std::make_unique<std::thread>([this, workload = workload_, core]() mutable {
// Set priority to low, and set affinity to CPU (core % num_cpus).
if (profile_manager_ != nullptr) {
zx_status_t status =
profile_manager_->SetThreadPriority(*zx::thread::self(), ZX_PRIORITY_LOW);
ZX_ASSERT(status == ZX_OK);
profile_manager_->SetThreadAffinity(*zx::thread::self(),
1u << (core % zx_system_get_num_cpus()));
ZX_ASSERT(status == ZX_OK);
}
// Run the workload.
workload(WorkIndicator(indicator_, utilization_));
// Ensure the function didn't return while ShouldStop() was still false.
ZX_ASSERT(indicator_.ShouldStop());
});
workers_.push_back(std::move(worker));
}
}
void CpuStressor::Stop() {
indicator_.Stop();
for (auto& thread : workers_) {
thread->join();
}
workers_.clear();
}
} // namespace hwstress