diff --git a/Cargo.lock b/Cargo.lock index dc27c2ca3..37f5919da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,18 +92,18 @@ dependencies = [ [[package]] name = "ansi-str" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" +checksum = "060de1453b69f46304b28274f382132f4e72c55637cf362920926a70d090890d" dependencies = [ "ansitok", ] [[package]] name = "ansitok" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" +checksum = "c0a8acea8c2f1c60f0a92a8cd26bf96ca97db56f10bbcab238bbe0cceba659ee" dependencies = [ "nom", "vte", @@ -121,12 +121,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.2" @@ -141,7 +135,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -307,6 +301,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.19" @@ -360,7 +369,7 @@ dependencies = [ "clap_lex", "is-terminal", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", ] @@ -370,10 +379,10 @@ version = "4.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81d7dc0031c3a59a04fc2ba395c8e2dd463cba1859275f065d225f6122221b45" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -398,12 +407,12 @@ dependencies = [ "flate2", "futures", "humansize", - "inquire", "jobserver", "libc", "log", "miow", "object", + "ratatui", "rayon", "regex", "reqwest", @@ -424,6 +433,20 @@ dependencies = [ "xz2", ] +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.10" @@ -519,15 +542,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.25.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "crossterm_winapi", - "libc", - "mio 0.8.11", + "mio", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -573,6 +596,41 @@ dependencies = [ "memchr", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.104", +] + [[package]] name = "database" version = "0.1.0" @@ -646,7 +704,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -689,15 +747,9 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "either" version = "1.8.1" @@ -880,7 +932,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -913,24 +965,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1063,6 +1097,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -1279,6 +1319,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1328,6 +1374,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "inferno" version = "0.11.15" @@ -1345,23 +1397,6 @@ dependencies = [ "str_stack", ] -[[package]] -name = "inquire" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" -dependencies = [ - "bitflags 2.6.0", - "crossterm", - "dyn-clone", - "fuzzy-matcher", - "fxhash", - "newline-converter", - "once_cell", - "unicode-segmentation", - "unicode-width 0.1.13", -] - [[package]] name = "insta" version = "1.40.0" @@ -1374,6 +1409,19 @@ dependencies = [ "similar", ] +[[package]] +name = "instability" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "intern" version = "0.1.0" @@ -1411,6 +1459,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1600,9 +1657,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -1634,18 +1691,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.3" @@ -1653,6 +1698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1684,15 +1730,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "newline-converter" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "nom" version = "7.1.3" @@ -1709,7 +1746,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", "itoa", ] @@ -1762,7 +1799,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -1791,10 +1828,12 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "papergrid" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b0f8def1f117e13c895f3eda65a7b5650688da29d6ad04635f61bc7b92eebd" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" dependencies = [ + "ansi-str", + "ansitok", "bytecount", "fnv", "unicode-width 0.2.0", @@ -1903,7 +1942,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -2029,14 +2068,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -2109,6 +2148,27 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "rayon" version = "1.10.0" @@ -2272,7 +2332,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.87", + "syn 2.0.104", "walkdir", ] @@ -2330,6 +2390,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ruzstd" version = "0.7.3" @@ -2418,7 +2484,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -2493,9 +2559,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -2508,7 +2574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.8.11", + "mio", "signal-hook", ] @@ -2556,7 +2622,7 @@ dependencies = [ "hyper", "inferno", "insta", - "itertools", + "itertools 0.10.5", "jemalloc-ctl", "jemallocator", "log", @@ -2657,6 +2723,34 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.4.1" @@ -2676,9 +2770,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2699,7 +2793,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -2725,26 +2819,28 @@ dependencies = [ [[package]] name = "tabled" -version = "0.17.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6709222f3973137427ce50559cd564dc187a95b9cfe01613d2f4e93610e510a" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" dependencies = [ "ansi-str", + "ansitok", "papergrid", "tabled_derive", + "testing_table", ] [[package]] name = "tabled_derive" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931be476627d4c54070a1f3a9739ccbfec9b36b39815106a20cce2243bbcefe1" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] @@ -2796,6 +2892,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "ansitok", + "unicode-width 0.2.0", +] + [[package]] name = "thiserror" version = "2.0.12" @@ -2813,7 +2919,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -2874,7 +2980,7 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -2886,7 +2992,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.3", + "mio", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2902,7 +3008,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -3114,15 +3220,26 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.14", +] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" @@ -3147,12 +3264,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "uuid" version = "1.16.0" @@ -3176,23 +3287,12 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" -dependencies = [ - "arrayvec 0.5.2", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ - "proc-macro2", - "quote", + "arrayvec", + "memchr", ] [[package]] @@ -3569,7 +3669,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", "synstructure", ] @@ -3590,7 +3690,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -3610,7 +3710,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", "synstructure", ] @@ -3631,7 +3731,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] [[package]] @@ -3664,5 +3764,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.104", ] diff --git a/collector/Cargo.toml b/collector/Cargo.toml index 8913efbcd..a822ce9f9 100644 --- a/collector/Cargo.toml +++ b/collector/Cargo.toml @@ -35,12 +35,11 @@ rustc-demangle = { version = "0.1", features = ["std"] } similar = "2.2" console = "0.15" object = "0.36.0" -tabled = { version = "0.17.0", features = ["ansi-str"] } +tabled = { version = "0.20", features = ["ansi-str", "ansi"] } humansize = "2.1.3" regex = "1.7.1" - analyzeme = "12.0.0" -inquire = "0.7.5" +ratatui = "0.29" benchlib = { path = "benchlib" } database = { path = "../database" } diff --git a/collector/src/compare.rs b/collector/src/compare.rs deleted file mode 100644 index 5327860b4..000000000 --- a/collector/src/compare.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::{str::FromStr, sync::Arc}; - -use database::{ - metric::Metric, - selector::{BenchmarkQuery, CompileBenchmarkQuery}, - ArtifactId, Connection, -}; -use tabled::{Table, Tabled}; - -static ALL_METRICS: &[Metric] = &[ - Metric::InstructionsUser, - Metric::Cycles, - Metric::WallTime, - Metric::MaxRSS, - Metric::LinkedArtifactSize, - Metric::AssemblyFileSize, - Metric::BranchMisses, - Metric::CacheMisses, - Metric::CodegenUnitLlvmIrCount, - Metric::CodegenUnitSize, - Metric::ContextSwitches, - Metric::CpuClock, - Metric::CpuClockUser, - Metric::CrateMetadataSize, - Metric::CyclesUser, - Metric::DepGraphSize, - Metric::DocByteSize, - Metric::DwoFileSize, - Metric::Faults, - Metric::FaultsUser, - Metric::LlvmBitcodeSize, - Metric::LlvmIrSize, - Metric::ObjectFileSize, - Metric::QueryCacheSize, - Metric::TaskClock, - Metric::TaskClockUser, - Metric::WorkProductIndexSize, -]; - -/// Compare 2 artifacts and print the result. -pub async fn compare_artifacts( - mut conn: Box, - metric: Option, - base: Option, - modified: Option, -) -> anyhow::Result<()> { - let index = database::Index::load(&mut *conn).await; - - let metric = match metric { - Some(v) => v, - None => { - let metric_str = inquire::Select::new( - "Choose metric:", - ALL_METRICS.iter().map(|m| m.as_str()).collect::>(), - ) - .prompt()?; - Metric::from_str(metric_str).map_err(|e| anyhow::anyhow!(e))? - } - }; - - let mut aids = index.artifacts().map(str::to_string).collect::>(); - if aids.len() < 2 { - return Err(anyhow::anyhow!( - "There are not enough artifacts to compare, at least two are needed" - )); - } - - let select_artifact_id = |name: &str, aids: &Vec| { - anyhow::Ok( - inquire::Select::new( - &format!("Choose {} artifact to compare:", name), - aids.clone(), - ) - .prompt()?, - ) - }; - - let base = match base { - Some(v) => v, - None => select_artifact_id("base", &aids)?.to_string(), - }; - aids.retain(|id| id != &base); - let modified = if let [new_modified] = &aids[..] { - let new_modified = new_modified.clone(); - println!( - "Only 1 artifact remains, automatically selecting: {}", - new_modified - ); - - new_modified - } else { - match modified { - Some(v) => v, - None => select_artifact_id("modified", &aids)?.to_string(), - } - }; - - let query = CompileBenchmarkQuery::default().metric(database::selector::Selector::One(metric)); - let resp = query - .execute( - &mut *conn, - &index, - Arc::new(vec![ArtifactId::Tag(base), ArtifactId::Tag(modified)]), - ) - .await - .unwrap(); - - let tuple_pstats = resp - .into_iter() - .map(|resp| { - let points = resp.series.points.collect::>(); - (points[0], points[1]) - }) - .collect::>(); - - #[derive(Tabled)] - struct Regression { - count: usize, - #[tabled(display_with = "display_range")] - range: (Option, Option), - #[tabled(display_with = "display_mean")] - mean: Option, - } - - fn format_value(value: Option) -> String { - match value { - Some(value) => format!("{:+.2}%", value), - None => "-".to_string(), - } - } - - fn display_range(&(min, max): &(Option, Option)) -> String { - format!("[{}, {}]", &format_value(min), &format_value(max)) - } - - fn display_mean(value: &Option) -> String { - match value { - Some(value) => format!("{:+.2}%", value), - None => "-".to_string(), - } - } - - impl From<&Vec> for Regression { - fn from(value: &Vec) -> Self { - let min = value.iter().copied().min_by(|a, b| a.total_cmp(b)); - let max = value.iter().copied().max_by(|a, b| a.total_cmp(b)); - let count = value.len(); - - Regression { - range: (min, max), - count, - mean: if count == 0 { - None - } else { - Some(value.iter().sum::() / count as f64) - }, - } - } - } - - let change = tuple_pstats - .iter() - .filter_map(|&(a, b)| match (a, b) { - (Some(a), Some(b)) => { - if a == 0.0 { - None - } else { - Some((b - a) / a * 100.0) - } - } - (_, _) => None, - }) - .collect::>(); - let negative_change = change - .iter() - .copied() - .filter(|&c| c < 0.0) - .collect::>(); - let positive_change = change - .iter() - .copied() - .filter(|&c| c > 0.0) - .collect::>(); - - #[derive(Tabled)] - struct NamedRegression { - name: String, - #[tabled(inline)] - regression: Regression, - } - - let regressions = [negative_change, positive_change, change] - .into_iter() - .map(|c| Regression::from(&c)) - .zip(["❌", "✅", "✅, ❌"]) - .map(|(c, label)| NamedRegression { - name: label.to_string(), - regression: c, - }) - .collect::>(); - - println!("{}", Table::new(regressions)); - - Ok(()) -} diff --git a/collector/src/compare/mod.rs b/collector/src/compare/mod.rs new file mode 100644 index 000000000..117be0c18 --- /dev/null +++ b/collector/src/compare/mod.rs @@ -0,0 +1,569 @@ +use anyhow::Context; +use database::selector::{BenchmarkQuery, CompileBenchmarkQuery, CompileTestCase}; +use database::{metric::Metric, ArtifactId, Commit, Connection, Index}; +use ratatui::prelude::Stylize; +use ratatui::widgets::{Cell, List, ListState, Paragraph, Row, Table, TableState}; +use ratatui::{ + crossterm::event::{self, Event, KeyCode}, + prelude::*, + widgets::Block, +}; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use tabled::Tabled; + +static ALL_METRICS: &[Metric] = &[ + Metric::InstructionsUser, + Metric::CyclesUser, + Metric::WallTime, + Metric::MaxRSS, + Metric::LinkedArtifactSize, + Metric::AssemblyFileSize, + Metric::BranchMisses, + Metric::CacheMisses, + Metric::CodegenUnitLlvmIrCount, + Metric::CodegenUnitSize, + Metric::ContextSwitches, + Metric::CpuClock, + Metric::CpuClockUser, + Metric::CrateMetadataSize, + Metric::Cycles, + Metric::DepGraphSize, + Metric::DocByteSize, + Metric::DwoFileSize, + Metric::Faults, + Metric::FaultsUser, + Metric::LlvmBitcodeSize, + Metric::LlvmIrSize, + Metric::ObjectFileSize, + Metric::QueryCacheSize, + Metric::TaskClock, + Metric::TaskClockUser, + Metric::WorkProductIndexSize, +]; + +/// Compare 2 artifacts and print the result. +pub async fn compare_artifacts( + mut conn: Box, + metric: Option, + base: Option, + modified: Option, +) -> anyhow::Result<()> { + let index = database::Index::load(&mut *conn).await; + + let metric = metric.unwrap_or(Metric::InstructionsUser); + + let mut aids = index.commits(); + if aids.len() < 2 { + return Err(anyhow::anyhow!( + "There are not enough artifacts to compare, at least two are needed" + )); + } + + // Error if the selected base/modified commits were not found + fn check_commit( + aids: &[Commit], + commit: Option, + label: &str, + ) -> anyhow::Result> { + commit + .map(|commit| { + aids.iter() + .find(|c| c.sha == commit) + .cloned() + .ok_or_else(|| anyhow::anyhow!("{label} commit {commit} not found")) + }) + .transpose() + } + + let base: Option = check_commit(&aids, base, "Base")?; + if let Some(ref base) = base { + aids.retain(|c| c.sha != base.sha); + } + let modified: Option = check_commit(&aids, modified, "Modified")?; + + let mut terminal = ratatui::init(); + + let db_state = DbState { index, db: conn }; + let mut screen: Box = match (base, modified) { + (Some(base), Some(modified)) => { + Box::new(CompareScreen::new(db_state, metric, base, modified).await?) + } + (Some(base), None) => next_selection_screen(db_state, metric, base, aids).await?, + (None, None) => Box::new(SelectArtifactScreen::new( + db_state, + metric, + aids, + SelectState::SelectingBase, + )), + (None, Some(_)) => { + return Err(anyhow::anyhow!( + "If modified commit is pre-selected, base commit must also be pre-selected" + )); + } + }; + + loop { + terminal.draw(|frame| { + screen.draw(frame); + })?; + if let Event::Key(key_event) = event::read()? { + match key_event.code { + KeyCode::Char('q') | KeyCode::Esc => break, + key => { + if let Some(action) = screen.handle_key(key).await? { + match action { + Action::SelectArtifacts(next_screen) => { + screen = next_screen; + } + } + } + } + } + } + } + ratatui::restore(); + + Ok(()) +} + +struct DbState { + index: Index, + db: Box, +} + +enum Action { + SelectArtifacts(Box), +} + +trait Screen { + fn draw(&mut self, frame: &mut Frame); + fn handle_key( + &mut self, + key: KeyCode, + ) -> Pin>> + '_>>; +} + +enum SelectState { + SelectingBase, + SelectingModified { base: Commit }, +} + +struct SelectArtifactScreen { + aids: Vec, + select_state: SelectState, + list_state: ListState, + db_state: Option, + metric: Metric, +} + +impl SelectArtifactScreen { + fn new( + db_state: DbState, + metric: Metric, + aids: Vec, + select_state: SelectState, + ) -> Self { + Self { + aids, + select_state, + list_state: ListState::default().with_selected(Some(0)), + db_state: Some(db_state), + metric, + } + } +} + +impl Screen for SelectArtifactScreen { + fn draw(&mut self, frame: &mut Frame) { + let items = self + .aids + .iter() + .map(|commit| format!("{} ({})", commit.sha, commit.date)) + .collect::>(); + let list = List::new(items) + .block(Block::bordered().title(format!( + "Select {} artifact", + if matches!(self.select_state, SelectState::SelectingBase) { + "base" + } else { + "modified" + } + ))) + .style(Style::new().white()) + .highlight_style(Style::new().bold()) + .highlight_symbol("> "); + frame.render_stateful_widget(list, frame.area(), &mut self.list_state); + } + + fn handle_key( + &mut self, + key: KeyCode, + ) -> Pin>> + '_>> { + Box::pin(async move { + match key { + KeyCode::Down => self.list_state.select_next(), + KeyCode::Up => self.list_state.select_previous(), + KeyCode::Enter => { + let mut aids = self.aids.clone(); + let index = self.list_state.selected().unwrap(); + let selected = aids.remove(index); + + let next_screen: Box = match &self.select_state { + SelectState::SelectingBase => { + next_selection_screen( + self.db_state.take().unwrap(), + self.metric, + selected, + aids, + ) + .await? + } + SelectState::SelectingModified { base } => Box::new( + CompareScreen::new( + self.db_state.take().unwrap(), + self.metric, + base.clone(), + selected, + ) + .await?, + ), + }; + return Ok(Some(Action::SelectArtifacts(next_screen))); + } + _ => {} + }; + Ok(None) + }) + } +} + +/// Directly goes to comparison if there is only a single artifact ID left, otherwise +/// opens the selection screen for the modified artifact. +async fn next_selection_screen( + db_state: DbState, + metric: Metric, + base: Commit, + aids: Vec, +) -> anyhow::Result> { + let screen = match aids.as_slice() { + [commit] => Box::new(CompareScreen::new(db_state, metric, base, commit.clone()).await?) + as Box, + _ => Box::new(SelectArtifactScreen::new( + db_state, + metric, + aids, + SelectState::SelectingModified { base }, + )), + }; + Ok(screen) +} + +struct TestCaseMeasurement { + case: CompileTestCase, + before: Option, + after: Option, +} + +impl TestCaseMeasurement { + fn change(&self) -> Option { + match (self.before, self.after) { + (Some(a), Some(b)) => { + if a == 0.0 { + None + } else { + Some((b - a) / a * 100.0) + } + } + (_, _) => None, + } + } +} + +struct CompareScreen { + base: Commit, + modified: Commit, + data: Vec, + metric: Metric, + db_state: DbState, + table_state: TableState, +} + +impl CompareScreen { + async fn new( + mut db_state: DbState, + metric: Metric, + base: Commit, + modified: Commit, + ) -> anyhow::Result { + let data = load_data( + metric, + &db_state.index, + db_state.db.as_mut(), + &base, + &modified, + ) + .await?; + Ok(Self { + base, + modified, + db_state, + metric, + data, + table_state: TableState::default(), + }) + } + + async fn reload_data(&mut self) -> anyhow::Result<()> { + self.data = load_data( + self.metric, + &self.db_state.index, + self.db_state.db.as_mut(), + &self.base, + &self.modified, + ) + .await?; + Ok(()) + } +} + +impl Screen for CompareScreen { + fn draw(&mut self, frame: &mut Frame) { + let summary_table = draw_summary_table(&self.data); + let layout = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints( + [ + // +2 because of borders + Constraint::Min((summary_table.lines().count() + 2) as u16), + Constraint::Length(2), + Constraint::Percentage(100), + ] + .as_ref(), + ) + .split(frame.area()); + + frame.render_widget( + Paragraph::new(Text::raw(summary_table)).block(Block::bordered().title("Summary")), + layout[0], + ); + + render_metric(frame, self.metric, layout[1]); + + let header = Row::new(vec![ + Line::from("Benchmark"), + Line::from("Profile"), + Line::from("Scenario"), + Line::from("Backend"), + Line::from("Change").right_aligned(), + ]) + .style(Style::default().bold()); + let widths = [ + Constraint::Percentage(25), + Constraint::Percentage(10), + Constraint::Percentage(25), + Constraint::Percentage(10), + Constraint::Percentage(30), + ]; + + let mut rows = self + .data + .iter() + .filter_map(|row| row.change().map(|change| (row, change))) + .collect::>(); + + rows.sort_by(|(_, a), (_, b)| { + a.abs() + .partial_cmp(&b.abs()) + .expect("cannot compare two results") + .reverse() + }); + + let table = Table::new( + rows.into_iter() + .map(|(measurement, change)| { + let benchmark = Cell::from(measurement.case.benchmark.to_string()); + let profile = Cell::from(measurement.case.profile.to_string()); + let scenario = Cell::from(measurement.case.scenario.to_string()); + let backend = Cell::from(measurement.case.backend.to_string()); + let change: Cell = { + let span = Line::from(format!("{change:+.2}%")); + if change < 0.0 { + span.green() + } else { + span.red() + } + .right_aligned() + .into() + }; + + Row::new([benchmark, profile, scenario, backend, change]) + }) + .collect::>(), + widths, + ) + .header(header) + .column_spacing(1) + .highlight_symbol("> ") + .block(Block::bordered().title("Measurements")) + .row_highlight_style(Style::new().bold()); + + let table_layout = + Layout::new(Direction::Horizontal, [Constraint::Max(120)]).split(layout[2]); + frame.render_stateful_widget(table, table_layout[0], &mut self.table_state); + } + + fn handle_key( + &mut self, + key: KeyCode, + ) -> Pin>> + '_>> { + Box::pin(async move { + match key { + KeyCode::Down => self.table_state.select_next(), + KeyCode::Up => self.table_state.select_previous(), + KeyCode::Char('a') => { + self.metric = select_metric(self.metric, -1); + self.reload_data().await?; + } + KeyCode::Char('s') => { + self.metric = select_metric(self.metric, 1); + self.reload_data().await?; + } + _ => {} + } + + Ok(None) + }) + } +} + +fn select_metric(current: Metric, direction: isize) -> Metric { + let index = ALL_METRICS.iter().position(|m| *m == current).unwrap_or(0) as isize; + let index = ((index + direction) + ALL_METRICS.len() as isize) % ALL_METRICS.len() as isize; + ALL_METRICS[index as usize] +} + +fn render_metric(frame: &mut Frame, metric: Metric, area: Rect) { + frame.render_widget( + Line::from(vec![ + "Metric: ".into(), + metric.as_str().bold(), + " (switch: A/S)".into(), + ]), + area, + ) +} + +async fn load_data( + metric: Metric, + index: &Index, + conn: &mut dyn Connection, + base: &Commit, + modified: &Commit, +) -> anyhow::Result> { + let query = CompileBenchmarkQuery::default().metric(database::selector::Selector::One(metric)); + let resp = query + .execute( + conn, + index, + Arc::new(vec![ + ArtifactId::Commit(base.clone()), + ArtifactId::Commit(modified.clone()), + ]), + ) + .await + .map_err(|e| anyhow::anyhow!("{e}")) + .context("Cannot load data from the DB")?; + + Ok(resp + .into_iter() + .map(|resp| { + let points = resp.series.points.clone().collect::>(); + TestCaseMeasurement { + case: resp.test_case, + before: points[0], + after: points[1], + } + }) + .collect::>()) +} + +fn calculate_changes(pstats: &[TestCaseMeasurement]) -> Vec { + pstats + .iter() + .filter_map(|measurement| measurement.change()) + .collect::>() +} + +fn draw_summary_table(pstats: &[TestCaseMeasurement]) -> String { + let change = calculate_changes(pstats); + let negative_change = change + .iter() + .copied() + .filter(|&c| c < 0.0) + .collect::>(); + let positive_change = change + .iter() + .copied() + .filter(|&c| c > 0.0) + .collect::>(); + + #[derive(Tabled, Debug)] + struct NamedRegression { + name: String, + #[tabled(inline)] + regression: Regression, + } + + let results = [positive_change, negative_change, change] + .into_iter() + .map(|c| Regression::from(&c)) + .zip(["❌", "✅", "✅, ❌"]) + .map(|(c, label)| NamedRegression { + name: label.to_string(), + regression: c, + }) + .collect::>(); + + tabled::Table::new(results).to_string() +} + +#[derive(Tabled, Debug)] +struct Regression { + count: usize, + #[tabled(display("display_range"))] + range: (Option, Option), + #[tabled(display("format_value"))] + mean: Option, +} + +fn format_value(value: &Option) -> String { + match value { + Some(value) => format!("{value:+.2}%"), + None => "-".to_string(), + } +} + +fn display_range(&(min, max): &(Option, Option)) -> String { + format!("[{}, {}]", &format_value(&min), &format_value(&max)) +} + +impl From<&Vec> for Regression { + fn from(value: &Vec) -> Self { + let min = value.iter().copied().min_by(|a, b| a.total_cmp(b)); + let max = value.iter().copied().max_by(|a, b| a.total_cmp(b)); + let count = value.len(); + + Regression { + range: (min, max), + count, + mean: if count == 0 { + None + } else { + Some(value.iter().sum::() / count as f64) + }, + } + } +}