Skip to content

Commit f96c03b

Browse files
committed
perf: replace vec allocation with mutable string buffer
Add graceful shutdown msg
1 parent 8faffdf commit f96c03b

File tree

8 files changed

+175
-152
lines changed

8 files changed

+175
-152
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.rs

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,34 @@ use stats::{
88
get_cpu_stats, get_disk_stats, get_memory_stats, get_network_stats, get_system_stats,
99
get_uptime_stats,
1010
};
11-
use sysinfo::{Disks, Networks, System};
12-
13-
struct ProcessedFlags {
14-
cpu_flags: Option<Vec<String>>,
15-
disk_flags: Option<Vec<String>>,
16-
memory_flags: Option<Vec<String>>,
17-
network_flags: Option<Vec<String>>,
18-
uptime_flags: Option<Vec<String>>,
11+
use sysinfo::{Components, Disks, Networks, System};
12+
13+
struct ProcessedFlags<'a> {
14+
cpu_flags: Option<&'a [String]>,
15+
disk_flags: Option<&'a [String]>,
16+
memory_flags: Option<&'a [String]>,
17+
network_flags: Option<&'a [String]>,
18+
uptime_flags: Option<&'a [String]>,
1919
}
2020

21-
impl ProcessedFlags {
21+
impl<'a> ProcessedFlags<'a> {
2222
fn cpu_flag_refs(&self) -> Option<Vec<&str>> {
2323
self.cpu_flags
24-
.as_ref()
2524
.map(|flags| flags.iter().map(String::as_str).collect())
2625
}
2726

2827
fn disk_flag_refs(&self) -> Option<Vec<&str>> {
2928
self.disk_flags
30-
.as_ref()
3129
.map(|flags| flags.iter().map(String::as_str).collect())
3230
}
3331

3432
fn memory_flag_refs(&self) -> Option<Vec<&str>> {
3533
self.memory_flags
36-
.as_ref()
3734
.map(|flags| flags.iter().map(String::as_str).collect())
3835
}
3936

4037
fn uptime_flag_refs(&self) -> Option<Vec<&str>> {
4138
self.uptime_flags
42-
.as_ref()
4339
.map(|flags| flags.iter().map(String::as_str).collect())
4440
}
4541
}
@@ -48,20 +44,21 @@ struct StatsContext<'a> {
4844
system: &'a mut System,
4945
disks: &'a mut Disks,
5046
networks: &'a mut Networks,
47+
components: &'a Components,
5148
}
5249

53-
struct StatsConfig {
54-
flags: ProcessedFlags,
50+
struct StatsConfig<'a> {
51+
flags: ProcessedFlags<'a>,
5552
refresh_kind: sysinfo::RefreshKind,
5653
}
5754

58-
fn process_cli_flags(cli: &cli::Cli) -> ProcessedFlags {
55+
fn process_cli_flags(cli: &cli::Cli) -> ProcessedFlags<'_> {
5956
ProcessedFlags {
60-
cpu_flags: cli.cpu.clone(),
61-
disk_flags: cli.disk.clone(),
62-
memory_flags: cli.memory.clone(),
63-
network_flags: cli.network.clone(),
64-
uptime_flags: cli.uptime.clone(),
57+
cpu_flags: cli.cpu.as_deref(),
58+
disk_flags: cli.disk.as_deref(),
59+
memory_flags: cli.memory.as_deref(),
60+
network_flags: cli.network.as_deref(),
61+
uptime_flags: cli.uptime.as_deref(),
6562
}
6663
}
6764

@@ -82,7 +79,6 @@ fn validate_network_interfaces(
8279
}
8380
}
8481

