├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── assets ├── screenshot.png └── screenshot02.png └── src ├── main.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "allocator-api2" 19 | version = "0.2.18" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.3.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "2.6.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 34 | 35 | [[package]] 36 | name = "cassowary" 37 | version = "0.3.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 40 | 41 | [[package]] 42 | name = "castaway" 43 | version = "0.2.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 46 | dependencies = [ 47 | "rustversion", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "1.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 55 | 56 | [[package]] 57 | name = "compact_str" 58 | version = "0.8.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 61 | dependencies = [ 62 | "castaway", 63 | "cfg-if", 64 | "itoa", 65 | "rustversion", 66 | "ryu", 67 | "static_assertions", 68 | ] 69 | 70 | [[package]] 71 | name = "crossterm" 72 | version = "0.28.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 75 | dependencies = [ 76 | "bitflags", 77 | "crossterm_winapi", 78 | "mio", 79 | "parking_lot", 80 | "rustix", 81 | "signal-hook", 82 | "signal-hook-mio", 83 | "winapi", 84 | ] 85 | 86 | [[package]] 87 | name = "crossterm_winapi" 88 | version = "0.9.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 91 | dependencies = [ 92 | "winapi", 93 | ] 94 | 95 | [[package]] 96 | name = "darling" 97 | version = "0.20.10" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 100 | dependencies = [ 101 | "darling_core", 102 | "darling_macro", 103 | ] 104 | 105 | [[package]] 106 | name = "darling_core" 107 | version = "0.20.10" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 110 | dependencies = [ 111 | "fnv", 112 | "ident_case", 113 | "proc-macro2", 114 | "quote", 115 | "strsim", 116 | "syn", 117 | ] 118 | 119 | [[package]] 120 | name = "darling_macro" 121 | version = "0.20.10" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 124 | dependencies = [ 125 | "darling_core", 126 | "quote", 127 | "syn", 128 | ] 129 | 130 | [[package]] 131 | name = "deranged" 132 | version = "0.3.11" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 135 | dependencies = [ 136 | "powerfmt", 137 | ] 138 | 139 | [[package]] 140 | name = "either" 141 | version = "1.13.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 144 | 145 | [[package]] 146 | name = "errno" 147 | version = "0.3.9" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 150 | dependencies = [ 151 | "libc", 152 | "windows-sys", 153 | ] 154 | 155 | [[package]] 156 | name = "fnv" 157 | version = "1.0.7" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 160 | 161 | [[package]] 162 | name = "hashbrown" 163 | version = "0.14.5" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 166 | dependencies = [ 167 | "ahash", 168 | "allocator-api2", 169 | ] 170 | 171 | [[package]] 172 | name = "heck" 173 | version = "0.5.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 176 | 177 | [[package]] 178 | name = "hermit-abi" 179 | version = "0.3.9" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 182 | 183 | [[package]] 184 | name = "ident_case" 185 | version = "1.0.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 188 | 189 | [[package]] 190 | name = "instability" 191 | version = "0.3.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" 194 | dependencies = [ 195 | "quote", 196 | "syn", 197 | ] 198 | 199 | [[package]] 200 | name = "itertools" 201 | version = "0.13.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 204 | dependencies = [ 205 | "either", 206 | ] 207 | 208 | [[package]] 209 | name = "itoa" 210 | version = "1.0.11" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 213 | 214 | [[package]] 215 | name = "libc" 216 | version = "0.2.158" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 219 | 220 | [[package]] 221 | name = "libloading" 222 | version = "0.8.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 225 | dependencies = [ 226 | "cfg-if", 227 | "windows-targets", 228 | ] 229 | 230 | [[package]] 231 | name = "linux-raw-sys" 232 | version = "0.4.14" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 235 | 236 | [[package]] 237 | name = "lock_api" 238 | version = "0.4.12" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 241 | dependencies = [ 242 | "autocfg", 243 | "scopeguard", 244 | ] 245 | 246 | [[package]] 247 | name = "log" 248 | version = "0.4.22" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 251 | 252 | [[package]] 253 | name = "lru" 254 | version = "0.12.4" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" 257 | dependencies = [ 258 | "hashbrown", 259 | ] 260 | 261 | [[package]] 262 | name = "mio" 263 | version = "1.0.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 266 | dependencies = [ 267 | "hermit-abi", 268 | "libc", 269 | "log", 270 | "wasi", 271 | "windows-sys", 272 | ] 273 | 274 | [[package]] 275 | name = "num-conv" 276 | version = "0.1.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 279 | 280 | [[package]] 281 | name = "num_threads" 282 | version = "0.1.7" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 285 | dependencies = [ 286 | "libc", 287 | ] 288 | 289 | [[package]] 290 | name = "nvidia" 291 | version = "0.1.13" 292 | dependencies = [ 293 | "crossterm", 294 | "nvml-wrapper", 295 | "ratatui", 296 | ] 297 | 298 | [[package]] 299 | name = "nvml-wrapper" 300 | version = "0.10.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "0c9bff0aa1d48904a1385ea2a8b97576fbdcbc9a3cfccd0d31fe978e1c4038c5" 303 | dependencies = [ 304 | "bitflags", 305 | "libloading", 306 | "nvml-wrapper-sys", 307 | "static_assertions", 308 | "thiserror", 309 | "wrapcenum-derive", 310 | ] 311 | 312 | [[package]] 313 | name = "nvml-wrapper-sys" 314 | version = "0.8.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "698d45156f28781a4e79652b6ebe2eaa0589057d588d3aec1333f6466f13fcb5" 317 | dependencies = [ 318 | "libloading", 319 | ] 320 | 321 | [[package]] 322 | name = "once_cell" 323 | version = "1.19.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 326 | 327 | [[package]] 328 | name = "parking_lot" 329 | version = "0.12.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 332 | dependencies = [ 333 | "lock_api", 334 | "parking_lot_core", 335 | ] 336 | 337 | [[package]] 338 | name = "parking_lot_core" 339 | version = "0.9.10" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 342 | dependencies = [ 343 | "cfg-if", 344 | "libc", 345 | "redox_syscall", 346 | "smallvec", 347 | "windows-targets", 348 | ] 349 | 350 | [[package]] 351 | name = "paste" 352 | version = "1.0.15" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 355 | 356 | [[package]] 357 | name = "powerfmt" 358 | version = "0.2.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 361 | 362 | [[package]] 363 | name = "proc-macro2" 364 | version = "1.0.86" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 367 | dependencies = [ 368 | "unicode-ident", 369 | ] 370 | 371 | [[package]] 372 | name = "quote" 373 | version = "1.0.37" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 376 | dependencies = [ 377 | "proc-macro2", 378 | ] 379 | 380 | [[package]] 381 | name = "ratatui" 382 | version = "0.28.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" 385 | dependencies = [ 386 | "bitflags", 387 | "cassowary", 388 | "compact_str", 389 | "crossterm", 390 | "instability", 391 | "itertools", 392 | "lru", 393 | "paste", 394 | "strum", 395 | "strum_macros", 396 | "time", 397 | "unicode-segmentation", 398 | "unicode-truncate", 399 | "unicode-width", 400 | ] 401 | 402 | [[package]] 403 | name = "redox_syscall" 404 | version = "0.5.4" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" 407 | dependencies = [ 408 | "bitflags", 409 | ] 410 | 411 | [[package]] 412 | name = "rustix" 413 | version = "0.38.37" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 416 | dependencies = [ 417 | "bitflags", 418 | "errno", 419 | "libc", 420 | "linux-raw-sys", 421 | "windows-sys", 422 | ] 423 | 424 | [[package]] 425 | name = "rustversion" 426 | version = "1.0.17" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 429 | 430 | [[package]] 431 | name = "ryu" 432 | version = "1.0.18" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 435 | 436 | [[package]] 437 | name = "scopeguard" 438 | version = "1.2.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 441 | 442 | [[package]] 443 | name = "serde" 444 | version = "1.0.210" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 447 | dependencies = [ 448 | "serde_derive", 449 | ] 450 | 451 | [[package]] 452 | name = "serde_derive" 453 | version = "1.0.210" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 456 | dependencies = [ 457 | "proc-macro2", 458 | "quote", 459 | "syn", 460 | ] 461 | 462 | [[package]] 463 | name = "signal-hook" 464 | version = "0.3.17" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 467 | dependencies = [ 468 | "libc", 469 | "signal-hook-registry", 470 | ] 471 | 472 | [[package]] 473 | name = "signal-hook-mio" 474 | version = "0.2.4" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 477 | dependencies = [ 478 | "libc", 479 | "mio", 480 | "signal-hook", 481 | ] 482 | 483 | [[package]] 484 | name = "signal-hook-registry" 485 | version = "1.4.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 488 | dependencies = [ 489 | "libc", 490 | ] 491 | 492 | [[package]] 493 | name = "smallvec" 494 | version = "1.13.2" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 497 | 498 | [[package]] 499 | name = "static_assertions" 500 | version = "1.1.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 503 | 504 | [[package]] 505 | name = "strsim" 506 | version = "0.11.1" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 509 | 510 | [[package]] 511 | name = "strum" 512 | version = "0.26.3" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 515 | dependencies = [ 516 | "strum_macros", 517 | ] 518 | 519 | [[package]] 520 | name = "strum_macros" 521 | version = "0.26.4" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 524 | dependencies = [ 525 | "heck", 526 | "proc-macro2", 527 | "quote", 528 | "rustversion", 529 | "syn", 530 | ] 531 | 532 | [[package]] 533 | name = "syn" 534 | version = "2.0.77" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 537 | dependencies = [ 538 | "proc-macro2", 539 | "quote", 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "thiserror" 545 | version = "1.0.63" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 548 | dependencies = [ 549 | "thiserror-impl", 550 | ] 551 | 552 | [[package]] 553 | name = "thiserror-impl" 554 | version = "1.0.63" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 557 | dependencies = [ 558 | "proc-macro2", 559 | "quote", 560 | "syn", 561 | ] 562 | 563 | [[package]] 564 | name = "time" 565 | version = "0.3.36" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 568 | dependencies = [ 569 | "deranged", 570 | "libc", 571 | "num-conv", 572 | "num_threads", 573 | "powerfmt", 574 | "serde", 575 | "time-core", 576 | ] 577 | 578 | [[package]] 579 | name = "time-core" 580 | version = "0.1.2" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 583 | 584 | [[package]] 585 | name = "unicode-ident" 586 | version = "1.0.12" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 589 | 590 | [[package]] 591 | name = "unicode-segmentation" 592 | version = "1.12.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 595 | 596 | [[package]] 597 | name = "unicode-truncate" 598 | version = "1.1.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 601 | dependencies = [ 602 | "itertools", 603 | "unicode-segmentation", 604 | "unicode-width", 605 | ] 606 | 607 | [[package]] 608 | name = "unicode-width" 609 | version = "0.1.14" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 612 | 613 | [[package]] 614 | name = "version_check" 615 | version = "0.9.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 618 | 619 | [[package]] 620 | name = "wasi" 621 | version = "0.11.0+wasi-snapshot-preview1" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 624 | 625 | [[package]] 626 | name = "winapi" 627 | version = "0.3.9" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 630 | dependencies = [ 631 | "winapi-i686-pc-windows-gnu", 632 | "winapi-x86_64-pc-windows-gnu", 633 | ] 634 | 635 | [[package]] 636 | name = "winapi-i686-pc-windows-gnu" 637 | version = "0.4.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 640 | 641 | [[package]] 642 | name = "winapi-x86_64-pc-windows-gnu" 643 | version = "0.4.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 646 | 647 | [[package]] 648 | name = "windows-sys" 649 | version = "0.52.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 652 | dependencies = [ 653 | "windows-targets", 654 | ] 655 | 656 | [[package]] 657 | name = "windows-targets" 658 | version = "0.52.6" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 661 | dependencies = [ 662 | "windows_aarch64_gnullvm", 663 | "windows_aarch64_msvc", 664 | "windows_i686_gnu", 665 | "windows_i686_gnullvm", 666 | "windows_i686_msvc", 667 | "windows_x86_64_gnu", 668 | "windows_x86_64_gnullvm", 669 | "windows_x86_64_msvc", 670 | ] 671 | 672 | [[package]] 673 | name = "windows_aarch64_gnullvm" 674 | version = "0.52.6" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 677 | 678 | [[package]] 679 | name = "windows_aarch64_msvc" 680 | version = "0.52.6" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 683 | 684 | [[package]] 685 | name = "windows_i686_gnu" 686 | version = "0.52.6" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 689 | 690 | [[package]] 691 | name = "windows_i686_gnullvm" 692 | version = "0.52.6" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 695 | 696 | [[package]] 697 | name = "windows_i686_msvc" 698 | version = "0.52.6" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 701 | 702 | [[package]] 703 | name = "windows_x86_64_gnu" 704 | version = "0.52.6" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 707 | 708 | [[package]] 709 | name = "windows_x86_64_gnullvm" 710 | version = "0.52.6" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 713 | 714 | [[package]] 715 | name = "windows_x86_64_msvc" 716 | version = "0.52.6" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 719 | 720 | [[package]] 721 | name = "wrapcenum-derive" 722 | version = "0.4.1" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" 725 | dependencies = [ 726 | "darling", 727 | "proc-macro2", 728 | "quote", 729 | "syn", 730 | ] 731 | 732 | [[package]] 733 | name = "zerocopy" 734 | version = "0.7.35" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 737 | dependencies = [ 738 | "zerocopy-derive", 739 | ] 740 | 741 | [[package]] 742 | name = "zerocopy-derive" 743 | version = "0.7.35" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 746 | dependencies = [ 747 | "proc-macro2", 748 | "quote", 749 | "syn", 750 | ] 751 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvidia" 3 | version = "0.1.13" 4 | edition = "2021" 5 | authors = ["Trevor Sullivan "] 6 | description = "A Rust utility to retrieve information about NVIDIA GPUs installed in your system." 7 | license = "MIT" 8 | homepage = "https://github.com/pcgeek86/nvidia-rs" 9 | 10 | [dependencies] 11 | crossterm = "0.28.1" 12 | nvml-wrapper = "0.10.0" 13 | ratatui = { version = "0.28.1", features = ["all-widgets"] } 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2024 Trevor Sullivan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NVIDIA CLI 2 | 3 | This is a command line tool to interface with NVIDIA GPUs. 4 | This tool has been developed in native Rust code. 🦀 5 | We rely on the `nvml-wrapper` crate, developed by [@cldfire](https://github.com/cldfire/). 6 | For the terminal user interface, we use the popular [ratatui](https://github.com/ratatui/ratatui) crate. 7 | 8 | ![screenshot](https://github.com/pcgeek86/nvidia-rs/blob/master/assets/screenshot02.png?raw=true) 9 | 10 | # Supported Configurations 11 | 12 | * Operating System: 13 | * Windows 11 14 | * Ubuntu Server 24.04 LTS Linux 15 | * NVIDIA Driver must be installed 16 | * Linux: `sudo apt-get install nvidia-driver-550-server --yes` 17 | * You can use the `nvidia-smi` command to validate that the NVIDIA GPU driver works correctly 18 | 19 | # Installation 20 | 21 | Ensure that the [Rust toolchain](https://rustup.rs) is installed on your system. 22 | 23 | ```bash 24 | cargo install nvidia 25 | ``` 26 | 27 | # Usage 28 | 29 | Run this command: 30 | 31 | ``` 32 | nvidia 33 | ``` 34 | 35 | The program refreshes statistics every 200 milliseconds. 36 | At the moment, this threshold is not configurable. 37 | To exit the program, press the `q` key on your keyboard. 38 | 39 | # Uninstallation 40 | 41 | You can remove the NVIDIA CLI tool with the following command: 42 | 43 | ``` 44 | cargo uninstall nvidia 45 | ``` 46 | 47 | # Possible Issues 48 | 49 | * This application only looks at the first NVIDIA GPU in your system. Multiple GPUs not currently supported 50 | * If you don't have the NVIDIA GeForce driver installed, this utility won't work 51 | * Only tested this application on Windows 11 and Ubuntu Server Linux 52 | 53 | # License 54 | 55 | MIT 56 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcgeek86/nvidia-rs/07a9cc9261aa64ffe9aea7a1ec6debceff7c0e9d/assets/screenshot.png -------------------------------------------------------------------------------- /assets/screenshot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcgeek86/nvidia-rs/07a9cc9261aa64ffe9aea7a1ec6debceff7c0e9d/assets/screenshot02.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::Event; 2 | use nvml_wrapper::{enum_wrappers::device::TemperatureSensor, Nvml}; 3 | 4 | use ratatui::{ 5 | crossterm::event::{self,KeyCode,KeyEventKind}, layout::{Constraint, Layout}, style::{Style, Stylize}, symbols::Marker, widgets::{block::Title, Axis, Block, Chart, Dataset, Widget}, DefaultTerminal, Frame 6 | }; 7 | 8 | mod tests; 9 | 10 | fn main() { 11 | // get_nvidia_gpu_info(); 12 | run_tui(); 13 | } 14 | 15 | fn run_tui() { 16 | let mut terminal = ratatui::init(); 17 | terminal.clear().expect("Failed to clear terminal"); 18 | 19 | let mut app = NvidiaApp::default(); 20 | let app_result = app.run_app(&mut terminal); 21 | 22 | ratatui::restore(); 23 | } 24 | 25 | #[derive(Debug, Default)] 26 | struct NvidiaApp { 27 | gpu_core_clock: [u32;30], 28 | gpu_temperature: [u32;30], 29 | gpu_device: String, 30 | exit: bool, 31 | } 32 | 33 | impl NvidiaApp { 34 | pub fn run_app(&mut self, terminal: &mut DefaultTerminal) -> std::io::Result<()> { 35 | let nvml = Nvml::init().expect("Failed to initialize NVML"); 36 | let gpu_device = nvml.device_by_index(0).expect("Failed to get device index 0"); 37 | self.gpu_device = gpu_device.name().expect("Failed to get GPU part number"); 38 | 39 | 40 | while !self.exit { 41 | self.update_state()?; 42 | let _ = terminal.draw(|frame| self.draw(frame))?; 43 | self.handle_events()?; 44 | } 45 | Ok(()) 46 | } 47 | 48 | // This function is used to update the main application state 49 | fn update_state(&mut self) -> std::io::Result<()> { 50 | let nvml = Nvml::init().expect("Failed to initialize NVML"); 51 | let gpu_device = nvml.device_by_index(0).expect("Failed to get device index 0"); 52 | 53 | // Update GPU Core Clock Speed 54 | let current_clock = gpu_device.clock_info(nvml_wrapper::enum_wrappers::device::Clock::Graphics).expect("Failed to retrieve GPU clock speed"); 55 | self.gpu_core_clock.rotate_left(1); 56 | self.gpu_core_clock[29] = current_clock; 57 | 58 | let gpu_current_temperature = gpu_device.temperature(TemperatureSensor::Gpu).expect("Failed to retrieve GPU temperature"); 59 | self.gpu_temperature.rotate_left(1); 60 | self.gpu_temperature[29] = gpu_current_temperature; 61 | 62 | Ok(()) 63 | } 64 | 65 | fn draw(&self, frame: &mut Frame) { 66 | frame.render_widget(self, frame.area()) 67 | } 68 | 69 | fn handle_events(&mut self) -> std::io::Result<()> { 70 | if !event::poll(std::time::Duration::from_millis(150)).unwrap() { 71 | return Ok(()); // Don't try to read any events if there aren't any available 72 | } 73 | match event::read()? { 74 | Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { 75 | self.handle_key_event(key_event); 76 | } 77 | _ => { } 78 | } 79 | Ok(()) 80 | } 81 | 82 | fn handle_key_event(&mut self, key_event: event::KeyEvent) { 83 | match key_event.code { 84 | KeyCode::Char('q') | KeyCode::Char('Q') => { 85 | self.exit(); 86 | }, 87 | _ => { } 88 | } 89 | } 90 | 91 | fn exit(&mut self) { 92 | self.exit = true; 93 | } 94 | } 95 | 96 | impl Widget for &NvidiaApp { 97 | fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { 98 | let [parent01, parent02,] = Layout::horizontal(Constraint::from_percentages([50,50])).areas(area); 99 | 100 | let [top01, bottom01] = Layout::vertical(Constraint::from_percentages([50,50])).areas(parent01); 101 | let [top02, bottom02] = Layout::vertical(Constraint::from_percentages([50,50])).areas(parent02); 102 | 103 | 104 | let title = Title::from(format!("NVIDIA GPU Monitor {0}", self.gpu_device)); 105 | let block01 = Block::bordered() 106 | .border_style(Style::new() 107 | .fg(ratatui::style::Color::Rgb(117, 255, 0)) 108 | ) 109 | .title(title.alignment(ratatui::layout::Alignment::Center)); 110 | 111 | let block02 = Block::bordered() 112 | .style(Style::new().cyan()); 113 | 114 | let block_bottom_left = Block::bordered(); 115 | block_bottom_left.render(bottom01, buf); 116 | 117 | let block_bottom_right = Block::bordered(); 118 | block_bottom_right.render(bottom02, buf); 119 | 120 | // We need to take the last 30 data points and turn it into an array of tuples that the ratatui chart can interpret 121 | let gpu_clock_data: [(f64, f64); 30] = self.gpu_core_clock.iter().zip(-29..=0).map(|i| (i.1 as f64, *i.0 as f64)).collect::>().try_into().unwrap(); 122 | let chart_gpu_clock_data = Dataset::default() 123 | .name("GPU Clock") 124 | .marker(Marker::Dot) 125 | .graph_type(ratatui::widgets::GraphType::Line) 126 | .data(&gpu_clock_data); 127 | let chart_gpu_clock_x_axis = Axis::default().title("Time").bounds([-30.0, 0.0]); 128 | let chart_gpu_clock_y_axis = Axis::default().title("GPU Clock Speed").bounds([0.0,2000.0]); 129 | let chart_gpu_clock = Chart::new(vec![chart_gpu_clock_data]) 130 | .block(block01) 131 | .x_axis(chart_gpu_clock_x_axis) 132 | .y_axis(chart_gpu_clock_y_axis) 133 | .style(Style::new().fg(ratatui::style::Color::Rgb(48,226,173))); 134 | chart_gpu_clock.render(top01, buf); 135 | 136 | 137 | let gpu_temperature_data: [(f64, f64); 30] = self.gpu_temperature.iter().zip(-29..=0).map(|i| (i.1 as f64, *i.0 as f64)).collect::>().try_into().expect("Failed to format GPU temperature data for charting"); 138 | let chart_gpu_temperature_data = Dataset::default() 139 | .name("GPU Temp") 140 | .graph_type(ratatui::widgets::GraphType::Line) 141 | .marker(Marker::Dot) 142 | .data(&gpu_temperature_data); 143 | let chart_gpu_temperature_x_axis = Axis::default().title("Time").bounds([-30.0, 0.0]); 144 | let chart_gpu_temperature_y_axis = Axis::default().title("Temp").bounds([20.0, 85.0]).labels(["20".bold(), "85".bold()]); 145 | let chart_gpu_temperature = Chart::new(vec![chart_gpu_temperature_data]) 146 | .block(block02) 147 | .x_axis(chart_gpu_temperature_x_axis) 148 | .y_axis(chart_gpu_temperature_y_axis) 149 | .style(Style::new().fg(ratatui::style::Color::Rgb(48, 226, 173))); 150 | chart_gpu_temperature.render(top02, buf); 151 | 152 | 153 | } 154 | } 155 | 156 | // Legacy function, not currently being called anywhere. 157 | // Just used this to test retrieving certain values from the NVIDIA GPU. 158 | #[allow(dead_code)] 159 | fn get_nvidia_gpu_info() { 160 | let nvml = nvml_wrapper::Nvml::init().expect("Failed to initialize NVML"); 161 | println!("You have {0} NVIDIA GPUs", nvml.device_count().expect("Failed to list NVIDIA GPUs")); 162 | 163 | println!("NVIDIA Driver version: {0}", nvml.sys_driver_version().expect("Failed to retrieve NVIDIA driver version")); 164 | println!("NVIDIA CUDA version: {0}", nvml.sys_cuda_driver_version().expect("Failed to retrieve NVIDIA CUDA version")); 165 | 166 | for gpu_index in 0..nvml.device_count().unwrap() { 167 | let gpu = nvml.device_by_index(gpu_index).expect("Failed to retrieve GPU with that index"); 168 | println!("{0}: GPU Architecture: {1}", gpu_index, gpu.architecture().expect("Failed to retrieve GPU architecture")); 169 | println!("{0}: GPU Brand: {1:?}", gpu_index, gpu.brand().expect("Failed to get GPU brand")); 170 | println!("{0}: 🌡️ GPU Temperature: {1:?} °C ", gpu_index, gpu.temperature(TemperatureSensor::Gpu).expect("Failed to retrieve GPU temperature")); 171 | // println!("{0}: GPU Driver Model: {1:?}", gpu_index, gpu.driver_model().expect("Failed to retrieve GPU driver model").current); 172 | println!("{0}: GPU Power Consumption: {1} watts", gpu_index, (gpu.power_usage().expect("Failed to get GPU power consumption")/1000) as f32); 173 | 174 | } 175 | } -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use super::*; 3 | use ratatui::{buffer::Buffer, layout::Rect, style::Style}; 4 | 5 | #[test] 6 | fn render() { 7 | let app = NvidiaApp::default(); 8 | let mut buf = Buffer::empty(Rect::new(0,0,50,4)); 9 | 10 | app.render(buf.area, &mut buf); 11 | 12 | 13 | } 14 | 15 | #[test] 16 | fn handle_key_event() -> std::io::Result<()> { 17 | let mut app = NvidiaApp::default(); 18 | app.handle_key_event(KeyCode::Char('q').into()); 19 | assert!(app.exit); 20 | Ok(()) 21 | } --------------------------------------------------------------------------------