/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use self::synchronized_heartbeat::{heartbeat_window_callback, lock_and_work}; use heartbeats_simple::HeartbeatPow as Heartbeat; use profile_traits::time::ProfilerCategory; use std::collections::HashMap; use std::env::var_os; use std::fs::File; use std::path::Path; /// Initialize heartbeats pub fn init(profile_heartbeats: bool) { lock_and_work(|hbs_opt| { if hbs_opt.is_none() { let mut hbs: Box> = Box::new(HashMap::new()); maybe_create_heartbeat( &mut hbs, ProfilerCategory::ApplicationHeartbeat, profile_heartbeats, ); *hbs_opt = Some(Box::into_raw(hbs)) } }); } /// Log regmaining buffer data and cleanup heartbeats pub fn cleanup() { let hbs_opt_box: Option>> = lock_and_work(|hbs_opt| { hbs_opt .take() .map(|hbs_ptr| unsafe { Box::from_raw(hbs_ptr) }) }); if let Some(mut hbs) = hbs_opt_box { for (_, v) in hbs.iter_mut() { // log any remaining heartbeat records before dropping log_heartbeat_records(v); } hbs.clear(); } } /// Check if a heartbeat exists for the given category pub fn is_heartbeat_enabled(category: &ProfilerCategory, profile_heartbeats: bool) -> bool { let is_enabled = lock_and_work(|hbs_opt| { hbs_opt.map_or(false, |hbs_ptr| unsafe { (*hbs_ptr).contains_key(category) }) }); is_enabled || is_create_heartbeat(category, profile_heartbeats) } /// Issue a heartbeat (if one exists) for the given category pub fn maybe_heartbeat( category: &ProfilerCategory, start_time: u64, end_time: u64, start_energy: u64, end_energy: u64, profile_heartbeats: bool, ) { lock_and_work(|hbs_opt| { if let Some(hbs_ptr) = *hbs_opt { unsafe { if !(*hbs_ptr).contains_key(category) { maybe_create_heartbeat(&mut (*hbs_ptr), category.clone(), profile_heartbeats); } if let Some(h) = (*hbs_ptr).get_mut(category) { (*h).heartbeat(0, 1, start_time, end_time, start_energy, end_energy); } } } }); } // TODO(cimes): Android doesn't really do environment variables. Need a better way to configure dynamically. fn is_create_heartbeat(category: &ProfilerCategory, profile_heartbeats: bool) -> bool { profile_heartbeats || var_os(format!("SERVO_HEARTBEAT_ENABLE_{:?}", category)).is_some() } fn open_heartbeat_log>(name: P) -> Option { match File::create(name) { Ok(f) => Some(f), Err(e) => { warn!("Failed to open heartbeat log: {}", e); None }, } } #[cfg(target_os = "android")] fn get_heartbeat_log(category: &ProfilerCategory) -> Option { open_heartbeat_log(format!("/sdcard/servo/heartbeat-{:?}.log", category)) } #[cfg(not(target_os = "android"))] fn get_heartbeat_log(category: &ProfilerCategory) -> Option { var_os(format!("SERVO_HEARTBEAT_LOG_{:?}", category)).and_then(|name| open_heartbeat_log(&name)) } fn get_heartbeat_window_size(category: &ProfilerCategory) -> usize { const WINDOW_SIZE_DEFAULT: usize = 1; match var_os(format!("SERVO_HEARTBEAT_WINDOW_{:?}", category)) { Some(w) => match w.into_string() { Ok(s) => s.parse::().unwrap_or(WINDOW_SIZE_DEFAULT), _ => WINDOW_SIZE_DEFAULT, }, None => WINDOW_SIZE_DEFAULT, } } /// Possibly create a heartbeat fn maybe_create_heartbeat( hbs: &mut HashMap, category: ProfilerCategory, profile_heartbeats: bool, ) { if is_create_heartbeat(&category, profile_heartbeats) { // get optional log file let logfile: Option = get_heartbeat_log(&category); // window size let window_size: usize = get_heartbeat_window_size(&category); // create the heartbeat match Heartbeat::new(window_size, Some(heartbeat_window_callback), logfile) { Ok(hb) => { debug!("Created heartbeat for {:?}", category); hbs.insert(category, hb); }, Err(e) => warn!("Failed to create heartbeat for {:?}: {}", category, e), } }; } /// Log heartbeat records up to the buffer index fn log_heartbeat_records(hb: &mut Heartbeat) { match hb.log_to_buffer_index() { Ok(_) => (), Err(e) => warn!("Failed to write heartbeat log: {}", e), } } mod synchronized_heartbeat { use super::log_heartbeat_records; use heartbeats_simple::HeartbeatPow as Heartbeat; use heartbeats_simple::HeartbeatPowContext as HeartbeatContext; use profile_traits::time::ProfilerCategory; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; static mut HBS: Option<*mut HashMap> = None; // unfortunately can't encompass the actual hashmap in a Mutex (Heartbeat isn't Send/Sync), so we'll use a spinlock static HBS_SPINLOCK: AtomicBool = AtomicBool::new(false); pub fn lock_and_work(work: F) -> R where F: FnOnce(&mut Option<*mut HashMap>) -> R, { while HBS_SPINLOCK.compare_and_swap(false, true, Ordering::SeqCst) {} let result = unsafe { work(&mut HBS) }; HBS_SPINLOCK.store(false, Ordering::SeqCst); result } /// Callback function used to log the window buffer. /// When this is called from native C, the heartbeat is safely locked internally and the global lock is held. /// If calling from this file, you must already hold the global lock! pub extern "C" fn heartbeat_window_callback(hb: *const HeartbeatContext) { unsafe { if let Some(hbs_ptr) = HBS { for (_, v) in (*hbs_ptr).iter_mut() { if &v.hb as *const HeartbeatContext == hb { log_heartbeat_records(v); } } } } } }