diff --git a/.vscode/settings.json b/.vscode/settings.json index 349695d..6191c63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,3 @@ { - "rust-analyzer.checkOnSave.command": "clippy", - "rust-analyzer.checkOnSave.extraArgs": ["--", "-W", "clippy::pedantic"], - "rust-analyzer.server.extraEnv": { - "EXECSNOOP_PATH": "/usr/sbin/execsnoop-bpfcc" - } + "rust-analyzer.check.overrideCommand": ["just", "check-json"], } diff --git a/Cargo.lock b/Cargo.lock index f02c250..4b4d300 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,35 +4,83 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-broadcast" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ "event-listener", "futures-core", - "parking_lot", ] [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -41,87 +89,102 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ + "async-lock", "async-task", "concurrent-queue", "fastrand", "futures-lite", - "once_cell", "slab", ] +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock", + "autocfg", + "blocking", + "futures-lite", +] + [[package]] name = "async-io" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", "socket2", "waker-fn", - "winapi", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] name = "async-recursion" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] -name = "atty" -version = "0.2.14" +name = "atoi" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" + [[package]] name = "autocfg" version = "1.1.0" @@ -129,26 +192,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "base64" -version = "0.13.1" +name = "bindgen" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", +] + +[[package]] +name = "bstr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + +[[package]] +name = "bytelines" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784face321c535fcd9a1456632fa720aa53ea0640b57341d961c8c09de2da59f" +dependencies = [ + "futures", + "tokio", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -156,10 +269,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] -name = "cache-padded" -version = "1.2.0" +name = "bytes" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "castaway" @@ -172,9 +285,27 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.74" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" +dependencies = [ + "smallvec", +] [[package]] name = "cfg-if" @@ -182,39 +313,63 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", +] + [[package]] name = "clap" -version = "4.0.19" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e67816e006b17427c9b4386915109b494fec2d929c63e3bd3561234cbf1bf1e" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" dependencies = [ - "atty", + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +dependencies = [ + "anstream", + "anstyle", "bitflags", "clap_lex", "once_cell", "strsim", - "termcolor", ] [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "compact_str" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5138945395949e7dfba09646dc9e766b548ff48e23deb5246890e6b64ae9e1b9" +checksum = "bff0805f79ecb1b35163f3957a6934ea8d04fcd36ef98b52e7316f63e72e73d1" dependencies = [ "castaway", + "cfg-if", "itoa", "ryu", "serde", + "static_assertions", ] [[package]] @@ -225,11 +380,11 @@ checksum = "c5b80dba65d26e0c4b692ad0312b837f1177e8175031af57fd1de4f3bc36b430" [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] @@ -252,15 +407,30 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -279,14 +449,14 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -314,9 +484,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +checksum = "0044ebdf7fbb2a772e0c0233a9d3173c5cd8af8ae7078d4c5188af44ffffaa4b" dependencies = [ "enumflags2_derive", "serde", @@ -324,13 +494,34 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "9d2c772ccdbdfd1967b4f5d79d17c98ebf92009fdcc838db7aa434462f600c26" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] @@ -341,28 +532,34 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "execsnoop" -version = "0.1.0" +version = "0.2.0" +dependencies = [ + "atoi", + "bstr", + "bytelines", + "tracing", +] [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "fomat-macros" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe56556a8c9f9f556150eb6b390bc1a8b3715fd2ddbb4585f36b6a5672c6a833" +checksum = "3f722aa875298d34a0ebb6004699f6f4ea830d36dec8ac2effdbbc840248a096" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -375,9 +572,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -385,15 +582,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -402,15 +599,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -423,32 +620,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -462,11 +659,24 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -483,21 +693,55 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -507,6 +751,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "ioprio" version = "0.2.0" @@ -514,14 +769,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aed03c9a950f47958e5c6c4f974cf1b609aee1857ecc34bf68166dc6af782efc" dependencies = [ "libc", - "nix 0.21.0", + "nix 0.21.2", +] + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "kdl" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062c875482ccb676fd40c804a40e3824d4464c18c364547456d1c8e8e951ae47" +dependencies = [ + "miette", + "nom", + "thiserror", +] [[package]] name = "lazy_static" @@ -529,22 +807,51 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.137" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] -name = "lock_api" -version = "0.4.9" +name = "libspa" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "667dfbb50c3d1f7ee1d33afdc04d1255923ece7642db3303046e7d63d997d77d" dependencies = [ - "autocfg", - "scopeguard", + "bitflags", + "cc", + "cookie-factory", + "errno", + "libc", + "libspa-sys", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5b88f52534df7ca88d451ae9628e22124e3cc5c60966465a7db479534c7a" +dependencies = [ + "bindgen", + "cc", + "system-deps", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + [[package]] name = "log" version = "0.4.17" @@ -579,51 +886,90 @@ dependencies = [ ] [[package]] -name = "next-gen" -version = "0.1.1" +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1962f0b64c859f27f9551c74afbdbec7090fa83518daf6c5eb5b31d153455beb" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ - "next-gen-proc_macros", - "unwind_safe", + "autocfg", ] [[package]] -name = "next-gen-proc_macros" -version = "0.1.1" +name = "miette" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abdc09c381c9336b9f2e9bd6067a9a5290d20e2d2e2296f275456121c33ae89" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a59395d2ffdd03894479cdd1ce4b7e0700d379d517f2d396cee2a4828707c5a0" +checksum = "8842972f23939443013dfd3720f46772b743e86f1a81d120d4b6fb090f87de1c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", ] [[package]] name = "nix" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "autocfg", "bitflags", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", + "static_assertions", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", ] [[package]] @@ -636,37 +982,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "ordered-stream" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034ce384018b245e8d8424bbe90577fbd91a533be74107e465e3474eb2285eef" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", -] - -[[package]] -name = "os_str_bytes" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +] [[package]] name = "overload" @@ -681,27 +1030,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project-lite" @@ -715,51 +1047,94 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pipewire" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2180a4a84b855be86e6cd72fa6fd4318278871d2b1082e7cd05fe64b135ccb" +dependencies = [ + "anyhow", + "bitflags", + "errno", + "libc", + "libspa", + "libspa-sys", + "nix 0.26.2", + "once_cell", + "pipewire-sys", + "thiserror", +] + +[[package]] +name = "pipewire-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a95290eedb7fb6aa3922fdc0261cd0ddeb940abcdbdef28778928106554d2123" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps", +] + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "polling" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ "autocfg", "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] +[[package]] +name = "qcell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76babcf954d92eee717a7c9bfc4921d91dfbf2124d9bfb6ed1b0b3d0c47361e3" +dependencies = [ + "once_cell", +] + [[package]] name = "quote" -version = "1.0.21" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -803,6 +1178,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -810,15 +1194,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -836,76 +1220,80 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "ron" -version = "0.8.0" -source = "git+https://github.com/MomoLangenstein/ron?branch=253-untagged-enums#afb960bb8b0402a79260533aa3b9d87a8abae72b" +name = "rustix" +version = "0.37.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d097081ed288dfe45699b72f5b5d648e5f15d64d900c7080273baa20c16a6849" dependencies = [ - "base64", "bitflags", - "serde", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "scopeguard" -version = "1.1.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", ] [[package]] @@ -928,11 +1316,26 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -945,9 +1348,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -967,35 +1370,65 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "system76-scheduler" -version = "1.2.2" +version = "2.0.0" dependencies = [ + "aho-corasick", "anyhow", + "atoi", + "bstr", "clap", "compact_str", "concat-in-place", - "const_format", "execsnoop", "fomat-macros", "futures", + "futures-lite", "ioprio", "itoa", "libc", - "next-gen", + "memchr", "num_cpus", - "ron", + "pipewire", + "qcell", "serde", "serde_repr", + "system76-scheduler-config", + "system76-scheduler-pipewire", "thiserror", "tokio", "tracing", @@ -1006,86 +1439,136 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +name = "system76-scheduler-config" +version = "2.0.0" dependencies = [ - "cfg-if", - "fastrand", + "compact_str", + "concat-in-place", + "const_format", + "fomat-macros", + "generator", + "ioprio", + "kdl", "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "memchr", + "tracing", + "wildmatch", +] + +[[package]] +name = "system76-scheduler-pipewire" +version = "0.1.0" +dependencies = [ + "atoi", + "bstr", + "itoa", + "pipewire", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "tempfile" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "tokio" -version = "1.21.2" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", + "bytes", + "libc", + "mio", "pin-project-lite", + "signal-hook-registry", + "socket2", "tokio-macros", + "tracing", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ + "indexmap", "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -1108,7 +1591,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1152,9 +1635,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "uds_windows" @@ -1168,40 +1651,51 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "unwind_safe" -version = "0.1.0" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "upower_dbus" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a39608eae3fae334e50dcf0730873e0aa26c665fe67276660783d10b71b28b" +checksum = "b0b1d77de98ab2e5187f5fa2b045b8c4eecfc49e5ccd07f16f4c89f008116f8c" dependencies = [ "serde", "serde_repr", "zbus", - "zvariant", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "version_check" version = "0.9.4" @@ -1229,6 +1723,12 @@ dependencies = [ "cc", ] +[[package]] +name = "wildmatch" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86" + [[package]] name = "winapi" version = "0.3.9" @@ -1245,87 +1745,186 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] [[package]] name = "zbus" -version = "3.4.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a0b85c5608c27d2306d67e955b9c6e23a42d824205c85038a7afbe19c0ae22" +checksum = "3dc29e76f558b2cb94190e8605ecfe77dd40f5df8c072951714b4b71a97f5848" dependencies = [ "async-broadcast", - "async-channel", "async-executor", + "async-fs", "async-io", "async-lock", "async-recursion", @@ -1340,7 +1939,8 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.25.0", + "lazy_static", + "nix 0.26.2", "once_cell", "ordered-stream", "rand", @@ -1348,6 +1948,7 @@ dependencies = [ "serde_repr", "sha1", "static_assertions", + "tokio", "tracing", "uds_windows", "winapi", @@ -1358,22 +1959,23 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.4.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b18018648e7e10ed856809befe7309002b87b2b12d5b282cb5040d7974b58677" +checksum = "62a80fd82c011cd08459eaaf1fd83d3090c1b61e6d5284360074a7475af3a85d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "regex", - "syn", + "syn 1.0.109", + "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5" +checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" dependencies = [ "serde", "static_assertions", @@ -1382,9 +1984,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.7.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794fb7f59af4105697b0449ba31731ee5dbb3e773a17dbdf3d36206ea1b1644" +checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8" dependencies = [ "byteorder", "enumflags2", @@ -1396,12 +1998,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.7.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd58d4b6c8e26d3dd2149c8c40c6613ef6451b9885ff1296d1ac86c388351a54" +checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] diff --git a/Cargo.toml b/Cargo.toml index c3b2f2b..bdfe8c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["daemon", "execsnoop"] \ No newline at end of file +members = ["config", "daemon", "execsnoop", "pipewire"] diff --git a/README.md b/README.md index c158434..1bc68e2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,24 @@ Scheduling service which optimizes Linux's CPU scheduler and automatically assig These changes result in a noticeable improvement in the experienced smoothness and performance of applications and games. The improved responsiveness of applications is most noticeable on older systems with budget hardware, whereas games will benefit from higher framerates and reduced jitter. This is because background applications and services will be given a smaller portion of leftover CPU budget after the active process has had the most time on the CPU. +## Install + +Requires dependencies as defined in the [debian/control](./debian/control) file: + +- cargo & rustc +- clang +- just +- libclang-dev +- libpipewire-0.3-dev +- pkg-config + +Then the included justfile can be used to build and install: + +```sh +just execsnoop=$(which execsnoop-bpfcc) build-release +sudo just sysconfdir=/usr/share install +``` + ## DBus - Interface: `com.system76.Scheduler` @@ -15,106 +33,73 @@ The `SetForeground(u32)` method can be called to change the active foreground pr The configuration file is stored at the following locations: -- User-config: `/etc/system76-scheduler/config.ron` -- Distribution: `/usr/share/system76-scheduler/config.ron` - -```rs -{ - // The priority to assign background tasks. - background: Some(5), +- System: `/etc/system76-scheduler/config.kdl` +- Distribution: `/usr/share/system76-scheduler/config.kdl` - // The priority to assign foreground tasks. - foreground: Some(-5), -} -``` +Presence of the system configuration will override the distribution configuration. The documented [default configuration can be found here](./data/config.kdl). -- When `background` is set to `None`, background process priorities will not be assigned. -- When `foreground` is set to `None`, foreground and background priorities will not be assigned. +Note that if the `background` and `foreground` assignment profiles are defined, then foreground process management will be enabled. Likewise, if a `pipewire` profile is defined, then pipewire process monitoring will be enabled. ## Process Priority Assignments -RON configuration files are stored at the following locations: +In addition to `config.kdl`, additional process scheduling profiles are stored in: -- User-config: `/etc/system76-scheduler/assignments/` -- Distribution: `/usr/share/system76-scheduler/assignments/` +- User-config: `/etc/system76-scheduler/process-scheduler/` +- Distribution: `/usr/share/system76-scheduler/process-scheduler/` -They define the default priorities for processes scanned. The syntax of `.ron` configuration files in these directories is as follows: +An [example configuration is provided here](./data/pop-os.kdl). It is parsed the same as the assignments and exceptions nodes in the main config, and profiles can inherit values from the previous assignment of the same name. -```rs -{ - (CPU_PRIORITY, IO_PRIORITY): [ - "exe_name1", - "exe_name2" - ], - CPU_PRIORITY: [ - "exe_name3", - "exe_name4" - ], - IO_PRIORITY: [ - "exe_name5" - ] +### Profile + +``` +assignments { + {{profile-name}} {{profile-properties}} } ``` -Where: +The `profile-name` can refer to any name of your choice. If the name matches a previous assignment, it will inherit the values from that assignment, plus any additional profile properties assigned. -- `CPU_PRIORITY` is a number between `-20` and `19` -- `IO_PRIORITY` is one of: - - `Idle` - - `Standard` - - `BestEffort(PRIORITY_LEVEL)` - - `Realtime(PRIORITY_LEVEL)` -- `PRIORITY_LEVEL` is a number between `0` and `7` +The `profile-properties` may contain any of +- Niceness priority, defined as `nice=-20` through `nice=19` -A real world example below: +- A scheduler policy defined as one of: + - `sched="batch"` + - `sched="idle"`, + - `sched="other"` + - `sched=(fifo)1` through `sched=(fifo)99` + - `sched=(rr)1` through `sched=(rr)99` -```rs -{ - // Very high - (-9, BestEffort(0)): [ - "easyeffects", - ], - // High priority - (-5, BestEffort(4)): [ - "gnome-shell", - "kwin", - "Xorg" - ], - // Default - 0: [ "dbus", "dbus-daemon", "systemd"], - // Absolute lowest priority - (19, Idle): [ - "c++", - "cargo", - "clang", - "cpp", - "g++", - "gcc", - "lld", - "make", - "rustc", - ] -} -``` +> Realtime scheduler policies assign a priority level between 1 and 99. Higher values have higher priority. It is recommended not to set a higher priority than hardware IRQs (>49) -## Process Priority Exceptions +- An I/O priority defined as one of + - `io="idle"` + - `io=(best-effort)0` through `io=(best-effort)7` + - `io=(realtime)0` through `io=(realtime)7` -RON configuration files are stored at the following locations: +> The best-effort and realtime classes have priority levels between 0 and 7, where 7 has the least priority, and 0 is the highest priority -- User-config: `/etc/system76-scheduler/exceptions/` -- Distribution: `/usr/share/system76-scheduler/exceptions/` +### Assignments -The files contain a list of process names that are prohibited from having priority adjustments. +Each child element of a profile defines th process(es) to assign to the profile. -```rs -([ -"pipewire", -"pipewire-pulse", -"wireplumber" -]) +```kdl +{{profile-name}} {{profile-properties}} { + "/match/by/cmdline" {{profile-properties}} + match-by-name {{profile-properties}} + * {{condition-properties}} {{profile-properties}} +} ``` +- A node name starting with a `/` is a match by command line path +- A node name otherwise is a match by process name +- `*` matches all processes, used with additional `condition-properties` + - properties are [wild-match'd](https://github.com/becheran/wildmatch) + - properties may start with `!` to exclude results matching the condition + - `cgroup="cgroup-path"` matches processes by a cgroup + - `parent="name"` matches processes by the process name of the parent + + ## CPU Scheduler Latency Configurations ### Default diff --git a/config/Cargo.toml b/config/Cargo.toml new file mode 100644 index 0000000..93752e2 --- /dev/null +++ b/config/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "system76-scheduler-config" +version = "2.0.0" +edition = "2021" + +[dependencies] +compact_str = { version = "0.7.0", features = ["serde"]} +concat-in-place = "1.1.0" +tracing = "0.1.37" +fomat-macros = "0.3.2" +const_format = "0.2.30" +kdl = "4.6.0" +generator = "0.7.3" +wildmatch = "2.1.1" +ioprio = "0.2.0" +memchr = "2.5.0" +libc = "0.2.141" diff --git a/config/src/cfs.rs b/config/src/cfs.rs new file mode 100644 index 0000000..250bcfb --- /dev/null +++ b/config/src/cfs.rs @@ -0,0 +1,119 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use compact_str::CompactString; +use kdl::KdlNode; +use std::collections::BTreeMap; + +/// CFS configurations +pub struct Config { + /// Enables CFS configuration + pub enable: bool, + + /// CFS profiles + pub profiles: BTreeMap, +} + +impl Default for Config { + fn default() -> Self { + let mut config = Self { + enable: false, + profiles: BTreeMap::new(), + }; + + config + .profiles + .insert("default".into(), crate::cfs::PROFILE_DEFAULT); + config + .profiles + .insert("responsive".into(), crate::cfs::PROFILE_RESPONSIVE); + config + } +} + +/// Default CFS profile +pub const PROFILE_DEFAULT: Profile = Profile { + latency: 6, + nr_latency: 8, + wakeup_granularity: 1.0, + bandwidth_size: 5, + preempt: "voluntary", +}; + +/// Responsive CFS profile +pub const PROFILE_RESPONSIVE: Profile = Profile { + latency: 4, + nr_latency: 10, + wakeup_granularity: 0.5, + bandwidth_size: 3, + preempt: "full", +}; + +/// CFS Profile +pub struct Profile { + /// Preemption latency for CPU-bound tasks in ns + pub latency: u64, + /// Used to calculate the minimum preemption granularity + pub nr_latency: u64, + /// Wakeup preemption granularity for CPU-bound tasks in ms + pub wakeup_granularity: f64, + /// Amount of time to allocate from global to local pool in us + pub bandwidth_size: u64, + /// The type of preemption to use. + pub preempt: &'static str, +} + +/// Parses CFS profiles from a KDL node +pub fn parse(nodes: &[KdlNode]) -> impl Iterator { + nodes.iter().map(|node| { + let mut config = PROFILE_DEFAULT; + + for (name, entry) in crate::kdl::iter_properties(node) { + match name { + "latency" => + { + #[allow(clippy::cast_sign_loss)] + if let Some(value) = entry.value().as_i64() { + config.latency = value as u64; + } + } + + "nr-latency" => + { + #[allow(clippy::cast_sign_loss)] + if let Some(value) = entry.value().as_i64() { + config.nr_latency = value as u64; + } + } + + "wakeup-granularity" => { + if let Some(value) = entry.value().as_f64() { + config.wakeup_granularity = value; + } + } + + "bandwidth-size" => + { + #[allow(clippy::cast_sign_loss)] + if let Some(value) = entry.value().as_i64() { + config.bandwidth_size = value as u64; + } + } + + "preempt" => { + if let Some(value) = entry.value().as_string() { + match value { + "voluntary" => config.preempt = "voluntary", + "full" => config.preempt = "full", + _ => tracing::warn!("preempt expected one of: voluntary full"), + } + } + } + + _ => (), + } + } + + (node.name().value(), config) + }) +} diff --git a/config/src/kdl.rs b/config/src/kdl.rs new file mode 100644 index 0000000..2aa6182 --- /dev/null +++ b/config/src/kdl.rs @@ -0,0 +1,72 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use std::str::FromStr; + +use kdl::{KdlDocument, KdlEntry, KdlNode, NodeKey}; + +pub fn fields(document: &KdlDocument) -> impl Iterator { + document + .nodes() + .iter() + .map(|node| (node.name().value(), node)) +} + +pub trait NodeExt { + /// Check the value of the `enable` property if set. + fn enabled(&self) -> Option; + + fn get_bool(&self, index: impl Into) -> Option; + + fn get_string(&self, index: impl Into) -> Option<&str>; + + fn get_u16(&self, index: impl Into) -> Option; +} + +impl NodeExt for KdlNode { + fn enabled(&self) -> Option { + self.get("enable")?.value().as_bool() + } + + fn get_bool(&self, index: impl Into) -> Option { + self.get(index)?.value().as_bool() + } + + fn get_string(&self, index: impl Into) -> Option<&str> { + self.get(index)?.value().as_string() + } + + fn get_u16(&self, index: impl Into) -> Option { + u16::try_from(self.get(index)?.value().as_i64()?).ok() + } +} + +pub fn iter_properties(node: &KdlNode) -> impl Iterator { + node.entries() + .iter() + .filter_map(|entry| entry.name().map(|id| (id.value(), entry))) +} + +pub trait EntryExt { + fn as_i8(&self) -> Option; + + fn as_u8(&self) -> Option; + + fn parse_to(&self) -> Option; +} + +impl EntryExt for KdlEntry { + fn as_i8(&self) -> Option { + self.value().as_i64().and_then(|raw| i8::try_from(raw).ok()) + } + + fn as_u8(&self) -> Option { + self.value().as_i64().and_then(|raw| u8::try_from(raw).ok()) + } + + fn parse_to(&self) -> Option { + self.value() + .as_string() + .and_then(|raw| raw.parse::().ok()) + } +} diff --git a/config/src/lib.rs b/config/src/lib.rs new file mode 100644 index 0000000..2ef9db2 --- /dev/null +++ b/config/src/lib.rs @@ -0,0 +1,72 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +#![deny(missing_docs)] + +//! System76 Scheduler's configuration parsing and logic. + +/// CFS configurations +pub mod cfs; + +pub(crate) mod kdl; + +mod parser; + +/// Process scheduler configurations +pub mod scheduler; + +use std::{ + fs::File, + io::{self, Read}, +}; + +const DISTRIBUTION_PATH: &str = "/usr/share/system76-scheduler/"; +const SYSTEM_CONF_PATH: &str = "/etc/system76-scheduler/"; + +/// System76 Scheduler configuration +#[must_use] +#[derive(Default)] +pub struct Config { + /// Controls autogrouping status + pub autogroup_enabled: bool, + + /// CFS profiles + pub cfs_profiles: cfs::Config, + + /// Process scheduler config + pub process_scheduler: scheduler::Config, +} + +/// Parses the scheduler's configuration files +pub fn config() -> Config { + parser::read_config() +} + +/// Locates configuration files of a given extension from the given paths. +pub fn configuration_files<'a>( + paths: &'a [&str], + extension: &'a str, +) -> impl Iterator + 'a { + generator::Gn::new_scoped(move |mut scope| { + for directory in paths { + if let Ok(dir) = std::fs::read_dir(directory) { + for entry in dir.filter_map(Result::ok) { + if let Some(file_name) = entry.file_name().to_str() { + if file_name.ends_with(extension) { + scope.yield_([directory, "/", file_name].concat()); + } + } + } + } + } + + generator::done!() + }) +} + +fn read_into_string<'a>(buf: &'a mut String, path: &str) -> io::Result<&'a str> { + let mut file = File::open(path)?; + buf.clear(); + file.read_to_string(buf)?; + Ok(&*buf) +} diff --git a/config/src/parser/cfs.rs b/config/src/parser/cfs.rs new file mode 100644 index 0000000..aa474a1 --- /dev/null +++ b/config/src/parser/cfs.rs @@ -0,0 +1,21 @@ +use crate::{cfs::Config, kdl::NodeExt}; +use kdl::KdlNode; + +impl Config { + /// Parses the CFS document node + pub fn read(&mut self, node: &KdlNode) { + self.enable = node.enabled().unwrap_or(true); + + if !self.enable { + return; + } + + let Some(profiles) = node.children() else { + return; + }; + + for (name, profile) in crate::cfs::parse(profiles.nodes()) { + self.profiles.insert(name.into(), profile); + } + } +} diff --git a/config/src/parser/mod.rs b/config/src/parser/mod.rs new file mode 100644 index 0000000..57f4cfb --- /dev/null +++ b/config/src/parser/mod.rs @@ -0,0 +1,146 @@ +mod cfs; +mod scheduler; + +use std::path::Path; + +use crate::kdl::NodeExt; +use crate::scheduler::ForegroundAssignments; +use crate::{configuration_files, Config, DISTRIBUTION_PATH, SYSTEM_CONF_PATH}; +use ::kdl::KdlDocument; +use const_format::concatcp; + +pub fn read_config() -> Config { + let buffer = &mut String::with_capacity(4096); + + let mut config = read_assignments(read_main(buffer), buffer); + + let background = config + .process_scheduler + .assignments + .profiles + .remove("background"); + + let foreground = config + .process_scheduler + .assignments + .profiles + .remove("foreground"); + + if let (Some(background), Some(foreground)) = (background, foreground) { + config.process_scheduler.foreground = Some(ForegroundAssignments { + background, + foreground, + }); + } + + config.process_scheduler.pipewire = config + .process_scheduler + .assignments + .profiles + .remove("pipewire"); + + config +} + +fn read_main(buffer: &mut String) -> Config { + const DIST_CONF: &str = concatcp!(DISTRIBUTION_PATH, "config.kdl"); + const SYSTEM_CONF: &str = concatcp!(SYSTEM_CONF_PATH, "config.kdl"); + + let mut config = Config::default(); + + let path = if Path::new(SYSTEM_CONF).exists() { + SYSTEM_CONF + } else if Path::new(DIST_CONF).exists() { + DIST_CONF + } else { + return config; + }; + + let span = tracing::warn_span!("parser::read_main", path); + let _entered = span.enter(); + + let Ok(buffer) = crate::read_into_string(buffer, path) else { + tracing::error!("failed to read file"); + return config; + }; + + let document = match buffer.parse::() { + Ok(document) => document, + Err(why) => { + let offset = why.span.offset(); + + let mut line_number = 1; + + let mut buffer = &buffer.as_bytes()[..offset]; + + while let Some(pos) = memchr::memchr(b'\n', buffer) { + line_number += 1; + buffer = &buffer[pos + 1..]; + } + + tracing::error!("parsing error on line {}: {}", line_number, why); + return config; + } + }; + + for node in document.nodes() { + match node.name().value() { + "autogroup-enabled" => { + config.autogroup_enabled = node.get_bool(0).unwrap_or(false); + } + "cfs-profiles" => config.cfs_profiles.read(node), + "process-scheduler" => config.process_scheduler.read(node), + "version" => (), + other => { + tracing::warn!("unknown element: {}", other); + } + } + } + + config +} + +fn read_assignments(mut config: Config, buffer: &mut String) -> Config { + const PATHS: [&str; 2] = [ + concatcp!(DISTRIBUTION_PATH, "process-scheduler/"), + concatcp!(SYSTEM_CONF_PATH, "process-scheduler/"), + ]; + + for path in configuration_files(&PATHS, ".kdl") { + if !Path::new(&path).exists() { + continue; + } + let span = tracing::warn_span!("parser::read_assignments", path); + let _entered = span.enter(); + + let Ok(buffer) = crate::read_into_string(buffer, &path) else { + continue; + }; + + let document = match buffer.parse::() { + Ok(document) => document, + Err(why) => { + tracing::error!("parsing error: {}", why); + continue; + } + }; + + for node in document.nodes() { + match node.name().value() { + "assignments" => { + config.process_scheduler.assignments.parse(node); + } + + "exceptions" => { + config.process_scheduler.assignments.parse_exceptions(node); + } + + other => { + tracing::warn!("unknown field: {}", other); + } + } + } + } + + config +} diff --git a/config/src/parser/scheduler.rs b/config/src/parser/scheduler.rs new file mode 100644 index 0000000..29fc913 --- /dev/null +++ b/config/src/parser/scheduler.rs @@ -0,0 +1,298 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use std::sync::Arc; + +use crate::kdl::NodeExt; +use crate::scheduler::{Assignments, Condition, Config, MatchCondition, Profile}; +use crate::{ + kdl::EntryExt, + scheduler::{IoClass, Niceness, SchedPolicy, SchedPriority}, +}; +use kdl::{KdlEntry, KdlIdentifier, KdlNode}; + +impl Config { + /// Parses the process-scheduler node + pub fn read(&mut self, node: &KdlNode) { + self.enable = node.enabled().unwrap_or(true); + + if !self.enable { + return; + } + + if let Some(fields) = node.children() { + for (name, node) in crate::kdl::fields(fields) { + match name { + "refresh-rate" => { + if let Some(value) = node.get_u16(0) { + self.refresh_rate = value; + } + } + + "execsnoop" => { + if let Some(value) = node.get_bool(0) { + self.execsnoop = value; + } + } + + "assignments" => self.assignments.parse(node), + + "exceptions" => self.assignments.parse_exceptions(node), + + other => { + tracing::warn!("unknown element: {}", other); + } + } + } + } + } +} + +impl Assignments { + /// Parses the assignments node + pub fn parse(&mut self, node: &KdlNode) { + #[derive(PartialEq, Eq)] + enum ParseCondition { + Include, + Exclude, + Name, + } + + let Some(document) = node.children() else { + return; + }; + + for profile_node in document.nodes() { + let profile_name = Arc::from(profile_node.name().value()); + + let span = tracing::warn_span!("Assignments::parse", profile = &*profile_name); + let _entered = span.enter(); + + // Stores the properties defined for this profile profile. + let (exists, profile) = self.profile(&profile_name).map_or_else( + || (false, Profile::new(profile_name.clone())), + |p| (true, p.clone()), + ); + + let profile = profile.parse(profile_node); + + if !exists { + self.profile_insert(profile_name.clone(), profile.clone()); + } + + if let Some(rules) = profile_node.children() { + for (number, pattern) in rules.nodes().iter().enumerate() { + let name = pattern.name().value(); + + let span = tracing::warn_span!("assignment", number = number + 1, name); + let _entered = span.enter(); + + let parse_condition = match name { + "include" => ParseCondition::Include, + "exclude" => ParseCondition::Exclude, + _ => ParseCondition::Name, + }; + + match parse_condition { + ParseCondition::Include | ParseCondition::Exclude => { + let mut condition = Condition::default(); + let mut profile = profile.clone(); + + for (property, entry) in + profile.parse_properties(crate::kdl::iter_properties(pattern)) + { + match property { + "cgroup" => { + condition.cgroup = + entry.value().as_string().map(MatchCondition::new); + } + "descends" => { + condition.descends = + entry.value().as_string().map(MatchCondition::new); + } + "name" => { + condition.name = + entry.value().as_string().map(MatchCondition::new); + } + "parent" => { + if let Some(parent) = entry.value().as_string() { + condition.parent.push(MatchCondition::new(parent)); + } + } + _ => { + tracing::error!("unknown property: {}", property); + } + } + } + + let has_condition = condition.cgroup.is_some() + || condition.descends.is_some() + || condition.name.is_some() + || !condition.parent.is_empty(); + + if has_condition { + self.assign_by_condition( + &profile_name, + condition, + profile, + ParseCondition::Include == parse_condition, + ); + } + } + + ParseCondition::Name => { + let profile = profile.clone().parse(pattern); + if name.starts_with('/') { + self.assign_by_cmdline(name, profile); + } else { + self.assign_by_name(name, profile); + } + } + } + } + } + } + } + + /// Parses the exceptions node + pub fn parse_exceptions(&mut self, node: &KdlNode) { + let Some(document) = node.children() else { + return; + }; + + for node in document.nodes() { + let exception = node.name().value(); + + if exception == "include" { + let mut condition = Condition::default(); + + for (property, entry) in crate::kdl::iter_properties(node) { + match property { + "cgroup" => { + if let Some(value) = entry.value().as_string() { + condition.cgroup = Some(MatchCondition::new(value)); + } + } + "descends" => { + if let Some(value) = entry.value().as_string() { + condition.descends = Some(MatchCondition::new(value)); + } + } + "parent" => { + if let Some(value) = entry.value().as_string() { + condition.parent.push(MatchCondition::new(value)); + } + } + _ => (), + } + } + + self.assign_exception_by_condition(condition); + } else if exception.starts_with('/') { + self.assign_exception_by_cmdline(exception); + } else { + self.assign_exception_by_name(exception); + } + } + } +} + +impl Profile { + /// Parses a profile node + pub fn parse(mut self, node: &KdlNode) -> Self { + for (property, _) in self.parse_properties(crate::kdl::iter_properties(node)) { + tracing::error!("unknown property: {}", property); + } + + self + } + + /// Parses the properties of the profile + pub fn parse_properties<'a>( + &'a mut self, + entries: impl Iterator + 'a, + ) -> impl Iterator + 'a { + entries.filter(|&(property, entry)| { + match property { + "io" => self.parse_io(entry), + "nice" => self.parse_nice(entry), + "sched" => self.parse_sched(entry), + _ => return true, + } + + false + }) + } + + /// Parses the `io` property + #[tracing::instrument(skip_all)] + pub fn parse_io(&mut self, entry: &KdlEntry) { + let class = entry + .ty() + .map(KdlIdentifier::value) + .or_else(|| entry.value().as_string()); + + let Some(class) = class else { + tracing::warn!("expects class: idle best-effort realtime"); + return; + }; + + let Ok(class) = class.parse::() else { + tracing::error!("unknown class: {}", class); + return; + }; + + self.io = match class { + IoClass::BestEffort => ioprio::Class::BestEffort( + ioprio::BePriorityLevel::from_level(entry.as_u8().unwrap_or(7)) + .unwrap_or_else(ioprio::BePriorityLevel::lowest), + ), + + IoClass::Idle => ioprio::Class::Idle, + + IoClass::Realtime => ioprio::Class::Realtime( + ioprio::RtPriorityLevel::from_level(entry.as_u8().unwrap_or(7)) + .unwrap_or_else(ioprio::RtPriorityLevel::lowest), + ), + }; + } + + /// Parses the `nice` property + #[tracing::instrument(skip_all)] + pub fn parse_nice(&mut self, entry: &KdlEntry) { + let Some(niceness) = entry.as_i8() else { + tracing::error!("expects number between -20 and 19"); + return + }; + + self.nice = Some(Niceness::from(niceness)); + } + + /// Parses the `sched` property + #[tracing::instrument(skip_all)] + pub fn parse_sched(&mut self, entry: &KdlEntry) { + if let Some(policy) = entry.ty().map(KdlIdentifier::value) { + let Ok(policy) = policy.parse::() else { + tracing::error!("unknown sched policy"); + return + }; + + let Some(priority) = entry.as_u8() else { + tracing::error!("expected priority assignment between 1-99"); + return + }; + + self.sched_policy = policy; + self.sched_priority = SchedPriority::from(priority); + + return; + } + + let Some(policy) = entry.parse_to::() else { + tracing::error!("expected one of: batch deadline fifo idle other rr"); + return + }; + + self.sched_policy = policy; + } +} diff --git a/config/src/scheduler/assignments.rs b/config/src/scheduler/assignments.rs new file mode 100644 index 0000000..e3a4ba7 --- /dev/null +++ b/config/src/scheduler/assignments.rs @@ -0,0 +1,160 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::Profile; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + sync::Arc, +}; +use wildmatch::WildMatch; + +/// Conditional assignment +#[derive(Default, Debug)] +pub struct Condition { + /// Match by process descendant + pub descends: Option, + /// Match by cgroup + pub cgroup: Option, + /// Match by process name + pub name: Option, + /// Match by process parent + pub parent: Vec, +} + +/// A wildcard string match which either is or isn't +#[must_use] +#[derive(Debug)] +pub enum MatchCondition { + /// Is a match for the wildcard + Is(WildMatch), + /// Is not a match for the wildcard + IsNot(WildMatch), +} + +impl MatchCondition { + /// Parses a `MatchCondition` + pub fn new(input: &str) -> Self { + if let Some(input) = input.strip_prefix('!') { + Self::IsNot(WildMatch::new(input)) + } else { + Self::Is(WildMatch::new(input)) + } + } + + /// Identifies if the input is a match for the condition + #[must_use] + pub fn matches(&self, input: &str) -> bool { + match self { + Self::Is(condition) => condition.matches(input), + Self::IsNot(condition) => !condition.matches(input), + } + } +} + +/// Process scheduler assignments +#[derive(Default, Debug)] +pub struct Assignments { + /// Conditional assignments + #[allow(clippy::type_complexity)] + pub conditions: HashMap, (Profile, Vec<(Condition, bool)>)>, + /// Exceptions by name + pub(crate) exceptions_by_name: BTreeSet>, + /// Exceptions by cmdline + pub(crate) exceptions_by_cmdline: BTreeSet>, + /// Conditional exceptions + pub exceptions_conditions: Vec, + /// Assignment profiles + pub(crate) profiles: BTreeMap, Profile>, + /// Profiles mapped by name + pub(crate) profile_by_name: BTreeMap, Profile>, + /// Profiles mapped by cmdline + pub(crate) profile_by_cmdline: BTreeMap, Profile>, +} + +impl Assignments { + /// Clears all assignments + pub fn clear(&mut self) { + self.conditions.clear(); + self.profiles.clear(); + self.profile_by_name.clear(); + self.profile_by_cmdline.clear(); + self.exceptions_by_cmdline.clear(); + self.exceptions_by_name.clear(); + self.exceptions_conditions.clear(); + } + + /// Get a matching profile for a process by its name + #[must_use] + pub fn get_by_name<'a>(&'a self, process: &str) -> Option<&'a Profile> { + self.profile_by_name.get(process) + } + + /// Get a matching profile for a process by its cmdline + #[must_use] + pub fn get_by_cmdline<'a>(&'a self, process: &str) -> Option<&'a Profile> { + self.profile_by_cmdline.get(process) + } + + /// Check if a process is excepted by its name + #[must_use] + pub fn is_exception_by_name(&self, name: &str) -> bool { + self.exceptions_by_name.contains(name) + } + + /// Check if a process is excepted by its cmdline + #[must_use] + pub fn is_exception_by_cmdline(&self, name: &str) -> bool { + self.exceptions_by_cmdline.contains(name) + } + + /// Get a profile by the profile's name + #[must_use] + pub fn profile<'a>(&'a self, profile: &str) -> Option<&'a Profile> { + self.profiles.get(profile) + } + + /// Insert a new profile + pub fn profile_insert(&mut self, name: Arc, profile: Profile) { + self.profiles.insert(name, profile); + } + + /// Assign a process to a profile by the process's name + pub fn assign_by_name(&mut self, name: &str, profile: Profile) { + self.profile_by_name.insert(name.into(), profile); + } + + /// Assign a condition to a profile + pub fn assign_by_condition( + &mut self, + name: &str, + condition: Condition, + profile: Profile, + include: bool, + ) { + self.conditions + .entry(name.into()) + .or_insert_with(|| (profile, Vec::new())) + .1 + .push((condition, include)); + } + + /// Assign a process to a profile by the process's cmdline + pub fn assign_by_cmdline(&mut self, name: &str, profile: Profile) { + self.profile_by_cmdline.insert(name.into(), profile); + } + + /// Assign a process as an exception by its cmdline + pub fn assign_exception_by_cmdline(&mut self, name: &str) { + self.exceptions_by_cmdline.insert(name.into()); + } + + /// Assign a condition as an exception + pub fn assign_exception_by_condition(&mut self, condition: Condition) { + self.exceptions_conditions.push(condition); + } + + /// Assign a process as an exception by its name + pub fn assign_exception_by_name(&mut self, name: &str) { + self.exceptions_by_name.insert(name.into()); + } +} diff --git a/config/src/scheduler/mod.rs b/config/src/scheduler/mod.rs new file mode 100644 index 0000000..4e23651 --- /dev/null +++ b/config/src/scheduler/mod.rs @@ -0,0 +1,205 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +mod assignments; +pub use assignments::{Assignments, Condition, MatchCondition}; + +mod profile; +pub use profile::Profile; + +use std::{borrow::Cow, str::FromStr}; + +/// Process scheduling configuration +pub struct Config { + /// Enables process scheduling + pub enable: bool, + /// Enables execsnoop + pub execsnoop: bool, + /// Defines the refresh rate for polling processes + pub refresh_rate: u16, + /// Process profile assignments + pub assignments: Assignments, + /// Foreground profiles + pub foreground: Option, + /// Pipewire profile + pub pipewire: Option, +} + +impl Default for Config { + fn default() -> Self { + Self { + enable: false, + execsnoop: false, + refresh_rate: 60, + assignments: Assignments::default(), + foreground: None, + pipewire: None, + } + } +} + +/// Foreground process profiles +pub struct ForegroundAssignments { + /// Background profile + pub background: Profile, + /// Foreground profile + pub foreground: Profile, +} + +/// I/O Class +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum IoClass { + /// Idle + Idle, + /// BestEffort + #[default] + BestEffort, + /// Realtime + Realtime, +} + +impl FromStr for IoClass { + type Err = (); + + fn from_str(s: &str) -> Result { + let class = match s { + "idle" => IoClass::Idle, + "best-effort" => IoClass::BestEffort, + "realtime" => IoClass::Realtime, + _ => return Err(()), + }; + + Ok(class) + } +} + +/// I/O policy +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum IoPolicy { + /// Idle + Idle, + /// Standard + Standard, + /// BestEffort + BestEffort(IoPriority), + /// Realtime + Realtime(IoPriority), +} + +/// I/O priority +/// +/// Restricts the value between 0 through 7. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct IoPriority(u8); + +impl IoPriority { + /// Value as a number + #[must_use] + pub fn get(self) -> u8 { + self.0 + } +} + +impl Default for IoPriority { + fn default() -> Self { + Self(7) + } +} + +impl From for IoPriority { + fn from(level: u8) -> Self { + Self(level.min(7)) + } +} + +/// Restricts the value between -20 through 19. +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Niceness(i8); + +impl Niceness { + /// Value as a number + #[must_use] + pub fn get(self) -> i8 { + self.0 + } +} + +impl From for Niceness { + fn from(level: i8) -> Self { + Self(level.min(19).max(-20)) + } +} + +/// Process assignment +pub enum Process<'a> { + /// Assign by cmdline + CmdLine(Cow<'a, str>), + /// Assign by name + Name(Cow<'a, str>), +} + +/// Scheduler configuration +pub struct Scheduler { + /// Scheduler policy + pub policy: SchedPolicy, + /// Scheduler priority + pub priority: SchedPriority, +} + +/// Scheduler policy +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(i32)] +pub enum SchedPolicy { + /// SCHED_BATCH + Batch = libc::SCHED_BATCH, + /// SCHED_FIFO + Fifo = libc::SCHED_FIFO, + /// SCHED_IDLE + Idle = libc::SCHED_IDLE, + /// SCHED_OTHER + #[default] + Other = libc::SCHED_OTHER, + /// SCHED_RR + Rr = libc::SCHED_RR, +} + +impl FromStr for SchedPolicy { + type Err = (); + + fn from_str(s: &str) -> Result { + let policy = match s { + "batch" => Self::Batch, + "fifo" => Self::Fifo, + "idle" => Self::Idle, + "other" => Self::Other, + "rr" => Self::Rr, + _ => return Err(()), + }; + + Ok(policy) + } +} + +/// A value between 1 and 99 +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct SchedPriority(u8); + +impl Default for SchedPriority { + fn default() -> Self { + Self(1) + } +} + +impl SchedPriority { + /// Value as a number + #[must_use] + pub fn get(self) -> u8 { + self.0 + } +} + +impl From for SchedPriority { + fn from(level: u8) -> Self { + Self(level.min(99).max(1)) + } +} diff --git a/config/src/scheduler/profile.rs b/config/src/scheduler/profile.rs new file mode 100644 index 0000000..7563349 --- /dev/null +++ b/config/src/scheduler/profile.rs @@ -0,0 +1,35 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use std::sync::Arc; + +use crate::scheduler::{Niceness, SchedPolicy, SchedPriority}; + +#[must_use] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +/// Process scheduler profile +pub struct Profile { + /// Profile name + pub name: Arc, + /// Niceness priority level + pub nice: Option, + /// I/O priority class + pub io: ioprio::Class, + /// Scheduler policy for a process + pub sched_policy: SchedPolicy, + /// Scheduler policy priority + pub sched_priority: SchedPriority, +} + +impl Profile { + /// Creates a process scheduler profile + pub const fn new(name: Arc) -> Self { + Self { + name, + nice: None, + io: ioprio::Class::BestEffort(ioprio::BePriorityLevel::lowest()), + sched_policy: SchedPolicy::Other, + sched_priority: SchedPriority(1), + } + } +} diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 42732d2..1865603 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -2,43 +2,44 @@ name = "system76-scheduler" description = "Linux service that manages process priorities and CFS scheduler latencies for improved responsiveness on the desktop" repository = "https://github.com/pop-os/system76-scheduler" -version = "1.2.2" +version = "2.0.0" edition = "2021" license = "MPL-2.0" publish = false [dependencies] -anyhow = "1.0.66" -futures = "0.3.25" -libc = "0.2.137" -num_cpus = "1.14.0" -serde = { version = "1.0.147", features = ["derive"] } -serde_repr = "0.1.9" -thiserror = "1.0.37" -upower_dbus = "0.3.0" -zbus = "3.4.0" -zvariant = "3.7.1" -# ron = "0.7.0" +anyhow = "1.0.70" +futures = "0.3.28" +libc = "0.2.141" +num_cpus = "1.15.0" +serde = { version = "1.0.160", features = ["derive"] } +serde_repr = "0.1.12" +thiserror = "1.0.40" +upower_dbus = "0.3.2" +zbus = { version = "3.11.1", default-features = false, features = ["tokio"] } +zvariant = "3.12.0" tracing = "0.1.37" concat-in-place = "1.1.0" -itoa = "1.0.4" -fomat-macros = "0.3.1" +itoa = "1.0.6" +fomat-macros = "0.3.2" ioprio = "0.2.0" -compact_str = { version = "0.6.1", features = ["serde"] } +compact_str = { version = "0.7.0", features = ["serde"] } execsnoop = { path = "../execsnoop" } -next-gen = "0.1.1" -const_format = "0.2.30" -clap = { version = "4.0.19", features = ["cargo"] } - -# Necessary for deserialization of untagged enums in assignments. -[dependencies.ron] -git = "https://github.com/MomoLangenstein/ron" -branch = "253-untagged-enums" +clap = { version = "4.2.2", features = ["cargo"] } +system76-scheduler-config = { path = "../config" } +aho-corasick = "0.7.20" +memchr = "2.5.0" +bstr = "1.4.0" +atoi = "2.0.0" +system76-scheduler-pipewire = { path = "../pipewire"} +pipewire = "0.6.0" +qcell = "0.5.3" +futures-lite = "1.13.0" [dependencies.tracing-subscriber] version = "0.3.16" features = ["env-filter"] [dependencies.tokio] -version = "1.21.2" -features = ["fs", "macros", "rt", "sync", "time"] +version = "1.27.0" +features = ["macros", "process", "rt", "sync", "time"] diff --git a/daemon/src/cpu.rs b/daemon/src/cfs/mod.rs similarity index 86% rename from daemon/src/cpu.rs rename to daemon/src/cfs/mod.rs index 2620c9b..376b51b 100644 --- a/daemon/src/cpu.rs +++ b/daemon/src/cfs/mod.rs @@ -1,8 +1,10 @@ -// Copyright 2021-2022 System76 +// Copyright 2021 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::config::cpu::Config; -use crate::paths::{SchedPaths, BANDWIDTH_SIZE_PATH}; +pub mod paths; + +use crate::config::cfs::Profile; +use paths::{SchedPaths, BANDWIDTH_SIZE_PATH}; use std::fmt::Display; use std::io::Write; use std::{fs, io}; @@ -11,7 +13,7 @@ use std::{fs, io}; #[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_possible_truncation)] -pub fn tweak(paths: &SchedPaths, conf: &Config) { +pub fn tweak(paths: &SchedPaths, conf: &Profile) { let modifier = latency_modifier(num_cpus::get() as f64); let min_gran = (modifier as f64 * conf.latency as f64 / conf.nr_latency as f64) as u64; @@ -23,7 +25,7 @@ pub fn tweak(paths: &SchedPaths, conf: &Config) { write_value(BANDWIDTH_SIZE_PATH, conf.bandwidth_size * 1000); if let Some(preempt_path) = paths.preempt { - write_value(preempt_path, &*conf.preempt); + write_value(preempt_path, conf.preempt); } } diff --git a/daemon/src/paths.rs b/daemon/src/cfs/paths.rs similarity index 100% rename from daemon/src/paths.rs rename to daemon/src/cfs/paths.rs diff --git a/daemon/src/config.rs b/daemon/src/config.rs deleted file mode 100644 index 3977a37..0000000 --- a/daemon/src/config.rs +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2021-2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use compact_str::CompactString; -use concat_in_place::strcat; -use serde::{Deserialize, Serialize}; -use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet}; -use std::ffi::OsStr; -use std::fs; -use std::path::{Path, PathBuf}; - -const DISTRIBUTION_PATH: &str = "/usr/share/system76-scheduler/"; -const SYSTEM_CONF_PATH: &str = "/etc/system76-scheduler/"; - -pub type Exceptions = BTreeSet; -pub type Assignments = BTreeMap; - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub enum IoPriority { - Idle, - Standard, - BestEffort(PriorityLevel), - Realtime(PriorityLevel), -} - -impl From for ioprio::Priority { - fn from(priority: IoPriority) -> ioprio::Priority { - use ioprio::{BePriorityLevel, Class, Priority, RtPriorityLevel}; - match priority { - IoPriority::BestEffort(value) => { - let level = BePriorityLevel::from_level(value.get()).unwrap(); - Priority::new(Class::BestEffort(level)) - } - IoPriority::Idle => Priority::new(Class::Idle), - IoPriority::Realtime(value) => { - let level = RtPriorityLevel::from_level(value.get()).unwrap(); - Priority::new(Class::Realtime(level)) - } - IoPriority::Standard => { - let level = BePriorityLevel::from_level(7).unwrap(); - Priority::new(Class::BestEffort(level)) - } - } - } -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[serde(from = "AssignmentRaw")] -pub struct Assignment(pub CpuPriority, pub IoPriority); - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[serde(untagged)] -pub enum AssignmentRaw { - Cpu(CpuPriority), - Io(IoPriority), - Both((CpuPriority, IoPriority)), -} - -impl From for Assignment { - fn from(raw: AssignmentRaw) -> Self { - match raw { - AssignmentRaw::Cpu(cpu) => Assignment(cpu, IoPriority::Standard), - AssignmentRaw::Io(io) => Assignment(CpuPriority::from(0), io), - AssignmentRaw::Both((cpu, io)) => Assignment(cpu, io), - } - } -} - -/// Restricts the value between -20 through 19. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[serde(transparent)] -pub struct CpuPriority(i8); - -impl CpuPriority { - pub fn get(self) -> i8 { - self.0 - } -} - -impl From for CpuPriority { - fn from(level: i8) -> Self { - Self(level.min(19).max(-20)) - } -} - -/// Restricts the value between 0 through 7. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[serde(transparent)] -pub struct PriorityLevel(u8); - -impl PriorityLevel { - pub fn get(self) -> u8 { - self.0 - } -} - -impl From for PriorityLevel { - fn from(level: u8) -> Self { - Self(level.min(7)) - } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Config { - #[serde(default)] - pub background: Option, - - #[serde(default)] - pub foreground: Option, - - #[serde(default)] - pub use_execsnoop: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - background: Some(5), - foreground: Some(-5), - use_execsnoop: true, - } - } -} - -impl Config { - pub fn read() -> Config { - let directories = [ - strcat!(SYSTEM_CONF_PATH "config.ron"), - strcat!(DISTRIBUTION_PATH "config.ron"), - ]; - - let mut buffer = String::with_capacity(4096); - - for path in directories { - let Ok(config) = crate::utils::read_into_string(&mut buffer, &path) else { - continue; - }; - - match ron::from_str(config) { - Ok(config) => return config, - Err(why) => { - tracing::error!( - "{:?}: {} on line {}, column {}", - path, - why.code, - why.position.line, - why.position.col - ); - } - } - } - - tracing::info!("Using default config values due to config error"); - - Config::default() - } -} - -/// Process names that should have assignments ignored. -#[allow(clippy::doc_markdown)] -pub fn exceptions() -> Exceptions { - let paths = [ - Path::new(concatcp!(DISTRIBUTION_PATH, "exceptions/")), - Path::new(concatcp!(SYSTEM_CONF_PATH, "exceptions/")), - ]; - - mk_gen!(let generator = configuration_files(&paths)); - - let mut exceptions = BTreeSet::new(); - - for path in generator { - if let Ok(string) = fs::read_to_string(&path) { - match ron::from_str::(&string) { - Ok(rules) => { - exceptions.extend(rules.into_iter()); - } - - Err(why) => { - tracing::error!( - "{:?}: {} on line {}, column {}", - path, - why.code, - why.position.line, - why.position.col - ); - } - } - } - } - - exceptions -} - -/// Stores process names and their preferred assignments. -#[allow(clippy::doc_markdown)] -pub fn assignments(exceptions: &Exceptions) -> Assignments { - let paths = [ - Path::new(concatcp!(DISTRIBUTION_PATH, "assignments/")), - Path::new(concatcp!(SYSTEM_CONF_PATH, "assignments/")), - ]; - - mk_gen!(let generator = configuration_files(&paths)); - - let mut assignments = BTreeMap::::new(); - - for path in generator { - if let Ok(string) = fs::read_to_string(&path) { - match ron::from_str::>(&string) { - Ok(buffer) => { - if tracing::event_enabled!(tracing::Level::INFO) { - let log = fomat_macros::fomat!( - (path.display()) ":\n" - for value in &buffer { - "\t" [value] "\n" - } - ); - - tracing::info!("{}", log); - } - - for (assignment, commands) in buffer { - for command in commands { - if !exceptions.contains(&command) { - assignments.insert(command, assignment); - } - } - } - } - Err(why) => { - tracing::error!( - "{:?}: {} on line {}, column {}", - path, - why.code, - why.position.line, - why.position.col - ); - } - } - } - } - - assignments -} - -#[generator(yield(PathBuf))] -fn configuration_files(paths: &[&Path]) { - for directory in paths { - if let Ok(dir) = directory.read_dir() { - for entry in dir.filter_map(Result::ok) { - let path = entry.path(); - if path.extension() == Some(OsStr::new("ron")) { - yield_!(path); - } - } - } - } -} - -pub mod cpu { - use serde::{Deserialize, Serialize}; - use std::{borrow::Cow, fs, path::Path}; - - use super::{preempt_default, strcat, DISTRIBUTION_PATH, SYSTEM_CONF_PATH}; - - const DEFAULT_CONFIG: Config = Config { - latency: 6, - nr_latency: 8, - wakeup_granularity: 1.0, - bandwidth_size: 5, - preempt: preempt_default(), - }; - - const RESPONSIVE_CONFIG: Config = Config { - latency: 4, - nr_latency: 10, - wakeup_granularity: 0.5, - bandwidth_size: 3, - preempt: Cow::Borrowed("full"), - }; - - #[derive(Deserialize, Serialize)] - pub struct Config { - /// Preemption latency for CPU-bound tasks in ns - pub latency: u64, - /// Used to calculate the minimum preemption granularity - pub nr_latency: u64, - /// Wakeup preemption granularity for CPU-bound tasks in ms - pub wakeup_granularity: f64, - /// Amount of time to allocate from global to local pool in us - pub bandwidth_size: u64, - /// The type of preemption to use. - #[serde(default = "preempt_default")] - pub preempt: Cow<'static, str>, - } - - impl Config { - pub fn config_path(config: &str) -> Option { - let mut path = strcat!(SYSTEM_CONF_PATH "cpu/" config ".ron"); - - if Path::new(&path).exists() { - return Some(path); - } - - path.clear(); - strcat!(&mut path, DISTRIBUTION_PATH "cpu/" config ".ron"); - - if Path::new(&path).exists() { - return Some(path); - } - - None - } - - pub fn custom_config(profile: &str) -> Option { - if let Some(path) = Self::config_path(profile) { - if let Ok(file) = fs::read_to_string(&path) { - if let Ok(conf) = ron::from_str(&file) { - return Some(conf); - } - } - } - - None - } - - pub fn responsive_config() -> Self { - let mut conf = RESPONSIVE_CONFIG; - - if let Some(config) = Self::custom_config("responsive") { - conf = config; - } - - conf - } - - pub fn default_config() -> Self { - let mut conf = DEFAULT_CONFIG; - - if let Some(config) = Self::custom_config("default") { - conf = config; - } - - conf - } - } -} - -const fn preempt_default() -> Cow<'static, str> { - Cow::Borrowed("voluntary") -} diff --git a/daemon/src/dbus.rs b/daemon/src/dbus.rs index 1b7cc23..573f740 100644 --- a/daemon/src/dbus.rs +++ b/daemon/src/dbus.rs @@ -39,7 +39,7 @@ pub(crate) struct Server { default_path = "/com/system76/Scheduler" )] pub trait Client { - #[dbus_interface(property)] + #[dbus_proxy(property)] fn cpu_mode(&self) -> zbus::fdo::Result; #[dbus_proxy(property)] @@ -97,3 +97,22 @@ impl Server { let _res = self.tx.send(Event::SetForegroundProcess(pid)).await; } } + +pub(crate) async fn interface_handle( + connection: &zbus::Connection, +) -> Option> { + let interface_result = connection + .object_server() + .interface::<_, Server>("/com/system76/Scheduler") + .await; + + let iface_handle = match interface_result { + Ok(iface_handler) => iface_handler, + Err(why) => { + tracing::error!("DBus interface not reachable: {:#?}", why); + return None; + } + }; + + Some(iface_handle) +} diff --git a/daemon/src/main.rs b/daemon/src/main.rs index fdf975c..f9f158f 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,76 +1,125 @@ // Copyright 2021-2022 System76 // SPDX-License-Identifier: MPL-2.0 -#[macro_use] -extern crate const_format; -#[macro_use] -extern crate next_gen; +#![deny(missing_docs)] + +//! System76 Scheduler + #[macro_use] extern crate zbus; -mod config; -mod cpu; +use qcell::LCellOwner; +pub use system76_scheduler_config as config; +use system76_scheduler_pipewire as scheduler_pipewire; + +mod cfs; mod dbus; -mod nice; -mod paths; mod priority; +mod process; +mod pw; +mod service; mod utils; -use crate::config::cpu::Config as CpuConfig; -use crate::paths::SchedPaths; +use cfs::paths::SchedPaths; use clap::ArgMatches; use dbus::{CpuMode, Server}; -use std::{collections::HashMap, path::Path, time::Duration}; +use std::time::{Duration, Instant}; use tokio::sync::mpsc::Sender; use upower_dbus::UPowerProxy; use zbus::{Connection, PropertyStream}; +use crate::utils::Buffer; + #[derive(Debug)] enum Event { - ExecCreate(execsnoop::Process), + ExecCreate(ExecCreate), OnBattery(bool), + Pipewire(scheduler_pipewire::ProcessEvent), + RefreshProcessMap, ReloadConfiguration, SetCpuMode, SetCustomCpuMode, SetForegroundProcess(u32), - UpdateProcessMap(HashMap>, Vec), } -#[tokio::main(flavor = "current_thread")] -async fn main() -> anyhow::Result<()> { - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "info"); - } +#[derive(Debug)] +struct ExecCreate { + pid: u32, + parent_pid: u32, + name: String, + cmdline: String, +} - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .with_writer(std::io::stderr) - .without_time() - .init(); - - let connection = Connection::system().await?; - - let matches = clap::command!() - .propagate_version(true) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand( - clap::Command::new("cpu") - .about("select a CFS scheduler profile") - .arg(clap::arg!([PROFILE])), - ) - .subcommand( - clap::Command::new("daemon") - .about("launch the system daemon") - .subcommand(clap::Command::new("reload").about("reload system configuration")), - ) - .get_matches(); +fn main() -> anyhow::Result<()> { + let mut result = Ok(()); - match matches.subcommand() { - Some(("cpu", matches)) => cpu(connection, matches).await, - Some(("daemon", matches)) => daemon(connection, matches).await, - _ => Ok(()), - } + LCellOwner::scope(|owner| { + pipewire::init(); + + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + let main = async { + let future = async { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "info"); + } + + tracing_subscriber::fmt() + .pretty() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_writer(std::io::stderr) + .without_time() + .with_line_number(false) + .with_file(false) + .with_target(false) + .init(); + + let connection = Connection::system().await?; + + let matches = clap::command!() + .propagate_version(true) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + clap::Command::new("cpu") + .about("select a CFS scheduler profile") + .arg(clap::arg!([PROFILE])), + ) + .subcommand( + clap::Command::new("daemon") + .about("launch the system daemon") + .subcommand( + clap::Command::new("reload").about("reload system configuration"), + ), + ) + .subcommand( + clap::Command::new("pipewire") + .about("monitor pipewire process ID activities"), + ) + .get_matches(); + + match matches.subcommand() { + Some(("cpu", matches)) => cpu(connection, matches).await, + Some(("daemon", matches)) => daemon(connection, matches, owner).await, + Some(("pipewire", _matches)) => pw::main().await, + _ => Ok(()), + } + }; + + result = tokio::task::LocalSet::new().run_until(future).await; + + unsafe { + pipewire::deinit(); + } + }; + + runtime.block_on(main); + }); + + result } async fn reload(connection: Connection) -> anyhow::Result<()> { @@ -98,19 +147,93 @@ async fn cpu(connection: Connection, args: &ArgMatches) -> anyhow::Result<()> { } #[allow(clippy::too_many_lines)] -async fn daemon(connection: Connection, args: &ArgMatches) -> anyhow::Result<()> { - match args.subcommand() { - Some(("reload", _)) => return reload(connection).await, - _ => (), +async fn daemon( + connection: Connection, + args: &ArgMatches, + owner: LCellOwner<'_>, +) -> anyhow::Result<()> { + let mut buffer = Buffer::new(); + + if let Some(("reload", _)) = args.subcommand() { + return reload(connection).await; } - tracing::info!("starting daemon service"); + let service = &mut service::Service::new(owner, SchedPaths::new()?); + service.reload_configuration(); - let paths = SchedPaths::new()?; + let (tx, mut rx) = tokio::sync::mpsc::channel(4); - let upower_proxy = UPowerProxy::new(&connection).await?; + let upower = UPowerProxy::new(&connection).await?; - let (tx, mut rx) = tokio::sync::mpsc::channel(4); + // Spawns an async task that watches for battery status notifications. + tokio::task::spawn_local(battery_monitor( + upower.receive_on_battery_changed().await, + tx.clone(), + )); + + // Controls the kernel's sched_autogroup setting. + autogroup_set(service.config.autogroup_enabled); + + // Tweaks CFS parameters based on battery status. + if service.config.cfs_profiles.enable { + service.cfs_on_battery(upower.on_battery().await.unwrap_or(false)); + } + + // If enabled, monitors processes and applies priorities to them. + if service.config.process_scheduler.enable { + // Schedules process updates + tokio::task::spawn_local({ + let refresh_rate = + Duration::from_secs(u64::from(service.config.process_scheduler.refresh_rate)); + let tx = tx.clone(); + async move { + let _res = tx.send(Event::RefreshProcessMap).await; + tokio::time::sleep(refresh_rate).await; + } + }); + + // Use execsnoop-bpfcc to watch for new processes being created. + if service.config.process_scheduler.execsnoop { + tracing::debug!("monitoring process IDs in realtime with execsnoop"); + let tx = tx.clone(); + let (scheduled_tx, mut scheduled_rx) = tokio::sync::mpsc::unbounded_channel(); + std::thread::spawn(move || { + if let Ok(mut watcher) = execsnoop::watch() { + // Listen for spawned process, scheduling them to be handled with a delay of 1 second after creation. + // The delay is to ensure that a process has been added to a cgroup + while let Some(process) = watcher.next() { + let Ok(cmdline) = std::str::from_utf8(process.cmd) else { + continue + }; + + let name = process::name(cmdline); + + let _res = scheduled_tx.send(( + Instant::now() + Duration::from_secs(2), + ExecCreate { + pid: process.pid, + parent_pid: process.parent_pid, + name: name.to_owned(), + cmdline: cmdline.to_owned(), + }, + )); + } + } + }); + + tokio::task::spawn_local(async move { + while let Some((delay, process)) = scheduled_rx.recv().await { + tokio::time::sleep_until(delay.into()).await; + let _res = tx.send(Event::ExecCreate(process)).await; + } + }); + } + + // Monitors pipewire-connected processes. + if service.config.process_scheduler.pipewire.is_some() { + tokio::task::spawn_local(pw::monitor(tx.clone())); + } + } connection .object_server() @@ -126,111 +249,91 @@ async fn daemon(connection: Connection, args: &ArgMatches) -> anyhow::Result<()> connection.request_name("com.system76.Scheduler").await?; - // Spawns an async task that watches for battery status notifications. - tokio::spawn(battery_monitor( - upower_proxy.receive_on_battery_changed().await, - tx.clone(), - )); - - // Spawns a process monitor that updates process map info. - tokio::spawn(process_monitor(tx.clone())); - - // Use execsnoop-bpfcc to watch for new processes being created. - if let Ok(watcher) = execsnoop::watch() { - let handle = tokio::runtime::Handle::current(); - let tx = tx.clone(); - std::thread::spawn(move || { - handle.block_on(async move { - for process in watcher { - let _res = tx.send(Event::ExecCreate(process)).await; - } - }); - }); - } - - let apply_config = |on_battery: bool| { - cpu::tweak( - &paths, - &if on_battery { - tracing::debug!("auto config applying default config"); - CpuConfig::default_config() - } else { - tracing::debug!("auto config applying responsive config"); - CpuConfig::responsive_config() - }, - ); - }; - - apply_config(upower_proxy.on_battery().await.unwrap_or(false)); - - let mut priority_service = priority::Service::default(); - priority_service.reload_configuration(); - while let Some(event) = rx.recv().await { - let interface_result = connection - .object_server() - .interface::<_, Server>("/com/system76/Scheduler") - .await; - - let iface_handle = match interface_result { - Ok(iface_handler) => iface_handler, - Err(why) => { - tracing::error!("DBus interface not reachable: {:#?}", why); - break; - } - }; - - let interface = iface_handle.get().await; - match event { - Event::ExecCreate(execsnoop::Process { - parent_pid, pid, .. + Event::ExecCreate(ExecCreate { + pid, + parent_pid, + name, + cmdline, }) => { - priority_service.assign_new_process(pid, parent_pid); + service.assign_new_process(&mut buffer, pid, parent_pid, name, cmdline); + service.assign_children(&mut buffer, pid); } - Event::UpdateProcessMap(map, background_processes) => { - priority_service.update_process_map(map, background_processes); + Event::RefreshProcessMap => { + service.process_map_refresh(&mut buffer); } Event::SetForegroundProcess(pid) => { - priority_service.set_foreground_process(pid); + tracing::debug!("setting {pid} as foreground process"); + service.set_foreground_process(&mut buffer, pid); + } + + Event::Pipewire(scheduler_pipewire::ProcessEvent::Add(process)) => { + service.set_pipewire_process(&mut buffer, process); + } + + Event::Pipewire(scheduler_pipewire::ProcessEvent::Remove(process)) => { + service.remove_pipewire_process(&mut buffer, process); } Event::OnBattery(on_battery) => { + let Some(handle) = dbus::interface_handle(&connection).await else { + break; + }; + + let interface = handle.get().await; + if let CpuMode::Auto = interface.cpu_mode { - apply_config(on_battery); + service.cfs_on_battery(on_battery); } } - Event::SetCpuMode => match interface.cpu_mode { - CpuMode::Auto => { - tracing::debug!("applying auto config"); - apply_config(upower_proxy.on_battery().await.unwrap_or(false)); - } + Event::SetCpuMode => { + let Some(handle) = dbus::interface_handle(&connection).await else { + break; + }; - CpuMode::Default => { - tracing::debug!("applying default config"); - cpu::tweak(&paths, &CpuConfig::default_config()); - } + let interface = handle.get().await; - CpuMode::Responsive => { - tracing::debug!("applying responsive config"); - cpu::tweak(&paths, &CpuConfig::responsive_config()); - } + match interface.cpu_mode { + CpuMode::Auto => { + tracing::debug!("applying auto config"); + service.cfs_on_battery(upower.on_battery().await.unwrap_or(false)); + } - CpuMode::Custom => (), - }, + CpuMode::Default => { + tracing::debug!("applying default config"); + service.cfs_apply(service.cfs_default_config()); + } + + CpuMode::Responsive => { + tracing::debug!("applying responsive config"); + service.cfs_apply(service.cfs_responsive_config()); + } + + CpuMode::Custom => (), + } + } Event::SetCustomCpuMode => { - if let Some(config) = CpuConfig::custom_config(&interface.cpu_profile) { + let Some(handle) = dbus::interface_handle(&connection).await else { + break; + }; + + let interface = handle.get().await; + + if let Some(profile) = service.cfs_config(&interface.cpu_profile) { tracing::debug!("applying {} config", interface.cpu_profile); - cpu::tweak(&paths, &config); + service.cfs_apply(profile); } } Event::ReloadConfiguration => { - priority_service.reload_configuration(); + tracing::debug!("reloading configuration"); + service.reload_configuration(); + autogroup_set(service.config.autogroup_enabled); } } } @@ -239,6 +342,8 @@ async fn daemon(connection: Connection, args: &ArgMatches) -> anyhow::Result<()> async fn battery_monitor(mut events: PropertyStream<'_, bool>, tx: Sender) { use futures::StreamExt; + + tracing::debug!("monitoring udev for battery hotplug events"); while let Some(event) = events.next().await { if let Ok(on_battery) = event.get().await { let _res = tx.send(Event::OnBattery(on_battery)).await; @@ -246,67 +351,7 @@ async fn battery_monitor(mut events: PropertyStream<'_, bool>, tx: Sender } } -async fn process_monitor(tx: Sender) { - let mut file_buf = String::with_capacity(2048); - - let mut background_processes = Vec::with_capacity(256); - let mut parents = HashMap::>::with_capacity(256); - - loop { - let Ok(procfs) = Path::new("/proc").read_dir() else { - tracing::error!("failed to read /proc directory: process monitoring stopped"); - return; - }; - - background_processes.clear(); - parents.clear(); - - for proc_entry in procfs.filter_map(Result::ok) { - let proc_path = proc_entry.path(); - - let pid = if let Some(pid) = proc_path - .file_name() - .and_then(std::ffi::OsStr::to_str) - .and_then(|p| p.parse::().ok()) - { - pid - } else { - continue; - }; - - let mut parent = None; - - let status = proc_path.join("status"); - - if let Ok(status) = crate::utils::read_into_string(&mut file_buf, &*status) { - for line in status.lines() { - if let Some(ppid) = line.strip_prefix("PPid:") { - if let Ok(ppid) = ppid.trim().parse::() { - parent = Some(ppid); - } - - break; - } - } - } - - parents.insert(pid, parent); - - // Prevents kernel processes from having their priorities changed. - if let Ok(exe) = std::fs::read_link(proc_path.join("exe")) { - if exe.file_name().is_some() { - background_processes.push(pid); - } - } - } - - let _res = tx - .send(Event::UpdateProcessMap( - parents.clone(), - background_processes.clone(), - )) - .await; - - tokio::time::sleep(Duration::from_secs(60)).await; - } +fn autogroup_set(enable: bool) { + const PATH: &str = "/proc/sys/kernel/sched_autogroup_enabled"; + let _res = std::fs::write(PATH, if enable { b"1" } else { b"0" }); } diff --git a/daemon/src/nice.rs b/daemon/src/nice.rs deleted file mode 100644 index 973f6e1..0000000 --- a/daemon/src/nice.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use concat_in_place::strcat; -use ioprio::{Pid, Target}; - -pub fn set_priority(process: u32, priority: (i32, ioprio::Priority)) { - let mut buffer = itoa::Buffer::new(); - let tasks = strcat!("/proc/" buffer.format(process) "/task"); - - let Ok(tasks) = std::fs::read_dir(tasks) else { - return; - }; - - for task in tasks.filter_map(Result::ok) { - let p = task - .file_name() - .to_str() - .and_then(|num| num.parse::().ok()); - - let Some(process) = p else { return }; - - tracing::debug!("set_priority {}: {:?}", process, priority); - - unsafe { - libc::setpriority(libc::PRIO_PROCESS, process, priority.0); - } - - #[allow(clippy::cast_possible_wrap)] - if let Err(why) = - ioprio::set_priority(Target::Process(Pid::from_raw(process as i32)), priority.1) - { - tracing::error!("failed to set ioprio: {:?}", why); - } - } -} diff --git a/daemon/src/priority.rs b/daemon/src/priority.rs index 1a478bd..3580b3e 100644 --- a/daemon/src/priority.rs +++ b/daemon/src/priority.rs @@ -1,237 +1,55 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::config::{Assignment, Assignments, Config, CpuPriority, Exceptions, IoPriority}; -use std::collections::HashMap; +use std::os::unix::prelude::OsStrExt; -type ProcessMap = HashMap>; +use concat_in_place::strcat; +use ioprio::{Pid, Target}; +use system76_scheduler_config::scheduler::{Profile, SchedPolicy, SchedPriority}; -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Priority { - Assignable, - Config((i32, IoPriority)), - Exception, - NotAssignable, -} - -impl Priority { - pub fn with_default(self, priority: (i32, IoPriority)) -> Option<(i32, ioprio::Priority)> { - let (cpu, io) = match self { - Priority::Assignable => priority, - Priority::Config(config) => config, - _ => return None, - }; - - Some((cpu, io.into())) - } -} - -pub enum AssignmentStatus { - Assigned, - Unset, -} - -#[derive(Default)] -pub struct Service { - pub assignments: Assignments, - pub config: Config, - pub exceptions: Exceptions, - pub foreground: Option, - pub foreground_processes: Vec, - pub process_map: ProcessMap, -} - -impl Service { - /// Assign a priority to a process - pub fn assign(&self, pid: u32, default: (i32, IoPriority)) -> AssignmentStatus { - if let Some(priority) = self.assigned_priority(pid).with_default(default) { - crate::nice::set_priority(pid, priority); - return AssignmentStatus::Assigned; - } - - AssignmentStatus::Unset - } - - /// Gets the config-assigned priority of a process. - #[must_use] - pub fn assigned_priority(&self, pid: u32) -> Priority { - let name = crate::utils::name_of_pid(pid); - - if let Some(name) = &name { - if self.exceptions.contains(name) { - return Priority::Exception; - } - } - - let Some(exe) = crate::utils::exe_of_pid(pid) else { - return Priority::NotAssignable; - }; - - if self.exceptions.contains(&*exe) { - return Priority::Exception; - } - - if let Some(Assignment(cpu, io)) = self.assignments.get(&*exe) { - if !is_assignable(pid, *cpu) { - return Priority::NotAssignable; - } - - return Priority::Config((cpu.get().into(), *io)); - } - - if let Some(name) = name { - if let Some(Assignment(cpu, io)) = self.assignments.get(&*name) { - if !is_assignable(pid, *cpu) { - return Priority::NotAssignable; - } - - return Priority::Config((cpu.get().into(), *io)); - } - } - - Priority::Assignable - } - - /// Assign a priority to a newly-created process. - pub fn assign_new_process(&mut self, pid: u32, parent_pid: u32) { - if let Some(foreground) = self.config.foreground { - if self.foreground_processes.contains(&parent_pid) - && !self.foreground_processes.contains(&pid) - { - let default = (i32::from(foreground), IoPriority::Standard); +use crate::utils::Buffer; - if let AssignmentStatus::Assigned = self.assign(pid, default) { - self.foreground_processes.push(pid); - return; - } - } - } +/// Get the priority of a process. +// pub fn get(pid: u32) -> i32 { +// unsafe { libc::getpriority(libc::PRIO_PROCESS, pid) } +// } - // Child processes inherit the priority of their parent. - // We want exceptions to avoid inheriting that priority. - if Priority::Exception == self.assigned_priority(pid) { - let parent_priority = priority(parent_pid); - let child_priority = priority(pid); +pub fn set(buffer: &mut Buffer, process: u32, profile: &Profile) { + buffer.path.clear(); + let tasks = strcat!(&mut buffer.path, "/proc/" buffer.itoa.format(process) "/task"); - if parent_priority == child_priority { - let level = ioprio::BePriorityLevel::lowest(); - let class = ioprio::Class::BestEffort(level); - let io_priority = ioprio::Priority::new(class); - crate::nice::set_priority(pid, (0, io_priority)); - } + let Ok(tasks) = std::fs::read_dir(tasks) else { + return; + }; + for task in tasks.filter_map(Result::ok) { + let Some(process) = atoi::atoi::(task.file_name().as_bytes()) else { return; - } - - if let Some(background) = self.config.background { - if self.foreground_processes.contains(&pid) { - return; - } - - let default = (i32::from(background), IoPriority::Idle); - let _status = self.assign(pid, default); - } - } - - /// Reloads the configuration files. - pub fn reload_configuration(&mut self) { - self.config = Config::read(); - self.exceptions = crate::config::exceptions(); - self.assignments = crate::config::assignments(&self.exceptions); - } - - /// Sets a process as the foreground. - pub fn set_foreground_process(&mut self, pid: u32) { - if let Some(foreground_priority) = self.config.foreground { - self.foreground = Some(pid); - - let background_priority = ( - i32::from(self.config.background.unwrap_or(0)), - IoPriority::Idle, - ); - - let foreground_priority = (i32::from(foreground_priority), IoPriority::Standard); - - // Unset priorities of previously-set processes. - let mut foreground = Vec::new(); - std::mem::swap(&mut foreground, &mut self.foreground_processes); - - for process in foreground.drain(..) { - if let Some(priority) = self - .assigned_priority(process) - .with_default(background_priority) - { - crate::nice::set_priority(process, priority); - } - } - - std::mem::swap(&mut foreground, &mut self.foreground_processes); - - if let Some(priority) = self - .assigned_priority(pid) - .with_default(foreground_priority) - { - crate::nice::set_priority(pid, priority); - self.foreground_processes.push(pid); - } - - 'outer: loop { - for (pid, parent) in &self.process_map { - if let Some(parent) = parent { - if self.foreground_processes.contains(parent) - && !self.foreground_processes.contains(pid) - { - if let Some(priority) = self - .assigned_priority(*pid) - .with_default(foreground_priority) - { - crate::nice::set_priority(*pid, priority); - self.foreground_processes.push(*pid); - continue 'outer; - } - } - } - } + }; - break; + if let Some(nice) = profile.nice { + unsafe { + libc::setpriority(libc::PRIO_PROCESS, process, libc::c_int::from(nice.get())); } } - } - /// Updates the list of assignable processes, and reassigns priorities. - pub fn update_process_map(&mut self, map: ProcessMap, background_processes: Vec) { - self.process_map = map; + set_policy(process, profile.sched_policy, profile.sched_priority); - // Assign background priority to all processes. - if let Some(background) = self.config.background { - for pid in background_processes { - if self.foreground_processes.contains(&pid) { - continue; - } - - if let Some(priority) = self - .assigned_priority(pid) - .with_default((i32::from(background), IoPriority::Idle)) - { - crate::nice::set_priority(pid, priority); - } - } - } - - // Reassign foreground processes in case they were overriden. - if let Some(process) = self.foreground.take() { - self.set_foreground_process(process); - } + #[allow(clippy::cast_possible_wrap)] + let _res = ioprio::set_priority( + Target::Process(Pid::from_raw(process as i32)), + ioprio::Priority::new(profile.io), + ); } } -/// A process is assignable if its priority is less than 9 or greater than -9. -pub fn is_assignable(pid: u32, cpu: CpuPriority) -> bool { - let current = priority(pid); - (current <= 9 && current >= -9) || cpu.get() <= -9 || cpu.get() >= 9 -} +pub fn set_policy(pid: u32, policy: SchedPolicy, sched_priority: SchedPriority) { + let param = libc::sched_param { + sched_priority: libc::c_int::from(sched_priority.get()), + }; -/// Get the priority of a process. -pub fn priority(pid: u32) -> i32 { - unsafe { libc::getpriority(libc::PRIO_PROCESS, pid) } + unsafe { + #[allow(clippy::cast_possible_wrap)] + libc::sched_setscheduler(pid as libc::c_int, policy as libc::c_int, ¶m); + } } diff --git a/daemon/src/process.rs b/daemon/src/process.rs new file mode 100644 index 0000000..04278b0 --- /dev/null +++ b/daemon/src/process.rs @@ -0,0 +1,207 @@ +use crate::{service::OwnedPriority, utils::Buffer}; +use bstr::ByteSlice; +use concat_in_place::strcat; +use qcell::{LCell, LCellOwner}; +use std::{ + collections::{ + hash_map::{DefaultHasher, Entry}, + HashMap, HashSet, + }, + hash::{Hash, Hasher}, + path::Path, + sync::{Arc, Weak}, +}; + +#[derive(Default)] +pub struct Process<'owner> { + pub id: u32, + pub parent_id: u32, + pub name: String, + pub cgroup: String, + pub cmdline: String, + pub forked_cmdline: String, + pub forked_name: String, + pub parent: Option>>>, + pub assigned_priority: OwnedPriority, + pub pipewire_ancestor: Option, +} + +impl<'owner> Hash for Process<'owner> { + fn hash(&self, hasher: &mut H) { + self.id.hash(hasher); + self.parent_id.hash(hasher); + } +} + +impl<'owner> Process<'owner> { + pub fn ancestors<'a>( + &self, + owner: &'a LCellOwner<'owner>, + ) -> impl Iterator>>> + 'a { + let mut parent_node = self.parent(); + std::iter::from_fn(move || { + if let Some(parent) = parent_node.take() { + parent_node = parent.ro(owner).parent(); + return Some(parent); + } + + None + }) + } + + pub fn hash_id(&self) -> u64 { + let mut hasher = DefaultHasher::default(); + self.hash(&mut hasher); + hasher.finish() + } + + pub fn parent(&self) -> Option>>> { + self.parent.as_ref().and_then(Weak::upgrade) + } +} + +#[derive(Default)] +pub struct Map<'owner> { + pub map: HashMap>>>, + pub drain: HashSet, +} + +impl<'owner> Map<'owner> { + /// Removes processes that remain in the drain filter. + pub fn drain_filter(&mut self) { + for hash in self.drain.drain() { + self.map.remove(&hash); + } + } + + /// This will be used to keep track of what processes were destroyed since the last refresh. + pub fn drain_filter_prepare(&mut self) { + self.drain.clear(); + for hash in self.map.keys() { + self.drain.insert(*hash); + } + } + + pub fn get_pid( + &self, + token: &LCellOwner<'owner>, + pid: u32, + ) -> Option<&Arc>>> { + self.map + .values() + .find(|&process| process.ro(token).id == pid) + } + + pub fn insert( + &mut self, + owner: &mut LCellOwner<'owner>, + process: Process<'owner>, + ) -> Arc>> { + match self.map.entry(process.hash_id()) { + Entry::Occupied(entry) => { + { + let entry = entry.get().rw(owner); + + entry.cgroup = process.cgroup; + entry.parent = process.parent; + + if entry.name != process.name { + std::mem::swap(&mut entry.forked_cmdline, &mut entry.cmdline); + std::mem::swap(&mut entry.forked_name, &mut entry.name); + entry.name = process.name; + entry.cmdline = process.cmdline; + entry.assigned_priority = OwnedPriority::NotAssignable; + } + } + + entry.get().clone() + } + Entry::Vacant(entry) => { + let process = Arc::new(LCell::new(process)); + + entry.insert(process.clone()); + process + } + } + } + + /// Removes a process and its parents from consideration for process removal. + pub fn retain_process_tree(&mut self, owner: &LCellOwner<'owner>, process: &Process<'owner>) { + self.drain.remove(&process.hash_id()); + + for parent in process.ancestors(owner) { + let parent = parent.ro(owner); + self.drain.remove(&parent.hash_id()); + } + } +} + +pub fn cgroup(buffer: &mut Buffer, pid: u32) -> Option<&str> { + buffer.path.clear(); + + let path = strcat!(&mut buffer.path, "/proc/" buffer.itoa.format(pid) "/cgroup"); + + let Ok(buffer) = crate::utils::read_into_string(&mut buffer.file, path) else { + return None; + }; + + memchr::memchr(b':', buffer.as_bytes()).map(|pos| &buffer[pos + 2..buffer.len() - 1]) +} + +pub fn cmdline(buffer: &mut Buffer, pid: u32) -> Option { + buffer.path.clear(); + + let path = strcat!(&mut buffer.path, "/proc/" buffer.itoa.format(pid) "/exe"); + + let Ok(exe) = std::fs::read_link(path) else { + return None; + }; + + Some( + exe.as_os_str() + .to_string_lossy() + .split_whitespace() + .next() + .map(String::from) + .unwrap_or_default(), + ) +} + +#[allow(dead_code)] +pub fn exists(buffer: &mut Buffer, pid: u32) -> bool { + buffer.path.clear(); + Path::new(strcat!(&mut buffer.path, "/proc/" buffer.itoa.format(pid) "/status")).exists() +} + +pub fn name(cmdline: &str) -> &str { + cmdline.rsplit('/').next().unwrap_or(cmdline) +} + +pub fn parent_id(buffer: &mut Buffer, pid: u32) -> Option { + buffer.path.clear(); + + let path = strcat!(&mut buffer.path, "/proc/" buffer.itoa.format(pid) "/status"); + + if let Some(value) = crate::utils::file_key(&mut buffer.file_raw, path, "PPid:") { + return atoi::atoi::(value); + } + + None +} + +pub fn children(buffer: &'_ mut Buffer, pid: u32) -> impl Iterator + '_ { + buffer.path.clear(); + buffer.file_raw.clear(); + + let pid = buffer.itoa.format(pid); + let path = Path::new(strcat!(&mut buffer.path, "/proc/" pid "/task/" pid "/children")); + + crate::utils::read_into_vec(&mut buffer.file_raw, path) + .ok() + .into_iter() + .flat_map(|bytes| { + bstr::BStr::new(bytes) + .fields() + .filter_map(atoi::atoi::) + }) +} diff --git a/daemon/src/pw.rs b/daemon/src/pw.rs new file mode 100644 index 0000000..3799849 --- /dev/null +++ b/daemon/src/pw.rs @@ -0,0 +1,157 @@ +use crate::Event; +use std::{ + collections::BTreeSet, + io::Write, + os::unix::{net::UnixStream, prelude::OwnedFd}, + path::PathBuf, + time::Duration, +}; +use system76_scheduler_pipewire::{processes_from_socket, ProcessEvent}; +use tokio::{io::AsyncBufReadExt, sync::mpsc::Sender}; + +pub async fn main() -> anyhow::Result<()> { + pipewire::init(); + let (tx, mut rx) = tokio::sync::mpsc::channel(4); + + let service = async move { + pipewire_service(tx).await; + anyhow::bail!("pipewire service exited") + }; + + let main = async move { + let stdout = &mut std::io::stdout().lock(); + + while let Some(event) = rx.recv().await { + event.to_bytes(stdout)?; + stdout.write_all(b"\n")?; + } + + Ok(()) + }; + + let result = futures_lite::future::race(service, main).await; + + unsafe { + pipewire::deinit(); + } + + result +} + +/// Monitor pipewire sockets and the process IDs connected to them. +async fn pipewire_service(tx: Sender) { + // TODO: Support stopping and restarting this on config changes. + enum SocketEvent { + Add(PathBuf), + Remove(PathBuf), + } + + let (pw_tx, mut pw_rx) = tokio::sync::mpsc::channel(1); + + let session_monitor = { + let pw_tx = pw_tx.clone(); + async move { + loop { + if let Ok(run_user_dir) = std::fs::read_dir("/run/user") { + for entry in run_user_dir.filter_map(Result::ok) { + let socket_path = entry.path().join("pipewire-0"); + if socket_path.exists() { + let _res = pw_tx.send(SocketEvent::Add(socket_path)).await; + } + } + } + + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + } + } + }; + + let session_spawner = async move { + let mut active_sessions = BTreeSet::::new(); + + while let Some(event) = pw_rx.recv().await { + match event { + SocketEvent::Add(socket) => { + if !active_sessions.contains(&socket) { + if let Ok(stream) = UnixStream::connect(&socket) { + let tx = tx.clone(); + let pw_tx = pw_tx.clone(); + std::thread::spawn(move || { + processes_from_socket(&OwnedFd::from(stream), move |event| { + let _res = tx.blocking_send(event); + }); + + let _res = pw_tx.blocking_send(SocketEvent::Remove(socket)); + }); + } + } + } + SocketEvent::Remove(socket) => { + active_sessions.remove(&socket); + } + } + } + }; + + futures_lite::future::zip(session_monitor, session_spawner).await; +} + +/// Spawns and manages a child process that monitors pipewire events from the pipewire subcommand. +/// +/// This is done to isolate libpipewire from the daemon. If a crash occurs from the pipewire-rs bindings, +/// or the libpipewire library itelf, this will gracefully restart the process without losing any data. +pub(crate) async fn monitor(tx: Sender) { + let mut managed = BTreeSet::::new(); + + loop { + tokio::time::sleep(Duration::from_secs(3)).await; + + let result = std::process::Command::new("system76-scheduler") + .arg("pipewire") + .stdin(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .spawn(); + + let Ok(mut child) = result else { + continue; + }; + + let Some(stdout) = child.stdout.take() else { + continue; + }; + + let Ok(stdout) = tokio::process::ChildStdout::from_std(stdout) else { + continue; + }; + + let mut stdout = tokio::io::BufReader::new(stdout); + let mut line = Vec::new(); + + loop { + line.clear(); + + match stdout.read_until(b'\n', &mut line).await { + Ok(0) | Err(_) => break, + Ok(_) => (), + } + + if let Some(event) = ProcessEvent::from_bytes(&line) { + match event { + ProcessEvent::Add(pid) => { + if !managed.insert(pid) { + continue; + } + } + ProcessEvent::Remove(pid) => { + if !managed.remove(&pid) { + continue; + } + } + } + + let _res = tx.send(Event::Pipewire(event)).await; + } + } + } +} diff --git a/daemon/src/service.rs b/daemon/src/service.rs new file mode 100644 index 0000000..55a13bc --- /dev/null +++ b/daemon/src/service.rs @@ -0,0 +1,596 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::cfs::paths::SchedPaths; +use crate::config::scheduler::Profile; +use crate::process::{self, Process}; +use crate::utils::Buffer; +use qcell::{LCell, LCellOwner}; +use std::collections::BTreeMap; +use std::{os::unix::prelude::OsStrExt, sync::Arc}; +use system76_scheduler_config::scheduler::Condition; + +pub struct Service<'owner> { + pub config: crate::config::Config, + cfs_paths: SchedPaths, + foreground_processes: Vec, + foreground: Option, + pipewire_processes: Vec, + process_map: process::Map<'owner>, + owner: LCellOwner<'owner>, +} + +impl<'owner> Service<'owner> { + pub fn new(owner: LCellOwner<'owner>, cfs_paths: SchedPaths) -> Self { + Self { + config: crate::config::Config::default(), + cfs_paths, + foreground: None, + foreground_processes: Vec::with_capacity(128), + pipewire_processes: Vec::with_capacity(4), + process_map: process::Map::default(), + owner, + } + } + + pub fn assign_process_priority(&mut self, process: &LCell<'owner, Process<'owner>>) { + if OwnedPriority::NotAssignable != process.ro(&self.owner).assigned_priority { + return; + } + + let priority = (|| { + let process = process.ro(&self.owner); + + if self.process_is_exception(process) { + return OwnedPriority::Exception; + } + + if let Some(profile) = self + .config + .process_scheduler + .assignments + .get_by_cmdline(&process.cmdline) + { + return OwnedPriority::Config(profile.clone()); + } + + if let Some(profile) = self + .config + .process_scheduler + .assignments + .get_by_name(&process.name) + { + return OwnedPriority::Config(profile.clone()); + } + + if process.cgroup.is_empty() { + return OwnedPriority::NotAssignable; + } + + // True when all conditions for a profile are met by a process. + let condition_met = |condition: &Condition| { + if let Some(ref cgroup) = condition.cgroup { + if !cgroup.matches(&process.cgroup) { + return false; + } + } + + if let Some(ref name) = condition.name { + if !name.matches(&process.name) { + return false; + } + } + + if !condition.parent.is_empty() { + let mut has_parent = false; + + if let Some(parent) = process.parent() { + let parent = parent.ro(&self.owner); + has_parent = condition + .parent + .iter() + .any(|condition| condition.matches(&parent.name)); + } + + if !has_parent { + return false; + } + } + + if let Some(ref descends_condition) = condition.descends { + let is_ancestor = process.ancestors(&self.owner).any(|parent| { + let parent = parent.ro(&self.owner); + descends_condition.matches(&parent.name) + }); + + if !is_ancestor { + return false; + } + } + + true + }; + + 'outer: for (profile, conditions) in self + .config + .process_scheduler + .assignments + .conditions + .values() + { + let mut assigned_profile = None; + + for (condition, include) in conditions { + match (condition_met(condition), *include) { + // Condition met for an include rule + (true, true) => assigned_profile = Some(profile), + // Condition met for an exclude rule + (true, false) => continue 'outer, + _ => (), + } + } + + if let Some(profile) = assigned_profile.take() { + return OwnedPriority::Config(profile.clone()); + } + } + + OwnedPriority::Assignable + })(); + + process.rw(&mut self.owner).assigned_priority = priority; + } + + /// Assigns children of a process in case they've not been assigned. + pub fn assign_children(&mut self, buffer: &mut Buffer, pid: u32) { + let mut tasks = Vec::new(); + let mut scan = vec![pid]; + let mut scanned = Vec::new(); + + while let Some(process) = scan.pop() { + scanned.push(process); + + for pid in process::children(buffer, process) { + if !(scan.contains(&pid) || scanned.contains(&pid)) { + scan.push(pid); + } + } + + tasks.push(process); + } + + for pid in tasks { + if self.process_map.get_pid(&self.owner, pid).is_none() { + let Some(parent_pid) = process::parent_id(buffer, pid) else { + continue + }; + + let Some(cmdline) = process::cmdline(buffer, pid) else { + continue + }; + + let name = process::name(&cmdline).to_owned(); + + self.assign_new_process(buffer, pid, parent_pid, name, cmdline); + } + } + } + + /// Assign a priority to a newly-created process, and record that process in the map. + pub fn assign_new_process( + &mut self, + buffer: &mut Buffer, + pid: u32, + parent_pid: u32, + name: String, + mut cmdline: String, + ) { + let parent = self.process_map.get_pid(&self.owner, parent_pid).cloned(); + + let mut cgroup = String::new(); + + if process::exists(buffer, pid) { + if cmdline.is_empty() { + cmdline = process::cmdline(buffer, pid).unwrap_or_default(); + } + + cgroup = process::cgroup(buffer, pid) + .map(String::from) + .unwrap_or_default(); + } + + // Add the process to the map, if it does not already exist. + let process = self.process_map.insert( + &mut self.owner, + Process { + id: pid, + parent_id: parent_pid, + cgroup, + cmdline, + name, + parent: parent.as_ref().map(Arc::downgrade), + ..Process::default() + }, + ); + + let mut pipewire_ancestor = None; + + 'outer: for process in process.ro(&self.owner).ancestors(&self.owner) { + let process = process.ro(&self.owner); + for &ancestor in &self.pipewire_processes { + if process.id == ancestor || process.parent_id == ancestor { + pipewire_ancestor = Some(ancestor); + break 'outer; + } + } + } + + if pipewire_ancestor.is_some() { + process.rw(&mut self.owner).pipewire_ancestor = pipewire_ancestor; + } + + self.assign_process_priority(&process); + self.apply_process_priority(buffer, process.ro(&self.owner)); + } + + pub fn apply_process_priority(&self, buffer: &mut Buffer, process: &Process<'owner>) { + let profile_default; + + let profile = match process.assigned_priority.as_ref() { + Priority::Assignable => { + if let Some(ref profile) = self.config.process_scheduler.pipewire { + if self.process_is_pipewire_assigned(process) { + crate::priority::set(buffer, process.id, profile); + return; + } + } + + if let (Some(assignments), Some(foreground)) = + (&self.config.process_scheduler.foreground, &self.foreground) + { + if process.id == *foreground || self.foreground_processes.contains(&process.id) + { + &assignments.foreground + } else { + &assignments.background + } + } else { + profile_default = Profile::new(Arc::from("default")); + &profile_default + } + } + + Priority::Config(profile) => profile, + + _ => return, + }; + + crate::priority::set(buffer, process.id, profile); + } + + pub fn cfs_apply(&self, config: &crate::config::cfs::Profile) { + if self.config.cfs_profiles.enable { + return; + } + + crate::cfs::tweak(&self.cfs_paths, config); + } + + pub fn cfs_on_battery(&self, on_battery: bool) { + self.cfs_apply(if on_battery { + self.cfs_default_config() + } else { + self.cfs_responsive_config() + }); + } + + pub fn cfs_config(&self, name: &str) -> Option<&crate::config::cfs::Profile> { + self.config.cfs_profiles.profiles.get(name) + } + + pub fn cfs_default_config(&self) -> &crate::config::cfs::Profile { + self.cfs_config("default") + .unwrap_or(&crate::config::cfs::PROFILE_DEFAULT) + } + + pub fn cfs_responsive_config(&self) -> &crate::config::cfs::Profile { + self.cfs_config("responsive") + .unwrap_or(&crate::config::cfs::PROFILE_RESPONSIVE) + } + + /// Gets the config-assigned priority of a process. + #[must_use] + pub fn process_assignment(&self, pid: u32) -> Priority { + let Some(process) = self.process_map.get_pid(&self.owner, pid) else { + return Priority::NotAssignable; + }; + + process.ro(&self.owner).assigned_priority.as_ref() + } + + // Check if the `process` has descended from the `ancestor` + pub fn process_descended_from(&self, process: &Process<'owner>, ancestor: u32) -> bool { + if process.parent_id == ancestor { + return true; + } + + process.ancestors(&self.owner).any(|process| { + let process = process.ro(&self.owner); + process.id == ancestor || process.parent_id == ancestor + }) + } + + // Check if the `process` is excepted from process priority changes + pub fn process_is_exception(&self, process: &Process<'owner>) -> bool { + // Return if listed as an exception by its cmdline path + if self + .config + .process_scheduler + .assignments + .is_exception_by_cmdline(&process.cmdline) + { + return true; + } + + // Return if listed as an exception by process name + if self + .config + .process_scheduler + .assignments + .is_exception_by_name(&process.name) + { + return true; + } + + // Condition-based exceptions + for condition in &self + .config + .process_scheduler + .assignments + .exceptions_conditions + { + // Checks if the process descends from an excepted parent process. + if let Some(condition) = &condition.descends { + if !condition.matches(&process.forked_name) { + let ancestry_match = process.ancestors(&self.owner).any(|parent| { + let parent = parent.ro(&self.owner); + condition.matches(&parent.name) || condition.matches(&parent.forked_name) + }); + + if !ancestry_match { + continue; + } + } + } + + // Checks if a process has a direct parent of the same name. + if !condition.parent.is_empty() { + let parent_match = condition.parent.iter().any(|condition| { + process.parent().map_or(false, |parent| { + let parent = parent.ro(&self.owner); + condition.matches(&parent.name) || condition.matches(&parent.forked_name) + }) + }); + + if !parent_match { + continue; + } + } + + return true; + } + + false + } + + pub fn process_is_pipewire_assigned(&self, process: &Process<'owner>) -> bool { + process.pipewire_ancestor.is_some() || self.pipewire_processes.contains(&process.id) + } + + /// Adds a new process to the process map + pub fn process_map_insert( + &mut self, + process: Process<'owner>, + ) -> Arc>> { + self.process_map.insert(&mut self.owner, process) + } + + /// Refreshes the process map + pub fn process_map_refresh(&mut self, buffer: &mut Buffer) { + self.process_map.drain_filter_prepare(); + + let mut parents = BTreeMap::new(); + let Ok(procfs) = std::fs::read_dir("/proc/") else { + tracing::error!("failed to read /proc directory: process monitoring stopped"); + return; + }; + + for proc_entry in procfs.filter_map(Result::ok) { + let file_name = proc_entry.file_name(); + + let mut process = Process::default(); + + match atoi::atoi::(file_name.as_bytes()) { + Some(pid) => process.id = pid, + None => continue, + } + + // Processes without a command line path are kernel threads + match process::cmdline(buffer, process.id) { + Some(cmdline) => process.cmdline = cmdline, + None => continue, + } + + process.name = process::name(&process.cmdline).to_owned(); + + if let Some(cgroup) = process::cgroup(buffer, process.id) { + process.cgroup = cgroup.to_owned(); + } + + if let Some(ppid) = process::parent_id(buffer, process.id) { + parents.insert(process.id, ppid); + process.parent_id = ppid; + } + + self.process_map.retain_process_tree(&self.owner, &process); + self.process_map_insert(process); + } + + for (pid, ppid) in parents { + if let Some(process) = self.process_map.get_pid(&self.owner, pid) { + if let Some(parent) = self.process_map.get_pid(&self.owner, ppid) { + process.rw(&mut self.owner).parent = Some(Arc::downgrade(parent)); + } + } + } + + self.process_map.drain_filter(); + + // Refresh priority assignments + let mut process_map = process::Map::default(); + std::mem::swap(&mut process_map, &mut self.process_map); + + for process in process_map.map.values() { + self.assign_process_priority(process); + self.apply_process_priority(buffer, process.ro(&self.owner)); + } + + std::mem::swap(&mut process_map, &mut self.process_map); + + // Reassign foreground processes in case they were overriden. + if let Some(process) = self.foreground.take() { + self.set_foreground_process(buffer, process); + } + } + + /// Reloads the configuration files. + pub fn reload_configuration(&mut self) { + self.config = crate::config::config(); + } + + /// Sets a process as the foreground. + pub fn set_foreground_process(&mut self, buffer: &mut Buffer, pid: u32) { + self.assign_children(buffer, pid); + + if let Some(ref assignments) = self.config.process_scheduler.foreground { + self.foreground = Some(pid); + self.foreground_processes.clear(); + self.foreground_processes.push(pid); + + for process in self.process_map.map.values() { + let process = process.ro(&self.owner); + + if let Priority::Assignable = self.process_assignment(process.id) { + let profile = if process.id == pid || self.process_descended_from(process, pid) + { + self.foreground_processes.push(process.id); + + if self.process_is_pipewire_assigned(process) { + continue; + } + + &assignments.foreground + } else if self.process_is_pipewire_assigned(process) { + continue; + } else { + &assignments.background + }; + + crate::priority::set(buffer, process.id, profile); + } + } + } + } + + /// Assigns a process to the pipewire profile if it does not already have an assignment. + pub fn set_pipewire_process(&mut self, buffer: &mut Buffer, process: u32) { + self.assign_children(buffer, process); + + if let Some(pipewire) = self.config.process_scheduler.pipewire.clone() { + if !self.pipewire_processes.contains(&process) { + if let Some(process) = self.process_map.get_pid(&self.owner, process) { + let process = process.ro(&self.owner); + if OwnedPriority::Assignable != process.assigned_priority { + return; + } + } + + self.pipewire_processes.push(process); + } + + for current_cell in self.process_map.map.values() { + let current = current_cell.ro(&self.owner); + let pid = current.id; + + if let Priority::Assignable = self.process_assignment(current.id) { + if pid == process { + crate::priority::set(buffer, process, &pipewire); + } else if self.process_descended_from(current, process) { + current_cell.rw(&mut self.owner).pipewire_ancestor = Some(process); + crate::priority::set(buffer, pid, &pipewire); + } + } + } + } + } + + /// Removes a process from the pipewire profile. + /// + /// Assigns the background or foreground process priority, if that feature is enabled. + pub fn remove_pipewire_process(&mut self, buffer: &mut Buffer, process_id: u32) { + let Some(index) = self.pipewire_processes.iter().position(|pid| *pid == process_id) else { + return; + }; + + self.pipewire_processes.remove(index); + + for process_cell in self.process_map.map.values() { + let process = process_cell.rw(&mut self.owner); + + if process.pipewire_ancestor == Some(process_id) || process.id == process_id { + process.pipewire_ancestor = None; + let process = process_cell.ro(&self.owner); + + if let Some(ref assignments) = self.config.process_scheduler.foreground { + if let Priority::Assignable = self.process_assignment(process.id) { + let profile = if self.foreground_processes.contains(&process.id) { + &assignments.foreground + } else { + &assignments.background + }; + + crate::priority::set(buffer, process.id, profile); + } + } + } + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Priority<'a> { + Assignable, + Config(&'a Profile), + Exception, + NotAssignable, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub enum OwnedPriority { + Assignable, + Config(Profile), + Exception, + #[default] + NotAssignable, +} + +impl OwnedPriority { + fn as_ref(&self) -> Priority { + match self { + Self::Assignable => Priority::Assignable, + Self::Config(profile) => Priority::Config(profile), + Self::Exception => Priority::Exception, + Self::NotAssignable => Priority::NotAssignable, + } + } +} diff --git a/daemon/src/utils.rs b/daemon/src/utils.rs index 6e2aa63..1c61883 100644 --- a/daemon/src/utils.rs +++ b/daemon/src/utils.rs @@ -4,51 +4,57 @@ use std::ffi::OsStr; use std::fs::File; use std::io::{self, Read}; -use std::path::Path; -use compact_str::CompactString; +use bstr::{BStr, ByteSlice}; -pub fn exe_of_pid(pid: u32) -> Option { - let mut itoa = itoa::Buffer::new(); - let exe = concat_in_place::strcat!("/proc/" itoa.format(pid) "/exe"); - - let Ok(exe) = std::fs::read_link(Path::new(&exe)) else { - return None; - }; +pub struct Buffer { + pub path: String, + pub file: String, + pub file_raw: Vec, + pub itoa: itoa::Buffer, +} - let Some(exe) = exe.file_name().and_then(std::ffi::OsStr::to_str) else { - return None; - }; +impl Buffer { + pub fn new() -> Self { + Self { + path: String::with_capacity(256), + file: String::with_capacity(4096), + file_raw: Vec::with_capacity(4096), + itoa: itoa::Buffer::new(), + } + } +} - let Some(exe) = exe.split_ascii_whitespace().next() else { - return None; - }; +pub fn read_into_string>(buf: &mut String, path: P) -> io::Result<&str> { + let mut file = File::open(path.as_ref())?; + buf.clear(); + file.read_to_string(buf)?; + Ok(&*buf) +} - Some(CompactString::from(exe)) +pub fn read_into_vec>(buf: &mut Vec, path: P) -> io::Result<&[u8]> { + let mut file = File::open(path.as_ref())?; + buf.clear(); + file.read_to_end(buf)?; + Ok(&*buf) } -pub fn name_of_pid(pid: u32) -> Option { - let mut itoa = itoa::Buffer::new(); - let path = concat_in_place::strcat!("/proc/" itoa.format(pid) "/status"); +pub fn file_key<'a>(buf: &'a mut Vec, path: &str, key: &str) -> Option<&'a [u8]> { + buf.clear(); - let Ok(buffer) = std::fs::read_to_string(&path) else { + let Ok(mut status) = crate::utils::read_into_vec(buf, path) else { return None; }; - let Some(name) = buffer.lines().next() else { - return None; - }; + while let Some(pos) = memchr::memchr(b'\n', status) { + let line = BStr::new(&status[..pos]); - let Some(name) = name.strip_prefix("Name:") else { - return None; - }; + if let Some(ppid) = line.strip_prefix(key.as_bytes()) { + return Some(BStr::new(ppid).trim()); + } - Some(CompactString::from(name.trim())) -} + status = &status[pos + 1..]; + } -pub fn read_into_string>(buf: &mut String, path: P) -> io::Result<&str> { - let mut file = File::open(path.as_ref())?; - buf.clear(); - file.read_to_string(buf)?; - Ok(&*buf) + None } diff --git a/data/assignments.ron b/data/assignments.ron deleted file mode 100644 index 9c79148..0000000 --- a/data/assignments.ron +++ /dev/null @@ -1,114 +0,0 @@ -// WARNING: Modifications to this file will not be preserved on upgrade. -// To configure, make new .ron files under /etc/system76-scheduler/assignments/. - -{ -// Prevent audio crackling from sound services. -(-11, Realtime(0)): [ - "pipewire", - "pipewire-pulse", - "wireplumber" -], -// Very high -(-9, BestEffort(0)): [ - "easyeffects", - "jamesdsp", -], -// High priority -(-5, BestEffort(4)): [ - "amsynth", - "gnome-shell", - "kwin", - "sway", - "steam", - "vrcompositor", - "vrdashboard", - "vrmonitor", - "vrserver", - "Xorg", -], -// Default -0: [ - "dbus", - "sshd", - "systemd", -], -// Low -(9, Idle): [ - "accounts-daemon", - "acpid", - "automount", - "avahi-daemon", - "bluetoothd", - "colord", - "cron", - "cups-browsed", - "cupsd", - "dconf-service", - "dnsmasq", - "dockerd", - "evolution-source-registry", - "evolution-calendar-factory", - "evolution-addressbook-factory", - "evolution-alarm-notify", - "fwupd", - "goa-daemon", - "goa-identify-service", - "gsd-a11y-settings", - "gsd-color", - "gsd-datetime", - "gsd-housekeeping", - "gsd-keyboard", - "gsd-media-keys", - "gsd-disk-utility-notify", - "gsd-power", - "gsd-print-notifications", - "gsd-rfkill", - "gsd-screensaver-proxy", - "gsd-sharing", - "gsd-smartcard", - "gsd-sound", - "gsd-wacom", - "gsd-xsettings", - "gsd-printer", - "hidpi-daemon", - "iwd", - "ModemManager", - "NetworkManager", - "pop-system-updater", - "rpcbind", - "system76-daemon", - "system76-firmware-daemon", - "system76-power", - "system76-scheduler", - "system76-user-daemon", - "thermald", - "udisksd", - "upowerd", -], -// Absolute lowest priority -(19, Idle): [ - "apt", - "apt-get", - "boinc", - "dpkg", - "FAHClient", - "FAHCoreWrapper", - "fossilize_replay", - "flatpak", - "c++", - "cargo", - "clang", - "cpp", - "g++", - "gcc", - "gradle", - "javac", - "lld", - "make", - "mold", - "mvn", - "rustc", - "tracker-miner-fs-3", - "update-initramfs", - "packagekitd", -]} diff --git a/data/com.system76.Scheduler.service b/data/com.system76.Scheduler.service index 0eb566f..d4ff3c6 100644 --- a/data/com.system76.Scheduler.service +++ b/data/com.system76.Scheduler.service @@ -2,6 +2,7 @@ Description=Automatically configure CPU scheduler for responsiveness on AC [Service] +ExecStartPre=/bin/sleep 10 ExecStart=/usr/bin/system76-scheduler daemon ExecReload=/usr/bin/system76-scheduler daemon reload Type=dbus diff --git a/data/config.kdl b/data/config.kdl new file mode 100644 index 0000000..1158276 --- /dev/null +++ b/data/config.kdl @@ -0,0 +1,89 @@ +// WARNING: Modifications to this file will not be preserved on upgrade. +// To configure, make a config.kdl file under /etc/system76-scheduler/. + +// Version of the configuration file +version "2.0" + +// Autogrouping nullifies nice priorities +autogroup-enabled false + +// Latency profiles the kernel's Completely Fair Scheduler +cfs-profiles enable=true { + // The kernel-default values which are ideal for battery life and servers + default latency=6 nr-latency=8 wakeup-granularity=1.0 bandwidth-size=5 preempt="voluntary" + + // Zen CFS parameters that make the desktop more responsive + responsive latency=4 nr-latency=10 wakeup-granularity=0.5 bandwidth-size=3 preempt="full" +} + +// Monitors and applies process priority adjustments +process-scheduler enable=true { + // How often to reload process assignments. 0 to disable + refresh-rate 60 + + // Enable realtime process priority adjustment with execsnoop + execsnoop true + + // Preset process assignment profiles + assignments { + // Prevent crackling and distortion from the sound server + sound-server nice=-15 sched=(fifo)49 io=(realtime)0 { + "/usr/bin/pipewire" + "/usr/bin/pipewire-pulse" + "/usr/bin/jackd" + } + + // Applications with realtime audio processing needs + recording nice=-9 io=(best-effort)0 + + // Enables pipewire process monitoring and assignments + pipewire nice=-6 io=(best-effort)0 + + // Video games + games nice=-5 io=(best-effort)0 + + // Desktop environments + desktop-environment nice=-3 io=(best-effort)0 + + // Foreground process profile + foreground nice=0 io=(best-effort)0 + + // Background process profile + background nice=6 io="idle" + + // Session services belonging to the user + session-services nice=9 io="idle" { + include cgroup="/user.slice/*.service" parent="systemd" + include cgroup="/user.slice/*/session.slice/*" parent="systemd" + exclude cgroup="/user.slice/*app-dbus*" + } + + // System services belonging to root + system-services nice=12 io="idle" { + include cgroup="/system.slice/*" + } + + // Package management processes + package-manager nice=15 sched="batch" io="idle" + + // CPU-intense background tasks + batch nice=19 sched="idle" io="idle" + } + + exceptions { + include descends="chrt" + include descends="gamemoderun" + include descends="ionice" + include descends="nice" + include descends="taskset" + chrt + dbus + dbus-broker + gamemoderun + ionice + nice + rtkit-daemon + systemd + taskset + } +} diff --git a/data/config.ron b/data/config.ron deleted file mode 100644 index a00a2a6..0000000 --- a/data/config.ron +++ /dev/null @@ -1,13 +0,0 @@ -// WARNING: Modifications to this file will not be preserved on upgrade. -// To configure, make a config.ron file under /etc/system76-scheduler/. - -( -// The priority to assign background tasks. -background: Some(5), - -// The priority to assign foreground tasks. -foreground: Some(-5), - -// Enables realtime process priority adjustment. -use_execsnoop: true, -) diff --git a/data/exceptions.ron b/data/exceptions.ron deleted file mode 100644 index 7e2b96f..0000000 --- a/data/exceptions.ron +++ /dev/null @@ -1,8 +0,0 @@ -// WARNING: Modifications to this file will not be preserved on upgrade. -// To configure, make new .ron files under /etc/system76-scheduler/exceptions/. - -[ - "ionice", - "nice", - "rtkit-daemon", -] diff --git a/data/pop_os.kdl b/data/pop_os.kdl new file mode 100644 index 0000000..a48bc44 --- /dev/null +++ b/data/pop_os.kdl @@ -0,0 +1,89 @@ +assignments { + recording { + amsynth + easyeffects + jamesdsp + jitsi + mumble + obs + teams + wireplumber + zoom + bitwig-studio + include name="Bitwig*" + } + + games { + lutris + steam + vrcompositor + vrdashboard + vrmonitor + vrserver + include descends="steam" + include descends="lutris" + } + + desktop-environment { + cosmic-comp + gnome-shell + i3wm + kwin + sway + Xorg + } + + session-services { + include parent="gnome-session-binary" + include parent="gvfsd" + } + + package-manager { + include name="apt-*" + include name="dpkg-*" + apt + dpkg + flatpak + fwupd + packagekitd + update-initramfs + } + + batch { + include name="sbuild-*" + "7z" + "7za" + "7zr" + ar + boinc + c++ + cargo + clang + cmake + cpp + FAHClient + FAHCoreWrapper + fossilize-replay + g++ + gcc + gradle + javac + ld + lld + make + mold + mvn + ninja + rust-analyzer + rustc + sbuild + tar + tracker-miner-fs-3 + unrar + zip + } +} + +exceptions { + +} \ No newline at end of file diff --git a/debian/changelog b/debian/changelog index 4fe2e96..b6d1da0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +system76-scheduler (2.0.0) jammy; urgency=medium + + * https://github.com/pop-os/system76-scheduler/releases/tag/2.0.0 + + -- Michael Murphy Tue, 07 Mar 2023 17:02:12 +0100 + system76-scheduler (1.2.2) jammy; urgency=medium * https://github.com/pop-os/system76-scheduler/releases/tag/1.2.2 diff --git a/debian/control b/debian/control index a454ea8..af02f3f 100644 --- a/debian/control +++ b/debian/control @@ -4,8 +4,12 @@ Priority: optional Maintainer: Michael Murphy Build-Depends: cargo, + clang, debhelper-compat (=10), just, + libclang-dev, + libpipewire-0.3-dev, + pkg-config, rustc (>=1.53), Standards-Version: 4.1.1 Homepage: https://github.com/pop-os/system76-scheduler diff --git a/debian/rules b/debian/rules index 08bfe54..b66500d 100755 --- a/debian/rules +++ b/debian/rules @@ -22,10 +22,7 @@ override_dh_auto_clean: fi override_dh_auto_build: - just rootdir=$(DESTDIR) \ - debug=$(DEBUG) \ - vendor=$(VENDOR) \ - execsnoop="/usr/sbin/execsnoop-bpfcc" + just execsnoop="/usr/sbin/execsnoop-bpfcc" build-vendored override_dh_auto_install: just rootdir=$(DESTDIR) sysconfdir="/usr/share" install diff --git a/execsnoop/Cargo.toml b/execsnoop/Cargo.toml index 6bd151d..03242a0 100644 --- a/execsnoop/Cargo.toml +++ b/execsnoop/Cargo.toml @@ -2,9 +2,13 @@ name = "execsnoop" description = "Watch process ID creation in Linux kernel with execsnoop-bpfcc" repository = "https://github.com/pop-os/system76-scheduler" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MPL-2.0" publish = true [dependencies] +atoi = "2.0.0" +bstr = "1.4.0" +bytelines = "2.4.0" +tracing = "0.1.37" diff --git a/execsnoop/src/lib.rs b/execsnoop/src/lib.rs index 7ee6b2f..bed316f 100644 --- a/execsnoop/src/lib.rs +++ b/execsnoop/src/lib.rs @@ -1,28 +1,67 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -use std::{ - io, - io::{BufRead, BufReader}, - process::{Command, Stdio}, -}; +#![deny(missing_docs)] +//! Spawns the execsnoop-bpfcc application to watch process executions. + +use atoi::atoi; +use bstr::{BStr, ByteSlice}; +use bytelines::ByteLines; +use std::io::{self, BufReader}; +use std::process::{Command, Stdio}; + +/// Process info #[derive(Clone, Debug)] -pub struct Process { +pub struct Process<'a> { + /// Process name + pub name: &'a [u8], + /// Process cmdline + pub cmd: &'a [u8], + /// Process PID pub pid: u32, + /// Process parent PID pub parent_pid: u32, } +/// Process iterator pub struct ProcessIterator { child: std::process::Child, - iterator: Box + Send>, + stream: ByteLines>, + name_buffer: Vec, + cmd_buffer: Vec, } -impl Iterator for ProcessIterator { - type Item = Process; +impl ProcessIterator { + /// Get the next process from the iterator + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option { + while let Some(Ok(line)) = self.stream.next() { + let mut fields = BStr::new(line).fields(); + + if let (Some(name), Some(pid), Some(parent_pid)) = + (fields.next(), fields.next(), fields.next()) + { + let cmd = fields.nth(1).unwrap_or_default(); + + if let (Some(pid), Some(parent_pid)) = (atoi::(pid), atoi::(parent_pid)) { + self.name_buffer.clear(); + self.name_buffer.extend_from_slice(name); + + self.cmd_buffer.clear(); + self.cmd_buffer.extend_from_slice(cmd); + + return Some(Process { + name: &self.name_buffer, + cmd: &self.cmd_buffer, + pid, + parent_pid, + }); + } + } + } - fn next(&mut self) -> Option { - self.iterator.next() + None } } @@ -48,44 +87,20 @@ pub fn watch() -> io::Result { .stderr(Stdio::null()) .stdin(Stdio::null()) .spawn() - .and_then(|mut child| { + .and_then(move |mut child| { let stdout = child.stdout.take().ok_or_else(|| { let _res = child.kill(); let _res = child.wait(); io::Error::new(io::ErrorKind::Other, "execsnoop-bpfcc lacks stdout pipe") })?; - let mut reader = BufReader::new(stdout); - - let mut line = String::with_capacity(128); + let stream = ByteLines::new(BufReader::with_capacity(16 * 1024, stdout)); Ok(ProcessIterator { child, - iterator: Box::new(std::iter::from_fn(move || loop { - match reader.read_line(&mut line) { - Err(_) | Ok(0) => return None, - _ => (), - } - - let mut fields = line.split_ascii_whitespace(); - - let pid = fields.nth(1); - let parent_pid = fields.next(); - - if let Some((pid, parent_pid)) = pid.zip(parent_pid) { - let pid = pid.parse::().ok(); - let parent_pid = parent_pid.parse::().ok(); - - if let Some((pid, parent_pid)) = pid.zip(parent_pid) { - let process = Process { pid, parent_pid }; - - line.clear(); - return Some(process); - } - } - - line.clear(); - })), + stream, + name_buffer: Vec::with_capacity(64), + cmd_buffer: Vec::with_capacity(128), }) }) } diff --git a/justfile b/justfile index 66851cc..e00a35b 100644 --- a/justfile +++ b/justfile @@ -1,51 +1,50 @@ +binary := 'system76-scheduler' +id := 'com.system76.Scheduler' + rootdir := '' prefix := '/usr' sysconfdir := '/etc' -root := rootdir + prefix -debug := '0' -vendor := '0' - -target := if debug == '1' { 'debug' } else { 'release' } -vendor_args := if vendor == '1' { '--frozen --offline' } else { '' } -debug_args := if debug == '1' { '' } else { '--release' } -cargo_args := vendor_args + ' ' + debug_args -binary := 'system76-scheduler' -id := 'com.system76.Scheduler' +bindir := clean(rootdir / prefix) / 'bin' +libdir := clean(rootdir / prefix) / 'lib' +confdir := clean(rootdir / sysconfdir) -bindir := root + '/bin' -libdir := root + '/lib' -confdir := rootdir + sysconfdir - -target_bin := bindir + '/' + binary +target-bin := bindir / binary # Path to execsnoop binary. execsnoop := '/usr/sbin/execsnoop-bpfcc' -# Compile pop-launcher -all: _extract_vendor - env EXECSNOOP_PATH={{execsnoop}} cargo build {{cargo_args}} - # Remove Cargo build artifacts clean: cargo clean -# Run `cargo check` -check: - env EXECSNOOP_PATH={{execsnoop}} cargo check - # Also remove .cargo and vendored dependencies distclean: rm -rf .cargo vendor vendor.tar target +# Compile with debug profile +build-debug *args: + env EXECSNOOP_PATH={{execsnoop}} cargo build {{args}} + +# Compile with release profile +build-release *args: (build-debug '--release' args) + +# Compile with a vendored tarball +build-vendored *args: vendor-extract (build-release '--frozen --offline' args) + +# Check for errors and linter warnings +check *args: + env EXECSNOOP_PATH={{execsnoop}} cargo clippy --all-features {{args}} -- -W clippy::pedantic + +# Runs a check with JSON message format for IDE integration +check-json: (check '--message-format=json') + # Install everything install: - mkdir -p {{confdir}}/system76-scheduler/assignments \ - {{confdir}}/system76-scheduler/exceptions - install -Dm0644 data/config.ron {{confdir}}/system76-scheduler/config.ron - install -Dm0644 data/assignments.ron {{confdir}}/system76-scheduler/assignments/default.ron - install -Dm0644 data/exceptions.ron {{confdir}}/system76-scheduler/exceptions/default.ron - install -Dm0755 target/{{target}}/{{binary}} {{target_bin}} + mkdir -p {{confdir}}/system76-scheduler/process-scheduler + install -Dm0644 data/config.kdl {{confdir}}/system76-scheduler/config.kdl + install -Dm0644 data/pop_os.kdl {{confdir}}/system76-scheduler/process-scheduler/pop_os.kdl + install -Dm0755 target/release/{{binary}} {{target-bin}} install -Dm0644 data/{{id}}.service {{libdir}}/systemd/system/{{id}}.service install -Dm0644 data/{{id}}.conf {{confdir}}/dbus-1/system.d/{{id}}.conf @@ -54,7 +53,7 @@ uninstall: rm -rf {{confdir}}/system76-scheduler \ {{confdir}}/dbus-1/system.d/{{id}}.conf \ {{libdir}}/systemd/system/{{id}}.service \ - {{target_bin}} + {{target-bin}} # Vendor Cargo dependencies locally vendor: @@ -67,10 +66,8 @@ vendor: tar pcf vendor.tar vendor rm -rf vendor -# Extracts vendored dependencies if vendor=1 -_extract_vendor: - #!/usr/bin/env sh - if test {{vendor}} = 1; then - rm -rf vendor - tar pxf vendor.tar - fi \ No newline at end of file +# Extracts vendored dependencies +[private] +vendor-extract: + rm -rf vendor + tar pxf vendor.tar \ No newline at end of file diff --git a/pipewire/Cargo.toml b/pipewire/Cargo.toml new file mode 100644 index 0000000..10eb9e3 --- /dev/null +++ b/pipewire/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "system76-scheduler-pipewire" +description = "PipeWire integration for system76-scheduler" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +atoi = "2.0.0" +bstr = "1.4.0" +itoa = "1.0.6" +pipewire = "0.6.0" diff --git a/pipewire/examples/monitor.rs b/pipewire/examples/monitor.rs new file mode 100644 index 0000000..c915c15 --- /dev/null +++ b/pipewire/examples/monitor.rs @@ -0,0 +1,19 @@ +use std::os::unix::{net::UnixStream, prelude::OwnedFd}; + +use system76_scheduler_pipewire::processes_from_socket; + +fn main() { + let (tx, rx) = std::sync::mpsc::sync_channel(0); + + std::thread::spawn(move || { + let file = UnixStream::connect("/run/user/1000/pipewire-0").unwrap(); + + processes_from_socket(&OwnedFd::from(file), move |event| { + let _res = tx.send(event); + }); + }); + + while let Ok(event) = rx.recv() { + println!("{:#?}", event); + } +} diff --git a/pipewire/src/lib.rs b/pipewire/src/lib.rs new file mode 100644 index 0000000..70d2ba2 --- /dev/null +++ b/pipewire/src/lib.rs @@ -0,0 +1,207 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +#![deny(missing_docs)] + +//! Pipewire integration for the System76 Scheduler + +use bstr::BStr; +use pipewire as pw; +use pw::{ + node::{Node, NodeInfo}, + proxy::ProxyT, + spa::ReadableDict, +}; +use std::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, + io, + os::unix::prelude::{AsRawFd, OwnedFd}, + rc::Rc, + time::Duration, +}; + +/// Node event +#[derive(Debug)] +pub enum NodeEvent<'a> { + /// Node info + Info(u32, &'a NodeInfo), + /// Node removal + Remove(u32), +} + +/// Process event +#[derive(Debug)] +pub enum ProcessEvent { + /// Process add + Add(u32), + /// Process remove + Remove(u32), +} + +impl ProcessEvent { + /// Parse a process event from bytes + #[must_use] + pub fn from_bytes(bytes: &[u8]) -> Option { + let mut fields = BStr::new(bytes).split(|b| *b == b' '); + + let method = fields.next()?; + let pid = atoi::atoi::(fields.next()?)?; + + match method { + b"add" => Some(ProcessEvent::Add(pid)), + b"rem" => Some(ProcessEvent::Remove(pid)), + _ => None, + } + } + + /// # Errors + /// + /// - Failure to write bytes to writer + pub fn to_bytes(&self, writer: &mut W) -> io::Result<()> { + let (method, pid) = match self { + ProcessEvent::Add(pid) => (b"add", *pid), + ProcessEvent::Remove(pid) => (b"rem", *pid), + }; + + writer.write_all(method)?; + writer.write_all(b" ")?; + writer.write_all(itoa::Buffer::new().format(pid).as_bytes()) + } +} + +/// Process information +#[must_use] +#[derive(Copy, Clone, Debug)] +pub struct Process { + /// Process ID + pub id: u32, +} + +impl Process { + /// Attains process info from a pipewire info node. + #[must_use] + pub fn from_node(info: &NodeInfo) -> Option { + let props = info.props()?; + props.get("application.process.binary")?; + + Some(Process { + id: props.get("application.process.id")?.parse::().ok()?, + }) + } +} + +/// Monitors the processes from a given ``PipeWire`` socket. +/// +/// ``PipeWire`` sockets are found in `/run/user/{{UID}}/pipewire-0`. +pub fn processes_from_socket(socket: &OwnedFd, mut func: impl FnMut(ProcessEvent) + 'static) { + let mut managed = BTreeMap::new(); + + let _res = nodes_from_socket(socket, move |event| match event { + NodeEvent::Info(pw_id, info) => { + if let Some(process) = Process::from_node(info) { + if managed.insert(pw_id, process.id).is_none() { + func(ProcessEvent::Add(process.id)); + } + } + } + + NodeEvent::Remove(pw_id) => { + if let Some(pid) = managed.remove(&pw_id) { + func(ProcessEvent::Remove(pid)); + } + } + }); +} + +/// Listens to information about nodes, passing that info into a callback. +/// +/// # Errors +/// +/// Errors if the pipewire connection fails +pub fn nodes_from_socket( + socket: &OwnedFd, + func: impl FnMut(NodeEvent) + 'static, +) -> Result<(), Box> { + let main_loop = pw::MainLoop::new()?; + let context = pw::Context::new(&main_loop)?; + let core = context.connect_fd(socket.as_raw_fd(), None)?; + + let registry = Rc::new(core.get_registry()?); + let registry_weak = Rc::downgrade(®istry); + + let nodes = Rc::new(RefCell::new(HashMap::new())); + let func = Rc::new(RefCell::new(func)); + + let remove_ids = Rc::new(RefCell::new(Vec::new())); + + let garbage_collector = main_loop.add_timer({ + let nodes = Rc::downgrade(&nodes); + let remove_ids = Rc::downgrade(&remove_ids); + move |_| { + if let Some(nodes) = nodes.upgrade() { + if let Some(remove_ids) = remove_ids.upgrade() { + for id in remove_ids.borrow_mut().drain(..) { + nodes.borrow_mut().remove(&id); + } + } + } + } + }); + + let _res = garbage_collector + .update_timer(Some(Duration::from_secs(60)), Some(Duration::from_secs(60))) + .into_result(); + + let _registry_listener = registry + .add_listener_local() + .global(move |obj| { + let Some(registry) = registry_weak.upgrade() else { + return; + }; + + if pw::types::ObjectType::Node == obj.type_ { + let Ok(node): Result = registry.bind(obj) else { + return; + }; + + let proxy = node.upcast_ref(); + let id = proxy.id(); + + let func_weak = Rc::downgrade(&func); + + let info_listener = node + .add_listener_local() + .info(move |info| { + if let Some(func) = func_weak.upgrade() { + func.borrow_mut()(NodeEvent::Info(id, info)); + } + }) + .register(); + + let func = Rc::downgrade(&func); + let remove_ids = Rc::downgrade(&remove_ids); + + let remove_listener = proxy + .add_listener_local() + .removed(move || { + if let Some(remove_ids) = remove_ids.upgrade() { + remove_ids.borrow_mut().push(id); + } + + if let Some(func) = func.upgrade() { + func.borrow_mut()(NodeEvent::Remove(id)); + } + }) + .register(); + + nodes + .borrow_mut() + .insert(id, (node, info_listener, remove_listener)); + } + }) + .register(); + + main_loop.run(); + Ok(()) +}