feat(loadavg): add module for load average

Inspired by https://github.com/starship/starship/discussions/1252#discussioncomment-347145

Example configuration:

    [loadavg]
    disabled = false

    [[loadavg.display]]
    threshold_one = 8.0
    style = "bold red"

    [[loadavg.display]]
    threshold_one = 4.0
    style = "bold yellow"

    [[loadavg.display]]
    threshold_one = 2.0
    style = "bold white"

    [[loadavg.display]]
    threshold_one = 0.0
    style = "dimmed white"
This commit is contained in:
Christian Göttsche 2022-11-12 20:45:18 +01:00
parent 1447957e97
commit 3a864f180b
8 changed files with 393 additions and 0 deletions

View File

@ -912,6 +912,27 @@
}
]
},
"loadavg": {
"default": {
"disabled": true,
"display": [
{
"style": "white bold",
"symbol": null,
"threshold_fifteen": null,
"threshold_five": null,
"threshold_one": null
}
],
"format": "[$symbol $one $five $fifteen]($style) ",
"symbol": "⌛"
},
"allOf": [
{
"$ref": "#/definitions/LoadavgConfig"
}
]
},
"localip": {
"default": {
"disabled": true,
@ -3930,6 +3951,71 @@
},
"additionalProperties": false
},
"LoadavgConfig": {
"type": "object",
"properties": {
"format": {
"default": "[$symbol $one $five $fifteen]($style) ",
"type": "string"
},
"symbol": {
"default": "⌛",
"type": "string"
},
"display": {
"default": [
{
"style": "white bold",
"symbol": null,
"threshold_fifteen": null,
"threshold_five": null,
"threshold_one": null
}
],
"type": "array",
"items": {
"$ref": "#/definitions/LoadavgDisplayConfig"
}
},
"disabled": {
"default": true,
"type": "boolean"
}
},
"additionalProperties": false
},
"LoadavgDisplayConfig": {
"type": "object",
"properties": {
"threshold_one": {
"default": null,
"type": "number",
"format": "float"
},
"threshold_five": {
"default": null,
"type": "number",
"format": "float"
},
"threshold_fifteen": {
"default": null,
"type": "number",
"format": "float"
},
"style": {
"default": "white bold",
"type": "string"
},
"symbol": {
"default": null,
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"LocalipConfig": {
"type": "object",
"properties": {

View File

@ -321,6 +321,7 @@ $nix_shell\
$conda\
$meson\
$spack\
$loadavg\
$memory_usage\
$aws\
$gcloud\
@ -2516,6 +2517,88 @@ The `line_break` module separates the prompt into two lines.
disabled = true
```
## Load Average
The `loadavg` module shows current system load average.
::: tip
This module is disabled by default.
To enable it, set `disabled` to `false` and define at least one `Load Average Display` in your configuration file.
:::
### Options
| Option | Default | Description |
| ---------- | ------------------------------------------ | --------------------------------------------------- |
| `format` | `'[$symbol $one $five $fifteen]($style) '` | The format for the module. |
| `symbol` | `"⌛"` | The symbol used before displaying the load average. |
| `disabled` | `true` | Disables the `loadavg` module. |
### Variables
| Variable | Example | Description |
| -------- | ------- | ------------------------------------ |
| one | `1.42` | The one minute load average. |
| five | `2.01` | The five minute load average. |
| fifteen | `10.98` | The fifteen minute load average. |
| symbol | `⌛` | Mirrors the value of option `symbol` |
| style\* | | Mirrors the value of option `style` |
*: This variable can only be used as a part of a style string
### Example
```toml
# ~/.config/starship.toml
[loadavg]
disabled = false
```
### Load Average Display
The `display` configuration option is used to define when the `Load Average` should be shown (threshold-*), which symbol would be used (symbol).
If no `display` is provided.
By default, the thresholds are set to an invalid value.
The default value for the `symbol` option is the value of `loadavg`'s `symbol` option.
#### Options
The `display` option is an array of the following table.
| Option | Default | Description |
| ------------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `threshold-one` | `NAN` | The lower bound for the one minute average. |
| `threshold-five` | `NAN` | The lower bound for the five minute average. |
| `threshold-fifteen` | `NAN` | The lower bound for the fifteen minute average. |
| `style` | `'white bold'` | The style used if the display option is in use. |
| `symbol` | | Optional symbol displayed if display option is in use, defaults to loadavg's `symbol` option. |
#### Example
```toml
[[loadavg.display]]
threshold_one = 8.0
style = "bold red"
[[loadavg.display]]
threshold_one = 4.0
style = "bold yellow"
[[loadavg.display]]
threshold_one = 2.0
style = "bold white"
[[loadavg.display]]
threshold_one = 0.0
style = "dimmed white"
symbol = "⏲"
```
## Local IP
The `localip` module shows the IPv4 address of the primary network interface.

54
src/configs/loadavg.rs Normal file
View File

@ -0,0 +1,54 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Serialize)]
#[cfg_attr(
feature = "config-schema",
derive(schemars::JsonSchema),
schemars(deny_unknown_fields)
)]
#[serde(default)]
pub struct LoadavgConfig<'a> {
pub format: &'a str,
pub symbol: &'a str,
#[serde(borrow)]
pub display: Vec<LoadavgDisplayConfig<'a>>,
pub disabled: bool,
}
impl<'a> Default for LoadavgConfig<'a> {
fn default() -> Self {
LoadavgConfig {
format: "[$symbol $one $five $fifteen]($style) ",
symbol: "",
display: vec![LoadavgDisplayConfig::default()],
disabled: true,
}
}
}
#[derive(Clone, Deserialize, Serialize)]
#[cfg_attr(
feature = "config-schema",
derive(schemars::JsonSchema),
schemars(deny_unknown_fields)
)]
#[serde(default)]
pub struct LoadavgDisplayConfig<'a> {
pub threshold_one: f32,
pub threshold_five: f32,
pub threshold_fifteen: f32,
pub style: &'a str,
pub symbol: Option<&'a str>,
}
impl<'a> Default for LoadavgDisplayConfig<'a> {
fn default() -> Self {
LoadavgDisplayConfig {
threshold_one: f32::NAN,
threshold_five: f32::NAN,
threshold_fifteen: f32::NAN,
style: "white bold",
symbol: None,
}
}
}

View File

@ -48,6 +48,7 @@ pub mod julia;
pub mod kotlin;
pub mod kubernetes;
pub mod line_break;
pub mod loadavg;
pub mod localip;
pub mod lua;
pub mod memory_usage;
@ -197,6 +198,8 @@ pub struct FullConfig<'a> {
kubernetes: kubernetes::KubernetesConfig<'a>,
line_break: line_break::LineBreakConfig,
#[serde(borrow)]
loadavg: loadavg::LoadavgConfig<'a>,
#[serde(borrow)]
localip: localip::LocalipConfig<'a>,
#[serde(borrow)]
lua: lua::LuaConfig<'a>,

View File

@ -98,6 +98,7 @@ pub const PROMPT_ORDER: &[&str] = &[
"conda",
"meson",
"spack",
"loadavg",
"memory_usage",
"aws",
"gcloud",

View File

@ -55,6 +55,7 @@ pub const ALL_MODULES: &[&str] = &[
"kotlin",
"kubernetes",
"line_break",
"loadavg",
"localip",
"lua",
"memory_usage",

162
src/modules/loadavg.rs Normal file
View File

@ -0,0 +1,162 @@
use systemstat::{LoadAverage, Platform, System};
use super::{Context, Module, ModuleConfig};
use crate::configs::loadavg::{LoadavgConfig, LoadavgDisplayConfig};
use crate::formatter::StringFormatter;
// Check whether a display config is active
fn is_active(config: &LoadavgDisplayConfig, loadavg: &LoadAverage) -> bool {
config.threshold_one <= loadavg.one
|| config.threshold_five <= loadavg.five
|| config.threshold_fifteen <= loadavg.fifteen
}
/// Creates a module with system load average information
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("loadavg");
let config = LoadavgConfig::try_load(module.config);
// As we default to disabled=true, we have to check here after loading our config module,
// before it was only checking against whatever is in the config starship.toml
if config.disabled {
return None;
}
let system = System::new();
let loadavg = match system.load_average() {
Ok(load) => load,
Err(e) => {
log::warn!("Failed to retrieve loadavg: {}", e);
return None;
}
};
// Parse config under `display`.
// Select the first style that match any threshold,
// if all thresholds are lower do not display loadavg module.
let display_config = config
.display
.iter()
.find(|display_style| is_active(display_style, &loadavg))?;
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|var, _| match var {
"symbol" => display_config.symbol.or(Some(config.symbol)),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(display_config.style)),
_ => None,
})
.map(|variable| match variable {
"one" => Some(Ok(format!("{:.2}", loadavg.one))),
"five" => Some(Ok(format!("{:.2}", loadavg.five))),
"fifteen" => Some(Ok(format!("{:.2}", loadavg.fifteen))),
_ => None,
})
.parse(None, Some(context))
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `loadavg`:\n{}", error);
return None;
}
});
Some(module)
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::ModuleRenderer;
#[test]
fn test_is_active() {
let mut config: LoadavgDisplayConfig = LoadavgDisplayConfig::default();
let loadavg: LoadAverage = LoadAverage {
one: 1.0,
five: 4.0,
fifteen: 8.0,
};
assert!(!is_active(&config, &loadavg));
config.threshold_one = 1.1;
assert!(!is_active(&config, &loadavg));
config.threshold_one = 1.0;
assert!(is_active(&config, &loadavg));
config.threshold_one = f32::NAN;
config.threshold_fifteen = 8.1;
assert!(!is_active(&config, &loadavg));
config.threshold_fifteen = 8.0;
assert!(is_active(&config, &loadavg));
}
#[test]
fn zero_threshold() {
let output = ModuleRenderer::new("loadavg")
.config(toml::toml! {
[loadavg]
disabled = false
[[loadavg.display]]
threshold_one = 0.0
})
.collect();
if System::new().load_average().is_ok() {
assert!(output.is_some())
} else {
assert!(output.is_none())
}
}
#[test]
fn impossible_threshold() {
let output = ModuleRenderer::new("loadavg")
.config(toml::toml! {
[loadavg]
disabled = false
[[loadavg.display]]
threshold_one = 9999.9
[[loadavg.display]]
threshold_five = 9999.9
[[loadavg.display]]
threshold_fifteen = 9999.9
})
.collect();
assert!(output.is_none())
}
#[test]
fn zero_last_threshold() {
let output = ModuleRenderer::new("loadavg")
.config(toml::toml! {
[loadavg]
disabled = false
[[loadavg.display]]
threshold_one = 9999.9
[[loadavg.display]]
threshold_five = 9999.9
[[loadavg.display]]
threshold_fifteen = 0.0
})
.collect();
if System::new().load_average().is_ok() {
assert!(output.is_some())
} else {
assert!(output.is_none())
}
}
}

View File

@ -45,6 +45,7 @@ mod julia;
mod kotlin;
mod kubernetes;
mod line_break;
mod loadavg;
mod localip;
mod lua;
mod memory_usage;
@ -148,6 +149,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"kotlin" => kotlin::module(context),
"kubernetes" => kubernetes::module(context),
"line_break" => line_break::module(context),
"loadavg" => loadavg::module(context),
"localip" => localip::module(context),
"lua" => lua::module(context),
"memory_usage" => memory_usage::module(context),
@ -262,6 +264,7 @@ pub fn description(module: &str) -> &'static str {
"kotlin" => "The currently installed version of Kotlin",
"kubernetes" => "The current Kubernetes context name and, if set, the namespace",
"line_break" => "Separates the prompt into two lines",
"loadavg" => "The current system load average",
"localip" => "The currently assigned ipv4 address",
"lua" => "The currently installed version of Lua",
"memory_usage" => "Current system memory and swap usage",