diff --git a/Cargo.lock b/Cargo.lock index d7b2e58..bdd1943 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "autocfg" @@ -90,9 +90,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block2" @@ -117,9 +117,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.55" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -137,9 +137,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.55" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.65" +version = "4.5.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" dependencies = [ "clap", ] @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" @@ -192,12 +192,12 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "ctrlc" -version = "3.5.1" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.30.1", + "nix", "windows-sys", ] @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -264,6 +264,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "getrandom" version = "0.3.4" @@ -276,6 +282,28 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -300,6 +328,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "indexmap" version = "2.13.0" @@ -307,7 +341,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -327,30 +363,59 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" dependencies = [ "jiff-static", + "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", + "windows-sys", ] [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -371,21 +436,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nix" @@ -447,15 +500,15 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -469,6 +522,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -480,9 +543,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", @@ -544,7 +607,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] @@ -558,9 +621,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -570,9 +633,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -581,9 +644,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustix" @@ -610,6 +673,12 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -640,6 +709,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -657,9 +739,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -668,12 +750,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys", @@ -681,9 +763,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -705,9 +787,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -726,9 +808,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" @@ -754,6 +842,49 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "watt" version = "1.0.0" @@ -764,8 +895,9 @@ dependencies = [ "ctrlc", "env_logger", "humantime", + "jiff", "log", - "nix 0.31.1", + "nix", "num_cpus", "proptest", "serde", @@ -799,6 +931,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "xtask" @@ -821,20 +1035,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.35" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.35" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 73f113e..7c04fdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,11 @@ clap_complete = "4.5.59" clap_complete_nushell = "4.5.9" ctrlc = "3.5.1" env_logger = "0.11.8" +humantime = "2.3.0" +jiff = "0.2.20" log = "0.4.28" nix = { features = [ "fs" ], version = "0.31.1" } num_cpus = "1.17.0" -humantime = "2.3.0" serde = { features = [ "derive" ], version = "1.0.228" } toml = "0.9.8" yansi = { features = [ "detect-env", "detect-tty" ], version = "1.0.1" } diff --git a/README.md b/README.md index 2a517d9..b7a7fe7 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ but Watt attempts to support multiple vendor implementations including: # High performance for sustained workloads with thermal protection [[rule]] if.all = [ - { is-more-than = 0.8, value = "%cpu-usage" }, + { is-more-than = 0.8, value = { cpu-usage-since = "2sec" } }, { is-less-than = 30.0, value = "$cpu-idle-seconds" }, { is-less-than = 75.0, value = "$cpu-temperature" }, ] @@ -262,15 +262,30 @@ Watt includes a powerful expression language for defining conditions: #### System Variables -- `"%cpu-usage"` - Current CPU usage percentage (0.0-1.0) +- `{ cpu-usage-since = "" }` - CPU usage percentage over a duration + (e.g., `"1sec"`, `"5sec"`) - `"$cpu-usage-volatility"` - CPU usage volatility measurement - `"$cpu-temperature"` - CPU temperature in Celsius - `"$cpu-temperature-volatility"` - CPU temperature volatility - `"$cpu-idle-seconds"` - Seconds since last significant CPU activity - `"$cpu-frequency-maximum"` - CPU hardware maximum frequency in MHz - `"$cpu-frequency-minimum"` - CPU hardware minimum frequency in MHz +- `"$cpu-scaling-maximum"` - Current CPU scaling maximum frequency in MHz (from + `scaling_max_freq`) +- `"%cpu-core-count"` - Number of CPU cores +- `{ load-average-since = "" }` - System load average over a duration + (e.g., `"5sec"`, `"1min"`) +- `"$hour-of-day"` - Current hour (0-23) based on system local time +- `"?lid-closed"` - Boolean indicating if laptop lid is closed - `"%power-supply-charge"` - Battery charge percentage (0.0-1.0) - `"%power-supply-discharge-rate"` - Current discharge rate +- `"%battery-cycles"` - Battery cycle count (aggregated average across all + batteries) +- `"%battery-health"` - Battery health percentage (0.0-1.0, aggregated average + across all batteries) +- `{ battery-cycles-for = "" }` - Battery cycle count for a specific + battery (e.g., `"BAT0"`) +- `{ battery-health-for = "" }` - Battery health for a specific battery - `"?discharging"` - Boolean indicating if system is on battery power - `"?frequency-available"` - Boolean indicating if CPU frequency control is available @@ -288,6 +303,7 @@ if.is-energy-performance-preference-available = "balance_performance" if.is-energy-perf-bias-available = "5" if.is-platform-profile-available = "low-power" if.is-driver-loaded = "intel_pstate" +if.is-battery-available = "BAT0" ``` Each will be `true` only if the named value is available on your system. If the @@ -305,7 +321,7 @@ You can use operators with TOML attribute sets: ```toml [[rule]] -if = { is-more-than = "%cpu-usage", value = 0.8 } +if = { is-more-than = { cpu-usage-since = "1sec" }, value = 0.8 } ``` However, `all` and `any` do not take a `value` argument, but instead take a list @@ -359,7 +375,7 @@ power.platform-profile = { if.is-platform-profile-available = "low-power", then # High performance mode for sustained high load [[rule]] if.all = [ - { is-more-than = 0.8, value = "%cpu-usage" }, + { is-more-than = 0.8, value = { cpu-usage-since = "2sec" } }, { is-less-than = 30.0, value = "$cpu-idle-seconds" }, { is-less-than = 75.0, value = "$cpu-temperature" }, ] @@ -374,7 +390,7 @@ cpu.turbo = { if = "?turbo-available", then = true } [[rule]] if.all = [ { not = "?discharging" }, - { is-more-than = 0.1, value = "%cpu-usage" }, + { is-more-than = 0.1, value = { cpu-usage-since = "1sec" } }, { is-less-than = 80.0, value = "$cpu-temperature" }, ] priority = 70 @@ -387,8 +403,8 @@ cpu.turbo = { if = "?turbo-available", then = true } # Moderate performance for medium load [[rule]] if.all = [ - { is-more-than = 0.4, value = "%cpu-usage" }, - { is-less-than = 0.8, value = "%cpu-usage" }, + { is-more-than = 0.4, value = { cpu-usage-since = "5sec" } }, + { is-less-than = 0.8, value = { cpu-usage-since = "5sec" } }, ] priority = 60 @@ -399,7 +415,7 @@ cpu.governor = { if.is-governor-available = "schedutil", then = "schedutil" } # Power saving during low activity [[rule]] if.all = [ - { is-less-than = 0.2, value = "%cpu-usage" }, + { is-less-than = 0.2, value = { cpu-usage-since = "10sec" } }, { is-more-than = 60.0, value = "$cpu-idle-seconds" }, ] priority = 50 diff --git a/watt/Cargo.toml b/watt/Cargo.toml index 1fb9142..9666803 100644 --- a/watt/Cargo.toml +++ b/watt/Cargo.toml @@ -20,6 +20,7 @@ clap-verbosity-flag.workspace = true ctrlc.workspace = true env_logger.workspace = true humantime.workspace = true +jiff.workspace = true log.workspace = true nix.workspace = true num_cpus.workspace = true diff --git a/watt/config.rs b/watt/config.rs index 28ef74d..6961f81 100644 --- a/watt/config.rs +++ b/watt/config.rs @@ -35,6 +35,16 @@ fn is_default(value: &T) -> bool { *value == T::default() } +fn find_battery<'a>( + power_supplies: &'a HashSet>, + name: &str, +) -> Option<&'a power_supply::PowerSupply> { + power_supplies + .iter() + .find(|ps| ps.name == name && ps.type_ == "Battery") + .map(|arc| arc.as_ref()) +} + #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)] #[serde(deny_unknown_fields, default, rename_all = "kebab-case")] pub struct CpusDelta { @@ -390,9 +400,20 @@ mod expression { named!(cpu_frequency_maximum => "$cpu-frequency-maximum"); named!(cpu_frequency_minimum => "$cpu-frequency-minimum"); + named!(cpu_scaling_maximum => "$cpu-scaling-maximum"); + + named!(cpu_core_count => "%cpu-core-count"); + + named!(lid_closed => "?lid-closed"); + + named!(hour_of_day => "$hour-of-day"); + named!(power_supply_charge => "%power-supply-charge"); named!(power_supply_discharge_rate => "%power-supply-discharge-rate"); + named!(battery_cycles => "$battery-cycles"); + named!(battery_health => "%battery-health"); + named!(discharging => "?discharging"); } @@ -421,6 +442,11 @@ pub enum Expression { value: Box, }, + IsBatteryAvailable { + #[serde(rename = "is-battery-available")] + value: Box, + }, + #[serde(with = "expression::frequency_available")] FrequencyAvailable, @@ -453,12 +479,45 @@ pub enum Expression { #[serde(with = "expression::cpu_frequency_minimum")] CpuFrequencyMinimum, + #[serde(with = "expression::cpu_scaling_maximum")] + CpuScalingMaximum, + + #[serde(with = "expression::cpu_core_count")] + CpuCoreCount, + + LoadAverageSince { + #[serde(rename = "load-average-since")] + duration: String, + }, + + #[serde(with = "expression::lid_closed")] + LidClosed, + + #[serde(with = "expression::hour_of_day")] + HourOfDay, + #[serde(with = "expression::power_supply_charge")] PowerSupplyCharge, #[serde(with = "expression::power_supply_discharge_rate")] PowerSupplyDischargeRate, + #[serde(with = "expression::battery_cycles")] + BatteryCycles, + + #[serde(with = "expression::battery_health")] + BatteryHealth, + + BatteryCyclesFor { + #[serde(rename = "battery-cycles-for")] + name: String, + }, + + BatteryHealthFor { + #[serde(rename = "battery-health-for")] + name: String, + }, + #[serde(with = "expression::discharging")] Discharging, @@ -620,9 +679,14 @@ pub struct EvalState<'peripherals, 'context> { pub cpu_frequency_maximum: Option, pub cpu_frequency_minimum: Option, + pub lid_closed: bool, + pub power_supply_charge: Option, pub power_supply_discharge_rate: Option, + pub battery_cycles: Option, + pub battery_health: Option, + pub discharging: bool, pub context: EvalContext<'context>, @@ -743,10 +807,21 @@ impl Expression { Boolean(crate::fs::exists(format!("/sys/module/{value}"))) }, + IsBatteryAvailable { value } => { + let value = eval!(value).try_into_string()?; + + Boolean(find_battery(&state.power_supplies, &value).is_some()) + }, FrequencyAvailable => Boolean(state.frequency_available), TurboAvailable => Boolean(state.turbo_available), - CpuUsage => Number(state.cpu_usage), + CpuUsage => { + bail!( + "`%cpu-usage` is deprecated and has been removed. Use \ + `cpu-usage-since = \"\"` instead. For example, \ + `cpu-usage-since = \"1sec\"` for CPU usage over the last second." + ) + }, CpuUsageSince { duration } => { let duration = humantime::parse_duration(duration) .with_context(|| format!("failed to parse duration '{duration}'"))?; @@ -777,11 +852,65 @@ impl Expression { CpuFrequencyMaximum => Number(try_ok!(state.cpu_frequency_maximum)), CpuFrequencyMinimum => Number(try_ok!(state.cpu_frequency_minimum)), + CpuScalingMaximum => { + let max = state + .cpus + .iter() + .filter_map(|cpu| cpu.frequency_mhz_maximum) + .max() + .map(|v| v as f64); + Number(try_ok!(max)) + }, + + CpuCoreCount => Number(state.cpus.len() as f64), + + LoadAverageSince { duration } => { + let duration = humantime::parse_duration(duration) + .with_context(|| format!("failed to parse duration '{duration}'"))?; + let recent_logs: Vec<&system::CpuLog> = state + .cpu_log + .iter() + .rev() + .take_while(|log| log.at.elapsed() < duration) + .collect(); + + if recent_logs.len() < 2 { + return Ok(None); + } + + Number( + recent_logs.iter().map(|log| log.load_average).sum::() + / recent_logs.len() as f64, + ) + }, + + LidClosed => Boolean(state.lid_closed), + + HourOfDay => { + let ts = jiff::Timestamp::now() + .in_tz("local") + .context("failed to get local timezone for `$hour-of-day`")?; + Number(ts.hour() as f64) + }, + PowerSupplyCharge => Number(try_ok!(state.power_supply_charge)), PowerSupplyDischargeRate => { Number(try_ok!(state.power_supply_discharge_rate)) }, + BatteryCycles => Number(try_ok!(state.battery_cycles)), + BatteryHealth => Number(try_ok!(state.battery_health)), + + BatteryCyclesFor { name } => { + let battery = find_battery(&state.power_supplies, name); + Number(try_ok!(battery.and_then(|ps| ps.cycles).map(|c| c as f64))) + }, + + BatteryHealthFor { name } => { + let battery = find_battery(&state.power_supplies, name); + Number(try_ok!(battery.and_then(|ps| ps.health))) + }, + Discharging => Boolean(state.discharging), literal @ (Boolean(_) | Number(_) | String(_)) => literal.clone(), @@ -1075,8 +1204,11 @@ mod tests { cpu_idle_seconds: 10.0, cpu_frequency_maximum: Some(base_freq as f64), cpu_frequency_minimum: Some(1000.0), + lid_closed: false, power_supply_charge: Some(0.8), power_supply_discharge_rate: Some(10.0), + battery_cycles: Some(100.0), + battery_health: Some(0.95), discharging: false, context: EvalContext::Cpu(&cpu), cpus: &cpus, @@ -1162,8 +1294,11 @@ mod tests { cpu_idle_seconds: 10.0, cpu_frequency_maximum: Some(3333.0), cpu_frequency_minimum: Some(1000.0), + lid_closed: false, power_supply_charge: Some(0.8), power_supply_discharge_rate: Some(10.0), + battery_cycles: Some(100.0), + battery_health: Some(0.95), discharging: false, context: EvalContext::Cpu(&cpu), cpus: &cpus, @@ -1237,8 +1372,11 @@ mod tests { cpu_idle_seconds: 0.0, cpu_frequency_maximum: Some(3333.0), cpu_frequency_minimum: Some(1000.0), + lid_closed: false, power_supply_charge: None, power_supply_discharge_rate: None, + battery_cycles: None, + battery_health: None, discharging: false, context: EvalContext::Cpu(&cpu), cpus: &cpus, diff --git a/watt/config.toml b/watt/config.toml index ee7e655..06f2f92 100644 --- a/watt/config.toml +++ b/watt/config.toml @@ -4,8 +4,8 @@ # Each rule can specify conditions and actions for CPU and power management. [[rule]] -name = "emergency-thermal-protection" if = { is-more-than = 85.0, value = "$cpu-temperature" } +name = "emergency-thermal-protection" priority = 100 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "power", then = "power" } @@ -15,8 +15,8 @@ cpu.governor = { if.is-governor-available = "powersave", th cpu.turbo = { if = "?turbo-available", then = false } [[rule]] -name = "critical-battery-preservation" if.all = [ "?discharging", { is-less-than = 0.3, value = "%power-supply-charge" } ] +name = "critical-battery-preservation" priority = 90 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "power", then = "power" } @@ -26,39 +26,51 @@ cpu.turbo = { if = "?turbo-available", then = false } power.platform-profile = { if.is-platform-profile-available = "low-power", then = "low-power" } [[rule]] -name = "high-load-performance-sustainance" if.all = [ - { is-more-than = 0.8, value = "%cpu-usage" }, + { is-more-than = 0.8, value = { cpu-usage-since = "2sec" } }, { is-less-than = 30.0, value = "$cpu-idle-seconds" }, { is-less-than = 75.0, value = "$cpu-temperature" }, ] +name = "high-load-performance-sustainance" priority = 80 -cpu.energy-perf-bias = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-perf-bias-available = "performance" }], then = "performance" } -cpu.energy-performance-preference = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-preference-available = "performance" }], then = "performance" } -cpu.governor = { if.is-governor-available = "performance", then = "performance" } -cpu.turbo = { if = "?turbo-available", then = true } +cpu.energy-perf-bias = { if.all = [ + { not.is-driver-loaded = "intel_pstate" }, + { is-energy-perf-bias-available = "performance" }, +], then = "performance" } +cpu.energy-performance-preference = { if.all = [ + { not.is-driver-loaded = "intel_pstate" }, + { is-energy-performance-preference-available = "performance" }, +], then = "performance" } +cpu.governor = { if.is-governor-available = "performance", then = "performance" } +cpu.turbo = { if = "?turbo-available", then = true } [[rule]] -name = "plugged-in-performance" if.all = [ { not = "?discharging" }, - { is-more-than = 0.1, value = "%cpu-usage" }, + { is-more-than = 0.1, value = { cpu-usage-since = "1sec" } }, { is-less-than = 80.0, value = "$cpu-temperature" }, ] +name = "plugged-in-performance" priority = 70 -cpu.energy-perf-bias = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-perf-bias-available = "balance-performance" }], then = "balance-performance" } -cpu.energy-performance-preference = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-preference-available = "performance" }], then = "performance" } -cpu.governor = { if.is-governor-available = "performance", then = "performance" } -cpu.turbo = { if = "?turbo-available", then = true } +cpu.energy-perf-bias = { if.all = [ + { not.is-driver-loaded = "intel_pstate" }, + { is-energy-perf-bias-available = "balance-performance" }, +], then = "balance-performance" } +cpu.energy-performance-preference = { if.all = [ + { not.is-driver-loaded = "intel_pstate" }, + { is-energy-performance-preference-available = "performance" }, +], then = "performance" } +cpu.governor = { if.is-governor-available = "performance", then = "performance" } +cpu.turbo = { if = "?turbo-available", then = true } [[rule]] -name = "moderate-load-balanced-performance" if.all = [ - { is-more-than = 0.4, value = "%cpu-usage" }, - { is-less-than = 0.8, value = "%cpu-usage" }, + { is-more-than = 0.4, value = { cpu-usage-since = "5sec" } }, + { is-less-than = 0.8, value = { cpu-usage-since = "5sec" } }, ] +name = "moderate-load-balanced-performance" priority = 60 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "balance-performance", then = "balance-performance" } @@ -66,11 +78,11 @@ cpu.energy-performance-preference = { if.is-energy-performance-preference-availa cpu.governor = { if.is-governor-available = "schedutil", then = "schedutil" } [[rule]] -name = "low-activity-power-saving" if.all = [ - { is-less-than = 0.2, value = "%cpu-usage" }, + { is-less-than = 0.2, value = { cpu-usage-since = "10sec" } }, { is-more-than = 60.0, value = "$cpu-idle-seconds" }, ] +name = "low-activity-power-saving" priority = 50 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "power", then = "power" } @@ -79,8 +91,8 @@ cpu.governor = { if.is-governor-available = "powersave", th cpu.turbo = { if = "?turbo-available", then = false } [[rule]] -name = "extended-idle-power-saving" if = { is-more-than = 300.0, value = "$cpu-idle-seconds" } +name = "extended-idle-power-saving" priority = 40 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "power", then = "power" } @@ -90,8 +102,8 @@ cpu.governor = { if.is-governor-available = "powersave", th cpu.turbo = { if = "?turbo-available", then = false } [[rule]] -name = "discharging-battery-conservation" if.all = [ "?discharging", { is-less-than = 0.5, value = "%power-supply-charge" } ] +name = "discharging-battery-conservation" priority = 30 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "power", then = "power" } @@ -102,8 +114,8 @@ cpu.turbo = { if = "?turbo-available", then = false } power.platform-profile = { if.is-platform-profile-available = "low-power", then = "low-power" } [[rule]] -name = "battery-balanced" if = "?discharging" +name = "battery-balanced" priority = 20 cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "balance-performance", then = "balance-performance" } @@ -114,8 +126,8 @@ cpu.governor = { if.is-governor-available = "powersave", th cpu.turbo = { if = "?turbo-available", then = false } [[rule]] -name = "default-balanced" cpu.energy-perf-bias = { if.is-energy-perf-bias-available = "balance-performance", then = "balance-performance" } cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "balance_performance", then = "balance_performance" } cpu.governor = { if.is-governor-available = "schedutil", then = "schedutil" } +name = "default-balanced" priority = 0 diff --git a/watt/power_supply.rs b/watt/power_supply.rs index bb9bf13..7c37e03 100644 --- a/watt/power_supply.rs +++ b/watt/power_supply.rs @@ -60,6 +60,9 @@ pub struct PowerSupply { pub charge_state: Option, pub charge_percent: Option, + pub cycles: Option, + pub health: Option, + pub charge_threshold_start: f64, pub charge_threshold_end: f64, @@ -155,6 +158,9 @@ impl PowerSupply { charge_state: None, charge_percent: None, + cycles: None, + health: None, + charge_threshold_start: 0.0, charge_threshold_end: 1.0, @@ -251,6 +257,35 @@ impl PowerSupply { .with_context(|| format!("failed to read {self} charge percent"))? .map(|percent| percent as f64 / 100.0); + self.cycles = fs::read_n::(self.path.join("cycle_count")) + .with_context(|| format!("failed to read {self} cycle count"))?; + + // Battery health as a percentage (0-100) + // Some systems report this as capacity_level or health + self.health = if let Some(health) = + fs::read_n::(self.path.join("health")) + .with_context(|| format!("failed to read {self} health"))? + { + Some(health as f64 / 100.0) + } else { + // Try to calculate health from energy_full vs energy_full_design + let energy_full = fs::read_n::(self.path.join("energy_full")) + .with_context(|| format!("failed to read {self} energy_full"))?; + + let energy_full_design = + fs::read_n::(self.path.join("energy_full_design")) + .with_context(|| { + format!("failed to read {self} energy_full_design") + })?; + + match (energy_full, energy_full_design) { + (Some(full), Some(design)) if design > 0 => { + Some(full as f64 / design as f64) + }, + _ => None, + } + }; + self.charge_threshold_start = fs::read_n::(self.path.join("charge_control_start_threshold")) .with_context(|| { diff --git a/watt/system.rs b/watt/system.rs index a50dd90..429ba49 100644 --- a/watt/system.rs +++ b/watt/system.rs @@ -41,6 +41,9 @@ pub struct CpuLog { /// CPU temperature in celsius. pub temperature: f64, + + /// Load average. + pub load_average: f64, } #[derive(Debug)] @@ -62,6 +65,8 @@ struct PowerSupplyLog { struct System { is_ac: bool, + lid_closed: bool, + load_average_1min: f64, load_average_5min: f64, load_average_15min: f64, @@ -76,6 +81,11 @@ struct System { power_supplies: HashSet>, /// Power supply status log. power_supply_log: VecDeque, + + /// Battery cycle count (aggregated average across all batteries). + battery_cycles: Option, + /// Battery health (aggregated average across all batteries). + battery_health: Option, } impl System { @@ -158,6 +168,15 @@ impl System { ); } + { + let start = Instant::now(); + self.scan_lid_state()?; + log::info!( + "scanned lid state in {millis}ms", + millis = start.elapsed().as_millis(), + ); + } + { let start = Instant::now(); self.scan_temperatures()?; @@ -184,6 +203,8 @@ impl System { temperature: self.cpu_temperatures.values().sum::() / self.cpu_temperatures.len() as f64, + + load_average: self.load_average_1min, }; log::debug!("appending CPU log item: {cpu_log:?}"); self.cpu_log.push_back(cpu_log); @@ -215,6 +236,54 @@ impl System { self.power_supply_log.push_back(power_supply_log); } + // Aggregate battery cycle count and health + let batteries: Vec<_> = self + .power_supplies + .iter() + .filter(|ps| ps.type_ == "Battery" && !ps.is_from_peripheral) + .collect(); + + if self.power_supplies.is_empty() || batteries.is_empty() { + self.battery_cycles = None; + self.battery_health = None; + } else { + // Calculate average cycle count across all batteries + let (cycle_sum, cycles) = + batteries + .iter() + .fold((0u64, 0u32), |(sum, count), power_supply| { + if let Some(cycles) = power_supply.cycles { + (sum + cycles, count + 1) + } else { + (sum, count) + } + }); + + self.battery_cycles = if cycles > 0 { + Some(cycle_sum as f64 / cycles as f64) + } else { + None + }; + + // Calculate average health across all batteries + let (health_sum, health_count) = + batteries + .iter() + .fold((0.0, 0u32), |(sum, count), power_supply| { + if let Some(health) = power_supply.health { + (sum + health, count + 1) + } else { + (sum, count) + } + }); + + self.battery_health = if health_count > 0 { + Some(health_sum / health_count as f64) + } else { + None + }; + } + Ok(()) } @@ -461,6 +530,105 @@ impl System { Ok(()) } + // Scan and identify the current lid state. + // XXX: Most "uniform" APIs for identifying this data rely on some abstraction + // library that *might or might not be installed*. The verbose fallback is, + // unfortunately, necessary as there is no guarantee that we can use those + // APIs. + fn scan_lid_state(&mut self) -> anyhow::Result<()> { + log::trace!("scanning lid state"); + + // Try ACPI button interface first + let acpi_lid_paths = [ + "/proc/acpi/button/lid/LID/state", // most likely to exist + "/proc/acpi/button/lid/LID0/state", + "/proc/acpi/button/lid/LID1/state", + ]; + + for path in acpi_lid_paths { + if let Some(content) = + fs::read(path).context("failed to read lid state from ACPI")? + { + // Content is typically "state: open" or "state: closed" + self.lid_closed = content.contains("closed"); + log::debug!("lid state from {path}: {content}"); + return Ok(()); + } + } + + // Try sysfs input device interface as fallback + const INPUT_PATH: &str = "/sys/class/input"; + + if let Some(input_entries) = + fs::read_dir(INPUT_PATH).context("failed to read input device entries")? + { + for entry in input_entries { + let entry = match entry { + Ok(entry) => entry, + Err(error) => { + log::debug!("failed to read input device entry: {error}"); + continue; + }, + }; + + let entry_path = entry.path(); + let device_name_path = entry_path.join("device/name"); + + let Some(name) = fs::read(&device_name_path).with_context(|| { + format!( + "failed to read input device name from '{path}'", + path = device_name_path.display(), + ) + })? + else { + continue; + }; + + // Look for lid switch input device + if name.trim() == "Lid Switch" { + // Read the lid switch state from the SW_LID capability + let state_path = entry_path.join("device/capabilities/sw"); + + let Some(sw_caps) = fs::read(&state_path).with_context(|| { + format!( + "failed to read switch capabilities from '{path}'", + path = state_path.display(), + ) + })? + else { + log::debug!( + "found lid switch at {path} but no switch capabilities file", + path = entry_path.display() + ); + continue; + }; + + // SW_LID is bit 0 in the capabilities bitmask + // The state file shows the current state of switches as a hex bitmask + // If bit 0 is set, the lid is closed + if let Ok(caps) = u64::from_str_radix(sw_caps.trim(), 16) { + self.lid_closed = (caps & 0x1) != 0; + log::debug!( + "lid state from input device {path}: {state}", + path = entry_path.display(), + state = if self.lid_closed { "closed" } else { "open" } + ); + return Ok(()); + } + } + } + } + + // If we reach here, this is likely a desktop or the lid state is not + // available Default to lid open (false) + log::debug!( + "no lid switch found, assuming desktop or lid state unavailable" + ); + self.lid_closed = false; + + Ok(()) + } + fn is_desktop(&mut self) -> anyhow::Result { log::debug!("checking chassis type to determine if system is a desktop"); if let Some(chassis_type) = fs::read("/sys/class/dmi/id/chassis_type") @@ -512,7 +680,7 @@ impl System { if !power_saving_exists { log::debug!("power saving paths do not exist, short circuting true"); - return Ok(true); // Likely a desktop. + return Ok(true); // likely a desktop. } // Default to assuming desktop if we can't determine. @@ -798,12 +966,17 @@ pub fn run_daemon(config: config::DaemonConfig) -> anyhow::Result<()> { .context("failed to read CPU hardware minimum frequency")? .map(|u64| u64 as f64), + lid_closed: system.lid_closed, + power_supply_charge: system .power_supply_log .back() .map(|log| log.charge), power_supply_discharge_rate: system.power_supply_discharge_rate(), + battery_cycles: system.battery_cycles, + battery_health: system.battery_health, + discharging: system.is_discharging(), context: config::EvalContext::WidestPossible,