| // 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(()) |
| }) |
| } |
| } |