other: skip the initial sleep on data collection initialization (#1779)

* deps: bump sysinfo

* remove sleep on startup

* missing collection set

* some logic around updating the battery list to match how it is now

* more refactoring

* oops

* forgot to initialize battery manager

* fix list updating logic + battery manager logic

* comment

* initialize should refresh list to true

* ah

* this works a bit nicer
This commit is contained in:
Clement Tsang 2025-08-13 02:57:45 -04:00 committed by GitHub
parent 868667add8
commit 0f212183fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 109 additions and 76 deletions

4
Cargo.lock generated
View File

@ -1510,9 +1510,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.36.1" version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
dependencies = [ dependencies = [
"libc", "libc",
"memchr", "memchr",

View File

@ -96,7 +96,7 @@ nvml-wrapper = { version = "0.11.0", optional = true, features = [
regex = "1.11.1" regex = "1.11.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
starship-battery = { version = "0.10.2", optional = true } starship-battery = { version = "0.10.2", optional = true }
sysinfo = "=0.36.1" sysinfo = "=0.37.0"
timeless = "0.0.14-alpha" timeless = "0.0.14-alpha"
toml_edit = { version = "0.22.27", features = ["serde"] } toml_edit = { version = "0.22.27", features = ["serde"] }
tui = { version = "0.29.0", package = "ratatui", features = [ tui = { version = "0.29.0", package = "ratatui", features = [

View File

@ -150,15 +150,22 @@ impl Default for SysinfoSource {
pub struct DataCollector { pub struct DataCollector {
pub data: Data, pub data: Data,
sys: SysinfoSource, sys: SysinfoSource,
use_current_cpu_total: bool,
unnormalized_cpu: bool,
last_collection_time: Instant, last_collection_time: Instant,
total_rx: u64,
total_tx: u64,
show_average_cpu: bool,
widgets_to_harvest: UsedWidgets, widgets_to_harvest: UsedWidgets,
filters: DataFilters, filters: DataFilters,
total_rx: u64,
total_tx: u64,
unnormalized_cpu: bool,
use_current_cpu_total: bool,
show_average_cpu: bool,
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
last_list_collection_time: Instant,
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
should_refresh_list: bool,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pid_mapping: HashMap<Pid, processes::PrevProcDetails>, pid_mapping: HashMap<Pid, processes::PrevProcDetails>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -180,11 +187,13 @@ pub struct DataCollector {
gpus_total_mem: Option<u64>, gpus_total_mem: Option<u64>,
} }
const LIST_REFRESH_TIME: Duration = Duration::from_secs(60);
impl DataCollector { impl DataCollector {
pub fn new(filters: DataFilters) -> Self { pub fn new(filters: DataFilters) -> Self {
// Initialize it to the past to force it to load on initialization. // Initialize it to the past to force it to load on initialization.
let now = Instant::now(); let now = Instant::now();
let last_collection_time = now.checked_sub(Duration::from_secs(600)).unwrap_or(now); let last_collection_time = now.checked_sub(LIST_REFRESH_TIME * 10).unwrap_or(now);
DataCollector { DataCollector {
data: Data::default(), data: Data::default(),
@ -213,32 +222,32 @@ impl DataCollector {
gpu_pids: None, gpu_pids: None,
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
gpus_total_mem: None, gpus_total_mem: None,
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
last_list_collection_time: last_collection_time,
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
should_refresh_list: true,
} }
} }
pub fn init(&mut self) { /// Update the check for updating things like lists of batteries, etc.
#[cfg(feature = "battery")] /// This is useful for things that we don't want to update all the time.
///
/// Note this should be set back to false if `self.last_list_collection_time` is updated.
#[inline]
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
fn update_refresh_list_check(&mut self) {
if self
.data
.collection_time
.duration_since(self.last_list_collection_time)
> LIST_REFRESH_TIME
{ {
if self.widgets_to_harvest.use_battery { self.should_refresh_list = true;
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
let battery_list: Vec<Battery> = batteries.filter_map(Result::ok).collect();
if !battery_list.is_empty() {
self.battery_list = Some(battery_list);
self.battery_manager = Some(battery_manager);
}
}
}
}
} }
self.update_data(); if self.should_refresh_list {
self.last_list_collection_time = self.data.collection_time;
// Sleep a few seconds to avoid potentially weird data. }
const SLEEP: Duration = get_sleep_duration();
std::thread::sleep(SLEEP);
self.data.cleanup();
} }
pub fn set_collection(&mut self, used_widgets: UsedWidgets) { pub fn set_collection(&mut self, used_widgets: UsedWidgets) {
@ -286,9 +295,6 @@ impl DataCollector {
// - Temperatures and temperature components list. // - Temperatures and temperature components list.
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
const LIST_REFRESH_TIME: Duration = Duration::from_secs(60);
let refresh_start = Instant::now();
if self.widgets_to_harvest.use_proc { if self.widgets_to_harvest.use_proc {
self.sys.system.refresh_processes_specifics( self.sys.system.refresh_processes_specifics(
sysinfo::ProcessesToUpdate::All, sysinfo::ProcessesToUpdate::All,
@ -301,13 +307,13 @@ impl DataCollector {
// For Windows, sysinfo also handles the users list. // For Windows, sysinfo also handles the users list.
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if refresh_start.duration_since(self.last_collection_time) > LIST_REFRESH_TIME { if self.should_refresh_list {
self.sys.users.refresh(); self.sys.users.refresh();
} }
} }
if self.widgets_to_harvest.use_temp { if self.widgets_to_harvest.use_temp {
if refresh_start.duration_since(self.last_collection_time) > LIST_REFRESH_TIME { if self.should_refresh_list {
self.sys.temps.refresh(true); self.sys.temps.refresh(true);
} }
@ -318,7 +324,7 @@ impl DataCollector {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if self.widgets_to_harvest.use_disk { if self.widgets_to_harvest.use_disk {
if refresh_start.duration_since(self.last_collection_time) > LIST_REFRESH_TIME { if self.should_refresh_list {
self.sys.disks.refresh(true); self.sys.disks.refresh(true);
} }
@ -330,10 +336,15 @@ impl DataCollector {
} }
pub fn update_data(&mut self) { pub fn update_data(&mut self) {
self.refresh_sysinfo_data();
self.data.collection_time = Instant::now(); self.data.collection_time = Instant::now();
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
{
self.update_refresh_list_check();
}
self.refresh_sysinfo_data();
self.update_cpu_usage(); self.update_cpu_usage();
self.update_memory_usage(); self.update_memory_usage();
self.update_temps(); self.update_temps();
@ -342,16 +353,23 @@ impl DataCollector {
self.update_batteries(); self.update_batteries();
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
self.update_gpus(); // update_gpus before procs for gpu_pids but after temps for appending self.update_gpus();
self.update_processes(); self.update_processes();
self.update_network_usage(); self.update_network_usage();
self.update_disks(); self.update_disks();
#[cfg(any(not(target_os = "linux"), feature = "battery"))]
{
self.should_refresh_list = false;
}
// Update times for future reference. // Update times for future reference.
self.last_collection_time = self.data.collection_time; self.last_collection_time = self.data.collection_time;
} }
/// Gets GPU data. Note this will usually append to other previously
/// collected data fields at the moment.
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
#[inline] #[inline]
fn update_gpus(&mut self) { fn update_gpus(&mut self) {
@ -462,15 +480,13 @@ impl DataCollector {
#[inline] #[inline]
fn update_network_usage(&mut self) { fn update_network_usage(&mut self) {
let current_instant = self.data.collection_time;
if self.widgets_to_harvest.use_net { if self.widgets_to_harvest.use_net {
let net_data = network::get_network_data( let net_data = network::get_network_data(
&self.sys.network, &self.sys.network,
self.last_collection_time, self.last_collection_time,
&mut self.total_rx, &mut self.total_rx,
&mut self.total_tx, &mut self.total_tx,
current_instant, self.data.collection_time,
&self.filters.net_filter, &self.filters.net_filter,
); );
@ -480,15 +496,59 @@ impl DataCollector {
} }
} }
/// Update battery information.
///
/// If the battery manager is not initialized, it will attempt to initialize it if at least one battery is found.
///
/// This function also refreshes the list of batteries if `self.should_refresh_list` is true.
#[inline] #[inline]
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
fn update_batteries(&mut self) { fn update_batteries(&mut self) {
if let Some(battery_manager) = &self.battery_manager { let battery_manager = match &self.battery_manager {
if let Some(battery_list) = &mut self.battery_list { Some(manager) => {
self.data.list_of_batteries = // Also check if we need to refresh the list of batteries.
Some(batteries::refresh_batteries(battery_manager, battery_list)); if self.should_refresh_list {
let battery_list = manager
.batteries()
.map(|batteries| batteries.filter_map(Result::ok).collect::<Vec<_>>());
if let Ok(battery_list) = battery_list {
if battery_list.is_empty() {
self.battery_list = None;
} else {
self.battery_list = Some(battery_list);
}
} else {
self.battery_list = None;
}
}
manager
} }
} None => {
if let Ok(manager) = Manager::new() {
let Ok(batteries) = manager.batteries() else {
return;
};
let battery_list = batteries.filter_map(Result::ok).collect::<Vec<_>>();
if battery_list.is_empty() {
return;
}
self.battery_list = Some(battery_list);
self.battery_manager.insert(manager)
} else {
return;
}
}
};
self.data.list_of_batteries = self
.battery_list
.as_mut()
.map(|battery_list| batteries::refresh_batteries(battery_manager, battery_list));
} }
#[inline] #[inline]
@ -510,31 +570,6 @@ impl DataCollector {
} }
} }
/// We set a sleep duration between 10ms and 250ms, ideally sysinfo's
/// [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] + 1.
///
/// We bound the upper end to avoid waiting too long (e.g. FreeBSD is 1s, which
/// I'm fine with losing accuracy on for the first refresh), and we bound the
/// lower end just to avoid the off-chance that refreshing too quickly causes
/// problems. This second case should only happen on unsupported systems via
/// sysinfo, in which case [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] is defined
/// as 0.
///
/// We also do `INTERVAL + 1` for some wiggle room, just in case.
const fn get_sleep_duration() -> Duration {
const MIN_SLEEP: u64 = 10;
const MAX_SLEEP: u64 = 250;
const INTERVAL: u64 = sysinfo::MINIMUM_CPU_UPDATE_INTERVAL.as_millis() as u64;
if INTERVAL < MIN_SLEEP {
Duration::from_millis(MIN_SLEEP)
} else if INTERVAL > MAX_SLEEP {
Duration::from_millis(MAX_SLEEP)
} else {
Duration::from_millis(INTERVAL + 1)
}
}
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
/// Deserialize [libxo](https://www.freebsd.org/cgi/man.cgi?query=libxo&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) JSON data /// Deserialize [libxo](https://www.freebsd.org/cgi/man.cgi?query=libxo&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) JSON data
fn deserialize_xo<T>(key: &str, data: &[u8]) -> Result<T, std::io::Error> fn deserialize_xo<T>(key: &str, data: &[u8]) -> Result<T, std::io::Error>

View File

@ -219,7 +219,7 @@ fn create_collection_thread(
let use_current_cpu_total = app_config_fields.use_current_cpu_total; let use_current_cpu_total = app_config_fields.use_current_cpu_total;
let unnormalized_cpu = app_config_fields.unnormalized_cpu; let unnormalized_cpu = app_config_fields.unnormalized_cpu;
let show_average_cpu = app_config_fields.show_average_cpu; let show_average_cpu = app_config_fields.show_average_cpu;
let update_time = app_config_fields.update_rate; let update_sleep = app_config_fields.update_rate;
thread::spawn(move || { thread::spawn(move || {
let mut data_state = collection::DataCollector::new(filters); let mut data_state = collection::DataCollector::new(filters);
@ -229,8 +229,6 @@ fn create_collection_thread(
data_state.set_unnormalized_cpu(unnormalized_cpu); data_state.set_unnormalized_cpu(unnormalized_cpu);
data_state.set_show_average_cpu(show_average_cpu); data_state.set_show_average_cpu(show_average_cpu);
data_state.init();
loop { loop {
// Check once at the very top... don't block though. // Check once at the very top... don't block though.
if let Some(is_terminated) = cancellation_token.try_check() { if let Some(is_terminated) = cancellation_token.try_check() {
@ -264,7 +262,7 @@ fn create_collection_thread(
} }
// Sleep while allowing for interruptions... // Sleep while allowing for interruptions...
if cancellation_token.sleep_with_cancellation(Duration::from_millis(update_time)) { if cancellation_token.sleep_with_cancellation(Duration::from_millis(update_sleep)) {
break; break;
} }
} }

View File

@ -672,7 +672,7 @@ macro_rules! parse_ms_option {
}}; }};
} }
/// How fast the screen refreshes /// How quickly we update data.
#[inline] #[inline]
fn get_update_rate(args: &BottomArgs, config: &Config) -> OptionResult<u64> { fn get_update_rate(args: &BottomArgs, config: &Config) -> OptionResult<u64> {
const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000; const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000;