use std::collections::HashMap;
use crate::{
config::UiColors,
metric_key::{ClusterId, MetricKey},
metrics::Metrics,
modules::soc::SocInfo,
signal,
};
pub(crate) type History = HashMap<MetricKey, signal::Signal<f32>>;
static DEFAULT_SIGNAL: std::sync::LazyLock<signal::Signal<f32>> =
std::sync::LazyLock::new(|| signal::Signal::with_capacity(0, 0.0));
pub(crate) trait HistoryExt {
fn get_or_default(&self, key: &MetricKey) -> &signal::Signal<f32>;
}
impl HistoryExt for History {
fn get_or_default(&self, key: &MetricKey) -> &signal::Signal<f32> {
self.get(key).unwrap_or(&DEFAULT_SIGNAL)
}
}
pub(crate) struct TabsState<'a> {
pub(crate) titles: Vec<&'a str>,
pub(crate) index: usize,
}
impl<'a> TabsState<'a> {
fn new(titles: Vec<&'a str>) -> TabsState<'a> {
TabsState { titles, index: 0 }
}
fn next(&mut self) {
self.index = (self.index + 1) % self.titles.len();
}
fn previous(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.titles.len() - 1;
}
}
}
pub(crate) struct AppColors {
raw_colors: UiColors,
}
impl AppColors {
fn color(code: u8) -> ratatui::style::Color {
ratatui::style::Color::Indexed(code)
}
pub(crate) fn accent(&self) -> ratatui::style::Color {
Self::color(self.raw_colors.accent)
}
pub(crate) fn gauge_fg(&self) -> ratatui::style::Color {
Self::color(self.raw_colors.gauge_fg)
}
pub(crate) fn gauge_bg(&self) -> ratatui::style::Color {
Self::color(self.raw_colors.gauge_bg)
}
pub(crate) fn history_fg(&self) -> ratatui::style::Color {
Self::color(self.raw_colors.history_fg)
}
pub(crate) fn history_bg(&self) -> ratatui::style::Color {
Self::color(self.raw_colors.history_bg)
}
}
pub(crate) struct App<'a> {
pub(crate) should_quit: bool,
pub(crate) tabs: TabsState<'a>,
pub(crate) colors: AppColors,
pub(crate) last_update: std::time::Instant,
pub(crate) metrics: Option<Metrics>,
pub(crate) soc_info: SocInfo,
pub(crate) history: History,
pub(crate) history_size: usize,
}
impl<'a> App<'a> {
pub fn new(soc_info: SocInfo, colors: UiColors, history_size: usize) -> Self {
Self {
should_quit: false,
tabs: TabsState::new(vec!["Overview", "CPU", "GPU", "Memory", "SoC"]),
colors: AppColors { raw_colors: colors },
last_update: std::time::Instant::now(),
metrics: None,
soc_info,
history: HashMap::new(),
history_size,
}
}
pub fn on_key(&mut self, c: char) {
match c {
'q' => {
self.should_quit = true;
}
'x' => {
self.should_quit = true;
}
_ => {}
}
}
pub fn on_ctrl(&mut self, c: char) {
if c == 'c' {
self.should_quit = true;
}
}
pub fn on_left(&mut self) {
self.tabs.previous();
}
pub fn on_right(&mut self) {
self.tabs.next();
}
pub(crate) fn on_metrics(&mut self, metrics: Metrics) {
self.last_update = std::time::Instant::now();
self.update_history(&metrics);
self.metrics = Some(metrics);
}
fn update_history(&mut self, metrics: &Metrics) {
for (idx, e_cluster) in metrics.e_clusters.iter().enumerate() {
let key = MetricKey::ClusterActivePercent(ClusterId::efficiency(idx as u8));
self.history
.entry(key)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * e_cluster.active_ratio());
for cpu in &e_cluster.cpus {
self.history
.entry(MetricKey::CpuActivePercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.active_ratio as f32);
self.history
.entry(MetricKey::CpuFreqPercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.freq_ratio() as f32);
}
}
for (idx, p_cluster) in metrics.p_clusters.iter().enumerate() {
let key = MetricKey::ClusterActivePercent(ClusterId::performance(idx as u8));
self.history
.entry(key)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * p_cluster.active_ratio());
for cpu in &p_cluster.cpus {
self.history
.entry(MetricKey::CpuActivePercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.active_ratio as f32);
self.history
.entry(MetricKey::CpuFreqPercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.freq_ratio() as f32);
}
}
for (idx, s_cluster) in metrics.s_clusters.iter().enumerate() {
let key = MetricKey::ClusterActivePercent(ClusterId::super_core(idx as u8));
self.history
.entry(key)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * s_cluster.active_ratio());
for cpu in &s_cluster.cpus {
self.history
.entry(MetricKey::CpuActivePercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.active_ratio as f32);
self.history
.entry(MetricKey::CpuFreqPercent(cpu.id))
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * cpu.freq_ratio() as f32);
}
}
self.history
.entry(MetricKey::GpuActivePercent)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * metrics.gpu.active_ratio as f32);
self.history
.entry(MetricKey::GpuFreqPercent)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * metrics.gpu.freq_ratio() as f32);
self.history
.entry(MetricKey::AneActivePercent)
.or_insert(signal::Signal::with_capacity(
self.history_size,
100.0,
))
.push(100.0 * metrics.consumption.ane_w / self.soc_info.max_ane_w as f32);
self.history
.entry(MetricKey::CpuPowerW)
.or_insert(signal::Signal::with_capacity(
self.history_size,
self.soc_info.max_cpu_w as f32,
))
.push(metrics.consumption.cpu_w);
self.history
.entry(MetricKey::GpuPowerW)
.or_insert(signal::Signal::with_capacity(
self.history_size,
self.soc_info.max_gpu_w as f32,
))
.push(metrics.consumption.gpu_w);
self.history
.entry(MetricKey::AnePowerW)
.or_insert(signal::Signal::with_capacity(
self.history_size,
self.soc_info.max_ane_w as f32,
))
.push(metrics.consumption.ane_w);
self.history
.entry(MetricKey::PackagePowerW)
.or_insert(signal::Signal::with_capacity(
self.history_size,
self.soc_info.max_package_w as f32,
))
.push(metrics.consumption.package_w);
self.history
.entry(MetricKey::RamUsageBytes)
.or_insert(signal::Signal::with_capacity(
self.history_size,
metrics.memory.ram_total as f32,
))
.push(metrics.memory.ram_used as f32);
self.history
.entry(MetricKey::SwapUsageBytes)
.or_insert(signal::Signal::with_capacity(
self.history_size,
metrics.memory.swap_total as f32,
))
.push(metrics.memory.swap_used as f32);
}
}