use bevy_platform::time::Instant;
#[cfg(all(feature = "node_profiling", not(feature = "std")))]
use bevy_platform::prelude::Vec;
use crate::context::FirewheelBitFlags;
use crate::graph::CompiledSchedule;
#[cfg(feature = "node_profiling")]
use firewheel_core::node::NodeID;
pub(crate) fn profiler_channel(
node_capacity: usize,
#[cfg(feature = "node_profiling")] graph_out_node_id: NodeID,
) -> (ProfilerTx, ProfilerRx) {
let (buffer_tx, buffer_rx) =
triple_buffer::TripleBuffer::new(&ProfilingData::with_node_capacity(
#[cfg(feature = "node_profiling")]
node_capacity,
#[cfg(feature = "node_profiling")]
graph_out_node_id,
))
.split();
let now = Instant::now();
#[allow(unused_mut)]
let mut heap_data = ProfilerHeapData::new(node_capacity, false);
#[cfg(feature = "node_profiling")]
heap_data.nodes.push(NodeProfileData {
node_id: graph_out_node_id,
cpu_usage: 0.0,
});
(
ProfilerTx {
buffer_tx,
version_counter: 0,
total_cpu_seconds_recip: 0.0,
proc_start_instant: now,
overall_cpu_usage: 0.0,
bookkeeping_cpu_usage: 0.0,
bookkeeping_cpu_usage_sum: 0.0,
is_profiling_bookkeeping: false,
bookkeeping_start_instant: now,
heap_data,
#[cfg(feature = "node_profiling")]
is_profiling_nodes: false,
#[cfg(feature = "node_profiling")]
node_profile_start_instant: now,
#[cfg(feature = "node_profiling")]
node_schedule_index: 0,
},
ProfilerRx { buffer_rx },
)
}
pub(crate) struct ProfilerTx {
buffer_tx: triple_buffer::Input<ProfilingData>,
version_counter: u64,
total_cpu_seconds_recip: f64,
proc_start_instant: Instant,
overall_cpu_usage: f64,
bookkeeping_cpu_usage: f64,
bookkeeping_cpu_usage_sum: f64,
is_profiling_bookkeeping: bool,
bookkeeping_start_instant: Instant,
heap_data: ProfilerHeapData,
#[cfg(feature = "node_profiling")]
is_profiling_nodes: bool,
#[cfg(feature = "node_profiling")]
node_profile_start_instant: Instant,
#[cfg(feature = "node_profiling")]
node_schedule_index: usize,
}
pub(crate) struct ProfilerHeapData {
#[cfg(feature = "node_profiling")]
nodes: Vec<NodeProfileData>,
#[cfg(feature = "node_profiling")]
node_cpu_sums: Vec<f64>,
#[cfg(feature = "node_profiling")]
triple_buf_allocations: [Vec<NodeProfileData>; 3],
}
impl ProfilerHeapData {
pub fn new(node_capacity: usize, triple_buf_allocations: bool) -> Self {
#[cfg(not(feature = "node_profiling"))]
let _ = node_capacity;
#[cfg(not(feature = "node_profiling"))]
let _ = triple_buf_allocations;
Self {
#[cfg(feature = "node_profiling")]
nodes: Vec::with_capacity(node_capacity),
#[cfg(feature = "node_profiling")]
node_cpu_sums: Vec::with_capacity(node_capacity),
#[cfg(feature = "node_profiling")]
triple_buf_allocations: if triple_buf_allocations {
[
Vec::with_capacity(node_capacity),
Vec::with_capacity(node_capacity),
Vec::with_capacity(node_capacity),
]
} else {
[Vec::new(), Vec::new(), Vec::new()]
},
}
}
}
impl ProfilerTx {
pub fn new_schedule(
&mut self,
schedule: &CompiledSchedule,
new_heap_data: &mut Option<ProfilerHeapData>,
) {
#[cfg(not(feature = "node_profiling"))]
let _ = schedule;
if let Some(new_heap_data) = new_heap_data.as_mut() {
core::mem::swap(&mut self.heap_data, new_heap_data);
}
#[cfg(feature = "node_profiling")]
{
self.heap_data.nodes.clear();
self.heap_data.node_cpu_sums.clear();
let graph_in_node_id = schedule.graph_in_node_id();
self.heap_data.nodes.extend(
schedule
.iter_node_ids()
.filter(|node_id| *node_id != graph_in_node_id)
.map(|node_id| NodeProfileData {
node_id,
cpu_usage: 0.0,
}),
);
}
}
pub fn new_process_loop(
&mut self,
proc_start_instant: Instant,
total_cpu_seconds_recip: f64,
flags: &FirewheelBitFlags,
) {
self.proc_start_instant = proc_start_instant;
self.total_cpu_seconds_recip = total_cpu_seconds_recip;
self.bookkeeping_cpu_usage_sum = 0.0;
self.bookkeeping_start_instant = proc_start_instant;
let new_is_profiling_bookkeeping =
flags.contains(FirewheelBitFlags::PROFILE_ENGINE_BOOKKEEPING);
if new_is_profiling_bookkeeping && !self.is_profiling_bookkeeping {
self.bookkeeping_cpu_usage = 0.0;
}
self.is_profiling_bookkeeping = new_is_profiling_bookkeeping;
#[cfg(feature = "node_profiling")]
{
let new_is_profiling_nodes = flags.contains(FirewheelBitFlags::PROFILE_NODES);
if new_is_profiling_nodes && !self.is_profiling_nodes {
for node in self.heap_data.nodes.iter_mut() {
node.cpu_usage = 0.0;
}
}
self.is_profiling_nodes = new_is_profiling_nodes;
if self.is_profiling_nodes {
self.heap_data.node_cpu_sums.clear();
self.heap_data
.node_cpu_sums
.resize(self.heap_data.nodes.len(), 0.0);
}
}
}
pub fn begin_new_bookkeeping_part(&mut self) {
if self.is_profiling_bookkeeping
&& let Some(now) = crate::time::now()
{
self.bookkeeping_start_instant = now;
}
}
pub fn bookkeeping_part_completed(&mut self) {
if self.is_profiling_bookkeeping {
let cpu_usage = self.bookkeeping_start_instant.elapsed().as_secs_f64()
* self.total_cpu_seconds_recip;
self.bookkeeping_cpu_usage_sum += cpu_usage;
}
}
#[cfg(feature = "node_profiling")]
pub fn begin_node_profiling(&mut self) {
self.node_schedule_index = 0;
if self.is_profiling_nodes
&& let Some(now) = crate::time::now()
{
self.node_profile_start_instant = now;
}
}
#[cfg(feature = "node_profiling")]
pub fn node_completed(&mut self) {
if self.is_profiling_nodes
&& let Some(new_profile_instant) = crate::time::now()
{
let node_cpu_usage = new_profile_instant
.duration_since(self.node_profile_start_instant)
.as_secs_f64()
* self.total_cpu_seconds_recip;
self.heap_data.node_cpu_sums[self.node_schedule_index] += node_cpu_usage;
self.node_profile_start_instant = new_profile_instant;
self.node_schedule_index += 1;
}
}
pub fn process_loop_completed(&mut self) {
let Some(now) = crate::time::now() else {
return;
};
#[cfg(feature = "node_profiling")]
if self.is_profiling_nodes {
for (node, &sum) in self
.heap_data
.nodes
.iter_mut()
.zip(self.heap_data.node_cpu_sums.iter())
{
node.cpu_usage = node.cpu_usage.max(sum);
}
}
let overall_cpu_usage = now.duration_since(self.proc_start_instant).as_secs_f64()
* self.total_cpu_seconds_recip;
self.overall_cpu_usage = self.overall_cpu_usage.max(overall_cpu_usage);
if self.is_profiling_bookkeeping {
let cpu_usage = now
.duration_since(self.bookkeeping_start_instant)
.as_secs_f64()
* self.total_cpu_seconds_recip;
self.bookkeeping_cpu_usage_sum += cpu_usage;
self.bookkeeping_cpu_usage = self
.bookkeeping_cpu_usage
.max(self.bookkeeping_cpu_usage_sum);
}
if self.buffer_tx.consumed() || self.version_counter == 0 {
{
let data = self.buffer_tx.input_buffer_mut();
data.version = self.version_counter;
data.overall_cpu_usage = self.overall_cpu_usage;
data.engine_bookkeeping_cpu_usage = self
.is_profiling_bookkeeping
.then_some(self.bookkeeping_cpu_usage);
#[cfg(feature = "node_profiling")]
if self.is_profiling_nodes {
if data.nodes.capacity() < self.heap_data.nodes.len()
&& let Some(new_vec) = self
.heap_data
.triple_buf_allocations
.iter_mut()
.find(|v| v.capacity() >= self.heap_data.nodes.len())
{
core::mem::swap(&mut data.nodes, new_vec);
}
data.nodes.clear();
data.nodes.extend_from_slice(&self.heap_data.nodes);
} else {
data.nodes.clear();
}
}
self.buffer_tx.publish();
self.version_counter += 1;
self.overall_cpu_usage = 0.0;
self.bookkeeping_cpu_usage = 0.0;
#[cfg(feature = "node_profiling")]
if self.is_profiling_nodes {
for node in self.heap_data.nodes.iter_mut() {
node.cpu_usage = 0.0;
}
}
}
}
}
pub(crate) struct ProfilerRx {
buffer_rx: triple_buffer::Output<ProfilingData>,
}
impl ProfilerRx {
pub fn fetch_info(&mut self) -> &ProfilingData {
self.buffer_rx.read()
}
}
#[derive(Default, Debug, Clone)]
pub struct ProfilingData {
pub version: u64,
pub overall_cpu_usage: f64,
pub engine_bookkeeping_cpu_usage: Option<f64>,
#[cfg(feature = "node_profiling")]
pub nodes: Vec<NodeProfileData>,
}
impl ProfilingData {
fn with_node_capacity(
#[cfg(feature = "node_profiling")] node_capacity: usize,
#[cfg(feature = "node_profiling")] graph_out_id: NodeID,
) -> Self {
#[cfg(feature = "node_profiling")]
let mut nodes = Vec::with_capacity(node_capacity);
#[cfg(feature = "node_profiling")]
nodes.push(NodeProfileData {
node_id: graph_out_id,
cpu_usage: 0.0,
});
Self {
version: 0,
overall_cpu_usage: 0.0,
engine_bookkeeping_cpu_usage: None,
#[cfg(feature = "node_profiling")]
nodes,
}
}
}
#[cfg(feature = "node_profiling")]
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct NodeProfileData {
pub node_id: NodeID,
pub cpu_usage: f64,
}