blob: 896202c62237afd5d338e9e7d98d5d2df7163e30 [file] [log] [blame]
// Copyright 2021 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.
use crate::common_utils::get_current_timestamp;
use crate::log_if_err;
use crate::message::{Message, MessageReturn};
use crate::node::Node;
use crate::platform_metrics::PlatformMetric;
use crate::shutdown_request::{RebootReason, ShutdownRequest};
use crate::temperature_handler::TemperatureFilter;
use crate::types::{Celsius, Seconds, ThermalLoad};
use anyhow::{format_err, Error, Result};
use async_trait::async_trait;
use fuchsia_async as fasync;
use fuchsia_inspect::{self as inspect, Property};
use futures::{StreamExt, TryFutureExt as _};
use serde_derive::Deserialize;
use serde_json as json;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use tracing::*;
/// Node: ThermalLoadDriver
///
/// Summary: The purpose of this node is to determine and communicate the thermal load value(s) in
/// the system.
///
/// The behavior is unique from the ThermalPolicy node (which is also capable of determining and
/// communicating thermal load values) because it is purpose-built with the ability to source
/// multiple temperature sensors to determine their individual thermal load values. It also
/// differs from ThermalPolicy because it calculates ThermalLoad based on observed filtered
/// temperature with respect to configured per-sensor onset/reboot temperatures, whereas
/// ThermalPolicy uses integral errors (where "error" is the filtered temperature delta with
/// respect to a configured target temperature).
///
/// To do this, the node polls each of the provided temperature handler nodes at their specified
/// polling intervals. The temperature is used to calculate a per-sensor thermal load value (each
/// sensor may configure its own unique onset and reboot temperatures which define that sensor's
/// thermal load range). As the thermal load on a given sensor changes, the new load value is
/// communicated to each of the `thermal_load_notify_nodes` nodes.
///
/// Handles Messages: N/A
///
/// Sends Messages:
/// - SystemShutdown
/// - UpdateThermalLoad
/// - GetDriverPath
/// - LogPlatformMetric
///
/// FIDL dependencies: N/A
pub struct ThermalLoadDriverBuilder<'a> {
temperature_input_configs: Vec<TemperatureInputConfig>,
system_shutdown_node: Rc<dyn Node>,
platform_metrics_node: Rc<dyn Node>,
thermal_load_notify_nodes: Vec<Rc<dyn Node>>,
inspect_root: Option<&'a inspect::Node>,
}
impl ThermalLoadDriverBuilder<'_> {
pub fn new_from_json(
json_data: json::Value,
nodes: &HashMap<String, Rc<dyn Node>>,
structured_config: &power_manager_config_lib::Config,
) -> Self {
#[derive(Deserialize)]
struct JsonTemperatureInputConfig {
temperature_handler_node_name: String,
onset_temperature_c: f64,
reboot_temperature_c: f64,
poll_interval_s: f64,
filter_time_constant_s: f64,
}
#[derive(Deserialize)]
struct Config {
temperature_input_configs: Vec<JsonTemperatureInputConfig>,
}
#[derive(Deserialize)]
struct Dependencies {
system_shutdown_node: String,
thermal_load_notify_nodes: Vec<String>,
platform_metrics_node: String,
}
#[derive(Deserialize)]
struct JsonData {
config: Config,
dependencies: Dependencies,
}
let data: JsonData = json::from_value(json_data).unwrap();
Self {
system_shutdown_node: nodes[&data.dependencies.system_shutdown_node].clone(),
platform_metrics_node: nodes[&data.dependencies.platform_metrics_node].clone(),
temperature_input_configs: data
.config
.temperature_input_configs
.iter()
.map(|config| TemperatureInputConfig {
temperature_handler_node: nodes[&config.temperature_handler_node_name].clone(),
onset_temperature: Celsius(config.onset_temperature_c),
reboot_temperature: Celsius(config.reboot_temperature_c),
poll_interval: Seconds(config.poll_interval_s),
filter_time_constant: Seconds(
if structured_config.disable_temperature_filter {
0.0
} else {
config.filter_time_constant_s
},
),
})
.collect(),
thermal_load_notify_nodes: data
.dependencies
.thermal_load_notify_nodes
.iter()
.map(|node_name| nodes[node_name].clone())
.collect(),
inspect_root: None,
}
}
pub fn build(self) -> Result<Rc<ThermalLoadDriver>, Error> {
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
let node = Rc::new(ThermalLoadDriver {
system_shutdown_node: self.system_shutdown_node,
inspect: inspect_root.create_child("ThermalLoadDriver"),
platform_metrics: self.platform_metrics_node,
thermal_load_notify_nodes: self.thermal_load_notify_nodes,
polling_tasks: RefCell::new(Vec::new()),
});
// Spawn a polling task for each of the temperature input configs. The polling tasks are
// collected into the node's `polling_tasks` field.
self.temperature_input_configs
.into_iter()
.for_each(|config| node.create_polling_task(config));
Ok(node)
}
}
pub struct ThermalLoadDriver {
/// Node that is used to initiate a system reboot when any temperature input source reaches
/// their configured reboot temperature.
system_shutdown_node: Rc<dyn Node>,
/// Nodes that we notify when the thermal load value for any sensor has changed.
thermal_load_notify_nodes: Vec<Rc<dyn Node>>,
/// Parent Inspect node for the ThermalLoadDriver node (named as "ThermalLoadDriver"). Each
/// polling task / temperature input source has a corresponding child node underneath this one.
inspect: inspect::Node,
/// Node that we'll notify with relevant platform metrics.
platform_metrics: Rc<dyn Node>,
/// Stores the Task objects that handle polling temperature sensors and taking appropriate
/// action as their thermal loads change. The bulk of the ThermalLoadDriver's real work happens
/// within these polling tasks. There exists a polling task for each individual temperature
/// sensor that this node monitors.
polling_tasks: RefCell<Vec<fasync::Task<()>>>,
}
impl ThermalLoadDriver {
/// Creates a new polling task to begin running immediately.
///
/// The function uses the provided TemperatureInputConfig to create and spawn a new polling
/// task. The polling task is responsible for polling the temperature sensor at the configured
/// interval, then taking appropriate action to determine thermal load and/or initiate thermal
/// shutdown. The new polling task is added to the `polling_tasks` vector to retain ownership
/// (instead of detaching the Task).
fn create_polling_task(self: &Rc<Self>, config: TemperatureInputConfig) {
let this = self.clone();
let polling_future = async move {
// Query the TemperatureHandler to find out the driver's topological path. This path is
// used to identify the source of thermal load changes in the system.
let sensor_path = this.query_driver_path(&config.temperature_handler_node).await?;
// Each polling task gets its own Inspect node
let inspect = TemperatureInputInspect::new(&this.inspect, &sensor_path, &config);
let mut periodic_timer = fasync::Interval::new(config.poll_interval.into());
let temperature_input = TemperatureInput::new(config);
// Enter the timer-based polling loop...
while let Some(()) = periodic_timer.next().await {
// Read a new filtered temperature value. Errors are logged but the polling loop
// will continue on the next iteration.
let new_thermal_load = match temperature_input.get_thermal_load().await {
Ok(load) => load,
Err(e) => {
error!(
"Failed to get updated thermal load for {} (err = {})",
&sensor_path, e
);
continue;
}
};
inspect.log_thermal_load(new_thermal_load);
if new_thermal_load >= ThermalLoad(100) {
log_if_err!(
this.initiate_thermal_shutdown().await,
"Failed to initiate thermal shutdown"
);
} else {
log_if_err!(
this.send_message_to_many(
&this.thermal_load_notify_nodes,
&Message::UpdateThermalLoad(new_thermal_load, sensor_path.clone())
)
.await
.into_iter()
.collect::<Result<Vec<_>, _>>(),
"Failed to send thermal load update"
);
}
}
Ok(())
}
.unwrap_or_else(|e: Error| error!("Failed to monitor sensor (err = {})", e));
self.polling_tasks.borrow_mut().push(fasync::Task::local(polling_future));
}
/// Queries the provided TemperatureHandler node for its associated driver path.
async fn query_driver_path(&self, temperature_handler: &Rc<dyn Node>) -> Result<String> {
match self.send_message(temperature_handler, &Message::GetDriverPath).await {
Ok(MessageReturn::GetDriverPath(path)) => Ok(path),
_ => Err(format_err!("Failed to get driver path for {}", temperature_handler.name())),
}
}
/// Initiates a thermal shutdown.
///
/// Sends a message to the SystemShutdown node to initiate a system shutdown due to extreme
/// temperatures.
async fn initiate_thermal_shutdown(&self) -> Result<()> {
log_if_err!(
self.send_message(
&self.platform_metrics,
&Message::LogPlatformMetric(PlatformMetric::ThrottlingResultShutdown)
)
.await,
"Failed to send ThrottlingResultShutdown metric"
);
match self
.send_message(
&self.system_shutdown_node,
&Message::SystemShutdown(ShutdownRequest::Reboot(RebootReason::HighTemperature)),
)
.await
{
Ok(_) => Ok(()),
Err(e) => Err(e.into()),
}
}
}
/// Describes the configuration for polling a single temperature sensor.
struct TemperatureInputConfig {
/// TemperatureHandler node to be polled for temperature readings.
temperature_handler_node: Rc<dyn Node>,
/// Temperature at which thermal load will begin to increase. A temperature value of
/// `onset_temperature` corresponds to a thermal load of 0. Beyond `onset_temperature`, thermal
/// load will increase linearly with temperature until reaching `reboot_temperature.
onset_temperature: Celsius,
/// Temperature at which this node will initiate a system reboot due to critical temperature. A
/// temperature value of `reboot_temperature` corresponds to a thermal load of 100.
reboot_temperature: Celsius,
/// Polling interval at which a new filtered temperature value will be read from the sensor.
poll_interval: Seconds,
/// Time constant to be used for filtering raw temperature readings. A value of 0 effectively
/// disables filtering.
filter_time_constant: Seconds,
}
/// Configuration and data source for a single temperature sensor.
struct TemperatureInput {
/// Temperature filter instance to provide filtered temperature inputs.
temperature_filter: TemperatureFilter,
/// Temperature at which thermal load will begin to increase. A temperature value of
/// `onset_temperature` corresponds to a thermal load of 0. Beyond `onset_temperature`, thermal
/// load will increase linearly with temperature until reaching `reboot_temperature.
onset_temperature: Celsius,
/// Temperature at which this node will initiate a system reboot due to critical temperature. A
/// temperature value of `reboot_temperature` corresponds to a thermal load of 100.
reboot_temperature: Celsius,
}
impl TemperatureInput {
fn new(config: TemperatureInputConfig) -> Self {
let temperature_filter =
TemperatureFilter::new(config.temperature_handler_node, config.filter_time_constant);
Self {
temperature_filter,
onset_temperature: config.onset_temperature,
reboot_temperature: config.reboot_temperature,
}
}
/// Gets the current thermal load value for this temperature input.
///
/// The function will first poll the temperature handler to retrieve the latest filtered
/// temperature value. The temperature is then converted to a thermal load value by also
/// considering the configured `onset_temperature` and `reboot_temperature` parameters.
async fn get_thermal_load(&self) -> Result<ThermalLoad> {
self.temperature_filter.get_temperature(get_current_timestamp()).await.map(|temperature| {
temperature_to_thermal_load(
temperature.filtered,
self.onset_temperature,
self.reboot_temperature,
)
})
}
}
/// Converts temperature to thermal load as a function of temperature, onset temperature, and reboot
/// temperature.
fn temperature_to_thermal_load(
temperature: Celsius,
onset_temperature: Celsius,
reboot_temperature: Celsius,
) -> ThermalLoad {
if temperature < onset_temperature {
ThermalLoad(0)
} else if temperature > reboot_temperature {
ThermalLoad(100)
} else {
ThermalLoad(
((temperature - onset_temperature).0 / (reboot_temperature - onset_temperature).0
* 100.0) as u32,
)
}
}
#[async_trait(?Send)]
impl Node for ThermalLoadDriver {
fn name(&self) -> String {
"ThermalLoadDriver".to_string()
}
}
struct TemperatureInputInspect {
thermal_load_property: inspect::UintProperty,
_root: inspect::Node,
}
impl TemperatureInputInspect {
fn new(parent: &inspect::Node, sensor_path: &str, config: &TemperatureInputConfig) -> Self {
let root = parent.create_child(sensor_path);
let thermal_load_property = root.create_uint("thermal_load", 0);
root.record_double("onset_temperature_c", config.onset_temperature.0);
root.record_double("reboot_temperature_c", config.reboot_temperature.0);
root.record_double("poll_interval_s", config.poll_interval.0);
root.record_double("filter_time_constant_s", config.filter_time_constant.0);
Self { thermal_load_property, _root: root }
}
fn log_thermal_load(&self, load: ThermalLoad) {
self.thermal_load_property.set(load.0.into());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::mock_node::{create_dummy_node, MessageMatcher, MockNode, MockNodeMaker};
use crate::{msg_eq, msg_ok_return};
use diagnostics_assertions::assert_data_tree;
/// Tests that each node config file has proper configuration for ThermalLoadDriver entries. The
/// test ensures that any TemperatureHandler nodes that are named inside the
/// temperature_input_configs array are also listed under the "dependencies" object. This is
/// important not only for tracking the true dependencies of a node, but also to be able to take
/// advantage of the node dependency tests in power_manager.rs (e.g.
/// test_each_node_config_file_dependency_ordering).
#[test]
pub fn test_config_files() -> Result<(), anyhow::Error> {
crate::common_utils::test_each_node_config_file(|config_file| {
let thermal_load_driver_nodes =
config_file.iter().filter(|n| n["type"] == "ThermalLoadDriver");
for node in thermal_load_driver_nodes {
let temperature_handler_node_deps = node["dependencies"].as_object().unwrap()
["temperature_handler_node_names"]
.as_array()
.unwrap()
.iter()
.map(|node_name| node_name.as_str().unwrap())
.collect::<Vec<_>>();
let temperature_config_node_refs = node["config"]["temperature_input_configs"]
.as_array()
.unwrap()
.iter()
.map(|temperature_config| {
temperature_config["temperature_handler_node_name"].as_str().unwrap()
})
.collect::<Vec<_>>();
if temperature_handler_node_deps != temperature_config_node_refs {
return Err(format_err!(
"TemperatureHandler nodes listed under \"dependencies\" must match
the TemperatureHandler nodes referenced in \"temperature_input_configs\""
));
}
}
Ok(())
})
}
/// Tests that well-formed node_config JSON can be used to create a new ThermalLoadDriverBuilder
/// instance.
#[fasync::run_singlethreaded(test)]
async fn test_new_from_json() {
let json_data = json::json!({
"type": "ThermalLoadDriver",
"name": "thermal_load_driver",
"config": {
"temperature_input_configs": [
{
"temperature_handler_node_name": "temp_sensor_1",
"onset_temperature_c": 50.0,
"reboot_temperature_c": 80.0,
"poll_interval_s": 1.0,
"filter_time_constant_s": 5.0
},
{
"temperature_handler_node_name": "temp_sensor_2",
"onset_temperature_c": 60.0,
"reboot_temperature_c": 90.0,
"poll_interval_s": 1.0,
"filter_time_constant_s": 10.0
}
]
},
"dependencies": {
"platform_metrics_node": "platform_metrics",
"system_shutdown_node": "shutdown",
"thermal_load_notify_nodes": [
"thermal_load_notify"
],
"temperature_handler_node_names": [
"temp_sensor_1",
"temp_sensor_2"
]
}
});
let mut nodes: HashMap<String, Rc<dyn Node>> = HashMap::new();
nodes.insert("temp_sensor_1".to_string(), create_dummy_node());
nodes.insert("temp_sensor_2".to_string(), create_dummy_node());
nodes.insert("shutdown".to_string(), create_dummy_node());
nodes.insert("thermal_load_notify".to_string(), create_dummy_node());
nodes.insert("platform_metrics".to_string(), create_dummy_node());
let structured_config = power_manager_config_lib::Config {
enable_debug_service: false,
node_config_path: String::new(),
disable_temperature_filter: false,
};
let _ = ThermalLoadDriverBuilder::new_from_json(json_data, &nodes, &structured_config);
}
// Convenience function to add an UpdateThermalLoad message to a mock node's expected messages.
fn expect_thermal_load(node: &Rc<MockNode>, thermal_load: u32, sensor_path: &str) {
node.add_msg_response_pair((
msg_eq!(UpdateThermalLoad(ThermalLoad(thermal_load), sensor_path.to_string())),
msg_ok_return!(UpdateThermalLoad),
));
}
// Convenience struct for running the ThermalLoadDriver's thermal input tasks.
struct NodeTestRunner {
mock_temperature_nodes: Vec<Rc<MockNode>>,
polling_tasks: Vec<fasync::Task<()>>,
executor: fasync::TestExecutor,
}
impl NodeTestRunner {
fn new(
executor: fasync::TestExecutor,
thermal_load_driver: Rc<ThermalLoadDriver>,
mock_temperature_nodes: Vec<Rc<MockNode>>,
) -> Self {
executor.set_fake_time(fasync::Time::from_nanos(0));
let mut this = Self {
executor,
polling_tasks: thermal_load_driver.polling_tasks.take(),
mock_temperature_nodes,
};
// Initialize the polling tasks (required so each polling task has a chance to set up
// their timers)
this.run_polling_tasks();
this
}
// Runs all polling tasks (one for each temperature input) for one iteration (stopping at
// their next timer).
fn run_polling_tasks(&mut self) {
// Run each polling task until stalled. The polling task will stall when it has
// completed one iteration and is waiting on the next iteration timer.
for task in self.polling_tasks.iter_mut() {
let _ = self.executor.run_until_stalled(task);
}
}
// Wakes each polling task's timer then runs the task until hitting the next timer.
fn wake_and_run_polling_tasks(&mut self) {
// Wake all pending timers and increment fake time accordingly
for _ in 0..self.polling_tasks.len() {
let wake_time = self.executor.wake_next_timer().unwrap();
if wake_time > self.executor.now() {
self.executor.set_fake_time(wake_time);
}
}
// There should not be any more pending timers
assert_eq!(self.executor.wake_next_timer(), None);
self.run_polling_tasks();
}
// Sets a fake temperature value for each temperature input, then runs each polling task for
// one iteration.
fn iterate_with_temperature_inputs(&mut self, temperature_inputs: &[f64]) {
for (i, temperature) in temperature_inputs.iter().enumerate() {
self.mock_temperature_nodes[i].add_msg_response_pair((
msg_eq!(ReadTemperature),
msg_ok_return!(ReadTemperature(Celsius(*temperature))),
));
}
self.wake_and_run_polling_tasks();
}
}
/// Tests the ThermalLoadDriver's ability to monitor multiple temperature input sources,
/// calculate their thermal loads independently, and send out thermal load change messages
/// correctly.
#[test]
fn test_multiple_temperature_inputs() {
let exec = fasync::TestExecutor::new_with_fake_time();
// Create mock nodes
let mut mock_maker = MockNodeMaker::new();
let system_shutdown_node = create_dummy_node();
let platform_metrics_node = create_dummy_node();
let mock_thermal_load_receiver = mock_maker.make("mock_thermal_load_receiver", vec![]);
let mock_temperature_handler_1 = mock_maker.make("temperature_handler_1", vec![]);
let mock_temperature_handler_2 = mock_maker.make("temperature_handler_2", vec![]);
// The ThermalLoadDriver asks for the driver path of all TemperatureHandler nodes during
// initialization
mock_temperature_handler_1.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path_1".to_string())),
));
mock_temperature_handler_2.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path_2".to_string())),
));
// Create the ThermalLoadDriver node. The node has two temperature input sources that are
// configured with differing onset/reboot temperatures, which adds a degree of testing for
// the ThermalLoadDriver's ability to track thermal load for each source separately.
let node = ThermalLoadDriverBuilder {
temperature_input_configs: vec![
TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler_1.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(50.0),
poll_interval: Seconds(1.0),
filter_time_constant: Seconds(1.0),
},
TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler_2.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(100.0),
poll_interval: Seconds(1.0),
filter_time_constant: Seconds(1.0),
},
],
system_shutdown_node,
platform_metrics_node,
thermal_load_notify_nodes: vec![mock_thermal_load_receiver.clone()],
inspect_root: None,
}
.build()
.unwrap();
// Create the test runner
let mut node_runner = NodeTestRunner::new(
exec,
node,
vec![mock_temperature_handler_1, mock_temperature_handler_2],
);
// Increase mock_1 temperature, expect a corresponding thermal load update
expect_thermal_load(&mock_thermal_load_receiver, 20, "fake_driver_path_1");
expect_thermal_load(&mock_thermal_load_receiver, 0, "fake_driver_path_2");
node_runner.iterate_with_temperature_inputs(&[10.0, 0.0]);
// Increase mock_2 temperature, expect a corresponding thermal load update
expect_thermal_load(&mock_thermal_load_receiver, 20, "fake_driver_path_1");
expect_thermal_load(&mock_thermal_load_receiver, 40, "fake_driver_path_2");
node_runner.iterate_with_temperature_inputs(&[10.0, 40.0]);
// Both temperatures remain constant, thermal load should still be sent
expect_thermal_load(&mock_thermal_load_receiver, 20, "fake_driver_path_1");
expect_thermal_load(&mock_thermal_load_receiver, 40, "fake_driver_path_2");
node_runner.iterate_with_temperature_inputs(&[10.0, 40.0]);
// Decrease temperature for both mocks, expect two corresponding thermal load updates
expect_thermal_load(&mock_thermal_load_receiver, 10, "fake_driver_path_1");
expect_thermal_load(&mock_thermal_load_receiver, 20, "fake_driver_path_2");
node_runner.iterate_with_temperature_inputs(&[5.0, 20.0]);
}
/// Tests that when any of the temperature handler input nodes exceed `reboot_temperature`, then
/// the ThermalLoadDriver node initiates a system reboot due to high temperature.
#[test]
fn test_trigger_shutdown() {
let exec = fasync::TestExecutor::new_with_fake_time();
// Create mock nodes
let mut mock_maker = MockNodeMaker::new();
let platform_metrics_node = create_dummy_node();
let mock_temperature_handler = mock_maker.make("temperature_handler", vec![]);
let mock_thermal_load_receiver = mock_maker.make("mock_thermal_load_receiver", vec![]);
let system_shutdown_node = mock_maker.make(
"mock_system_shutdown_node",
vec![(
msg_eq!(SystemShutdown(ShutdownRequest::Reboot(RebootReason::HighTemperature))),
msg_ok_return!(SystemShutdown),
)],
);
// The ThermalLoadDriver asks for the driver path of all TemperatureHandler nodes during
// initialization
mock_temperature_handler.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path".to_string())),
));
let node = ThermalLoadDriverBuilder {
temperature_input_configs: vec![TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(50.0),
poll_interval: Seconds(1.0),
filter_time_constant: Seconds(1.0),
}],
system_shutdown_node,
platform_metrics_node,
thermal_load_notify_nodes: vec![mock_thermal_load_receiver],
inspect_root: None,
}
.build()
.unwrap();
// Create the test runner
let mut node_runner = NodeTestRunner::new(exec, node, vec![mock_temperature_handler]);
// With a single iteration, this temperature will cause a system reboot
node_runner.iterate_with_temperature_inputs(&[50.0]);
// The system_shutdown_node mock verifies that the SystemShutdown message is sent by the
// ThermalLoadDriver
}
/// Tests that the expected Inspect properties are present.
#[test]
fn test_inspect_data() {
let exec = fasync::TestExecutor::new_with_fake_time();
let inspector = inspect::Inspector::default();
// Create mock nodes
let mut mock_maker = MockNodeMaker::new();
let platform_metrics_node = create_dummy_node();
let mock_temperature_handler_1 = mock_maker.make("temperature_handler_1", vec![]);
let mock_temperature_handler_2 = mock_maker.make("temperature_handler_2", vec![]);
let mock_thermal_load_receiver = mock_maker.make("mock_thermal_load_receiver", vec![]);
let system_shutdown_node = mock_maker.make("mock_system_shutdown_node", vec![]);
// The ThermalLoadDriver asks for the driver path of all TemperatureHandler nodes during
// initialization
mock_temperature_handler_1.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path_1".to_string())),
));
mock_temperature_handler_2.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path_2".to_string())),
));
let node = ThermalLoadDriverBuilder {
temperature_input_configs: vec![
TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler_1.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(50.0),
poll_interval: Seconds(1.99),
filter_time_constant: Seconds(10.0),
},
TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler_2.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(100.0),
poll_interval: Seconds(2.0),
filter_time_constant: Seconds(20.0),
},
],
system_shutdown_node,
platform_metrics_node,
thermal_load_notify_nodes: vec![mock_thermal_load_receiver.clone()],
inspect_root: Some(inspector.root()),
}
.build()
.unwrap();
// Create the test runner
let mut node_runner = NodeTestRunner::new(
exec,
node,
vec![mock_temperature_handler_1, mock_temperature_handler_2],
);
// Provide some fake temperature values that cause a thermal load change for both inputs
expect_thermal_load(&mock_thermal_load_receiver, 20, "fake_driver_path_1");
expect_thermal_load(&mock_thermal_load_receiver, 50, "fake_driver_path_2");
node_runner.iterate_with_temperature_inputs(&[10.0, 50.0]);
// Verify the expected thermal load values are present for both temperature inputs
assert_data_tree!(
inspector,
root: {
"ThermalLoadDriver": {
fake_driver_path_1: {
onset_temperature_c: 0.0,
reboot_temperature_c: 50.0,
poll_interval_s: 1.99,
filter_time_constant_s: 10.0,
thermal_load: 20u64
},
fake_driver_path_2: {
onset_temperature_c: 0.0,
reboot_temperature_c: 100.0,
poll_interval_s: 2.0,
filter_time_constant_s: 20.0,
thermal_load: 50u64
}
},
}
);
}
/// Tests for correct platform metrics sent to the PlatformMetrics node.
#[test]
fn test_platform_metrics() {
let exec = fasync::TestExecutor::new_with_fake_time();
// Create mock nodes
let mut mock_maker = MockNodeMaker::new();
let mock_platform_metrics = mock_maker.make("mock_platform_metrics", vec![]);
let mock_temperature_handler = mock_maker.make("temperature_handler", vec![]);
let mock_thermal_load_receiver = mock_maker.make("mock_thermal_load_receiver", vec![]);
let mock_system_shutdown_node = mock_maker.make("mock_system_shutdown_node", vec![]);
// The ThermalLoadDriver asks for the driver path of all TemperatureHandler nodes during
// initialization
mock_temperature_handler.add_msg_response_pair((
msg_eq!(GetDriverPath),
msg_ok_return!(GetDriverPath("fake_driver_path".to_string())),
));
let node = ThermalLoadDriverBuilder {
temperature_input_configs: vec![TemperatureInputConfig {
temperature_handler_node: mock_temperature_handler.clone(),
onset_temperature: Celsius(0.0),
reboot_temperature: Celsius(50.0),
poll_interval: Seconds(1.0),
filter_time_constant: Seconds(1.0),
}],
system_shutdown_node: mock_system_shutdown_node.clone(),
platform_metrics_node: mock_platform_metrics.clone(),
thermal_load_notify_nodes: vec![mock_thermal_load_receiver.clone()],
inspect_root: None,
}
.build()
.unwrap();
// Create the test runner
let mut node_runner = NodeTestRunner::new(exec, node, vec![mock_temperature_handler]);
// Verify if a sensor causes thermal shutdown then `ThrottlingResultShutdown` is sent
mock_system_shutdown_node.add_msg_response_pair((
msg_eq!(SystemShutdown(ShutdownRequest::Reboot(RebootReason::HighTemperature))),
msg_ok_return!(SystemShutdown),
));
mock_platform_metrics.add_msg_response_pair((
msg_eq!(LogPlatformMetric(PlatformMetric::ThrottlingResultShutdown)),
msg_ok_return!(LogPlatformMetric),
));
node_runner.iterate_with_temperature_inputs(&[50.0]);
}
}