85-
// Only fail if no interfaces are available at all
8682
if available_interfaces.is_empty() {
8783
anyhow::bail!("No network interfaces available on this system");
8884
}
@@ -95,20 +91,18 @@ async fn send_initial_system_stats(
9591
sketchybar: &Sketchybar,
9692
system: &mut System,
9793
refresh_kind: &sysinfo::RefreshKind,
94+
buf: &mut String,
9895
) -> Result<()> {
9996
if cli.all || cli.system.is_some() {
10097
system.refresh_specifics(*refresh_kind);
10198
let system_flags = match &cli.system {
10299
Some(flags) => flags.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
103100
None => cli::all_system_flags(),
104101
};
102+
buf.clear();
103+
get_system_stats(&system_flags, buf);
105104
sketchybar
106-
.send_message(
107-
"trigger",
108-
"system_stats",
109-
Some(&get_system_stats(&system_flags).join("")),
110-
cli.verbose,
111-
)
105+
.send_message("trigger", "system_stats", Some(buf), cli.verbose)
112106
.await?;
113107
}
114108

@@ -120,14 +114,23 @@ async fn get_stats(cli: &cli::Cli, sketchybar: &Sketchybar) -> Result<()> {
120114
let mut system = System::new_with_specifics(refresh_kind);
121115
let mut disks = Disks::new_with_refreshed_list();
122116
let mut networks = Networks::new_with_refreshed_list();
117+
let components = Components::new_with_refreshed_list();
123118

124-
// Validate network interfaces exist if specified
125119
if let Some(network_flags) = &cli.network {
126120
validate_network_interfaces(&networks, network_flags, cli.verbose)?;
127121
}
128122

129123
let flags = process_cli_flags(cli);
130-
send_initial_system_stats(cli, sketchybar, &mut system, &refresh_kind).await?;
124+
let mut message_buffer = String::with_capacity(512);
125+
126+
send_initial_system_stats(
127+
cli,
128+
sketchybar,
129+
&mut system,
130+
&refresh_kind,
131+
&mut message_buffer,
132+
)
133+
.await?;
131134

132135
let config = StatsConfig {
133136
flags,
@@ -138,54 +141,57 @@ async fn get_stats(cli: &cli::Cli, sketchybar: &Sketchybar) -> Result<()> {
138141
system: &mut system,
139142
disks: &mut disks,
140143
networks: &mut networks,
144+
components: &components,
141145
};
142146

143-
run_stats_loop(cli, sketchybar, &config, &mut context).await
147+
run_stats_loop(cli, sketchybar, &config, &mut context, &mut message_buffer).await
144148
}
145149

146150
async fn run_stats_loop(
147151
cli: &cli::Cli,
148152
sketchybar: &Sketchybar,
149-
config: &StatsConfig,
153+
config: &StatsConfig<'_>,
150154
context: &mut StatsContext<'_>,
155+
message_buffer: &mut String,
151156
) -> Result<()> {
152157
let mut network_refresh_tick = 0;
153158

154159
loop {
155-
let (commands, updated_tick) =
156-
collect_stats_commands(cli, config, context, network_refresh_tick).await?;
157-
network_refresh_tick = updated_tick;
158-
159-
if cli.verbose {
160-
println!("Current message: {}", commands.join(""));
160+
tokio::select! {
161+
result = collect_stats_commands(cli, config, context, network_refresh_tick, message_buffer) => {
162+
network_refresh_tick = result?;
163+
164+
if cli.verbose {
165+
println!("Current message: {}", message_buffer);
166+
}
167+
sketchybar
168+
.send_message("trigger", "system_stats", Some(message_buffer), cli.verbose)
169+
.await?;
170+
}
171+
_ = tokio::signal::ctrl_c() => {
172+
if cli.verbose {
173+
println!("Received shutdown signal, cleaning up...");
174+
}
175+
println!("SketchyBar Stats Provider is shutting down.");
176+
return Ok(());
177+
}
161178
}
162-
sketchybar
163-
.send_message(
164-
"trigger",
165-
"system_stats",
166-
Some(&commands.join("")),
167-
cli.verbose,
168-
)
169-
.await?;
170179
}
171180
}
172181

173182
async fn collect_stats_commands(
174183
cli: &cli::Cli,
175-
config: &StatsConfig,
184+
config: &StatsConfig<'_>,
176185
context: &mut StatsContext<'_>,
177186
network_refresh_tick: u32,
178-
) -> Result<(Vec<String>, u32)> {
179-
let mut commands: Vec<String> = Vec::new();
187+
buf: &mut String,
188+
) -> Result<u32> {
189+
buf.clear();
180190

181191
tokio::time::sleep(tokio::time::Duration::from_secs(cli.interval.into())).await;
182192
context.system.refresh_specifics(config.refresh_kind);
183193
context.disks.refresh(true);
184194

185-
// Refresh network interfaces less frequently than other stats to reduce overhead.
186-
// Network interfaces don't change rapidly, so we only refresh the full interface
187-
// list every N stat collection cycles (configurable via --network-refresh-rate).
188-
// In between full refreshes, we just update existing interface data.
189195
let mut updated_tick = network_refresh_tick + 1;
190196
if updated_tick >= cli.network_refresh_rate {
191197
*context.networks = Networks::new_with_refreshed_list();
@@ -195,54 +201,59 @@ async fn collect_stats_commands(
195201
}
196202

197203
if cli.all {
198-
commands.push(get_cpu_stats(context.system, &cli::all_cpu_flags(), cli.no_units).join(""));
199-
commands.push(get_disk_stats(context.disks, &cli::all_disk_flags(), cli.no_units).join(""));
200-
commands.push(
201-
get_memory_stats(context.system, &cli::all_memory_flags(), cli.no_units).join(""),
204+
get_cpu_stats(
205+
context.system,
206+
context.components,
207+
&cli::all_cpu_flags(),
208+
cli.no_units,
209+
buf,
202210
);
203-
commands
204-
.push(get_network_stats(context.networks, None, cli.interval, cli.no_units).join(""));
205-
commands.push(get_uptime_stats(&cli::all_uptime_flags()));
211+
get_disk_stats(context.disks, &cli::all_disk_flags(), cli.no_units, buf);
212+
get_memory_stats(context.system, &cli::all_memory_flags(), cli.no_units, buf);
213+
get_network_stats(context.networks, None, cli.interval, cli.no_units, buf);
214+
get_uptime_stats(&cli::all_uptime_flags(), buf);
206215
} else {
207216
if let Some(cpu_flag_refs) = config.flags.cpu_flag_refs() {
208-
commands.push(get_cpu_stats(context.system, &cpu_flag_refs, cli.no_units).join(""));
217+
get_cpu_stats(
218+
context.system,
219+
context.components,
220+
&cpu_flag_refs,
221+
cli.no_units,
222+
buf,
223+
);
209224
}
210225

211226
if let Some(disk_flag_refs) = config.flags.disk_flag_refs() {
212-
commands.push(get_disk_stats(context.disks, &disk_flag_refs, cli.no_units).join(""));
227+
get_disk_stats(context.disks, &disk_flag_refs, cli.no_units, buf);
213228
}
214229

215230
if let Some(memory_flag_refs) = config.flags.memory_flag_refs() {
216-
commands
217-
.push(get_memory_stats(context.system, &memory_flag_refs, cli.no_units).join(""));
231+
get_memory_stats(context.system, &memory_flag_refs, cli.no_units, buf);
218232
}
219233

220-
if let Some(network_flags) = &config.flags.network_flags {
221-
commands.push(
222-
get_network_stats(
223-
context.networks,
224-
Some(network_flags),
225-
cli.interval,
226-
cli.no_units,
227-
)
228-
.join(""),
234+
if let Some(network_flags) = config.flags.network_flags {
235+
get_network_stats(
236+
context.networks,
237+
Some(network_flags),
238+
cli.interval,
239+
cli.no_units,
240+
buf,
229241
);
230242
}
231243

232244
if let Some(uptime_flag_refs) = config.flags.uptime_flag_refs() {
233-
commands.push(get_uptime_stats(&uptime_flag_refs));
245+
get_uptime_stats(&uptime_flag_refs, buf);
234246
}
235247
}
236248

237-
Ok((commands, updated_tick))
249+
Ok(updated_tick)
238250
}
239251

240252
#[cfg(target_os = "macos")]
241253
#[tokio::main]
242254
async fn main() -> Result<()> {
243255
let cli = cli::parse_args();
244256

245-
// Validate CLI arguments
246257
cli::validate_cli(&cli).context("Invalid CLI arguments")?;
247258

248259
println!("SketchyBar Stats Provider is running.");

src/stats/cpu.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
1+
use std::fmt::Write;
12
use sysinfo::{Components, System};
23

3-
pub fn get_cpu_stats(s: &System, flags: &[&str], no_units: bool) -> Vec<String> {
4+
pub fn get_cpu_stats(
5+
s: &System,
6+
components: &Components,
7+
flags: &[&str],
8+
no_units: bool,
9+
buf: &mut String,
10+
) {
411
let cpu_count = s.cpus().len() as f32;
512

6-
let mut result = Vec::new();
7-
813
for &flag in flags {
914
match flag {
1015
"count" => {
11-
result.push(format!("CPU_COUNT=\"{cpu_count}\" "));
16+
let _ = write!(buf, "CPU_COUNT=\"{cpu_count}\" ");
1217
}
1318
"frequency" => {
1419
let total_frequency: u64 = s.cpus().iter().map(|cpu| cpu.frequency()).sum();
1520
let avg_freq = total_frequency / cpu_count as u64;
1621
let unit = if no_units { "" } else { "MHz" };
17-
result.push(format!("CPU_FREQUENCY=\"{avg_freq}{unit}\" "));
22+
let _ = write!(buf, "CPU_FREQUENCY=\"{avg_freq}{unit}\" ");
1823
}
1924
"temperature" => {
20-
let components = Components::new_with_refreshed_list();
2125
let mut total_temp: f32 = 0.0;
2226
let mut count: u32 = 0;
2327

2428
let cpu_labels = ["CPU", "PMU", "SOC"];
2529

26-
for component in &components {
30+
for component in components {
2731
if cpu_labels
2832
.iter()
2933
.any(|&label| component.label().contains(label))
@@ -41,25 +45,22 @@ pub fn get_cpu_stats(s: &System, flags: &[&str], no_units: bool) -> Vec<String>
4145
-1.0
4246
};
4347

44-
let formatted_temp = if average_temp != -1.0 {
45-
format!("{average_temp:.1}")
46-
} else {
47-
"N/A".to_string()
48-
};
49-
5048
let unit = if no_units { "" } else { "°C" };
51-
result.push(format!("CPU_TEMP=\"{formatted_temp}{unit}\" "));
49+
if average_temp != -1.0 {
50+
let _ = write!(buf, "CPU_TEMP=\"{average_temp:.1}{unit}\" ");
51+
} else {
52+
let _ = write!(buf, "CPU_TEMP=\"N/A{unit}\" ");
53+
}
5254
}
5355
"usage" => {
5456
let unit = if no_units { "" } else { "%" };
55-
result.push(format!(
57+
let _ = write!(
58+
buf,
5659
"CPU_USAGE=\"{:.0}{unit}\" ",
5760
s.global_cpu_usage().round()
58-
));
61+
);
5962
}
6063
_ => {}
6164
}
6265
}
63-
64-
result
6566
}

0 commit comments

Comments
 (0)