blob: 0d6f8e1a4b626e7509610f3d956becb6d9cfa101 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::node::Node;
use anyhow::{Context, Error};
use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
use fuchsia_inspect::component;
use futures::{
future::LocalBoxFuture,
stream::{FuturesUnordered, StreamExt},
};
use serde_json as json;
use std::collections::HashMap;
use std::rc::Rc;
// nodes
use crate::{
activity_handler, cpu_control_handler, cpu_device_handler, cpu_manager, cpu_stats_handler,
crash_report_handler, dev_control_handler, driver_manager_handler, input_settings_handler,
lid_shutdown, platform_metrics, shutdown_watcher, syscall_handler, system_power_mode_handler,
system_profile_handler, system_shutdown_handler, temperature_handler, thermal_load_driver,
thermal_policy, thermal_shutdown, thermal_state_handler,
};
/// Path to the node config JSON file.
const NODE_CONFIG_PATH: &'static str = "/pkg/config/power_manager/node_config.json";
pub struct PowerManager {
nodes: HashMap<String, Rc<dyn Node>>,
}
impl PowerManager {
pub fn new() -> Self {
Self { nodes: HashMap::new() }
}
/// Perform the node initialization and begin running the PowerManager.
pub async fn run(&mut self) -> Result<(), Error> {
// Create a new ServiceFs to handle incoming service requests for the various services that
// the PowerManager hosts.
let mut fs = ServiceFs::new_local();
// Allow our services to be discovered.
fs.take_and_serve_directory_handle()?;
// Required call to serve the inspect tree
let inspector = component::inspector();
inspect_runtime::serve(inspector, &mut fs)?;
// Create the nodes according to the config file
let node_futures = FuturesUnordered::new();
self.create_nodes_from_config(&mut fs, &node_futures).await?;
// TODO (https://fxbug.dev/98138) Remove this is a load-bearing print
// used in the OOM test E2E test when this bug is resolved.
fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_boot::WriteOnlyLogMarker>()?
.get()
.await?
.write("power-manager: service initialization complete".as_bytes())?;
// Run the ServiceFs (handles incoming request streams) and node futures. This future never
// completes.
futures::stream::select(fs, node_futures).collect::<()>().await;
Ok(())
}
/// Create the nodes by reading and parsing the node config JSON file.
async fn create_nodes_from_config<'a, 'b, 'c>(
&mut self,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
node_futures: &FuturesUnordered<LocalBoxFuture<'c, ()>>,
) -> Result<(), Error> {
let contents = std::fs::read_to_string(NODE_CONFIG_PATH)?;
let json_data: json::Value = serde_json5::from_str(&contents)
.context(format!("Failed to parse file {}", NODE_CONFIG_PATH))?;
self.create_nodes(json_data, service_fs, node_futures).await
}
/// Creates the nodes using the specified JSON object, adding them to the `nodes` HashMap.
async fn create_nodes<'a, 'b, 'c>(
&mut self,
json_data: json::Value,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
node_futures: &FuturesUnordered<LocalBoxFuture<'c, ()>>,
) -> Result<(), Error> {
// Iterate through each object in the top-level array, which represents configuration for a
// single node
for node_config in json_data.as_array().unwrap().iter() {
let node = self
.create_node(node_config.clone(), service_fs, node_futures)
.await
.with_context(|| format!("Failed creating node {}", node_config["name"]))?;
self.nodes.insert(node_config["name"].as_str().unwrap().to_string(), node);
}
Ok(())
}
/// Uses the supplied `json_data` to construct a single node, where `json_data` is the JSON
/// object corresponding to a single node configuration.
async fn create_node<'a, 'b, 'c>(
&mut self,
json_data: json::Value,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
node_futures: &FuturesUnordered<LocalBoxFuture<'c, ()>>,
) -> Result<Rc<dyn Node>, Error> {
Ok(match json_data["type"].as_str().unwrap() {
"ActivityHandler" => {
activity_handler::ActivityHandlerBuilder::new_from_json(json_data, &self.nodes)
.build(node_futures)?
}
"CrashReportHandler" => {
crash_report_handler::CrashReportHandlerBuilder::new().build()?
}
"CpuControlHandler" => {
cpu_control_handler::CpuControlHandlerBuilder::new_from_json(json_data, &self.nodes)
.build()
.await?
}
"CpuDeviceHandler" => {
cpu_device_handler::CpuDeviceHandlerBuilder::new_from_json(json_data, &self.nodes)
.build()
.await?
}
"CpuManager" => {
cpu_manager::CpuManagerBuilder::new_from_json(json_data, &self.nodes)
.build()
.await?
}
"CpuStatsHandler" => {
cpu_stats_handler::CpuStatsHandlerBuilder::new_from_json(json_data, &self.nodes)
.build()
.await?
}
"DeviceControlHandler" => {
dev_control_handler::DeviceControlHandlerBuilder::new_from_json(
json_data,
&self.nodes,
)
.build()
.await?
}
"DriverManagerHandler" => {
driver_manager_handler::DriverManagerHandlerBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()
.await?
}
"InputSettingsHandler" => {
input_settings_handler::InputSettingsHandlerBuilder::new_from_json(
json_data,
&self.nodes,
)
.build(node_futures)?
}
"LidShutdown" => {
lid_shutdown::LidShutdownBuilder::new_from_json(json_data, &self.nodes)
.build(node_futures)
.await?
}
"PlatformMetrics" => {
platform_metrics::PlatformMetricsBuilder::new_from_json(json_data, &self.nodes)
.build(node_futures)?
}
"SyscallHandler" => syscall_handler::SyscallHandlerBuilder::new().build().await?,
"SystemPowerModeHandler" => {
system_power_mode_handler::SystemPowerModeHandlerBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()?
}
"SystemProfileHandler" => {
system_profile_handler::SystemProfileHandlerBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()?
}
"SystemShutdownHandler" => {
system_shutdown_handler::SystemShutdownHandlerBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()?
}
"ShutdownWatcher" => shutdown_watcher::ShutdownWatcherBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()?,
"TemperatureHandler" => {
temperature_handler::TemperatureHandlerBuilder::new_from_json(
json_data,
&self.nodes,
)
.build()
.await?
}
"ThermalLoadDriver" => {
thermal_load_driver::ThermalLoadDriverBuilder::new_from_json(json_data, &self.nodes)
.build()?
}
"ThermalPolicy" => {
thermal_policy::ThermalPolicyBuilder::new_from_json(json_data, &self.nodes)
.build(node_futures)
.await?
}
"ThermalShutdown" => {
thermal_shutdown::ThermalShutdownBuilder::new_from_json(json_data, &self.nodes)
.build(node_futures)?
}
"ThermalStateHandler" => {
thermal_state_handler::ThermalStateHandlerBuilder::new_from_json(
json_data,
&self.nodes,
service_fs,
)
.build()?
}
unknown => panic!("Unknown node type: {}", unknown),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::format_err;
use fuchsia_async as fasync;
use std::collections::HashSet;
/// Tests that well-formed configuration JSON does not cause an unexpected panic in the
/// `create_nodes` function. With this test JSON, we expect a panic with the message stated
/// below indicating a node with the given type doesn't exist. By this point the JSON parsing
/// will have already been validated.
#[fasync::run_singlethreaded(test)]
#[should_panic(expected = "Unknown node type: test_type")]
async fn test_create_nodes() {
let json_data = json::json!([
{
"type": "test_type",
"name": "test_name"
},
]);
let mut power_manager = PowerManager::new();
let node_futures = FuturesUnordered::new();
power_manager
.create_nodes(json_data, &mut ServiceFs::new_local(), &node_futures)
.await
.unwrap();
}
/// Tests that all nodes in a given config file have a unique name.
#[test]
fn test_config_file_unique_names() -> Result<(), anyhow::Error> {
crate::utils::test_each_node_config_file(|config_file| {
let mut set = HashSet::new();
for node in config_file {
let node_name = node["name"].as_str().unwrap().to_string();
if set.contains(&node_name) {
return Err(format_err!("Node with name {} already specified", node_name));
}
set.insert(node_name);
}
Ok(())
})
}
/// Tests each node config file for correct node dependency ordering. The test expects a node's
/// dependencies to be listed under a "dependencies" object as an array or nested object.
///
/// For each node (`dependent_node`) in the config file, the test ensures that any node
/// specified within that node config's "dependencies" object (`required_node_name`) occurs in
/// the node config file at a position before the dependent node.
#[test]
fn test_each_node_config_file_dependency_ordering() -> Result<(), anyhow::Error> {
fn to_string(v: &serde_json::Value) -> String {
v.as_str().unwrap().to_string()
}
// Flattens the provided JSON value to extract all child strings. This is used to extract
// node dependency names from a node config's "dependencies" object even for nodes with a
// more complex format.
fn flatten_node_names(obj: &serde_json::Value) -> Vec<String> {
use serde_json::Value;
match obj {
Value::String(s) => vec![s.to_string()],
Value::Array(arr) => arr.iter().map(|v| flatten_node_names(v)).flatten().collect(),
Value::Object(obj) => {
obj.values().map(|v| flatten_node_names(v)).flatten().collect()
}
e => panic!("Invalid JSON type in dependency object: {:?}", e),
}
}
crate::utils::test_each_node_config_file(|config_file| {
for (dependent_idx, dependent_node) in config_file.iter().enumerate() {
if let Some(dependencies_obj) = dependent_node.get("dependencies") {
for required_node_name in flatten_node_names(dependencies_obj) {
let dependent_node_name = to_string(&dependent_node["name"]);
let required_node_index = config_file
.iter()
.position(|n| to_string(&n["name"]) == required_node_name);
match required_node_index {
Some(found_at_index) if found_at_index > dependent_idx => {
return Err(anyhow::format_err!(
"Dependency {} must be specified before node {}",
required_node_name,
dependent_node_name
));
}
Some(found_at_index) if found_at_index == dependent_idx => {
return Err(anyhow::format_err!(
"Invalid to specify self as dependency for node {}",
dependent_node_name
));
}
None => {
return Err(anyhow::format_err!(
"Missing dependency {} for node {}",
required_node_name,
dependent_node_name
));
}
_ => {}
}
}
}
}
Ok(())
})
}
}