blob: 7456c04b175fdc4fbd453f7752e7f8af67fdc96b [file]
#include "tlbmc/collector/thermal_collector.h"
#include <algorithm>
#include <atomic>
#include <chrono> // NOLINT
#include <cstdint>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "g3/macros.h"
#include "thread/thread.h"
#include <nlohmann/json_fwd.hpp>
#include "tlbmc/collector/fru_collector.h"
#include "tlbmc/collector/sensor_collector_aggregator.h"
#include "tlbmc/configs/entity_config.h"
#include "thermal_config.pb.h"
#include "tlbmc/thermal/controller/algorithm/dff.h"
#include "tlbmc/thermal/controller/algorithm/eat.h"
#include "tlbmc/thermal/controller/algorithm/first_order_adrc.h"
#include "tlbmc/thermal/controller/algorithm/pid.h"
#include "tlbmc/thermal/controller/algorithm/second_order_adrc.h"
#include "tlbmc/thermal/controller/algorithm/stepwise.h"
#include "tlbmc/thermal/controller/controller.h"
#include "tlbmc/thermal/controller/dff_controller.h"
#include "tlbmc/thermal/controller/eat_controller.h"
#include "tlbmc/thermal/controller/fan_pid_controller.h"
#include "tlbmc/thermal/controller/first_order_adrc_controller.h"
#include "tlbmc/thermal/controller/optimizer/pid/frequency_response_estimation/frequency_response_estimation.h"
#include "tlbmc/thermal/controller/optimizer/pid/pid_optimizer.h"
#include "tlbmc/thermal/controller/optimizer/pid/ziegler_nichols/differentiated_ziegler_nichols.h"
#include "tlbmc/thermal/controller/optimizer/pid/ziegler_nichols/ziegler_nichols.h"
#include "tlbmc/thermal/controller/pid_controller.h"
#include "tlbmc/thermal/controller/second_order_adrc_controller.h"
#include "tlbmc/thermal/controller/stepwise_controller.h"
#include "tlbmc/thermal/debug_mode.h"
#include "tlbmc/thermal/zone_manager.h"
namespace milotic_tlbmc {
absl::flat_hash_map<std::string, thermal::FirstOrderAdrcController*>
ThermalCollector::
LinkFirstOrderAdrcControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::FirstOrderAdrcController*>
controller_id_to_first_order_adrc_controller;
for (const auto& [zone_id_and_controller_id, first_order_adrc_controller] :
zone_id_and_controller_id_to_first_order_adrc_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
first_order_adrc_controller->GetZoneManager()->AddThermalController(
controller_id, first_order_adrc_controller.get());
LOG(WARNING) << absl::StrFormat(
"1st-order ADRC controller %s linked to zone %d", controller_id,
zone_id);
controller_id_to_first_order_adrc_controller.insert(
{controller_id, first_order_adrc_controller.get()});
}
return controller_id_to_first_order_adrc_controller;
}
absl::flat_hash_map<std::string, thermal::SecondOrderAdrcController*>
ThermalCollector::
LinkSecondOrderAdrcControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::SecondOrderAdrcController*>
controller_id_to_second_order_adrc_controller;
for (const auto& [zone_id_and_controller_id, second_order_adrc_controller] :
zone_id_and_controller_id_to_second_order_adrc_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
second_order_adrc_controller->GetZoneManager()->AddThermalController(
controller_id, second_order_adrc_controller.get());
LOG(WARNING) << absl::StrFormat(
"2nd-order ADRC controller %s linked to zone %d", controller_id,
zone_id);
controller_id_to_second_order_adrc_controller.insert(
{controller_id, second_order_adrc_controller.get()});
}
return controller_id_to_second_order_adrc_controller;
}
absl::flat_hash_map<std::string, thermal::PidController*>
ThermalCollector::LinkPidControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::PidController*>
controller_id_to_pid_controller;
for (const auto& [zone_id_and_controller_id, pid_controller] :
zone_id_and_controller_id_to_pid_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
pid_controller->GetZoneManager()->AddThermalController(
controller_id, pid_controller.get());
LOG(WARNING) << absl::StrFormat("PID controller %s linked to zone %d",
controller_id, zone_id);
controller_id_to_pid_controller.insert(
{controller_id, pid_controller.get()});
}
return controller_id_to_pid_controller;
}
absl::flat_hash_map<std::string, thermal::StepwiseController*>
ThermalCollector::LinkStepwiseControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::StepwiseController*>
controller_id_to_stepwise_controller;
for (const auto& [zone_id_and_controller_id, stepwise_controller] :
zone_id_and_controller_id_to_stepwise_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
stepwise_controller->GetZoneManager()->AddThermalController(
controller_id, stepwise_controller.get());
LOG(WARNING) << absl::StrFormat("Stepwise controller %s linked to zone %d",
controller_id, zone_id);
controller_id_to_stepwise_controller.insert(
{controller_id, stepwise_controller.get()});
}
return controller_id_to_stepwise_controller;
}
absl::flat_hash_map<std::string, thermal::FanPidController*>
ThermalCollector::LinkFanPidControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::FanPidController*>
controller_id_to_fan_pid_controller;
for (const auto& [zone_id_and_controller_id, fan_pid_controller] :
zone_id_and_controller_id_to_fan_pid_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
fan_pid_controller->GetZoneManager()->AddThermalController(
controller_id, fan_pid_controller.get());
LOG(WARNING) << absl::StrFormat("Fan PID controller %s linked to zone %d",
controller_id, zone_id);
controller_id_to_fan_pid_controller.insert(
{controller_id, fan_pid_controller.get()});
}
return controller_id_to_fan_pid_controller;
}
absl::flat_hash_map<std::string, thermal::DffController*>
ThermalCollector::LinkDffControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::DffController*>
controller_id_to_dff_controller;
for (const auto& [zone_id_and_controller_id, dff_controller] :
zone_id_and_controller_id_to_dff_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
dff_controller->GetZoneManager()->AddThermalController(
controller_id, dff_controller.get());
LOG(WARNING) << absl::StrFormat("DFF controller %s linked to zone %d",
controller_id, zone_id);
controller_id_to_dff_controller.insert(
{controller_id, dff_controller.get()});
}
return controller_id_to_dff_controller;
}
absl::flat_hash_map<std::string, thermal::EatController*>
ThermalCollector::LinkEatControllerToZoneAndCreateIdToControllerMap() {
absl::flat_hash_map<std::string, thermal::EatController*>
controller_id_to_eat_controller;
for (const auto& [zone_id_and_controller_id, eat_controller] :
zone_id_and_controller_id_to_eat_controller_) {
const auto& [zone_id, controller_id] = zone_id_and_controller_id;
eat_controller->GetZoneManager()->AddThermalController(
controller_id, eat_controller.get());
std::vector<thermal::ZoneManager*> affected_zones;
affected_zones.reserve(eat_controller->GetAffectedZoneIds().size());
for (int affected_zone_id : eat_controller->GetAffectedZoneIds()) {
auto zone_manager = zone_id_to_zone_manager_.find(affected_zone_id);
if (zone_manager == zone_id_to_zone_manager_.end()) {
LOG(ERROR) << absl::StrFormat("Zone %d not found for EAT controller %s",
affected_zone_id, controller_id);
continue;
}
affected_zones.push_back(zone_manager->second.get());
}
if (!eat_controller->SetAffectedZones(std::move(affected_zones)).ok()) {
LOG(ERROR) << absl::StrFormat(
"Failed to set affected zones for EAT controller %s", controller_id);
}
LOG(WARNING) << absl::StrFormat("EAT controller %s linked to zone %d",
controller_id, zone_id);
controller_id_to_eat_controller.insert(
{controller_id, eat_controller.get()});
}
return controller_id_to_eat_controller;
}
ThermalCollector::ThermalCollector(
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::FirstOrderAdrcController>>&&
zone_id_and_controller_id_to_first_order_adrc_controller,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::SecondOrderAdrcController>>&&
zone_id_and_controller_id_to_second_order_adrc_controller,
absl::flat_hash_map<int, std::unique_ptr<thermal::ZoneManager>>&&
zone_id_to_zone_manager,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::PidController>>&&
zone_id_and_controller_id_to_pid_controller,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::StepwiseController>>&&
zone_id_and_controller_id_to_stepwise_controller,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::FanPidController>>&&
zone_id_and_controller_id_to_fan_pid_controller,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::DffController>>&&
zone_id_and_controller_id_to_dff_controller,
absl::flat_hash_map<std::string, std::vector<int>>&&
controller_id_to_zone_ids,
absl::flat_hash_map<int, std::unique_ptr<thermal::ZoneManager>>&&
zone_id_to_supportive_zone_manager,
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::EatController>>&&
zone_id_and_controller_id_to_eat_controller,
std::unique_ptr<ThermalThreadManager> thermal_thread_manager,
ecclesia::ThreadFactoryInterface* thread_factory)
: zone_id_to_zone_manager_(std::move(zone_id_to_zone_manager)),
zone_id_and_controller_id_to_first_order_adrc_controller_(
std::move(zone_id_and_controller_id_to_first_order_adrc_controller)),
zone_id_and_controller_id_to_second_order_adrc_controller_(
std::move(zone_id_and_controller_id_to_second_order_adrc_controller)),
zone_id_and_controller_id_to_pid_controller_(
std::move(zone_id_and_controller_id_to_pid_controller)),
zone_id_and_controller_id_to_stepwise_controller_(
std::move(zone_id_and_controller_id_to_stepwise_controller)),
zone_id_and_controller_id_to_fan_pid_controller_(
std::move(zone_id_and_controller_id_to_fan_pid_controller)),
zone_id_and_controller_id_to_dff_controller_(
std::move(zone_id_and_controller_id_to_dff_controller)),
controller_id_to_zone_ids_(std::move(controller_id_to_zone_ids)),
zone_id_to_supportive_zone_manager_(
std::move(zone_id_to_supportive_zone_manager)),
zone_id_and_controller_id_to_eat_controller_(
std::move(zone_id_and_controller_id_to_eat_controller)),
// Link thermal controllers to their zone managers.
// Initialize maps for ControllerIdToController.
controller_id_to_controller_({
.controller_id_to_first_order_adrc_controller =
LinkFirstOrderAdrcControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_second_order_adrc_controller =
LinkSecondOrderAdrcControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_pid_controller =
LinkPidControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_stepwise_controller =
LinkStepwiseControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_fan_pid_controller =
LinkFanPidControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_dff_controller =
LinkDffControllerToZoneAndCreateIdToControllerMap(),
.controller_id_to_eat_controller =
LinkEatControllerToZoneAndCreateIdToControllerMap(),
}),
thermal_thread_manager_(std::move(thermal_thread_manager)),
thread_factory_(thread_factory) {}
ThermalCollector::~ThermalCollector() {
StopThermalControl();
// In the case of EmptySensorCollector, avoid dereferencing nullptr.
if (thermal_thread_manager_ == nullptr) {
return;
}
thermal_thread_manager_->task_scheduler->Stop();
thermal_thread_manager_->work_guards.clear();
thermal_control_timers_.clear();
for (const std::shared_ptr<boost::asio::io_context>& io_context :
thermal_thread_manager_->io_contexts) {
io_context->stop();
}
for (const std::unique_ptr<ecclesia::ThreadInterface>& thread :
thermal_thread_manager_->threads) {
thread->Join();
}
}
absl::StatusOr<std::unique_ptr<ThermalCollector>> ThermalCollector::Create(
const Params& params, SensorCollectorAggregator* absl_nonnull aggregator,
FruCollector* absl_nonnull fru_collector,
EntityConfig* absl_nonnull entity_config) {
auto thermal_thread_manager =
std::make_unique<ThermalThreadManager>(params.clock);
auto io_context = std::make_shared<boost::asio::io_context>();
thermal_thread_manager->io_contexts.push_back(io_context);
thermal_thread_manager->work_guards.push_back(
boost::asio::make_work_guard(*io_context));
bool tuning_enabled = params.tuning_enabled;
uint32_t failsafe_log_count_per_sec =
params.failsafe_logger_config.failsafe_log_limit_per_second();
if (failsafe_log_count_per_sec < 1) {
failsafe_log_count_per_sec = 20;
}
std::string sample_data_file_prefix = params.sample_data_file_prefix;
absl::flat_hash_map<std::string, std::vector<int>> controller_id_to_zone_ids;
// Create zone managers.
absl::flat_hash_map<int, std::unique_ptr<thermal::ZoneManager>>
zone_id_to_zone_manager;
for (const ZoneManagerConfig& zone_config :
params.zone_manager_configs.zone_manager_configs()) {
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::ZoneManager> zone_manager,
thermal::ZoneManager::Create({
.id = zone_config.id(),
.ms_per_fan_cycle = zone_config.ms_per_fan_cycle(),
.ms_per_thermal_cycle = zone_config.ms_per_thermal_cycle(),
.failsafe_log_count_per_sec = failsafe_log_count_per_sec,
.debug_mode = thermal::DebugMode(
zone_config.debug_mode().debug_enabled(),
zone_config.debug_mode().debug_pid_enabled(),
zone_config.debug_mode().debug_dff_enabled(), tuning_enabled),
.aggregator = aggregator,
.fru_collector = fru_collector,
.entity_config = entity_config,
.zone_failsafe_percent = zone_config.zone_failsafe_percent(),
.setpoint_upper_bound = zone_config.setpoint_upper_bound(),
.setpoint_lower_bound = zone_config.setpoint_lower_bound(),
.minimum_thermal_setpoint = zone_config.minimum_thermal_setpoint(),
.input_sensors = {zone_config.input_sensors().begin(),
zone_config.input_sensors().end()},
.output_fans = {zone_config.output_fans().begin(),
zone_config.output_fans().end()},
.sample_data_file_prefix = sample_data_file_prefix,
}));
LOG(WARNING) << absl::StrFormat("Zone manager %d created",
zone_manager->GetId());
zone_id_to_zone_manager[zone_config.id()] = std::move(zone_manager);
}
// Create 1st-order ADRC controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::FirstOrderAdrcController>>
zone_id_and_controller_id_to_first_order_adrc_controller;
for (const FirstOrderAdrcControllerConfig& adrc_config :
params.first_order_adrc_controller_configs
.first_order_adrc_controller_configs()) {
for (int zone_id : adrc_config.zone_manager_id()) {
FirstOrderAdrcLoopConfig adrc_loop_config =
adrc_config.first_order_adrc_loop_config();
auto adrc_loop = std::make_unique<thermal::FirstOrderAdrcThermalLoop>(
adrc_loop_config);
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::FirstOrderAdrcController> adrc_controller,
thermal::FirstOrderAdrcController::Create({
.id = adrc_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.first_order_adrc_loop = std::move(adrc_loop),
.setpoint = adrc_config.setpoint(),
.input_sensors = {adrc_config.input_sensors().begin(),
adrc_config.input_sensors().end()},
}));
LOG(WARNING) << absl::StrFormat(
"1st-order ADRC controller %s created for zone %d",
adrc_controller->GetId(), zone_id);
controller_id_to_zone_ids[adrc_controller->GetId()].push_back(
adrc_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_first_order_adrc_controller[std::make_tuple(
zone_id, adrc_controller->GetId())] = std::move(adrc_controller);
}
}
// Create 2nd-order ADRC controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::SecondOrderAdrcController>>
zone_id_and_controller_id_to_second_order_adrc_controller;
for (const SecondOrderAdrcControllerConfig& adrc_config :
params.second_order_adrc_controller_configs
.second_order_adrc_controller_configs()) {
for (int zone_id : adrc_config.zone_manager_id()) {
SecondOrderAdrcLoopConfig adrc_loop_config =
adrc_config.second_order_adrc_loop_config();
std::vector<thermal::DisturbanceFactors> disturbance_factors;
std::vector<std::vector<std::string>> disturbance_sensor_lists;
for (const QuantifiableDisturbance& quantifiable_disturbance :
adrc_config.quantifiable_disturbances()) {
disturbance_factors.push_back(thermal::DisturbanceFactors{
.d = quantifiable_disturbance.d(),
.reference_setpoint =
quantifiable_disturbance.reference_setpoint()});
std::vector<std::string> disturbance_sensor_list(
quantifiable_disturbance.input_sensors().begin(),
quantifiable_disturbance.input_sensors().end());
disturbance_sensor_lists.push_back(disturbance_sensor_list);
}
auto adrc_loop = std::make_unique<thermal::SecondOrderAdrcThermalLoop>(
adrc_loop_config, disturbance_factors);
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::SecondOrderAdrcController> adrc_controller,
thermal::SecondOrderAdrcController::Create({
.id = adrc_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.second_order_adrc_loop = std::move(adrc_loop),
.setpoint = adrc_config.setpoint(),
.input_sensors = {adrc_config.input_sensors().begin(),
adrc_config.input_sensors().end()},
.disturbance_sensor_lists = std::move(disturbance_sensor_lists),
}));
LOG(WARNING) << absl::StrFormat(
"2nd-order ADRC controller %s created for zone %d",
adrc_controller->GetId(), zone_id);
controller_id_to_zone_ids[adrc_controller->GetId()].push_back(
adrc_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_second_order_adrc_controller[std::make_tuple(
zone_id, adrc_controller->GetId())] = std::move(adrc_controller);
}
}
// Create PID controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::PidController>>
zone_id_and_controller_id_to_pid_controller;
for (const PidControllerConfig& pid_config :
params.pid_controller_configs.pid_controller_configs()) {
for (int zone_id : pid_config.zone_manager_id()) {
auto pid_loop = std::make_unique<thermal::PidThermalLoop>(
pid_config.pid_loop_config());
thermal::PidOptimizerInfo pid_optimizer_info;
if (pid_config.has_pid_autotuner_config()) {
ECCLESIA_ASSIGN_OR_RETURN(thermal::PidOptimizerInfo tuner,
CreatePidAutoTuner(pid_config));
pid_optimizer_info = std::move(tuner);
}
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::PidController> pid_controller,
thermal::PidController::Create({
.id = pid_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.pid_loop = std::move(pid_loop),
.setpoint = pid_config.setpoint(),
.input_process_type = pid_config.input_process_type(),
.input_sensors = {pid_config.input_sensors().begin(),
pid_config.input_sensors().end()},
.pid_optimizer_info = std::move(pid_optimizer_info),
}));
LOG(WARNING) << absl::StrFormat("PID controller %s created for zone %d",
pid_controller->GetId(), zone_id);
controller_id_to_zone_ids[pid_controller->GetId()].push_back(
pid_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_pid_controller[std::make_tuple(
zone_id, pid_controller->GetId())] = std::move(pid_controller);
}
}
// Create stepwise controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::StepwiseController>>
zone_id_and_controller_id_to_stepwise_controller;
for (const StepwiseControllerConfig& stepwise_config :
params.stepwise_controller_configs.stepwise_controller_configs()) {
for (int zone_id : stepwise_config.zone_manager_id()) {
auto stepwise_loop = std::make_unique<thermal::StepwiseThermalLoop>(
stepwise_config.stepwise_loop_config());
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::StepwiseController> stepwise_controller,
thermal::StepwiseController::Create({
.id = stepwise_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.stepwise_loop = std::move(stepwise_loop),
.input_sensors = {stepwise_config.input_sensors().begin(),
stepwise_config.input_sensors().end()},
.is_ceiling = stepwise_config.is_ceiling(),
}));
LOG(WARNING) << absl::StrFormat(
"Stepwise controller %s created for zone %d",
stepwise_controller->GetId(), zone_id);
controller_id_to_zone_ids[stepwise_controller->GetId()].push_back(
stepwise_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_stepwise_controller[std::make_tuple(
zone_id, stepwise_controller->GetId())] =
std::move(stepwise_controller);
}
}
// Create fan PID controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::FanPidController>>
zone_id_and_controller_id_to_fan_pid_controller;
for (const FanPidControllerConfig& fan_pid_config :
params.fan_pid_controller_configs.fan_pid_controller_configs()) {
for (int zone_id : fan_pid_config.zone_manager_id()) {
auto pid_loop = std::make_unique<thermal::PidThermalLoop>(
fan_pid_config.pid_loop_config());
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::FanPidController> fan_pid_controller,
thermal::FanPidController::Create({
.id = fan_pid_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.pid_loop = std::move(pid_loop),
.input_fans = {fan_pid_config.input_fans().begin(),
fan_pid_config.input_fans().end()},
.output_fans = {fan_pid_config.output_fans().begin(),
fan_pid_config.output_fans().end()},
}));
LOG(WARNING) << absl::StrFormat(
"Fan PID controller %s created for zone %d",
fan_pid_controller->GetId(), zone_id);
controller_id_to_zone_ids[fan_pid_controller->GetId()].push_back(
fan_pid_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_fan_pid_controller[std::make_tuple(
zone_id, fan_pid_controller->GetId())] =
std::move(fan_pid_controller);
}
}
// Create DFF controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::DffController>>
zone_id_and_controller_id_to_dff_controller;
for (const DffControllerConfig& dff_config :
params.dff_controller_configs.dff_controller_configs()) {
for (int zone_id : dff_config.zone_manager_id()) {
auto dff_loop = std::make_unique<thermal::DffThermalLoop>(
dff_config.dff_loop_config());
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::DffController> dff_controller,
thermal::DffController::Create({
.id = dff_config.id(),
.zone_manager = zone_id_to_zone_manager[zone_id].get(),
.dff_loop = std::move(dff_loop),
.input_sensors = {dff_config.input_sensors().begin(),
dff_config.input_sensors().end()},
.affected_controller_ids =
{dff_config.affected_controller_ids().begin(),
dff_config.affected_controller_ids().end()},
}));
LOG(WARNING) << absl::StrFormat("DFF controller %s created for zone %d",
dff_controller->GetId(), zone_id);
controller_id_to_zone_ids[dff_controller->GetId()].push_back(
dff_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_dff_controller[std::make_tuple(
zone_id, dff_controller->GetId())] = std::move(dff_controller);
}
}
// Create supportive zone managers.
absl::flat_hash_map<int, std::unique_ptr<thermal::ZoneManager>>
zone_id_to_supportive_zone_manager;
for (const SupportiveZoneManagerConfig& zone_config :
params.supportive_zone_manager_configs
.supportive_zone_manager_configs()) {
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::ZoneManager> supportive_zone_manager,
thermal::ZoneManager::Create({
.id = zone_config.id(),
.ms_per_thermal_cycle = zone_config.ms_per_thermal_cycle(),
.debug_mode = thermal::DebugMode(
zone_config.debug_mode().debug_enabled(),
zone_config.debug_mode().debug_pid_enabled(),
zone_config.debug_mode().debug_dff_enabled(), tuning_enabled),
.aggregator = aggregator,
.fru_collector = fru_collector,
.entity_config = entity_config,
.input_sensors = {zone_config.input_sensors().begin(),
zone_config.input_sensors().end()},
.sample_data_file_prefix = sample_data_file_prefix,
}));
LOG(WARNING) << absl::StrFormat("Supportive zone manager %d created",
supportive_zone_manager->GetId());
zone_id_to_supportive_zone_manager[zone_config.id()] =
std::move(supportive_zone_manager);
}
// Create EAT controllers.
absl::flat_hash_map<std::tuple<int, std::string>,
std::unique_ptr<thermal::EatController>>
zone_id_and_controller_id_to_eat_controller;
for (const EatControllerConfig& eat_config :
params.eat_controller_configs.eat_controller_configs()) {
for (int zone_id : eat_config.zone_manager_id()) {
auto eat_loop = std::make_unique<thermal::EatThermalLoop>(
eat_config.eat_loop_config());
ECCLESIA_ASSIGN_OR_RETURN(
std::unique_ptr<thermal::EatController> eat_controller,
thermal::EatController::Create({
.id = eat_config.id(),
.zone_manager = zone_id_to_supportive_zone_manager[zone_id].get(),
.eat_loop = std::move(eat_loop),
.input_sensors = {eat_config.input_sensors().begin(),
eat_config.input_sensors().end()},
.affected_zone_ids = {eat_config.affected_zone_ids().begin(),
eat_config.affected_zone_ids().end()},
}));
LOG(WARNING) << absl::StrFormat("EAT controller %s created for zone %d",
eat_controller->GetId(), zone_id);
controller_id_to_zone_ids[eat_controller->GetId()].push_back(
eat_controller->GetZoneManager()->GetId());
zone_id_and_controller_id_to_eat_controller[std::make_tuple(
zone_id, eat_controller->GetId())] = std::move(eat_controller);
}
}
return absl::WrapUnique(new ThermalCollector(
std::move(zone_id_and_controller_id_to_first_order_adrc_controller),
std::move(zone_id_and_controller_id_to_second_order_adrc_controller),
std::move(zone_id_to_zone_manager),
std::move(zone_id_and_controller_id_to_pid_controller),
std::move(zone_id_and_controller_id_to_stepwise_controller),
std::move(zone_id_and_controller_id_to_fan_pid_controller),
std::move(zone_id_and_controller_id_to_dff_controller),
std::move(controller_id_to_zone_ids),
std::move(zone_id_to_supportive_zone_manager),
std::move(zone_id_and_controller_id_to_eat_controller),
std::move(thermal_thread_manager), params.thread_factory));
}
std::vector<const thermal::ZoneManager*> ThermalCollector::GetAllThermalZones()
const {
std::vector<const thermal::ZoneManager*> thermal_zones;
thermal_zones.reserve(zone_id_to_zone_manager_.size() +
zone_id_to_supportive_zone_manager_.size());
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
thermal_zones.push_back(zone_manager.get());
}
for (const auto& [zone_id, zone_manager] :
zone_id_to_supportive_zone_manager_) {
thermal_zones.push_back(zone_manager.get());
}
std::sort(thermal_zones.begin(), thermal_zones.end(),
[](const thermal::ZoneManager* a, const thermal::ZoneManager* b) {
return a->GetId() < b->GetId();
});
return thermal_zones;
}
std::vector<const thermal::FirstOrderAdrcController*>
ThermalCollector::GetAllFirstOrderAdrcControllers() const {
std::vector<const thermal::FirstOrderAdrcController*>
first_order_adrc_controllers;
first_order_adrc_controllers.reserve(
controller_id_to_controller_.controller_id_to_first_order_adrc_controller
.size());
for (const auto& [controller_id, first_order_adrc_controller] :
controller_id_to_controller_
.controller_id_to_first_order_adrc_controller) {
first_order_adrc_controllers.push_back(first_order_adrc_controller);
}
std::sort(first_order_adrc_controllers.begin(),
first_order_adrc_controllers.end(),
[](const thermal::FirstOrderAdrcController* a,
const thermal::FirstOrderAdrcController* b) {
return a->GetId() < b->GetId();
});
return first_order_adrc_controllers;
}
std::vector<const thermal::SecondOrderAdrcController*>
ThermalCollector::GetAllSecondOrderAdrcControllers() const {
std::vector<const thermal::SecondOrderAdrcController*>
second_order_adrc_controllers;
second_order_adrc_controllers.reserve(
controller_id_to_controller_.controller_id_to_second_order_adrc_controller
.size());
for (const auto& [controller_id, second_order_adrc_controller] :
controller_id_to_controller_
.controller_id_to_second_order_adrc_controller) {
second_order_adrc_controllers.push_back(second_order_adrc_controller);
}
std::sort(second_order_adrc_controllers.begin(),
second_order_adrc_controllers.end(),
[](const thermal::SecondOrderAdrcController* a,
const thermal::SecondOrderAdrcController* b) {
return a->GetId() < b->GetId();
});
return second_order_adrc_controllers;
}
std::vector<const thermal::PidController*>
ThermalCollector::GetAllPidControllers() const {
std::vector<const thermal::PidController*> pid_controllers;
pid_controllers.reserve(
controller_id_to_controller_.controller_id_to_pid_controller.size());
for (const auto& [controller_id, pid_controller] :
controller_id_to_controller_.controller_id_to_pid_controller) {
pid_controllers.push_back(pid_controller);
}
std::sort(
pid_controllers.begin(), pid_controllers.end(),
[](const thermal::PidController* a, const thermal::PidController* b) {
return a->GetId() < b->GetId();
});
return pid_controllers;
}
std::vector<const thermal::StepwiseController*>
ThermalCollector::GetAllStepwiseControllers() const {
std::vector<const thermal::StepwiseController*> stepwise_controllers;
stepwise_controllers.reserve(
controller_id_to_controller_.controller_id_to_stepwise_controller.size());
for (const auto& [controller_id, stepwise_controller] :
controller_id_to_controller_.controller_id_to_stepwise_controller) {
stepwise_controllers.push_back(stepwise_controller);
}
std::sort(stepwise_controllers.begin(), stepwise_controllers.end(),
[](const thermal::StepwiseController* a,
const thermal::StepwiseController* b) {
return a->GetId() < b->GetId();
});
return stepwise_controllers;
}
std::vector<const thermal::FanPidController*>
ThermalCollector::GetAllFanPidControllers() const {
std::vector<const thermal::FanPidController*> fan_pid_controllers;
fan_pid_controllers.reserve(
controller_id_to_controller_.controller_id_to_fan_pid_controller.size());
for (const auto& [controller_id, fan_pid_controller] :
controller_id_to_controller_.controller_id_to_fan_pid_controller) {
fan_pid_controllers.push_back(fan_pid_controller);
}
std::sort(fan_pid_controllers.begin(), fan_pid_controllers.end(),
[](const thermal::FanPidController* a,
const thermal::FanPidController* b) {
return a->GetId() < b->GetId();
});
return fan_pid_controllers;
}
std::vector<const thermal::DffController*>
ThermalCollector::GetAllDffControllers() const {
std::vector<const thermal::DffController*> dff_controllers;
dff_controllers.reserve(
controller_id_to_controller_.controller_id_to_dff_controller.size());
for (const auto& [controller_id, dff_controller] :
controller_id_to_controller_.controller_id_to_dff_controller) {
dff_controllers.push_back(dff_controller);
}
std::sort(
dff_controllers.begin(), dff_controllers.end(),
[](const thermal::DffController* a, const thermal::DffController* b) {
return a->GetId() < b->GetId();
});
return dff_controllers;
}
std::vector<const thermal::EatController*>
ThermalCollector::GetAllEatControllers() const {
std::vector<const thermal::EatController*> eat_controllers;
eat_controllers.reserve(
controller_id_to_controller_.controller_id_to_eat_controller.size());
for (const auto& [controller_id, eat_controller] :
controller_id_to_controller_.controller_id_to_eat_controller) {
eat_controllers.push_back(eat_controller);
}
std::sort(
eat_controllers.begin(), eat_controllers.end(),
[](const thermal::EatController* a, const thermal::EatController* b) {
return a->GetId() < b->GetId();
});
return eat_controllers;
}
absl::StatusOr<const thermal::ZoneManager*>
ThermalCollector::GetThermalZoneById(int zone_id) const {
// First check if the zone is a non-supportive zone.
auto itr_zone_manager = zone_id_to_zone_manager_.find(zone_id);
// If not, check if the zone is a supportive zone.
auto itr_supportive_zone_manager =
zone_id_to_supportive_zone_manager_.find(zone_id);
if (itr_zone_manager == zone_id_to_zone_manager_.end() &&
itr_supportive_zone_manager ==
zone_id_to_supportive_zone_manager_.end()) {
return absl::NotFoundError(
absl::StrCat("Zone with id ", zone_id, " not found"));
}
return itr_zone_manager != zone_id_to_zone_manager_.end()
? itr_zone_manager->second.get()
: itr_supportive_zone_manager->second.get();
}
absl::StatusOr<const thermal::FirstOrderAdrcController*>
ThermalCollector::GetFirstOrderAdrcControllerById(
absl::string_view controller_id) const {
auto itr =
controller_id_to_controller_.controller_id_to_first_order_adrc_controller
.find(controller_id);
if (itr == controller_id_to_controller_
.controller_id_to_first_order_adrc_controller.end()) {
return absl::NotFoundError(absl::StrCat("FirstOrderAdrcController with id ",
controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::SecondOrderAdrcController*>
ThermalCollector::GetSecondOrderAdrcControllerById(
absl::string_view controller_id) const {
auto itr =
controller_id_to_controller_.controller_id_to_second_order_adrc_controller
.find(controller_id);
if (itr == controller_id_to_controller_
.controller_id_to_second_order_adrc_controller.end()) {
return absl::NotFoundError(absl::StrCat(
"SecondOrderAdrcController with id ", controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::PidController*>
ThermalCollector::GetPidControllerById(absl::string_view controller_id) const {
auto itr = controller_id_to_controller_.controller_id_to_pid_controller.find(
controller_id);
if (itr ==
controller_id_to_controller_.controller_id_to_pid_controller.end()) {
return absl::NotFoundError(
absl::StrCat("PidController with id ", controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::StepwiseController*>
ThermalCollector::GetStepwiseControllerById(
absl::string_view controller_id) const {
auto itr =
controller_id_to_controller_.controller_id_to_stepwise_controller.find(
controller_id);
if (itr ==
controller_id_to_controller_.controller_id_to_stepwise_controller.end()) {
return absl::NotFoundError(absl::StrCat("StepwiseController with id ",
controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::FanPidController*>
ThermalCollector::GetFanPidControllerById(
absl::string_view controller_id) const {
auto itr =
controller_id_to_controller_.controller_id_to_fan_pid_controller.find(
controller_id);
if (itr ==
controller_id_to_controller_.controller_id_to_fan_pid_controller.end()) {
return absl::NotFoundError(
absl::StrCat("FanPidController with id ", controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::DffController*>
ThermalCollector::GetDffControllerById(absl::string_view controller_id) const {
auto itr = controller_id_to_controller_.controller_id_to_dff_controller.find(
controller_id);
if (itr ==
controller_id_to_controller_.controller_id_to_dff_controller.end()) {
return absl::NotFoundError(
absl::StrCat("DffController with id ", controller_id, " not found"));
}
return itr->second;
}
absl::StatusOr<const thermal::EatController*>
ThermalCollector::GetEatControllerById(absl::string_view controller_id) const {
auto itr = controller_id_to_controller_.controller_id_to_eat_controller.find(
controller_id);
if (itr ==
controller_id_to_controller_.controller_id_to_eat_controller.end()) {
return absl::NotFoundError(
absl::StrCat("EatController with id ", controller_id, " not found"));
}
return itr->second;
}
nlohmann::json ThermalCollector::ToJson() const {
nlohmann::json::object_t response;
nlohmann::json::object_t zone_managers;
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
nlohmann::json zone_manager_json;
zone_manager_json["zone_index"] = zone_id;
zone_manager_json["failsafe_percent"] =
zone_manager->GetZoneFailsafePercent();
zone_manager_json["fan_mode"] =
(zone_manager->GetManualMode() ? "Manual" : "Auto");
zone_manager_json["min_thermal_output"] =
zone_manager->GetMinimumThermalSetpoint();
zone_managers[absl::StrCat("Zone_", zone_id)] =
std::move(zone_manager_json);
}
response["Zones"] = std::move(zone_managers);
nlohmann::json::object_t pid_controllers;
for (const auto& [controller_id, pid_controller] :
controller_id_to_controller_.controller_id_to_pid_controller) {
nlohmann::json pid_controller_json;
pid_controller_json["feed_forward_gain"] =
pid_controller->GetPidLoop()->GetFeedForwardGain();
pid_controller_json["feed_forward_offset"] =
pid_controller->GetPidLoop()->GetFeedForwardOffset();
pid_controller_json["proportional_coefficient"] =
pid_controller->GetPidLoop()->GetCoeffProportional();
pid_controller_json["integral_coefficient"] =
pid_controller->GetPidLoop()->GetCoeffIntegral();
pid_controller_json["derivative_coefficient"] =
pid_controller->GetPidLoop()->GetCoeffDerivative();
pid_controller_json["integral_limit_max"] =
pid_controller->GetPidLoop()->GetIntegralLimitMax();
pid_controller_json["integral_limit_min"] =
pid_controller->GetPidLoop()->GetIntegralLimitMin();
pid_controller_json["output_limit_max"] =
pid_controller->GetPidLoop()->GetOutputLimitMax();
pid_controller_json["output_limit_min"] =
pid_controller->GetPidLoop()->GetOutputLimitMin();
pid_controller_json["slew_neg"] =
pid_controller->GetPidLoop()->GetSlewNeg();
pid_controller_json["slew_pos"] =
pid_controller->GetPidLoop()->GetSlewPos();
pid_controller_json["setpoint"] = pid_controller->GetSetpoint();
for (const PidControllerSensorInfo& sensor :
pid_controller->GetInputSensors()) {
pid_controller_json["input_sensors"].push_back(sensor.sensor_key());
}
if (controller_id_to_zone_ids_.contains(controller_id)) {
pid_controller_json["owner_zone"] =
controller_id_to_zone_ids_.at(controller_id);
}
pid_controllers[controller_id] = std::move(pid_controller_json);
}
response["PidControllers"] = std::move(pid_controllers);
nlohmann::json::object_t fan_pid_controllers;
for (const auto& [controller_id, fan_pid_controller] :
controller_id_to_controller_.controller_id_to_fan_pid_controller) {
nlohmann::json fan_pid_controller_json;
fan_pid_controller_json["feed_forward_gain"] =
fan_pid_controller->GetPidLoop()->GetFeedForwardGain();
fan_pid_controller_json["feed_forward_offset"] =
fan_pid_controller->GetPidLoop()->GetFeedForwardOffset();
fan_pid_controller_json["proportional_coefficient"] =
fan_pid_controller->GetPidLoop()->GetCoeffProportional();
fan_pid_controller_json["integral_coefficient"] =
fan_pid_controller->GetPidLoop()->GetCoeffIntegral();
fan_pid_controller_json["derivative_coefficient"] =
fan_pid_controller->GetPidLoop()->GetCoeffDerivative();
fan_pid_controller_json["integral_limit_max"] =
fan_pid_controller->GetPidLoop()->GetIntegralLimitMax();
fan_pid_controller_json["integral_limit_min"] =
fan_pid_controller->GetPidLoop()->GetIntegralLimitMin();
fan_pid_controller_json["output_limit_max"] =
fan_pid_controller->GetPidLoop()->GetOutputLimitMax();
fan_pid_controller_json["output_limit_min"] =
fan_pid_controller->GetPidLoop()->GetOutputLimitMin();
fan_pid_controller_json["slew_neg"] =
fan_pid_controller->GetPidLoop()->GetSlewNeg();
fan_pid_controller_json["slew_pos"] =
fan_pid_controller->GetPidLoop()->GetSlewPos();
for (absl::string_view fan : fan_pid_controller->GetInputFans()) {
fan_pid_controller_json["input_fans"].push_back(fan);
}
for (absl::string_view fan_name : fan_pid_controller->GetOutputFans()) {
fan_pid_controller_json["output_fans"].push_back(fan_name);
}
if (controller_id_to_zone_ids_.contains(controller_id)) {
fan_pid_controller_json["owner_zone"] =
controller_id_to_zone_ids_.at(controller_id);
}
fan_pid_controllers[controller_id] = std::move(fan_pid_controller_json);
}
response["FanPidControllers"] = std::move(fan_pid_controllers);
nlohmann::json::object_t second_order_adrc_controllers;
for (const auto& [controller_id, second_order_adrc_controller] :
controller_id_to_controller_
.controller_id_to_second_order_adrc_controller) {
nlohmann::json second_order_adrc_controller_json =
second_order_adrc_controller->ToJson();
if (auto it = controller_id_to_zone_ids_.find(controller_id);
it != controller_id_to_zone_ids_.end()) {
second_order_adrc_controller_json["owner_zone"] = it->second;
}
second_order_adrc_controllers[controller_id] =
std::move(second_order_adrc_controller_json);
}
response["SecondOrderAdrcControllers"] =
std::move(second_order_adrc_controllers);
nlohmann::json::object_t first_order_adrc_controllers;
for (const auto& [controller_id, first_order_adrc_controller] :
controller_id_to_controller_
.controller_id_to_first_order_adrc_controller) {
nlohmann::json first_order_adrc_controller_json =
first_order_adrc_controller->ToJson();
if (auto it = controller_id_to_zone_ids_.find(controller_id);
it != controller_id_to_zone_ids_.end()) {
first_order_adrc_controller_json["owner_zone"] = it->second;
}
first_order_adrc_controllers[controller_id] =
std::move(first_order_adrc_controller_json);
}
response["FirstOrderAdrcControllers"] =
std::move(first_order_adrc_controllers);
return response;
}
void ThermalCollector::StartThermalControl() {
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
auto timer = std::make_shared<boost::asio::steady_timer>(
**thermal_thread_manager_->io_contexts.begin());
thermal_control_timers_.push_back(timer);
LOG(WARNING) << "Start thermal control for zone `" << zone_id << "`";
boost::asio::post(**thermal_thread_manager_->io_contexts.begin(),
[this, zone_manager = zone_manager.get(), timer] {
ProcessThermalControl(
zone_manager, timer, /*first_run=*/true,
/*ms_elapsed_in_current_cycle=*/0);
});
}
for (const auto& [zone_id, zone_manager] :
zone_id_to_supportive_zone_manager_) {
auto timer = std::make_shared<boost::asio::steady_timer>(
**thermal_thread_manager_->io_contexts.begin());
thermal_control_timers_.push_back(timer);
LOG(WARNING) << "Start thermal control for supportive zone `" << zone_id
<< "`";
boost::asio::post(**thermal_thread_manager_->io_contexts.begin(),
[this, zone_manager = zone_manager.get(), timer] {
ProcessThermalControl(
zone_manager, timer, /*first_run=*/true,
/*ms_elapsed_in_current_cycle=*/0);
});
}
}
void ThermalCollector::StartCollection() {
StartThermalControl();
for (const auto& io_context : thermal_thread_manager_->io_contexts) {
thermal_thread_manager_->threads.push_back(
thread_factory_->New([io_context]() { io_context->run(); }));
}
}
void ThermalCollector::SetManualMode(bool enable) {
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
zone_manager->SetManualMode(enable);
zone_manager->TryLogThermalDebugMessage(
absl::StrFormat("Zone %d set to manual mode %s.", zone_id,
(enable ? "true" : "false")));
}
}
absl::flat_hash_map<int, bool> ThermalCollector::GetZoneIdToManualModes()
const {
absl::flat_hash_map<int, bool> zone_id_to_manual_modes;
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
zone_id_to_manual_modes[zone_manager->GetId()] =
zone_manager->GetManualMode();
}
return zone_id_to_manual_modes;
}
void ThermalCollector::SetManualPwm(absl::string_view fan_name, double pwm) {
for (const auto& [zone_id, zone_manager] : zone_id_to_zone_manager_) {
zone_manager->SetManualPwm(fan_name, pwm);
}
}
absl::Status ThermalCollector::RequestPidControllerCoefficientsTuning(
absl::string_view controller_id,
const thermal::PidController::PidControllerCoefficients& coefficients) {
auto pid_controller_it =
controller_id_to_controller_.controller_id_to_pid_controller.find(
controller_id);
if (pid_controller_it ==
controller_id_to_controller_.controller_id_to_pid_controller.end()) {
return absl::NotFoundError(
absl::StrCat("PidController with id ", controller_id, " not found"));
}
if (!pid_controller_it->second->GetZoneManager()->GetTuningEnabled()) {
return absl::InternalError(
absl::StrCat("Tuning mode is not enabled for the controller ",
controller_id, "'s zone."));
}
auto zone_ids_it = controller_id_to_zone_ids_.find(controller_id);
if (zone_ids_it == controller_id_to_zone_ids_.end()) {
return absl::NotFoundError(absl::StrCat(
"There is no zone associated with controller ", controller_id));
}
absl::MutexLock lock(tuning_mutex_);
controller_id_to_pending_pid_coefficient_[controller_id] = coefficients;
for (int zone_id : zone_ids_it->second) {
zone_ids_with_pending_tuning_request_.insert(zone_id);
}
return absl::OkStatus();
}
absl::Status ThermalCollector::RequestFanPidControllerCoefficientsTuning(
absl::string_view controller_id,
const thermal::FanPidController::FanPidControllerCoefficients&
coefficients) {
auto fan_pid_controller_it =
controller_id_to_controller_.controller_id_to_fan_pid_controller.find(
controller_id);
if (fan_pid_controller_it ==
controller_id_to_controller_.controller_id_to_fan_pid_controller.end()) {
return absl::NotFoundError(
absl::StrCat("FanPidController with id ", controller_id, " not found"));
}
if (!fan_pid_controller_it->second->GetZoneManager()->GetTuningEnabled()) {
return absl::InternalError(
absl::StrCat("Tuning mode is not enabled for the controller ",
controller_id, "'s zone."));
}
auto zone_ids_it = controller_id_to_zone_ids_.find(controller_id);
if (zone_ids_it == controller_id_to_zone_ids_.end()) {
return absl::NotFoundError(absl::StrCat(
"There is no zone associated with controller ", controller_id));
}
absl::MutexLock lock(tuning_mutex_);
controller_id_to_pending_fan_pid_coefficient_[controller_id] = coefficients;
for (int zone_id : zone_ids_it->second) {
zone_ids_with_pending_tuning_request_.insert(zone_id);
}
return absl::OkStatus();
}
void ThermalCollector::ApplyPendingTuningRequests(int zone_id) {
absl::MutexLock lock(tuning_mutex_);
if (!zone_ids_with_pending_tuning_request_.contains(zone_id)) {
return;
}
thermal::ZoneManager* zone_manager = zone_id_to_zone_manager_[zone_id].get();
// Update coefficients for thermal controllers.
for (const thermal::ThermalController* controller :
zone_manager->GetAllThermalControllers()) {
const std::string controller_id = controller->GetId();
// Try to apply PID controller tuning requests.
auto pid_controller_it = zone_id_and_controller_id_to_pid_controller_.find(
std::make_tuple(zone_id, controller_id));
if (pid_controller_it !=
zone_id_and_controller_id_to_pid_controller_.end()) {
auto pending_pid_coefficient_it =
controller_id_to_pending_pid_coefficient_.find(controller_id);
if (pending_pid_coefficient_it !=
controller_id_to_pending_pid_coefficient_.end()) {
pid_controller_it->second->SetCoefficients(
pending_pid_coefficient_it->second);
}
}
}
// Update coefficients for fan controllers.
for (const thermal::ThermalController* controller :
zone_manager->GetAllFanControllers()) {
const std::string controller_id = controller->GetId();
// Try to apply fan PID controller tuning requests.
auto fan_pid_controller_it =
zone_id_and_controller_id_to_fan_pid_controller_.find(
std::make_tuple(zone_id, controller_id));
if (fan_pid_controller_it !=
zone_id_and_controller_id_to_fan_pid_controller_.end()) {
auto pending_fan_pid_coefficient_it =
controller_id_to_pending_fan_pid_coefficient_.find(controller_id);
if (pending_fan_pid_coefficient_it !=
controller_id_to_pending_fan_pid_coefficient_.end()) {
fan_pid_controller_it->second->SetCoefficients(
pending_fan_pid_coefficient_it->second);
}
}
}
zone_ids_with_pending_tuning_request_.erase(zone_id);
}
bool ThermalCollector::HasPendingTuningRequest(
absl::string_view controller_id) {
auto zone_ids_it = controller_id_to_zone_ids_.find(controller_id);
if (zone_ids_it == controller_id_to_zone_ids_.end()) {
return false;
}
absl::MutexLock lock(tuning_mutex_);
for (int zone_id : zone_ids_it->second) {
if (zone_ids_with_pending_tuning_request_.contains(zone_id)) {
return true;
}
}
return false;
}
absl::StatusOr<thermal::PidOptimizerInfo> ThermalCollector::CreatePidAutoTuner(
const PidControllerConfig& pid_controller_config) {
// Create the auto-tuner.
std::unique_ptr<thermal::optimizer::PidOptimizer> pid_optimizer = nullptr;
if (pid_controller_config.pid_autotuner_config()
.has_ziegler_nichols_config()) {
ECCLESIA_ASSIGN_OR_RETURN(
pid_optimizer, thermal::optimizer::PidAutotunerZieglerNichols::Create(
pid_controller_config));
} else if (pid_controller_config.pid_autotuner_config()
.has_differentiated_ziegler_nichols_config()) {
ECCLESIA_ASSIGN_OR_RETURN(
pid_optimizer,
thermal::optimizer::PidAutotunerDifferentiatedZieglerNichols::Create(
pid_controller_config));
} else if (pid_controller_config.pid_autotuner_config()
.has_frequency_response_estimation_config()) {
ECCLESIA_ASSIGN_OR_RETURN(
pid_optimizer,
thermal::optimizer::PidAutotunerFrequencyResponseEstimation::Create(
pid_controller_config));
}
if (pid_optimizer == nullptr) {
return absl::InvalidArgumentError(
"No valid PID auto-tuner config is found in the PID controller "
"config.");
}
// Set up auto-tuner states.
thermal::optimizer::PidOptimizerStates pid_optimizer_states;
pid_optimizer_states.pid_optimizer_init_params = {
.coeff_p = pid_controller_config.pid_loop_config().coeff_proportional(),
.coeff_i = pid_controller_config.pid_loop_config().coeff_integral(),
.coeff_d = pid_controller_config.pid_loop_config().coeff_derivative(),
};
return thermal::PidOptimizerInfo{
.states = pid_optimizer_states,
.optimizer = std::move(pid_optimizer),
};
}
void ThermalCollector::ProcessOneThermalLoop(
thermal::ZoneManager* zone_manager) {
zone_manager->ClearSetpoints();
zone_manager->UpdateAllSensorValues();
zone_manager->ProcessThermalControllers();
zone_manager->DetermineMaximumSetpoint();
}
void ThermalCollector::ProcessThermalControl(
thermal::ZoneManager* zone_manager,
const std::shared_ptr<boost::asio::steady_timer>& timer, bool first_run,
uint64_t ms_elapsed_in_current_cycle) {
if (stop_thermal_control_) {
return;
}
std::chrono::steady_clock::time_point next_activation_time;
if (first_run) {
zone_manager->InitializeThermalStates();
ProcessOneThermalLoop(zone_manager);
next_activation_time = std::chrono::steady_clock::now();
// Avoid dumping the first iteration data, which is for preparation of
// successive runs.
if (zone_manager->GetDebugEnabled()) {
zone_manager->ClearSampledData();
}
} else {
next_activation_time = timer->expiry();
}
uint64_t ms_per_fan_cycle = zone_manager->GetMsPerFanCycle();
// Push forward the original expiration time of timer, instead of just
// resetting it with expires_after() from now, to make sure the interval
// is of the expected duration, and not stretched out by CPU time taken.
next_activation_time += std::chrono::milliseconds(ms_per_fan_cycle);
timer->expires_at(next_activation_time);
timer->async_wait(
[this, zone_manager, timer, ms_elapsed_in_current_cycle,
ms_per_fan_cycle](const boost::system::error_code& ec) mutable {
if (stop_thermal_control_.load(std::memory_order_relaxed) ||
ec == boost::asio::error::operation_aborted ||
ec == boost::system::errc::operation_canceled) {
return; // timer being canceled, stop loop
}
// Under tuning mode, try update coefficients first, if there is any
// pending request.
if (zone_manager->GetTuningEnabled()) {
ApplyPendingTuningRequests(zone_manager->GetId());
}
// Under manual mode, simply trigger the next thermal control iteration,
// and then return, without executing the thermal and fan cycles.
if (zone_manager->GetManualMode()) {
ProcessThermalControl(zone_manager, timer, /*first_run=*/false,
ms_elapsed_in_current_cycle);
return;
}
uint64_t ms_per_thermal_cycle = zone_manager->GetMsPerThermalCycle();
// Process thermal cycles no more frequently than fan cycles.
// If the thermal interval is not an exact multiple of the fan interval,
// there will be some remainder left over.
bool process_thermal_cycle = false;
if (ms_elapsed_in_current_cycle >= ms_per_thermal_cycle) {
// Keep the timing correct, as the intervals are overlapping.
ms_elapsed_in_current_cycle -= ms_per_thermal_cycle;
ProcessOneThermalLoop(zone_manager);
process_thermal_cycle = true;
} else {
// We need to update fan sensor values for fan controllers in EVERY
// iteration. Alternatively, we may just update fan rpms here, instead
// of all sensors, but this is hardly a performance improvement, and
// will introduce more complexity.
zone_manager->UpdateAllSensorValues();
}
// Process fan controllers and write fan speeds in every iteration.
zone_manager->ProcessFanControllers();
zone_manager->WriteFanSpeeds();
if (zone_manager->GetDebugEnabled()) {
// Clear the sampled data if only fan cycles are processed, to avoid
// data misalignment.
process_thermal_cycle ? zone_manager->DumpSampledData()
: zone_manager->ClearSampledData();
}
// Count how many milliseconds have elapsed to determine when to perform
// thermal cycles, in a proper ratio with fan cycles.
ms_elapsed_in_current_cycle += ms_per_fan_cycle;
ProcessThermalControl(zone_manager, timer,
/*first_run=*/false, ms_elapsed_in_current_cycle);
});
}
std::unique_ptr<EmptyThermalCollector> EmptyThermalCollector::Create() {
return std::make_unique<EmptyThermalCollector>();
}
std::vector<const thermal::ZoneManager*>
EmptyThermalCollector::GetAllThermalZones() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllThermalZones is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::FirstOrderAdrcController*>
EmptyThermalCollector::GetAllFirstOrderAdrcControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllFirstOrderAdrcControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::SecondOrderAdrcController*>
EmptyThermalCollector::GetAllSecondOrderAdrcControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllSecondOrderAdrcControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::PidController*>
EmptyThermalCollector::GetAllPidControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllPidControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::StepwiseController*>
EmptyThermalCollector::GetAllStepwiseControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllStepwiseControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::DffController*>
EmptyThermalCollector::GetAllDffControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllDffControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::FanPidController*>
EmptyThermalCollector::GetAllFanPidControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllFanPidControllers is "
"called. This will return an empty list.";
return {};
}
std::vector<const thermal::EatController*>
EmptyThermalCollector::GetAllEatControllers() const {
LOG(WARNING) << "EmptyThermalCollector::GetAllEatControllers is "
"called. This will return an empty list.";
return {};
}
absl::StatusOr<const thermal::ZoneManager*>
EmptyThermalCollector::GetThermalZoneById(int zone_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetThermalZoneById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::FirstOrderAdrcController*>
EmptyThermalCollector::GetFirstOrderAdrcControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetFirstOrderAdrcControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::SecondOrderAdrcController*>
EmptyThermalCollector::GetSecondOrderAdrcControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetSecondOrderAdrcControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::PidController*>
EmptyThermalCollector::GetPidControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetPidControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::StepwiseController*>
EmptyThermalCollector::GetStepwiseControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetStepwiseControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::DffController*>
EmptyThermalCollector::GetDffControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetDffControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::EatController*>
EmptyThermalCollector::GetEatControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetEatControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
absl::StatusOr<const thermal::FanPidController*>
EmptyThermalCollector::GetFanPidControllerById(
absl::string_view controller_id) const {
LOG(WARNING) << "EmptyThermalCollector::GetFanPidControllerById is "
"called. This will return a nullptr.";
return nullptr;
}
nlohmann::json EmptyThermalCollector::GetSchedulerStats() const {
return nlohmann::json::parse(
"{\"Warning\": \"EmptyThermalCollector used.\"}");
}
nlohmann::json EmptyThermalCollector::ToJson() const {
return nlohmann::json::parse(
"{\"Warning\": \"EmptyThermalCollector used.\"}");
}
void EmptyThermalCollector::StartCollection() {
LOG(WARNING) << "EmptyThermalCollector::StartCollection is "
"called. This does nothing.";
}
void EmptyThermalCollector::SetManualMode(bool enable) {
LOG(WARNING) << "EmptyThermalCollector::SetManualMode is "
"called. This does nothing.";
}
absl::flat_hash_map<int, bool> EmptyThermalCollector::GetZoneIdToManualModes()
const {
LOG(WARNING) << "EmptyThermalCollector::GetZoneIdToManualModes is "
"called. This will return an empty map.";
return {};
}
void EmptyThermalCollector::SetManualPwm(absl::string_view fan_name,
double pwm) {
LOG(WARNING) << "EmptyThermalCollector::SetManualPwm is "
"called. This does nothing.";
}
absl::Status EmptyThermalCollector::RequestPidControllerCoefficientsTuning(
absl::string_view controller_id,
const thermal::PidController::PidControllerCoefficients& coefficients) {
LOG(WARNING)
<< "EmptyThermalCollector::RequestPidControllerCoefficientsTuning is "
"called. This does nothing.";
return absl::OkStatus();
}
absl::Status EmptyThermalCollector::RequestFanPidControllerCoefficientsTuning(
absl::string_view controller_id,
const thermal::FanPidController::FanPidControllerCoefficients&
coefficients) {
LOG(WARNING)
<< "EmptyThermalCollector::RequestFanPidControllerCoefficientsTuning is "
"called. This does nothing.";
return absl::OkStatus();
}
} // namespace milotic_tlbmc