├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── slurm-sys ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── lib.rs │ └── wrapper.h ├── slurm ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── build.rs ├── examples │ ├── account.rs │ ├── recent.rs │ ├── rsinfo.rs │ └── submit-echo.rs └── src │ └── lib.rs └── src ├── colorio.rs ├── main.rs ├── recent.rs ├── status.rs └── util.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2019 Peter K. G. Williams and collaborators 2 | # Licensed under the MIT License 3 | 4 | language: rust 5 | 6 | cache: cargo 7 | 8 | matrix: 9 | include: 10 | - os: linux 11 | sudo: required 12 | dist: xenial 13 | rust: stable 14 | - os: linux 15 | sudo: required 16 | dist: xenial 17 | rust: beta 18 | - os: linux 19 | sudo: required 20 | dist: xenial 21 | rust: nightly 22 | allow_failures: 23 | - rust: nightly 24 | 25 | # Don't CI branches besides master. PR's still get built! But this prevents 26 | # PRs being built twice in the standard workflow. 27 | branches: 28 | only: 29 | - master 30 | 31 | addons: 32 | apt: 33 | packages: 34 | - libslurm-dev 35 | - libslurmdb-dev 36 | 37 | env: 38 | global: 39 | - SLURM_LIBDIR=/usr/lib/x86_64-linux-gnu 40 | - SLURM_INCDIR=/usr/include 41 | 42 | before_script: 43 | - rustup component add rustfmt 44 | 45 | script: 46 | - | 47 | cargo fmt --all -- --check && 48 | cargo build --verbose && 49 | cargo test 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | See [slurm-sys/CHANGELOG.md](slurm-sys/CHANGELOG.md) and 2 | [slurm/CHANGELOG.md](slurm/CHANGELOG.md) for changes in those respective 3 | crates. This changelog concerns the `slurmplus` command-line tool. 4 | 5 | 6 | # 0.1.3 (2018 May 27) 7 | 8 | - New version that builds against `slurm-sys` and `slurm` 0.1.3, which should 9 | get our API docs building on `docs.rs`. 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.6.10" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 10 | dependencies = [ 11 | "memchr 2.2.1", 12 | ] 13 | 14 | [[package]] 15 | name = "aho-corasick" 16 | version = "0.7.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" 19 | dependencies = [ 20 | "memchr 2.2.1", 21 | ] 22 | 23 | [[package]] 24 | name = "android-tzdata" 25 | version = "0.1.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 28 | 29 | [[package]] 30 | name = "android_system_properties" 31 | version = "0.1.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 34 | dependencies = [ 35 | "libc", 36 | ] 37 | 38 | [[package]] 39 | name = "ansi_term" 40 | version = "0.12.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 43 | dependencies = [ 44 | "winapi", 45 | ] 46 | 47 | [[package]] 48 | name = "atty" 49 | version = "0.2.13" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 52 | dependencies = [ 53 | "libc", 54 | "winapi", 55 | ] 56 | 57 | [[package]] 58 | name = "autocfg" 59 | version = "0.1.6" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" 62 | 63 | [[package]] 64 | name = "backtrace" 65 | version = "0.3.38" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" 68 | dependencies = [ 69 | "backtrace-sys", 70 | "cfg-if 0.1.10", 71 | "libc", 72 | "rustc-demangle", 73 | ] 74 | 75 | [[package]] 76 | name = "backtrace-sys" 77 | version = "0.1.31" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" 80 | dependencies = [ 81 | "cc", 82 | "libc", 83 | ] 84 | 85 | [[package]] 86 | name = "bindgen" 87 | version = "0.35.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "b023955126e7909ab9fc1d1973965b8b004f1f388afb5c589640ab483b3b0ad2" 90 | dependencies = [ 91 | "cexpr", 92 | "cfg-if 0.1.10", 93 | "clang-sys", 94 | "clap", 95 | "env_logger", 96 | "lazy_static", 97 | "log", 98 | "peeking_take_while", 99 | "quote 0.3.15", 100 | "regex 0.2.11", 101 | "which", 102 | ] 103 | 104 | [[package]] 105 | name = "bitflags" 106 | version = "1.2.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 109 | 110 | [[package]] 111 | name = "bumpalo" 112 | version = "3.10.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 115 | 116 | [[package]] 117 | name = "cc" 118 | version = "1.0.79" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 121 | 122 | [[package]] 123 | name = "cexpr" 124 | version = "0.2.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "42aac45e9567d97474a834efdee3081b3c942b2205be932092f53354ce503d6c" 127 | dependencies = [ 128 | "nom", 129 | ] 130 | 131 | [[package]] 132 | name = "cfg-if" 133 | version = "0.1.10" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 136 | 137 | [[package]] 138 | name = "cfg-if" 139 | version = "1.0.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 142 | 143 | [[package]] 144 | name = "chrono" 145 | version = "0.4.41" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 148 | dependencies = [ 149 | "android-tzdata", 150 | "iana-time-zone", 151 | "js-sys", 152 | "num-traits", 153 | "wasm-bindgen", 154 | "windows-link", 155 | ] 156 | 157 | [[package]] 158 | name = "clang-sys" 159 | version = "0.22.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "939a1a34310b120d26eba35c29475933128b0ec58e24b43327f8dbe6036fc538" 162 | dependencies = [ 163 | "glob", 164 | "libc", 165 | "libloading", 166 | ] 167 | 168 | [[package]] 169 | name = "clap" 170 | version = "2.34.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 173 | dependencies = [ 174 | "ansi_term", 175 | "atty", 176 | "bitflags", 177 | "strsim", 178 | "textwrap", 179 | "unicode-width", 180 | "vec_map", 181 | ] 182 | 183 | [[package]] 184 | name = "codespan-reporting" 185 | version = "0.11.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 188 | dependencies = [ 189 | "termcolor", 190 | "unicode-width", 191 | ] 192 | 193 | [[package]] 194 | name = "core-foundation-sys" 195 | version = "0.8.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 198 | 199 | [[package]] 200 | name = "cxx" 201 | version = "1.0.92" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" 204 | dependencies = [ 205 | "cc", 206 | "cxxbridge-flags", 207 | "cxxbridge-macro", 208 | "link-cplusplus", 209 | ] 210 | 211 | [[package]] 212 | name = "cxx-build" 213 | version = "1.0.92" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" 216 | dependencies = [ 217 | "cc", 218 | "codespan-reporting", 219 | "once_cell", 220 | "proc-macro2", 221 | "quote 1.0.24", 222 | "scratch", 223 | "syn", 224 | ] 225 | 226 | [[package]] 227 | name = "cxxbridge-flags" 228 | version = "1.0.92" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" 231 | 232 | [[package]] 233 | name = "cxxbridge-macro" 234 | version = "1.0.92" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" 237 | dependencies = [ 238 | "proc-macro2", 239 | "quote 1.0.24", 240 | "syn", 241 | ] 242 | 243 | [[package]] 244 | name = "either" 245 | version = "1.5.3" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 248 | 249 | [[package]] 250 | name = "env_logger" 251 | version = "0.5.13" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" 254 | dependencies = [ 255 | "atty", 256 | "humantime", 257 | "log", 258 | "regex 1.3.1", 259 | "termcolor", 260 | ] 261 | 262 | [[package]] 263 | name = "failure" 264 | version = "0.1.8" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 267 | dependencies = [ 268 | "backtrace", 269 | "failure_derive", 270 | ] 271 | 272 | [[package]] 273 | name = "failure_derive" 274 | version = "0.1.8" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 277 | dependencies = [ 278 | "proc-macro2", 279 | "quote 1.0.24", 280 | "syn", 281 | "synstructure", 282 | ] 283 | 284 | [[package]] 285 | name = "glob" 286 | version = "0.2.11" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" 289 | 290 | [[package]] 291 | name = "heck" 292 | version = "0.3.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 295 | dependencies = [ 296 | "unicode-segmentation", 297 | ] 298 | 299 | [[package]] 300 | name = "humantime" 301 | version = "1.3.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 304 | dependencies = [ 305 | "quick-error", 306 | ] 307 | 308 | [[package]] 309 | name = "iana-time-zone" 310 | version = "0.1.53" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 313 | dependencies = [ 314 | "android_system_properties", 315 | "core-foundation-sys", 316 | "iana-time-zone-haiku", 317 | "js-sys", 318 | "wasm-bindgen", 319 | "winapi", 320 | ] 321 | 322 | [[package]] 323 | name = "iana-time-zone-haiku" 324 | version = "0.1.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 327 | dependencies = [ 328 | "cxx", 329 | "cxx-build", 330 | ] 331 | 332 | [[package]] 333 | name = "itertools" 334 | version = "0.14.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 337 | dependencies = [ 338 | "either", 339 | ] 340 | 341 | [[package]] 342 | name = "js-sys" 343 | version = "0.3.59" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 346 | dependencies = [ 347 | "wasm-bindgen", 348 | ] 349 | 350 | [[package]] 351 | name = "lazy_static" 352 | version = "1.4.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 355 | 356 | [[package]] 357 | name = "libc" 358 | version = "0.2.172" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 361 | 362 | [[package]] 363 | name = "libloading" 364 | version = "0.5.2" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 367 | dependencies = [ 368 | "cc", 369 | "winapi", 370 | ] 371 | 372 | [[package]] 373 | name = "link-cplusplus" 374 | version = "1.0.8" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 377 | dependencies = [ 378 | "cc", 379 | ] 380 | 381 | [[package]] 382 | name = "log" 383 | version = "0.4.8" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 386 | dependencies = [ 387 | "cfg-if 0.1.10", 388 | ] 389 | 390 | [[package]] 391 | name = "memchr" 392 | version = "1.0.2" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" 395 | dependencies = [ 396 | "libc", 397 | ] 398 | 399 | [[package]] 400 | name = "memchr" 401 | version = "2.2.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 404 | 405 | [[package]] 406 | name = "nom" 407 | version = "3.2.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" 410 | dependencies = [ 411 | "memchr 1.0.2", 412 | ] 413 | 414 | [[package]] 415 | name = "num-traits" 416 | version = "0.2.8" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 419 | dependencies = [ 420 | "autocfg", 421 | ] 422 | 423 | [[package]] 424 | name = "once_cell" 425 | version = "1.13.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 428 | 429 | [[package]] 430 | name = "peeking_take_while" 431 | version = "0.1.2" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 434 | 435 | [[package]] 436 | name = "pkg-config" 437 | version = "0.3.16" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" 440 | 441 | [[package]] 442 | name = "proc-macro-error" 443 | version = "1.0.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "8931031034aa65c73f3f1a05c3ec0fa51287fcd06557ecf4e88b2768bdca375e" 446 | dependencies = [ 447 | "proc-macro-error-attr", 448 | "proc-macro2", 449 | "quote 1.0.24", 450 | "syn", 451 | "version_check", 452 | ] 453 | 454 | [[package]] 455 | name = "proc-macro-error-attr" 456 | version = "1.0.1" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "2147536f412ee7ae5529364ed50172ca0220fd64591e236296f45f36b38b2f98" 459 | dependencies = [ 460 | "proc-macro2", 461 | "quote 1.0.24", 462 | "syn", 463 | "syn-mid", 464 | "version_check", 465 | ] 466 | 467 | [[package]] 468 | name = "proc-macro2" 469 | version = "1.0.52" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" 472 | dependencies = [ 473 | "unicode-ident", 474 | ] 475 | 476 | [[package]] 477 | name = "quick-error" 478 | version = "1.2.2" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 481 | 482 | [[package]] 483 | name = "quote" 484 | version = "0.3.15" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 487 | 488 | [[package]] 489 | name = "quote" 490 | version = "1.0.24" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "50686e0021c4136d1d453b2dfe059902278681512a34d4248435dc34b6b5c8ec" 493 | dependencies = [ 494 | "proc-macro2", 495 | ] 496 | 497 | [[package]] 498 | name = "regex" 499 | version = "0.2.11" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" 502 | dependencies = [ 503 | "aho-corasick 0.6.10", 504 | "memchr 2.2.1", 505 | "regex-syntax 0.5.6", 506 | "thread_local", 507 | "utf8-ranges", 508 | ] 509 | 510 | [[package]] 511 | name = "regex" 512 | version = "1.3.1" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" 515 | dependencies = [ 516 | "aho-corasick 0.7.6", 517 | "memchr 2.2.1", 518 | "regex-syntax 0.6.12", 519 | "thread_local", 520 | ] 521 | 522 | [[package]] 523 | name = "regex-syntax" 524 | version = "0.5.6" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" 527 | dependencies = [ 528 | "ucd-util", 529 | ] 530 | 531 | [[package]] 532 | name = "regex-syntax" 533 | version = "0.6.12" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" 536 | 537 | [[package]] 538 | name = "rustc-demangle" 539 | version = "0.1.16" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 542 | 543 | [[package]] 544 | name = "scratch" 545 | version = "1.0.5" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" 548 | 549 | [[package]] 550 | name = "slurm" 551 | version = "0.1.3" 552 | dependencies = [ 553 | "chrono", 554 | "clap", 555 | "failure", 556 | "failure_derive", 557 | "itertools", 558 | "libc", 559 | "slurm-sys", 560 | ] 561 | 562 | [[package]] 563 | name = "slurm-sys" 564 | version = "0.1.3" 565 | dependencies = [ 566 | "bindgen", 567 | "pkg-config", 568 | ] 569 | 570 | [[package]] 571 | name = "slurmplus" 572 | version = "0.1.3" 573 | dependencies = [ 574 | "chrono", 575 | "failure", 576 | "failure_derive", 577 | "itertools", 578 | "slurm", 579 | "structopt", 580 | "termcolor", 581 | "users", 582 | ] 583 | 584 | [[package]] 585 | name = "strsim" 586 | version = "0.8.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 589 | 590 | [[package]] 591 | name = "structopt" 592 | version = "0.3.26" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 595 | dependencies = [ 596 | "clap", 597 | "lazy_static", 598 | "structopt-derive", 599 | ] 600 | 601 | [[package]] 602 | name = "structopt-derive" 603 | version = "0.4.18" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 606 | dependencies = [ 607 | "heck", 608 | "proc-macro-error", 609 | "proc-macro2", 610 | "quote 1.0.24", 611 | "syn", 612 | ] 613 | 614 | [[package]] 615 | name = "syn" 616 | version = "1.0.99" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote 1.0.24", 622 | "unicode-ident", 623 | ] 624 | 625 | [[package]] 626 | name = "syn-mid" 627 | version = "0.5.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" 630 | dependencies = [ 631 | "proc-macro2", 632 | "quote 1.0.24", 633 | "syn", 634 | ] 635 | 636 | [[package]] 637 | name = "synstructure" 638 | version = "0.12.1" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" 641 | dependencies = [ 642 | "proc-macro2", 643 | "quote 1.0.24", 644 | "syn", 645 | "unicode-xid", 646 | ] 647 | 648 | [[package]] 649 | name = "termcolor" 650 | version = "1.4.1" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 653 | dependencies = [ 654 | "winapi-util", 655 | ] 656 | 657 | [[package]] 658 | name = "textwrap" 659 | version = "0.11.0" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 662 | dependencies = [ 663 | "unicode-width", 664 | ] 665 | 666 | [[package]] 667 | name = "thread_local" 668 | version = "0.3.6" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 671 | dependencies = [ 672 | "lazy_static", 673 | ] 674 | 675 | [[package]] 676 | name = "ucd-util" 677 | version = "0.1.5" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874" 680 | 681 | [[package]] 682 | name = "unicode-ident" 683 | version = "1.0.3" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 686 | 687 | [[package]] 688 | name = "unicode-segmentation" 689 | version = "1.3.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" 692 | 693 | [[package]] 694 | name = "unicode-width" 695 | version = "0.1.6" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" 698 | 699 | [[package]] 700 | name = "unicode-xid" 701 | version = "0.2.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 704 | 705 | [[package]] 706 | name = "users" 707 | version = "0.11.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 710 | dependencies = [ 711 | "libc", 712 | "log", 713 | ] 714 | 715 | [[package]] 716 | name = "utf8-ranges" 717 | version = "1.0.4" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" 720 | 721 | [[package]] 722 | name = "vec_map" 723 | version = "0.8.1" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 726 | 727 | [[package]] 728 | name = "version_check" 729 | version = "0.9.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 732 | 733 | [[package]] 734 | name = "wasm-bindgen" 735 | version = "0.2.82" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 738 | dependencies = [ 739 | "cfg-if 1.0.0", 740 | "wasm-bindgen-macro", 741 | ] 742 | 743 | [[package]] 744 | name = "wasm-bindgen-backend" 745 | version = "0.2.82" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 748 | dependencies = [ 749 | "bumpalo", 750 | "log", 751 | "once_cell", 752 | "proc-macro2", 753 | "quote 1.0.24", 754 | "syn", 755 | "wasm-bindgen-shared", 756 | ] 757 | 758 | [[package]] 759 | name = "wasm-bindgen-macro" 760 | version = "0.2.82" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 763 | dependencies = [ 764 | "quote 1.0.24", 765 | "wasm-bindgen-macro-support", 766 | ] 767 | 768 | [[package]] 769 | name = "wasm-bindgen-macro-support" 770 | version = "0.2.82" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 773 | dependencies = [ 774 | "proc-macro2", 775 | "quote 1.0.24", 776 | "syn", 777 | "wasm-bindgen-backend", 778 | "wasm-bindgen-shared", 779 | ] 780 | 781 | [[package]] 782 | name = "wasm-bindgen-shared" 783 | version = "0.2.82" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 786 | 787 | [[package]] 788 | name = "which" 789 | version = "1.0.5" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "e84a603e7e0b1ce1aa1ee2b109c7be00155ce52df5081590d1ffb93f4f515cb2" 792 | dependencies = [ 793 | "libc", 794 | ] 795 | 796 | [[package]] 797 | name = "winapi" 798 | version = "0.3.9" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 801 | dependencies = [ 802 | "winapi-i686-pc-windows-gnu", 803 | "winapi-x86_64-pc-windows-gnu", 804 | ] 805 | 806 | [[package]] 807 | name = "winapi-i686-pc-windows-gnu" 808 | version = "0.4.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 811 | 812 | [[package]] 813 | name = "winapi-util" 814 | version = "0.1.3" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" 817 | dependencies = [ 818 | "winapi", 819 | ] 820 | 821 | [[package]] 822 | name = "winapi-x86_64-pc-windows-gnu" 823 | version = "0.4.0" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 826 | 827 | [[package]] 828 | name = "windows-link" 829 | version = "0.1.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 832 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2018 Peter Williams and collaborators 2 | # Licensed under the MIT licene. 3 | 4 | [package] 5 | name = "slurmplus" 6 | version = "0.1.3" 7 | authors = ["Peter Williams "] 8 | description = "A command-line tool for nice interaction with the Slurm workload manager." 9 | homepage = "https://github.com/pkgw/slurm-rs" 10 | documentation = "https://docs.rs/crate/slurmplus" 11 | repository = "https://github.com/pkgw/slurm-rs" 12 | readme = "README.md" 13 | keywords = ["slurm"] 14 | categories = ["concurrency", "science"] 15 | license = "MIT" 16 | 17 | [workspace] 18 | 19 | [dependencies] 20 | chrono = "0.4" 21 | failure = "0.1" 22 | failure_derive = "0.1" 23 | itertools = "0.14" 24 | slurm = { path = "slurm", version = "0.1.3" } 25 | structopt = "0.3" 26 | termcolor = "1.4" 27 | users = "0.11" 28 | 29 | [package.metadata.docs.rs] 30 | # Hack to get us building on docs.rs: 31 | rustc-args = ["--cfg", "slurmrs_on_docs_rs"] 32 | rustdoc-args = ["--cfg", "slurmrs_on_docs_rs"] 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE: Unmaintained 2 | 3 | This repo is unmaintained and hasn't been updated in a long time. It worked pretty 4 | decently, but I haven't needed it for a while. 5 | 6 | Architecturally, the API/ABI to the Slurm libraries was not very stable, and was 7 | pretty hard to use. It would probably be better to develop a crate that invokes 8 | the Slurm CLI programs under the hood, rather than trying to link against the 9 | shared libraries. 10 | 11 | 12 | # slurm-rs: slurm and slurmplus 13 | 14 | Rust bindings for the [Slurm workload manager](https://slurm.schedmd.com/), 15 | and a command-line program (`slurmplus`) that provides some useful 16 | functionality. 17 | 18 | [![](http://meritbadge.herokuapp.com/slurm)](https://crates.io/crates/slurm) 19 | [![](https://docs.rs/slurm/badge.svg)](https://docs.rs/slurm) 20 | 21 | - [slurm-sys on crates.io](https://crates.io/crates/slurm-sys) 22 | - [slurm on crates.io](https://crates.io/crates/slurm) 23 | - [slurmplus on crates.io](https://crates.io/crates/slurmplus) 24 | - [Rust API documentation for the slurm crate](https://docs.rs/slurm) 25 | 26 | The coverage of the underlying Slurm feature set is far from complete, but the 27 | basic framework is in place. 28 | 29 | For a summary of recent changes to the code, see 30 | [CHANGELOG.md](./CHANGELOG.md) for the command-line tool, 31 | [slurm/CHANGELOG.md](slurm/CHANGELOG.md) for the developer-facing library, and 32 | [slurm-sys/CHANGELOG.md](slurm-sys/CHANGELOG.md) for the low-level FFI 33 | bindings. 34 | 35 | 36 | ## Building and Compatibility 37 | 38 | See the README for the `slurm-sys` subdirectory for some notes on how to build 39 | against your Slurm library correctly. You must have a functional `rustfmt` 40 | installed. You may also need to set some environment variables to allow the 41 | build script to locate your Slurm libraries and include files. 42 | 43 | At the moment, this crate is being developed against Slurm 17.11. The Slurm C 44 | API is not especially stable, so it is possible that this crate will fail to 45 | compile against other versions of Slurm, or even exhibit wrong runtime 46 | behavior. The goal is for the crate to work with a wide range of Slurm 47 | versions, and there is code infrastructure to adapt to the evolving C API. If 48 | the crate fails to build for a reason that appears to be related to the 49 | version of Slurm that you're using, please file an issue with the details. 50 | 51 | This crate also requires that the Slurm accounting database library 52 | `libslurmdb` is available. Contributions to relax this requirement would be 53 | welcome. 54 | 55 | 56 | ## Licensing 57 | 58 | Licensed under the MIT License. 59 | -------------------------------------------------------------------------------- /slurm-sys/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.3 (2018 May 27) 2 | 3 | - Yet another iteration to try to see if we can get our docs to build 4 | on `docs.rs`. 5 | -------------------------------------------------------------------------------- /slurm-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2018 Peter Williams and collaborators 2 | # Licensed under the MIT License. 3 | 4 | [package] 5 | name = "slurm-sys" 6 | version = "0.1.3" 7 | authors = ["Peter Williams "] 8 | links = "slurm" 9 | build = "build.rs" 10 | description = "Low-level FFI bindings to the libslurm and libslurmdb libraries." 11 | homepage = "https://github.com/pkgw/slurm-rs" 12 | documentation = "https://docs.rs/slurm-sys" 13 | repository = "https://github.com/pkgw/slurm-rs" 14 | readme = "README.md" 15 | keywords = ["slurm"] 16 | categories = ["concurrency", "external-ffi-bindings", "science"] 17 | license = "MIT" 18 | 19 | [build-dependencies] 20 | bindgen = "0.35" 21 | pkg-config = "^0.3" 22 | 23 | [package.metadata.docs.rs] 24 | # Hack to get us building on docs.rs: 25 | rustc-args = ["--cfg", "slurmrs_on_docs_rs"] 26 | rustdoc-args = ["--cfg", "slurmrs_on_docs_rs"] 27 | -------------------------------------------------------------------------------- /slurm-sys/README.md: -------------------------------------------------------------------------------- 1 | # slurm-sys 2 | 3 | This crate provides low-level bindings to the `libslurm` and `libslurmdb` 4 | libraries associated with the [Slurm](https://slurm.schedmd.com/) workload 5 | manager. 6 | 7 | ## Building 8 | 9 | You must have a working version of 10 | [rustfmt](https://github.com/rust-lang-nursery/rustfmt) installed in order to 11 | build this crate correctly! To handle the evolving Slurm C API, this crate's 12 | build script parses the output of `bindgen` in a simplistic manner. Without 13 | `rustfmt`, the code is not formatted in a way that the build script can 14 | handle. 15 | 16 | By default, this crate's build script will use a 17 | [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) search for 18 | `slurm` to determine the necessary library and include search paths. Not all 19 | Slurm installs come with a `pkg-config` file, however. If that is the case for 20 | you, set the environment variables `SLURM_LIBDIR` and, optionally, 21 | `SLURM_INCDIR` to point to the directories containing the Slurm shared 22 | libraries and include files, respectively. In particular, these variables 23 | should be set such that the files `$SLURM_LIBDIR/libslurm.so` and 24 | `$SLURM_INCDIR/slurm/slurm.h` exist. 25 | 26 | 27 | ## Licensing 28 | 29 | Licensed under the MIT License. 30 | -------------------------------------------------------------------------------- /slurm-sys/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 Peter Williams and collaborators 2 | // Licensed under the MIT license. 3 | 4 | //! The Slurm version requirement here is totally made up. 5 | //! 6 | //! I want the docs of slurm-rs to be available on docs.rs, which requires us 7 | //! to be able to compile this bindings module on that platform. The code 8 | //! doesn't need to be *runnable*, but we need to be able to build it. 9 | //! Unsurprisingly, the docs.rs VM does not happen to have libslurm installed, 10 | //! and it currently looks unlikely that there will ever be a mechanism to 11 | //! install it ourselves. Therefore we undertake a massive hack: we download a 12 | //! pre-generated version of the binding file rather than creating it with 13 | //! bindgen, and we don't have this module link with any libraries. This would 14 | //! be a disaster if we actually wanted to run the resulting code, but we 15 | //! don't. 16 | //! 17 | //! In this case we download the pre-generated file using `wget` since it is 18 | //! available on docs.rs and we avoid having to link this file with all sorts 19 | //! of network libraries. We could store it in Git, but the file is big and I 20 | //! want to avoid the possibility of confusion. 21 | //! 22 | //! The other thing is that this file needs to be able to know that it's being 23 | //! built on docs.rs in order to activate the hack! That's done by (ab)using 24 | //! the `rustc_args` and `rustdoc_args` properties of the 25 | //! `package.metadata.docs.rs` section of Cargo.toml. 26 | 27 | extern crate bindgen; 28 | extern crate pkg_config; 29 | 30 | use std::env; 31 | use std::fs::File; 32 | use std::io::prelude::*; 33 | use std::io::BufReader; 34 | use std::path::PathBuf; 35 | use std::process::Command; 36 | 37 | const PREBUILT_BINDINGS_URL: &str = 38 | "https://gist.github.com/pkgw/40e36f9dc0d771323205fc0617ac7141/\ 39 | raw/6405dba98cd0eec7fab483b3d090b919e1383094/bindings.rs"; 40 | 41 | fn main() { 42 | let mut do_the_bindgen = true; 43 | let mut builder = bindgen::Builder::default().header("src/wrapper.h"); 44 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 45 | let bindings_path = out_dir.join("bindings.rs"); 46 | 47 | if cfg!(slurmrs_on_docs_rs) { 48 | // Activate the hack! 49 | do_the_bindgen = false; 50 | Command::new("curl") 51 | .arg("-sSL") 52 | .arg("-o") 53 | .arg(&bindings_path) 54 | .arg(PREBUILT_BINDINGS_URL) 55 | .status() 56 | .expect("failed to execute process"); 57 | } else if let Ok(libdir) = env::var("SLURM_LIBDIR") { 58 | // Some Slurm installs don't have a pkg-config file. 59 | println!("cargo:rustc-link-search=native={}", libdir); 60 | println!("cargo:rustc-link-lib=dylib=slurm"); 61 | println!("cargo:rustc-link-lib=dylib=slurmdb"); 62 | 63 | if let Ok(incdir) = env::var("SLURM_INCDIR") { 64 | builder = builder.clang_arg(format!("-I{}", incdir)); 65 | } 66 | } else { 67 | let slurm = pkg_config::Config::new() 68 | .atleast_version("15.0") 69 | .probe("slurm") 70 | .unwrap(); 71 | 72 | println!("cargo:rustc-link-lib=dylib=slurmdb"); 73 | 74 | for ref path in &slurm.include_paths { 75 | builder = builder.clang_arg(format!("-I{}", path.display())); 76 | } 77 | } 78 | 79 | if do_the_bindgen { 80 | let bindings = builder 81 | .whitelist_type("job_.*") 82 | .whitelist_type("slurm_.*") 83 | .whitelist_type("slurmdb_.*") 84 | .whitelist_function("slurm_.*") 85 | .whitelist_function("slurmdb_.*") 86 | .whitelist_var("ESCRIPT.*") 87 | .whitelist_var("ESLURM.*") 88 | .whitelist_var("SLURM.*") 89 | .whitelist_var("SLURMDB.*") 90 | .whitelist_var("SLURMRS.*") 91 | .rustfmt_bindings(true) 92 | .generate() 93 | .expect("Unable to generate bindings"); 94 | 95 | bindings 96 | .write_to_file(&bindings_path) 97 | .expect("Couldn't write bindings!"); 98 | } 99 | 100 | // Now, we (grossly) parse the bindings file and emit a second file. This 101 | // contains information that the main `slurm` crate can use in *its* 102 | // build.rs to auto-enable Cargo features that will then allow the main 103 | // codebase to conditionally compile Rust interfaces that depend on what 104 | // the C code supports. This all is the least-bad approach I can devise 105 | // that deals with the fact that the C API is not super stable. 106 | 107 | let bindings_file = File::open(&bindings_path).expect(&format!( 108 | "couldn't open bindgen output file {}", 109 | bindings_path.display() 110 | )); 111 | let bindings_buf = BufReader::new(bindings_file); 112 | 113 | let features_path = out_dir.join("features.rs"); 114 | let mut features_file = File::create(&features_path).expect(&format!( 115 | "couldn't create features output file {}", 116 | features_path.display() 117 | )); 118 | 119 | writeln!(features_file, "pub const C_API_FEATURES: &[&str] = &[").expect(&format!( 120 | "couldn't write to features output file {}", 121 | features_path.display() 122 | )); 123 | 124 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 125 | enum State { 126 | Scanning, 127 | CheckingSelectedStepT, 128 | CheckingSubmitResponseMsg, 129 | } 130 | 131 | let mut state = State::Scanning; 132 | let mut n_lines = 0; 133 | 134 | for maybe_line in bindings_buf.lines() { 135 | let line = maybe_line.expect(&format!( 136 | "couldn't read bindgen output file {}", 137 | bindings_path.display() 138 | )); 139 | n_lines += 1; 140 | 141 | match state { 142 | State::Scanning => { 143 | if line.starts_with("pub struct slurmdb_selected_step_t {") { 144 | state = State::CheckingSelectedStepT; 145 | } else if line.starts_with("pub struct submit_response_msg {") { 146 | state = State::CheckingSubmitResponseMsg; 147 | } else if line.starts_with("pub const job_states_JOB_DEADLINE") { 148 | writeln!(features_file, "\"job_state_deadline\",").expect(&format!( 149 | "couldn't write to features output file {}", 150 | features_path.display() 151 | )); 152 | } else if line.starts_with("pub const job_states_JOB_OOM") { 153 | writeln!(features_file, "\"job_state_oom\",").expect(&format!( 154 | "couldn't write to features output file {}", 155 | features_path.display() 156 | )); 157 | } 158 | } 159 | 160 | State::CheckingSelectedStepT => { 161 | if line == "}" { 162 | state = State::Scanning; 163 | } else if line.contains("pack_job_offset") { 164 | writeln!(features_file, "\"selected_step_t_pack_job_offset\",").expect( 165 | &format!( 166 | "couldn't write to features output file {}", 167 | features_path.display() 168 | ), 169 | ); 170 | } 171 | } 172 | 173 | State::CheckingSubmitResponseMsg => { 174 | if line == "}" { 175 | state = State::Scanning; 176 | } else if line.contains("job_submit_user_msg") { 177 | writeln!(features_file, "\"submit_response_user_message\",").expect(&format!( 178 | "couldn't write to features output file {}", 179 | features_path.display() 180 | )); 181 | } 182 | } 183 | } 184 | } 185 | 186 | // If rustfmt is unavailable, the output is all on two (very long) lines. Can't parse that. 187 | assert!( 188 | n_lines > 100, 189 | "to build this crate you must install a functional \"rustfmt\" (see README.md)" 190 | ); 191 | 192 | writeln!(features_file, "];").expect(&format!( 193 | "couldn't write to features output file {}", 194 | features_path.display() 195 | )); 196 | } 197 | -------------------------------------------------------------------------------- /slurm-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 Peter Williams and collaborators 2 | // Licensed under the MIT license. 3 | 4 | //! Low-level bindings to the `libslurm` and `libslurmdb` libraries. 5 | //! 6 | //! The [Slurm](https://slurm.schedmd.com/) workload manager a system for 7 | //! scheduling and running jobs on large computing clusters. It is often used 8 | //! in scientific HPC (high-performance computing) contexts. 9 | //! 10 | //! These bindings provide nothing beyond the barest minimum needed to 11 | //! interface to the C code unsafely. As such, this crate has no documentation 12 | //! beyond the text you see here. Use a higher-level Rust crate in application 13 | //! code. 14 | 15 | #![allow(non_upper_case_globals)] 16 | #![allow(non_camel_case_types)] 17 | #![allow(non_snake_case)] 18 | 19 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 20 | include!(concat!(env!("OUT_DIR"), "/features.rs")); 21 | 22 | /// This function can be passed as a callback to functions like 23 | /// `slurm_list_create` that want a deallocator argument. `slurm_xfree` 24 | /// doesn't work because (1) it takes a pointer *to a* pointer, so that it can 25 | /// zero it out; and (2) it takes additional arguments populated from C 26 | /// preprocessor `__FILE__` and `__LINE__` directives. 27 | #[no_mangle] 28 | pub extern "C" fn slurmrs_free(ptr: *mut std::os::raw::c_void) { 29 | let mut copy = ptr; 30 | const TEXT: &[u8] = b"slurm-rs\0"; 31 | unsafe { slurm_xfree(&mut copy, TEXT.as_ptr() as _, 1, TEXT.as_ptr() as _) }; 32 | } 33 | -------------------------------------------------------------------------------- /slurm-sys/src/wrapper.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017-2018 Peter Williams and collaborators */ 2 | /* Licensed under the MIT License. */ 3 | 4 | #include 5 | #include 6 | 7 | /* Expose some #defines as enumeration values. Two enums needed because 8 | * otherwise NO_VAL64 upgrades the other value(s) to 64-bit storage. */ 9 | 10 | enum { 11 | SLURMRS_NO_VAL = NO_VAL, 12 | }; 13 | 14 | #ifdef NO_VAL64 15 | enum { 16 | SLURMRS_NO_VAL64 = NO_VAL64, 17 | }; 18 | #endif 19 | 20 | /* The official API doesn't expose the memory management functions, 21 | * but we need them: see discussion in the Rust docs. */ 22 | 23 | extern void *slurm_try_xmalloc(size_t size, const char *file_name, int line, const char *func_name); 24 | extern void slurm_xfree(void **pointer, const char *file_name, int line, const char *func_name); 25 | -------------------------------------------------------------------------------- /slurm/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.3 (2018 May 27) 2 | 3 | - New version that builds against `slurm-sys` 0.1.3, which should get our API 4 | docs building on `docs.rs`. 5 | -------------------------------------------------------------------------------- /slurm/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2018 Peter Williams and collaborators 2 | # Licensed under the MIT licene. 3 | 4 | [package] 5 | name = "slurm" 6 | version = "0.1.3" 7 | authors = ["Peter Williams "] 8 | description = "Interface to the Slurm workload manager." 9 | homepage = "https://github.com/pkgw/slurm-rs" 10 | documentation = "https://docs.rs/slurm" 11 | repository = "https://github.com/pkgw/slurm-rs" 12 | readme = "README.md" 13 | keywords = ["slurm"] 14 | categories = ["concurrency", "science"] 15 | license = "MIT" 16 | 17 | [dependencies] 18 | chrono = "0.4" 19 | clap = "2.34" 20 | failure = "0.1" 21 | failure_derive = "0.1" 22 | itertools = "0.14" 23 | libc = "0.2" 24 | slurm-sys = { path = "../slurm-sys", version = "0.1.3" } 25 | 26 | [build-dependencies] 27 | slurm-sys = { path = "../slurm-sys", version = "0.1.3" } # needed to learn what C API provides 28 | 29 | [package.metadata.docs.rs] 30 | # Hack to get us building on docs.rs: 31 | rustc-args = ["--cfg", "slurmrs_on_docs_rs"] 32 | rustdoc-args = ["--cfg", "slurmrs_on_docs_rs"] 33 | -------------------------------------------------------------------------------- /slurm/README.md: -------------------------------------------------------------------------------- 1 | # slurm-rs 2 | 3 | Rust bindings for the [Slurm workload manager](https://slurm.schedmd.com/). 4 | 5 | The API coverage is far from complete, but the basic framework is in place. 6 | 7 | 8 | ## Building and Compatibility 9 | 10 | See the README for the sibling `../slurm-sys` directory for some notes on how 11 | to build against your Slurm library correctly. You must have a functional 12 | `rustfmt` installed. You may also need to set some environment variables to 13 | allow the build script to locate your Slurm libraries and include files. 14 | 15 | At the moment, this crate is being developed against Slurm 17.11. The Slurm C 16 | API is not especially stable, so it is possible that this crate will fail to 17 | compile against other versions of Slurm, or even exhibit wrong runtime 18 | behavior. The goal is for the crate to work with a wide range of Slurm 19 | versions, and there is code infrastructure to adapt to the evolving C API. If 20 | the crate fails to build for a reason that appears to be related to the 21 | version of Slurm that you're using, please file an issue with the details. 22 | 23 | This crate also requires that the Slurm accounting database library 24 | `libslurmdb` is available. Contributions to relax this requirement would be 25 | welcome. 26 | 27 | 28 | ## Licensing 29 | 30 | Licensed under the MIT License. 31 | -------------------------------------------------------------------------------- /slurm/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT license. 3 | 4 | //! Fairly gross hack to adapt to the changing C API. 5 | //! 6 | //! The `slurm-sys` build script generates the Rust API from the Slurm C 7 | //! headers, then scans the output file to check for various features of the C 8 | //! API that have come and gone over various releases. It creates a special 9 | //! constant in the Rust API that enumerates those features. 10 | //! 11 | //! Here, we use this constant to add feature flags to the `rustc` arguments 12 | //! used to compile this module. That way we can use `#[cfg]` directives to 13 | //! conditionally compile Rust code based on what's available in the C API, 14 | //! without the user having to know or care about what's going on under the hood. 15 | //! 16 | //! (In principle some features *should* be exposed at higher levels: say that 17 | //! a new version adds a new major feature and certain upstream programs need 18 | //! to know that it is available. We don't have that situation yet, though.) 19 | 20 | extern crate slurm_sys; 21 | 22 | fn main() { 23 | for feat in slurm_sys::C_API_FEATURES { 24 | println!("cargo:rustc-cfg=slurm_api_{}", feat); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /slurm/examples/account.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Demonstration of querying the Slurmdb job accounting database. 5 | */ 6 | 7 | extern crate chrono; 8 | #[macro_use] 9 | extern crate clap; 10 | extern crate failure; 11 | extern crate slurm; 12 | 13 | use chrono::Utc; 14 | use clap::{App, Arg}; 15 | use failure::Error; 16 | use slurm::JobStepRecordSharedFields; 17 | use std::process; 18 | 19 | fn main() { 20 | let matches = App::new("rsinfo") 21 | .version(crate_version!()) 22 | .about("Print accounting information about one job.") 23 | .arg( 24 | Arg::with_name("JOBID") 25 | .help("The jobid of the job in question") 26 | .required(true) 27 | .index(1), 28 | ) 29 | .get_matches(); 30 | 31 | let jobid = matches.value_of("JOBID").unwrap(); 32 | 33 | process::exit(match inner(jobid) { 34 | Ok(code) => code, 35 | 36 | Err(e) => { 37 | eprintln!("fatal error in account"); 38 | for cause in e.causes() { 39 | eprintln!(" caused by: {}", cause); 40 | } 41 | 1 42 | } 43 | }); 44 | } 45 | 46 | fn inner(jobid: &str) -> Result { 47 | let jobid = jobid.parse::()?; 48 | 49 | let mut filter = slurm::JobFiltersOwned::default(); 50 | filter 51 | .step_list_mut() 52 | .append(slurm::JobStepFilterOwned::new(jobid)); 53 | 54 | let db = slurm::DatabaseConnectionOwned::new()?; 55 | let jobs = db.get_jobs(&filter)?; 56 | let now = Utc::now(); 57 | 58 | for job in jobs.iter() { 59 | println!("{} {}", job.job_id(), job.job_name()); 60 | 61 | if let Some(d) = job.eligible_wait_duration() { 62 | println!( 63 | " time for job to become eligible to run: {} s", 64 | d.num_seconds() 65 | ); 66 | } else { 67 | let wait = now.signed_duration_since(job.submit_time()); 68 | println!( 69 | " job not yet eligible to run; time since submission: {} s", 70 | wait.num_seconds() 71 | ); 72 | } 73 | 74 | if let Some(d) = job.wait_duration() { 75 | println!(" wait time: {} s", d.num_seconds()); 76 | } else if let Some(t_el) = job.eligible_time() { 77 | let wait = now.signed_duration_since(t_el); 78 | println!( 79 | " job not yet started; time since eligibility: {} s", 80 | wait.num_seconds() 81 | ); 82 | } else { 83 | println!(" still waiting to start"); 84 | } 85 | 86 | if let Some(t_st) = job.start_time() { 87 | let t_limit = t_st + chrono::Duration::minutes(job.time_limit() as i64); 88 | let remaining = t_limit.signed_duration_since(now).num_minutes(); 89 | if remaining > 0 { 90 | println!(" time left until job hits time limit: {} min", remaining); 91 | } 92 | } 93 | 94 | for step in job.steps().iter() { 95 | println!(" step {} {}", step.step_id(), step.step_name()); 96 | 97 | if let Some(d) = step.wallclock_duration() { 98 | println!(" wallclock runtime: {} s", d.num_seconds()); 99 | println!(" exit code: {}", step.exit_code().unwrap()); 100 | } else if let Some(t_st) = step.start_time() { 101 | let wait = now.signed_duration_since(t_st); 102 | println!( 103 | " step not yet finished; time since start: {} s", 104 | wait.num_seconds() 105 | ); 106 | } else { 107 | println!(" step not yet finished"); 108 | } 109 | 110 | if let Some(b) = step.max_vm_size() { 111 | println!(" max VM size: {:.2} MiB", (b as f64) / 1024.); 112 | } else { 113 | println!(" max VM size not available (probably because step not finished)"); 114 | } 115 | } 116 | } 117 | 118 | Ok(0) 119 | } 120 | -------------------------------------------------------------------------------- /slurm/examples/recent.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Make a list of recent jobs belonging to this user. 5 | */ 6 | 7 | extern crate chrono; 8 | #[macro_use] 9 | extern crate clap; 10 | extern crate failure; 11 | extern crate itertools; 12 | extern crate slurm; 13 | 14 | use chrono::{Duration, Utc}; 15 | use clap::App; 16 | use failure::Error; 17 | use itertools::Itertools; 18 | use slurm::JobStepRecordSharedFields; 19 | use std::collections::HashMap; 20 | use std::process; 21 | 22 | fn main() { 23 | let _matches = App::new("rsinfo") 24 | .version(crate_version!()) 25 | .about("Make a list of recent jobs") 26 | .get_matches(); 27 | 28 | process::exit(match inner() { 29 | Ok(code) => code, 30 | 31 | Err(e) => { 32 | eprintln!("fatal error in account"); 33 | for cause in e.causes() { 34 | eprintln!(" caused by: {}", cause); 35 | } 36 | 1 37 | } 38 | }); 39 | } 40 | 41 | fn inner() -> Result { 42 | let now = Utc::now(); 43 | let min_start = now - Duration::days(7); 44 | 45 | let mut filter = slurm::JobFiltersOwned::default(); 46 | filter.userid_list_mut().append("555409"); 47 | filter.usage_start(min_start); 48 | 49 | let db = slurm::DatabaseConnectionOwned::new()?; 50 | let jobs = db.get_jobs(&filter)?; 51 | 52 | for (arrayid, group) in &jobs 53 | .iter() 54 | .group_by(|job| job.array_job_id().unwrap_or_else(|| job.job_id())) 55 | { 56 | let mut n_jobs = 0; 57 | let mut last_state = slurm::JobState::Failed; 58 | let mut states = HashMap::new(); 59 | 60 | for job in group { 61 | if n_jobs == 0 { 62 | print!("{} {}: ", arrayid, job.job_name()); 63 | } 64 | 65 | n_jobs += 1; 66 | last_state = job.state(); 67 | let slot = states.entry(last_state).or_insert(0); 68 | *slot += 1; 69 | } 70 | 71 | if n_jobs == 1 { 72 | println!("{:2}", last_state.shortcode()); 73 | } else { 74 | let seen_states = states.keys().sorted(); 75 | let text = seen_states 76 | .iter() 77 | .map(|s| format!("{} {}", states.get(s).unwrap(), s.shortcode())) 78 | .join(", "); 79 | println!("{} ({} total)", text, n_jobs); 80 | } 81 | } 82 | 83 | Ok(0) 84 | } 85 | -------------------------------------------------------------------------------- /slurm/examples/rsinfo.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Print out information about a job. 5 | */ 6 | 7 | #[macro_use] 8 | extern crate clap; 9 | extern crate failure; 10 | extern crate slurm; 11 | 12 | use clap::{App, Arg}; 13 | use failure::Error; 14 | use std::process; 15 | 16 | fn main() { 17 | let matches = App::new("rsinfo") 18 | .version(crate_version!()) 19 | .about("Print information about one job.") 20 | .arg( 21 | Arg::with_name("JOBID") 22 | .help("The jobid of the job in question") 23 | .required(true) 24 | .index(1), 25 | ) 26 | .get_matches(); 27 | 28 | let jobid = matches.value_of("JOBID").unwrap(); 29 | 30 | process::exit(match inner(jobid) { 31 | Ok(code) => code, 32 | 33 | Err(e) => { 34 | eprintln!("fatal error in rsinfo"); 35 | for cause in e.causes() { 36 | eprintln!(" caused by: {}", cause); 37 | } 38 | 1 39 | } 40 | }); 41 | } 42 | 43 | fn inner(jobid: &str) -> Result { 44 | let jobid = jobid.parse::()?; 45 | let info = slurm::get_job_info(jobid)?; 46 | println!("Job ID: {}", info.job_id()); 47 | println!("Partition: {}", info.partition()); 48 | Ok(0) 49 | } 50 | -------------------------------------------------------------------------------- /slurm/examples/submit-echo.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Submit a hello-world echo job 5 | */ 6 | 7 | #[macro_use] 8 | extern crate clap; 9 | #[macro_use] 10 | extern crate failure; 11 | extern crate slurm; 12 | 13 | use clap::App; 14 | use failure::Error; 15 | use std::env; 16 | use std::process; 17 | 18 | fn main() { 19 | let _matches = App::new("submit-echo") 20 | .version(crate_version!()) 21 | .about("Submit a hello-world echo job") 22 | .get_matches(); 23 | 24 | process::exit(match inner() { 25 | Ok(code) => code, 26 | 27 | Err(e) => { 28 | eprintln!("failed to submit job"); 29 | for cause in e.causes() { 30 | eprintln!(" caused by: {}", cause); 31 | } 32 | 1 33 | } 34 | }); 35 | } 36 | 37 | fn inner() -> Result { 38 | let cwd = env::current_dir()?; 39 | 40 | let log = { 41 | let mut p = cwd.clone(); 42 | p.push("%j.log"); 43 | p.to_str() 44 | .ok_or(format_err!("cannot stringify log path"))? 45 | .to_owned() 46 | }; 47 | 48 | let mut desc = slurm::JobDescriptorOwned::new(); 49 | 50 | desc.set_name("helloworld") 51 | .set_argv(&["helloworld"]) 52 | .inherit_environment() 53 | .set_stderr_path(&log) 54 | .set_stdin_path("/dev/null") 55 | .set_stdout_path(&log) 56 | .set_work_dir_cwd()? 57 | .set_script( 58 | "#! /bin/bash 59 | set -e -x 60 | echo hello world \"$@\" 61 | ", 62 | ) 63 | .set_gid_current() // JobDescriptor args must come after due to the return type 64 | .set_num_tasks(1) 65 | .set_time_limit(5) 66 | .set_uid_current(); 67 | 68 | let msg = desc.submit_batch()?; 69 | println!("new job id: {}", msg.job_id()); 70 | Ok(0) 71 | } 72 | -------------------------------------------------------------------------------- /slurm/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Interface to the [Slurm](https://slurm.schedmd.com/) workload manager. 5 | 6 | Slurm is a system for scheduling and running jobs on large computing clusters. 7 | It is often used in scientific HPC (high-performance computing) contexts. 8 | 9 | This crate provides hooks for submitting new jobs and interrogating their 10 | status. Support for other kinds of operations, such as canceling jobs or 11 | altering their runtime parameters, would be entirely appropriate but has not 12 | yet been implemented. 13 | 14 | # Example: querying a running job 15 | 16 | ```no_run 17 | extern crate failure; 18 | extern crate slurm; 19 | 20 | fn print_random_job_information(jobid: slurm::JobId) -> Result<(), failure::Error> { 21 | let info = slurm::get_job_info(jobid)?; 22 | println!("Job ID: {}", info.job_id()); // same as what we put in 23 | println!("Job's partition: {}", info.partition()); 24 | Ok(()) 25 | } 26 | ``` 27 | 28 | # Example: querying a completed job 29 | 30 | To gather information about jobs that have completed, you must connect to the 31 | Slurm accounting database and query it. 32 | 33 | ```no_run 34 | extern crate chrono; 35 | extern crate failure; 36 | extern crate slurm; 37 | 38 | fn print_other_job_information(jobid: slurm::JobId) -> Result<(), failure::Error> { 39 | let mut filter = slurm::JobFiltersOwned::default(); 40 | filter.step_list_mut().append(slurm::JobStepFilterOwned::new(jobid)); 41 | 42 | let db = slurm::DatabaseConnectionOwned::new()?; 43 | let jobs = db.get_jobs(&filter)?; 44 | let now = chrono::Utc::now(); 45 | 46 | for job in jobs.iter() { 47 | println!("Job ID {}, name {}", job.job_id(), job.job_name()); 48 | 49 | if let Some(d) = job.wait_duration() { 50 | println!(" job started; wait time: {} s", d.num_seconds()); 51 | } else if let Some(t_el) = job.eligible_time() { 52 | let wait = now.signed_duration_since(t_el).num_seconds(); 53 | println!(" job not yet started; time since eligibility: {} s", wait); 54 | } else { 55 | println!(" job not yet eligible to run"); 56 | } 57 | } 58 | 59 | Ok(()) 60 | } 61 | ``` 62 | 63 | # Submitting a “Hello World” job 64 | 65 | ```no_run 66 | extern crate failure; 67 | extern crate slurm; 68 | 69 | fn submit_hello_world() -> Result { 70 | let cwd = std::env::current_dir()?; 71 | 72 | let log = { 73 | let mut p = cwd.clone(); 74 | p.push("%j.log"); 75 | p.to_str().ok_or(failure::err_msg("cannot stringify log path"))?.to_owned() 76 | }; 77 | 78 | let mut desc = slurm::JobDescriptorOwned::new(); 79 | 80 | desc.set_name("helloworld") 81 | .set_argv(&["helloworld"]) 82 | .inherit_environment() 83 | .set_stderr_path(&log) 84 | .set_stdin_path("/dev/null") 85 | .set_stdout_path(&log) 86 | .set_work_dir_cwd()? 87 | .set_script("#! /bin/bash \ 88 | set -e -x \ 89 | echo hello world \"$@\"") 90 | .set_gid_current() // JobDescriptor args must come after due to the return type 91 | .set_num_tasks(1) 92 | .set_time_limit(5) 93 | .set_uid_current(); 94 | 95 | let msg = desc.submit_batch()?; 96 | println!("new job id: {}", msg.job_id()); 97 | Ok(msg.job_id()) 98 | } 99 | ``` 100 | 101 | # A note on memory management 102 | 103 | The Slurm C library uses a (primitive) custom memory allocator for its data 104 | structures. Because we must maintain compatibility with this allocator, we 105 | have to allocate all of our data structures from the heap rather than the 106 | stack. Almost all of the structures exposed here come in both “borrowed” and 107 | “owned” flavors; they are largely equivalent, but only the owned versions free 108 | their data when they go out of scope. Borrowed structures need not be 109 | immutable, but it is not possible to modify them in ways that require freeing 110 | or allocating memory associated with their sub-structures. 111 | 112 | */ 113 | 114 | extern crate chrono; 115 | #[macro_use] 116 | extern crate failure; 117 | #[macro_use] 118 | extern crate failure_derive; 119 | extern crate libc; 120 | extern crate slurm_sys; 121 | 122 | use chrono::{DateTime, Duration, TimeZone, Utc}; 123 | use failure::Error; 124 | use std::borrow::Cow; 125 | use std::default::Default; 126 | use std::ffi::CStr; 127 | use std::fmt::{Display, Error as FmtError, Formatter}; 128 | use std::marker::PhantomData; 129 | use std::ops::{Deref, DerefMut}; 130 | use std::os::raw::{c_char, c_int, c_void}; 131 | 132 | /// A job identifier number; this will always be `u32`. 133 | pub type JobId = u32; 134 | 135 | /// A job-step identifier number; this will always be `u32`. 136 | pub type StepId = u32; 137 | 138 | /// A quick macro framework to map low-level slurm API errors to a Rust interface. 139 | macro_rules! declare_slurm_errors { 140 | ($(<$rustname:ident, $sysname:path, $doc:expr;>),*) => { 141 | /// Specifically-enumerated errors that we can get from the Slurm API. 142 | /// 143 | /// This is not exhaustive; the only specifically implemented options are ones 144 | /// that are expected to be of interest to general developers. 145 | #[derive(Copy, Clone, Debug, Eq, Fail, Hash, PartialEq)] 146 | pub enum SlurmError { 147 | $( 148 | #[doc=$doc] $rustname, 149 | )* 150 | 151 | /// Some other Slurm error. 152 | Other(c_int), 153 | } 154 | 155 | impl SlurmError { 156 | fn from_slurm(errno: c_int) -> SlurmError { 157 | match errno as u32 { 158 | $( 159 | $sysname => SlurmError::$rustname, 160 | )* 161 | _ => SlurmError::Other(errno), 162 | } 163 | } 164 | 165 | fn to_slurm(&self) -> c_int { 166 | match self { 167 | $( 168 | &SlurmError::$rustname => $sysname as c_int, 169 | )* 170 | &SlurmError::Other(errno) => errno, 171 | } 172 | } 173 | } 174 | } 175 | } 176 | 177 | declare_slurm_errors!( 178 | , 180 | 182 | ); 183 | 184 | impl Display for SlurmError { 185 | fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { 186 | let e = self.to_slurm(); 187 | let m = unsafe { CStr::from_ptr(slurm_sys::slurm_strerror(e)) }; 188 | write!(f, "{} (Slurm errno {})", m.to_string_lossy(), e) 189 | } 190 | } 191 | 192 | /// Most Slurm API calls return an zero on success. The library API docs state 193 | /// that the return code on error is -1, and this macro encapsulates the task 194 | /// of obtaining an errno and converting it to a result. However, in at least 195 | /// one case the return code is an errno, which would be a nicer pattern from 196 | /// a thread-safety standpoint. 197 | macro_rules! stry { 198 | ($op:expr) => {{ 199 | if $op != 0 { 200 | let e = unsafe { slurm_sys::slurm_get_errno() }; 201 | Err(SlurmError::from_slurm(e)) 202 | } else { 203 | Ok(()) 204 | }? 205 | }}; 206 | } 207 | 208 | /// This is like `stry!` but also wraps the Slurm call in an `unsafe{}` block, 209 | /// since most (all?) of the times we're doing this, we're using the C API. 210 | macro_rules! ustry { 211 | ($op:expr) => { 212 | stry!(unsafe { $op }) 213 | }; 214 | } 215 | 216 | /// This is like `stry!` but for unsafe Slurm calls that return pointers. 217 | macro_rules! pstry { 218 | ($op:expr) => {{ 219 | let ptr = unsafe { $op }; 220 | 221 | if ptr.is_null() { 222 | let e = unsafe { slurm_sys::slurm_get_errno() }; 223 | Err(SlurmError::from_slurm(e)) 224 | } else { 225 | Ok(ptr) 226 | }? 227 | }}; 228 | } 229 | 230 | /// Allocate memory using Slurm's allocator. 231 | fn slurm_alloc_array(count: usize) -> *mut T { 232 | const TEXT: &[u8] = b"slurm-rs\0"; 233 | let ptr = unsafe { 234 | slurm_sys::slurm_try_xmalloc( 235 | std::mem::size_of::() * count, 236 | TEXT.as_ptr() as _, 237 | 1, 238 | TEXT.as_ptr() as _, 239 | ) 240 | }; 241 | 242 | if ptr.is_null() { 243 | panic!("Slurm memory allocation failed"); 244 | } 245 | 246 | ptr as _ 247 | } 248 | 249 | /// Allocate a structure using Slurm's allocator. 250 | fn slurm_alloc() -> *mut T { 251 | slurm_alloc_array(1) 252 | } 253 | 254 | /// Allocate a C-style string using Slurm's allocator, encoding it as UTF-8. 255 | fn slurm_alloc_utf8_string>(s: S) -> *mut c_char { 256 | let bytes = s.as_ref().as_bytes(); 257 | let n = bytes.len() + 1; 258 | let ptr = slurm_alloc_array(n); 259 | let dest = unsafe { std::slice::from_raw_parts_mut(ptr, n) }; 260 | dest[..n - 1].copy_from_slice(bytes); 261 | dest[n - 1] = b'\0'; 262 | ptr as _ 263 | } 264 | 265 | /// Allocate an array of C-style strings using Slurm's allocator. 266 | /// 267 | /// The strings are encoded as UTF8. Returns the pointer to the string array 268 | /// and the number of strings allocated, which may not be known by the caller 269 | /// if the argument is an iterator of indeterminate size. 270 | fn slurm_alloc_utf8_string_array, S: AsRef>( 271 | strings: I, 272 | ) -> (*mut *mut c_char, usize) { 273 | let buf: Vec<_> = strings.into_iter().collect(); 274 | let ptr = slurm_alloc_array(buf.len()); 275 | let sl = unsafe { std::slice::from_raw_parts_mut(ptr, buf.len()) }; 276 | 277 | for (i, s) in buf.iter().enumerate() { 278 | sl[i] = slurm_alloc_utf8_string(s.as_ref()) as _; 279 | } 280 | 281 | (ptr, buf.len()) 282 | } 283 | 284 | /// Free a structure using Slurm's allocator. 285 | /// 286 | /// A mutable reference to the pointer is required; after freeing, the pointer 287 | /// is nullified. This call is a no-op if the input pointer is already null. 288 | fn slurm_free(thing: &mut *mut T) { 289 | const TEXT: &[u8] = b"slurm-rs\0"; 290 | let p = &mut (*thing as *mut c_void); 291 | unsafe { slurm_sys::slurm_xfree(p, TEXT.as_ptr() as _, 1, TEXT.as_ptr() as _) }; 292 | } 293 | 294 | /// Free an array of strings allocated through Slurm's allocator. 295 | /// 296 | /// A mutable reference to the pointer is required; after freeing, the pointer 297 | /// is nullified. This call is a no-op if the input pointer is already null. 298 | fn slurm_free_string_array(ptr_ref: &mut *mut *mut c_char, count: usize) { 299 | if ptr_ref.is_null() { 300 | return; 301 | } 302 | 303 | let sl = unsafe { std::slice::from_raw_parts_mut(*ptr_ref, count) }; 304 | 305 | for mut sub_ptr in sl { 306 | slurm_free(&mut sub_ptr); 307 | } 308 | 309 | slurm_free(ptr_ref); 310 | } 311 | 312 | /// A helper trait that lets us generically iterate over lists. It must be 313 | /// public so that we can expose `Iterator` for `SlurmListIteratorOwned`. 314 | pub trait UnownedFromSlurmPointer { 315 | /// Create an unowned wrapper object from a Slurm pointer. 316 | fn unowned_from_slurm_pointer(ptr: *mut c_void) -> Self; 317 | } 318 | 319 | /// Sub-helpers for the "job state enum" macros. 320 | /// 321 | /// Some states are not available in older versions of Slurm. We jump through 322 | /// hoops to keep the Rust API consistent while not expecting those options 323 | /// from the C code. 324 | /// 325 | /// In particular, for each potential enumeration item, we pass the main macro 326 | /// the name of a "filter" macro that it then invokes in various cases. 327 | /// In the `jse_all` version of the filter, it evaluates to the whatever item 328 | /// we want for a present and handle-able enumeration value. 329 | /// 330 | /// For enum values that might not be present, we define different filter 331 | /// macros, use `#[cfg]` rules to choose whether (1) we just defer to 332 | /// `jse_all` (when that value is present) or (2) various "can't-happen" values. 333 | /// 334 | /// In the particular case of the "system value" of each enum case, we use 335 | /// hardcoded dummy constants -- these must distinct because the values show 336 | /// up in a match expression. The Slurm data structures only use 1 byte to 337 | /// transfer the job state, so we can expect that valid values will be in the 338 | /// range 0-255. The `job_states` type, however, is at least u32. 339 | /// 340 | /// XXX `to_slurm` should return a Result, and error out for inexpressible 341 | /// values. 342 | 343 | macro_rules! jse_all { 344 | (SYSVAL, $sysname:ident) => { 345 | slurm_sys::$sysname 346 | }; 347 | (FROMVAL, $rustname:ident) => { 348 | Ok(JobState::$rustname) 349 | }; 350 | } 351 | 352 | #[cfg(slurm_api_job_state_deadline)] 353 | macro_rules! jse_deadline { 354 | ($action:ident, $arg:ident) => { 355 | jse_all!($action, $arg) 356 | }; 357 | } 358 | 359 | #[cfg(not(slurm_api_job_state_deadline))] 360 | macro_rules! jse_deadline { 361 | (SYSVAL, $sysname:ident) => { 362 | 0xFF00 363 | }; 364 | (FROMVAL, $rustname:ident) => { 365 | Err(format_err!("illegal job state code 0xFF00")) 366 | }; 367 | } 368 | 369 | #[cfg(slurm_api_job_state_oom)] 370 | macro_rules! jse_oom { 371 | ($action:ident, $arg:ident) => { 372 | jse_all!($action, $arg) 373 | }; 374 | } 375 | 376 | #[cfg(not(slurm_api_job_state_oom))] 377 | macro_rules! jse_oom { 378 | (SYSVAL, $sysname:ident) => { 379 | 0xFF01 380 | }; 381 | (FROMVAL, $rustname:ident) => { 382 | Err(format_err!("illegal job state code 0xFF01")) 383 | }; 384 | } 385 | 386 | /// Helper for interfacing between the C `job_state` enum and our own type. 387 | macro_rules! make_job_state_enum { 388 | ($(<$rustname:ident, $shortcode:ident, $filter:ident, $sysname:ident, $doc:expr;>),*) => { 389 | /// States that a job or job step can be in. 390 | /// 391 | /// The `Deadline` and `OutOfMemory` states are not available in all 392 | /// versions of Slurm. Calling `to_slurm()` on one of these values 393 | /// when built against such a version of Slurm will yield a nonsense 394 | /// value that will probably cause bad things to happen. (TODO: 395 | /// research the precise versions of Slurm in which these were added.) 396 | #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 397 | pub enum JobState { 398 | $( 399 | #[doc=$doc] $rustname, 400 | )* 401 | } 402 | 403 | impl JobState { 404 | fn from_slurm(s: slurm_sys::job_states) -> Result { 405 | match s { 406 | $( 407 | $filter!(SYSVAL, $sysname) => $filter!(FROMVAL, $rustname), 408 | )* 409 | other => Err(format_err!("unrecognized job state code {}", other)), 410 | } 411 | } 412 | 413 | #[allow(unused)] 414 | fn to_slurm(&self) -> slurm_sys::job_states { 415 | match self { 416 | $( 417 | &JobState::$rustname => $filter!(SYSVAL, $sysname), 418 | )* 419 | } 420 | } 421 | 422 | pub fn shortcode(&self) -> &str { 423 | match self { 424 | $( 425 | &JobState::$rustname => stringify!($shortcode), 426 | )* 427 | } 428 | } 429 | } 430 | } 431 | } 432 | 433 | make_job_state_enum! { 434 | , 435 | , 436 | , 437 | , 438 | , 439 | , 440 | , 441 | , 442 | , 443 | , 444 | , 445 | 446 | } 447 | 448 | /// Helper for creating public structs that directly wrap Slurm API 449 | /// structures. Because we must use Slurm's internal allocator, these all wrap 450 | /// native pointers. It's a bit annoying but as far as I can tell it's what we 451 | /// have to do. All of these types are "borrowed" items; they should not 452 | /// implement Drop methods. 453 | macro_rules! make_slurm_wrap_struct { 454 | ($rust_name:ident, $slurm_name:path, $doc:expr) => { 455 | #[doc = $doc] 456 | #[derive(Debug)] 457 | pub struct $rust_name(*mut $slurm_name); 458 | 459 | impl $rust_name { 460 | /// Access the underlying slurm_sys struct immutably. 461 | #[allow(unused)] 462 | #[inline(always)] 463 | fn sys_data(&self) -> &$slurm_name { 464 | unsafe { &(*self.0) } 465 | } 466 | 467 | /// Access the underlying slurm_sys struct mutably. 468 | #[allow(unused)] 469 | #[inline(always)] 470 | fn sys_data_mut(&mut self) -> &mut $slurm_name { 471 | unsafe { &mut (*self.0) } 472 | } 473 | 474 | /// Transmute a reference to a pointer to the underlying datatype 475 | /// into a reference to this wrapper struct. This leverages the 476 | /// fact that the wrapper type is a unit struct that is basically 477 | /// just a pointer itself. This function allows us to return 478 | /// references to fields of various `slurm_sys` structs as if 479 | /// they were our Rust wrapper types. 480 | #[allow(unused)] 481 | #[inline(always)] 482 | unsafe fn transmute_ptr<'a>(ptr: &'a *mut $slurm_name) -> &'a Self { 483 | std::mem::transmute(ptr) 484 | } 485 | 486 | /// Like `transmute_ptr`, but mutable. 487 | #[allow(unused)] 488 | #[inline(always)] 489 | unsafe fn transmute_ptr_mut<'a>(ptr: &'a mut *mut $slurm_name) -> &'a mut Self { 490 | std::mem::transmute(ptr) 491 | } 492 | } 493 | 494 | impl UnownedFromSlurmPointer for $rust_name { 495 | #[inline(always)] 496 | fn unowned_from_slurm_pointer(ptr: *mut c_void) -> Self { 497 | $rust_name(ptr as _) 498 | } 499 | } 500 | }; 501 | } 502 | 503 | /// Helper for creating "owned" versions of unowned structs. This is super 504 | /// tedious but I think it's what we need to do to correctly interface with 505 | /// Slurm's allocator. 506 | macro_rules! make_owned_version { 507 | (@customdrop $unowned_type:ident, $owned_name:ident, $doc:expr) => { 508 | #[doc=$doc] 509 | #[derive(Debug)] 510 | pub struct $owned_name($unowned_type); 511 | 512 | impl Deref for $owned_name { 513 | type Target = $unowned_type; 514 | 515 | fn deref(&self) -> &$unowned_type { 516 | &self.0 517 | } 518 | } 519 | 520 | impl DerefMut for $owned_name { 521 | fn deref_mut(&mut self) -> &mut $unowned_type { 522 | &mut self.0 523 | } 524 | } 525 | 526 | impl $owned_name { 527 | /// This function is unsafe because it may not be valid for the 528 | /// returned value to be filled with zeros. (Slurm is generally 529 | /// pretty good about all-zeros being OK, though.) 530 | #[allow(unused)] 531 | unsafe fn alloc_zeroed() -> Self { 532 | $owned_name($unowned_type(slurm_alloc())) 533 | } 534 | 535 | /// This function is unsafe because it can potentially leak memory 536 | /// if not used correctly. 537 | #[allow(unused)] 538 | unsafe fn give_up_ownership(mut self) -> $unowned_type { 539 | let ptr = (self.0).0; 540 | (self.0).0 = 0 as _; // ensures that slurm_free() doesn't free the memory 541 | $unowned_type(ptr) 542 | } 543 | 544 | /// This function is unsafe because we commit ourselves to freeing 545 | /// the passed-in pointer, which could potentially be bad if we 546 | /// don't in fact own it. 547 | #[allow(unused)] 548 | unsafe fn assume_ownership(ptr: *mut c_void) -> Self { 549 | $owned_name($unowned_type(ptr as _)) 550 | } 551 | } 552 | }; 553 | 554 | ($unowned_type:ident, $owned_name:ident, $doc:expr) => { 555 | make_owned_version!(@customdrop $unowned_type, $owned_name, $doc); 556 | 557 | impl Drop for $owned_name { 558 | fn drop(&mut self) { 559 | slurm_free(&mut (self.0).0); 560 | } 561 | } 562 | }; 563 | } 564 | 565 | // The slurm list type gets custom implementations because we give it a type 566 | // parameter to allow typed access. 567 | 568 | /// A list of some kind of object known to Slurm. 569 | /// 570 | /// These lists show up in a variety of places in the Slurm API. As with the 571 | /// other core structures exposed by this crate, this type represents a 572 | /// *borrowed* reference to a list. 573 | #[derive(Debug)] 574 | pub struct SlurmList(slurm_sys::List, PhantomData); 575 | 576 | impl SlurmList { 577 | unsafe fn transmute_ptr<'a>(ptr: &'a slurm_sys::List) -> &'a Self { 578 | std::mem::transmute(ptr) 579 | } 580 | 581 | unsafe fn transmute_ptr_mut<'a>(ptr: &'a mut slurm_sys::List) -> &'a mut Self { 582 | std::mem::transmute(ptr) 583 | } 584 | } 585 | 586 | impl SlurmList { 587 | pub fn iter<'a>(&'a self) -> SlurmListIteratorOwned<'a, T> { 588 | let ptr = unsafe { slurm_sys::slurm_list_iterator_create(self.0) }; 589 | 590 | if ptr.is_null() { 591 | panic!("failed to create list iterator"); 592 | } 593 | 594 | SlurmListIteratorOwned(ptr as _, PhantomData) 595 | } 596 | } 597 | 598 | /// An owned version of `SlurmList`. 599 | #[derive(Debug)] 600 | pub struct SlurmListOwned(SlurmList); 601 | 602 | impl Deref for SlurmListOwned { 603 | type Target = SlurmList; 604 | 605 | fn deref(&self) -> &SlurmList { 606 | &self.0 607 | } 608 | } 609 | 610 | impl DerefMut for SlurmListOwned { 611 | fn deref_mut(&mut self) -> &mut SlurmList { 612 | &mut self.0 613 | } 614 | } 615 | 616 | impl SlurmListOwned { 617 | #[allow(unused)] 618 | unsafe fn give_up_ownership(mut self) -> SlurmList { 619 | let ptr = (self.0).0; 620 | (self.0).0 = 0 as _; // ensures that slurm_free() doesn't free the memory 621 | SlurmList(ptr, PhantomData) 622 | } 623 | 624 | #[allow(unused)] 625 | unsafe fn assume_ownership(ptr: *mut c_void) -> Self { 626 | SlurmListOwned(SlurmList(ptr as _, PhantomData)) 627 | } 628 | } 629 | 630 | impl Drop for SlurmListOwned { 631 | fn drop(&mut self) { 632 | unsafe { slurm_sys::slurm_list_destroy((self.0).0) }; 633 | } 634 | } 635 | 636 | /// Customized support for lists of strings. 637 | impl SlurmList<*mut c_char> { 638 | pub fn iter<'a>(&'a self) -> SlurmStringListIteratorOwned<'a> { 639 | let ptr = unsafe { slurm_sys::slurm_list_iterator_create(self.0) }; 640 | 641 | if ptr.is_null() { 642 | panic!("failed to create list iterator"); 643 | } 644 | 645 | SlurmStringListIteratorOwned(ptr as _, PhantomData) 646 | } 647 | 648 | pub fn append>(&mut self, s: S) { 649 | let ptr = slurm_alloc_utf8_string(s); 650 | 651 | if self.0.is_null() { 652 | self.0 = unsafe { slurm_sys::slurm_list_create(Some(slurm_sys::slurmrs_free)) }; 653 | } 654 | 655 | unsafe { 656 | slurm_sys::slurm_list_append(self.0, ptr as _); 657 | } 658 | } 659 | } 660 | 661 | // Likewise for iterating through lists, except the iterators are always owned 662 | #[derive(Debug)] 663 | pub struct SlurmListIteratorOwned<'a, T: 'a + UnownedFromSlurmPointer>( 664 | *mut slurm_sys::listIterator, 665 | PhantomData<&'a T>, 666 | ); 667 | 668 | impl<'a, T: 'a + UnownedFromSlurmPointer> Drop for SlurmListIteratorOwned<'a, T> { 669 | fn drop(&mut self) { 670 | unsafe { slurm_sys::slurm_list_iterator_destroy(self.0) }; 671 | } 672 | } 673 | 674 | impl<'a, T: 'a + UnownedFromSlurmPointer> Iterator for SlurmListIteratorOwned<'a, T> { 675 | type Item = T; 676 | 677 | fn next(&mut self) -> Option { 678 | let ptr = unsafe { slurm_sys::slurm_list_next(self.0) }; 679 | 680 | if ptr.is_null() { 681 | None 682 | } else { 683 | Some(T::unowned_from_slurm_pointer(ptr)) 684 | } 685 | } 686 | } 687 | 688 | /// A helper for iterating through lists of strings. 689 | #[derive(Debug)] 690 | pub struct SlurmStringListIteratorOwned<'a>(*mut slurm_sys::listIterator, PhantomData<&'a str>); 691 | 692 | impl<'a> Drop for SlurmStringListIteratorOwned<'a> { 693 | fn drop(&mut self) { 694 | unsafe { slurm_sys::slurm_list_iterator_destroy(self.0) }; 695 | } 696 | } 697 | 698 | impl<'a> Iterator for SlurmStringListIteratorOwned<'a> { 699 | type Item = Cow<'a, str>; 700 | 701 | fn next(&mut self) -> Option> { 702 | let ptr = unsafe { slurm_sys::slurm_list_next(self.0) }; 703 | 704 | if ptr.is_null() { 705 | None 706 | } else { 707 | Some(unsafe { CStr::from_ptr(ptr as _) }.to_string_lossy()) 708 | } 709 | } 710 | } 711 | 712 | // Now we can finally start wrapping types that we care about. 713 | 714 | make_slurm_wrap_struct!( 715 | JobInfo, 716 | slurm_sys::job_info, 717 | "\ 718 | Information about a running job. 719 | 720 | The following items in the Slurm API are *not* exposed in these Rust bindings: 721 | 722 | ```ignore 723 | pub struct job_info { 724 | pub account: *mut c_char, 725 | pub admin_comment: *mut c_char, 726 | pub alloc_node: *mut c_char, 727 | pub alloc_sid: u32, 728 | pub array_bitmap: *mut c_void, 729 | pub array_job_id: u32, 730 | pub array_task_id: u32, 731 | pub array_max_tasks: u32, 732 | pub array_task_str: *mut c_char, 733 | pub assoc_id: u32, 734 | pub batch_flag: u16, 735 | pub batch_host: *mut c_char, 736 | pub bitflags: u32, 737 | pub boards_per_node: u16, 738 | pub burst_buffer: *mut c_char, 739 | pub burst_buffer_state: *mut c_char, 740 | pub cluster: *mut c_char, 741 | pub cluster_features: *mut c_char, 742 | pub command: *mut c_char, 743 | pub comment: *mut c_char, 744 | pub contiguous: u16, 745 | pub core_spec: u16, 746 | pub cores_per_socket: u16, 747 | pub billable_tres: f64, 748 | pub cpus_per_task: u16, 749 | pub cpu_freq_min: u32, 750 | pub cpu_freq_max: u32, 751 | pub cpu_freq_gov: u32, 752 | pub deadline: time_t, 753 | pub delay_boot: u32, 754 | pub dependency: *mut c_char, 755 | pub derived_ec: u32, 756 | pub eligible_time: time_t, 757 | pub end_time: time_t, 758 | pub exc_nodes: *mut c_char, 759 | pub exc_node_inx: *mut i32, 760 | pub exit_code: u32, 761 | pub features: *mut c_char, 762 | pub fed_origin_str: *mut c_char, 763 | pub fed_siblings_active: u64, 764 | pub fed_siblings_active_str: *mut c_char, 765 | pub fed_siblings_viable: u64, 766 | pub fed_siblings_viable_str: *mut c_char, 767 | pub gres: *mut c_char, 768 | pub gres_detail_cnt: u32, 769 | pub gres_detail_str: *mut *mut c_char, 770 | pub group_id: u32, 771 | pub job_resrcs: *mut job_resources_t, 772 | pub job_state: u32, 773 | pub last_sched_eval: time_t, 774 | pub licenses: *mut c_char, 775 | pub max_cpus: u32, 776 | pub max_nodes: u32, 777 | pub mcs_label: *mut c_char, 778 | pub name: *mut c_char, 779 | pub network: *mut c_char, 780 | pub nodes: *mut c_char, 781 | pub nice: u32, 782 | pub node_inx: *mut i32, 783 | pub ntasks_per_core: u16, 784 | pub ntasks_per_node: u16, 785 | pub ntasks_per_socket: u16, 786 | pub ntasks_per_board: u16, 787 | pub num_cpus: u32, 788 | pub num_nodes: u32, 789 | pub num_tasks: u32, 790 | pub pack_job_id: u32, 791 | pub pack_job_id_set: *mut c_char, 792 | pub pack_job_offset: u32, 793 | pub pn_min_memory: u64, 794 | pub pn_min_cpus: u16, 795 | pub pn_min_tmp_disk: u32, 796 | pub power_flags: u8, 797 | pub preempt_time: time_t, 798 | pub pre_sus_time: time_t, 799 | pub priority: u32, 800 | pub profile: u32, 801 | pub qos: *mut c_char, 802 | pub reboot: u8, 803 | pub req_nodes: *mut c_char, 804 | pub req_node_inx: *mut i32, 805 | pub req_switch: u32, 806 | pub requeue: u16, 807 | pub resize_time: time_t, 808 | pub restart_cnt: u16, 809 | pub resv_name: *mut c_char, 810 | pub sched_nodes: *mut c_char, 811 | pub select_jobinfo: *mut dynamic_plugin_data_t, 812 | pub shared: u16, 813 | pub show_flags: u16, 814 | pub sockets_per_board: u16, 815 | pub sockets_per_node: u16, 816 | pub start_time: time_t, 817 | pub start_protocol_ver: u16, 818 | pub state_desc: *mut c_char, 819 | pub state_reason: u16, 820 | pub std_err: *mut c_char, 821 | pub std_in: *mut c_char, 822 | pub std_out: *mut c_char, 823 | pub submit_time: time_t, 824 | pub suspend_time: time_t, 825 | pub time_limit: u32, 826 | pub time_min: u32, 827 | pub threads_per_core: u16, 828 | pub tres_req_str: *mut c_char, 829 | pub tres_alloc_str: *mut c_char, 830 | pub user_id: u32, 831 | pub user_name: *mut c_char, 832 | pub wait4switch: u32, 833 | pub wckey: *mut c_char, 834 | pub work_dir: *mut c_char, 835 | } 836 | ``` 837 | 838 | " 839 | ); 840 | 841 | impl JobInfo { 842 | /// Get this job's ID. 843 | pub fn job_id(&self) -> JobId { 844 | self.sys_data().job_id 845 | } 846 | 847 | /// Get the cluster partition on which this job resides. 848 | pub fn partition(&self) -> Cow { 849 | unsafe { CStr::from_ptr(self.sys_data().partition) }.to_string_lossy() 850 | } 851 | } 852 | 853 | /// Get information about a single job. 854 | /// 855 | /// The job must still be running. If it existed but is no longer running, 856 | /// the result is an error (errno 2017, "invalid job id"). 857 | /// 858 | /// While the (successful) return value of this function is not a `JobInfo` 859 | /// struct, it is a type that derefs to `JobInfo`, and so can be used like 860 | /// one. 861 | pub fn get_job_info(jid: JobId) -> Result { 862 | let mut msg: *mut slurm_sys::job_info_msg_t = 0 as _; 863 | 864 | ustry!(slurm_sys::slurm_load_job(&mut msg, jid, 0)); 865 | 866 | let rc = unsafe { (*msg).record_count }; 867 | if rc != 1 { 868 | return Err(format_err!( 869 | "expected exactly one info record for job {}; got {} items", 870 | jid, 871 | rc 872 | )); 873 | } 874 | 875 | Ok(unsafe { SingleJobInfoMessageOwned::assume_ownership(msg as _) }) 876 | } 877 | 878 | make_slurm_wrap_struct!( 879 | SingleJobInfoMessage, 880 | slurm_sys::job_info_msg_t, 881 | "Information about a single job. 882 | 883 | This type implements `Deref` to `JobInfo` and so can be essentially be 884 | treated as a `JobInfo`. Due to how the Slurm library manages memory, this 885 | separate type is necessary in some cases." 886 | ); 887 | 888 | impl Deref for SingleJobInfoMessage { 889 | type Target = JobInfo; 890 | 891 | fn deref(&self) -> &JobInfo { 892 | unsafe { JobInfo::transmute_ptr(&self.sys_data().job_array) } 893 | } 894 | } 895 | 896 | impl DerefMut for SingleJobInfoMessage { 897 | fn deref_mut(&mut self) -> &mut JobInfo { 898 | unsafe { JobInfo::transmute_ptr_mut(&mut self.sys_data_mut().job_array) } 899 | } 900 | } 901 | 902 | make_owned_version!(@customdrop SingleJobInfoMessage, SingleJobInfoMessageOwned, 903 | "An owned version of `SingleJobInfoMessage`."); 904 | 905 | impl Drop for SingleJobInfoMessageOwned { 906 | fn drop(&mut self) { 907 | unsafe { slurm_sys::slurm_free_job_info_msg((self.0).0) }; 908 | } 909 | } 910 | 911 | make_slurm_wrap_struct!( 912 | DatabaseConnection, 913 | c_void, 914 | "A connection to the Slurm accounting database." 915 | ); 916 | 917 | impl DatabaseConnection { 918 | /// Query for information about jobs. 919 | pub fn get_jobs(&self, filters: &JobFilters) -> Result, SlurmError> { 920 | let ptr = pstry!(slurm_sys::slurmdb_jobs_get(self.0, filters.0)); 921 | Ok(unsafe { SlurmListOwned::assume_ownership(ptr as _) }) 922 | } 923 | } 924 | 925 | make_owned_version!(@customdrop DatabaseConnection, DatabaseConnectionOwned, 926 | "An owned version of `DatabaseConnection`."); 927 | 928 | impl DatabaseConnectionOwned { 929 | /// Connect to the Slurm database. 930 | pub fn new() -> Result { 931 | let ptr = pstry!(slurm_sys::slurmdb_connection_get()); 932 | Ok(unsafe { DatabaseConnectionOwned::assume_ownership(ptr) }) 933 | } 934 | } 935 | 936 | impl Drop for DatabaseConnectionOwned { 937 | fn drop(&mut self) { 938 | // This function can return error codes, but we're not in a position 939 | // to do anything about it in the Drop call. 940 | let _ignored = unsafe { slurm_sys::slurmdb_connection_close(&mut (self.0).0) }; 941 | } 942 | } 943 | 944 | make_slurm_wrap_struct!( 945 | JobFilters, 946 | slurm_sys::slurmdb_job_cond_t, 947 | "\ 948 | A set of filters for identifying jobs of interest when querying the Slurm 949 | accounting database. 950 | 951 | The following items in the Slurm API are *not* exposed in these Rust bindings: 952 | 953 | ```ignore 954 | pub struct slurmdb_job_cond_t { 955 | pub acct_list: List, 956 | pub associd_list: List, 957 | pub cluster_list: List, 958 | pub cpus_max: u32, 959 | pub cpus_min: u32, 960 | pub duplicates: u16, 961 | pub exitcode: i32, 962 | pub format_list: List, 963 | pub groupid_list: List, 964 | pub jobname_list: List, 965 | pub nodes_max: u32, 966 | pub nodes_min: u32, 967 | pub partition_list: List, 968 | pub qos_list: List, 969 | pub resv_list: List, 970 | pub resvid_list: List, 971 | pub state_list: List, 972 | pub timelimit_max: u32, 973 | pub timelimit_min: u32, 974 | pub usage_end: time_t, 975 | pub used_nodes: *mut c_char, 976 | pub wckey_list: List, 977 | pub without_steps: u16, 978 | pub without_usage_truncation: u16, 979 | } 980 | ``` 981 | 982 | " 983 | ); 984 | 985 | impl JobFilters { 986 | pub fn step_list(&self) -> &SlurmList { 987 | unsafe { SlurmList::transmute_ptr(&self.sys_data().step_list) } 988 | } 989 | 990 | pub fn step_list_mut(&mut self) -> &mut SlurmList { 991 | unsafe { SlurmList::transmute_ptr_mut(&mut self.sys_data_mut().step_list) } 992 | } 993 | 994 | /// Add a filter on the earliest job "usage time". 995 | /// 996 | /// TODO: what is "usage time" really? 997 | pub fn usage_start(&mut self, time: DateTime) -> &Self { 998 | self.sys_data_mut().without_usage_truncation = 0; 999 | self.sys_data_mut().usage_start = time.timestamp() as _; 1000 | self 1001 | } 1002 | 1003 | /// Access the list of user ID numbers that will match this set of filters. 1004 | /// 1005 | /// Note that this list should consist of *textual* representations of 1006 | /// *numeric* user IDs. Yes, it's silly. 1007 | pub fn userid_list(&self) -> &SlurmList<*mut c_char> { 1008 | unsafe { SlurmList::transmute_ptr(&self.sys_data().userid_list) } 1009 | } 1010 | 1011 | /// Mutably access the list of user ID numbers that will match this set of 1012 | /// filters. 1013 | pub fn userid_list_mut(&mut self) -> &mut SlurmList<*mut c_char> { 1014 | unsafe { SlurmList::transmute_ptr_mut(&mut self.sys_data_mut().userid_list) } 1015 | } 1016 | } 1017 | 1018 | make_owned_version!( 1019 | JobFilters, 1020 | JobFiltersOwned, 1021 | "An owned version of `JobFilters`" 1022 | ); 1023 | 1024 | impl Default for JobFiltersOwned { 1025 | fn default() -> Self { 1026 | let mut inst = unsafe { Self::alloc_zeroed() }; 1027 | { 1028 | let sdm = inst.sys_data_mut(); 1029 | sdm.without_usage_truncation = 1; 1030 | } 1031 | inst 1032 | } 1033 | } 1034 | 1035 | make_slurm_wrap_struct!( 1036 | JobStepFilter, 1037 | slurm_sys::slurmdb_selected_step_t, 1038 | "A filter for selecting jobs and job steps." 1039 | ); 1040 | 1041 | make_owned_version!(@customdrop JobStepFilter, JobStepFilterOwned, "An owned version of `JobStepFilter`."); 1042 | 1043 | impl Drop for JobStepFilterOwned { 1044 | fn drop(&mut self) { 1045 | unsafe { slurm_sys::slurmdb_destroy_selected_step((self.0).0 as _) }; 1046 | } 1047 | } 1048 | 1049 | impl JobStepFilterOwned { 1050 | /// Create a new job step filter. 1051 | pub fn new(jid: JobId) -> Self { 1052 | let mut inst = unsafe { Self::alloc_zeroed() }; 1053 | { 1054 | let sdm = inst.sys_data_mut(); 1055 | sdm.array_task_id = slurm_sys::SLURMRS_NO_VAL; 1056 | sdm.jobid = jid; 1057 | #[cfg(slurm_api_selected_step_t_pack_job_offset)] 1058 | { 1059 | sdm.pack_job_offset = slurm_sys::SLURMRS_NO_VAL; 1060 | } 1061 | sdm.stepid = slurm_sys::SLURMRS_NO_VAL; 1062 | } 1063 | inst 1064 | } 1065 | } 1066 | 1067 | impl SlurmList { 1068 | pub fn append(&mut self, item: JobStepFilterOwned) { 1069 | let item = unsafe { item.give_up_ownership() }; 1070 | 1071 | if self.0.is_null() { 1072 | // XXX if malloc fails, I think this function will abort under us. 1073 | self.0 = unsafe { 1074 | slurm_sys::slurm_list_create(Some(slurm_sys::slurmdb_destroy_selected_step)) 1075 | }; 1076 | } 1077 | 1078 | unsafe { 1079 | slurm_sys::slurm_list_append(self.0, item.0 as _); 1080 | } 1081 | } 1082 | } 1083 | 1084 | make_slurm_wrap_struct!( 1085 | JobRecord, 1086 | slurm_sys::slurmdb_job_rec_t, 1087 | "\ 1088 | Accounting information about a job. 1089 | 1090 | The following items in the Slurm API are *not* exposed in these Rust bindings: 1091 | 1092 | ```ignore 1093 | pub struct slurmdb_job_rec_t { 1094 | pub account: *mut c_char, 1095 | pub admin_comment: *mut c_char, 1096 | pub alloc_gres: *mut c_char, 1097 | pub alloc_nodes: u32, 1098 | pub array_max_tasks: u32, 1099 | pub array_task_id: u32, 1100 | pub array_task_str: *mut c_char, 1101 | pub associd: u32, 1102 | pub blockid: *mut c_char, 1103 | pub cluster: *mut c_char, 1104 | pub derived_ec: u32, 1105 | pub derived_es: *mut c_char, 1106 | pub first_step_ptr: *mut c_void, 1107 | pub gid: u32, 1108 | pub lft: u32, 1109 | pub mcs_label: *mut c_char, 1110 | pub partition: *mut c_char, 1111 | pub pack_job_id: u32, 1112 | pub pack_job_offset: u32, 1113 | pub priority: u32, 1114 | pub qosid: u32, 1115 | pub req_cpus: u32, 1116 | pub req_gres: *mut c_char, 1117 | pub req_mem: u64, 1118 | pub resvid: u32, 1119 | pub resv_name: *mut c_char, 1120 | pub show_full: u32, 1121 | pub steps: List, 1122 | pub track_steps: u16, 1123 | pub tres_req_str: *mut c_char, 1124 | pub uid: u32, 1125 | pub used_gres: *mut c_char, 1126 | pub user: *mut c_char, 1127 | pub wckey: *mut c_char, 1128 | pub wckeyid: u32, 1129 | pub work_dir: *mut c_char, 1130 | } 1131 | ``` 1132 | 1133 | (The above listing omits fields that would be handled by the 1134 | `JobStepRecordSharedFields` trait.) 1135 | " 1136 | ); 1137 | 1138 | /// A trait for accessing fields common to SlurmDB job records and step 1139 | /// records. 1140 | pub trait JobStepRecordSharedFields { 1141 | /// Get the job/step's end time, or None if it has not yet ended. 1142 | fn end_time(&self) -> Option>; 1143 | 1144 | /// Get the job/step's exit code, or None if it has not yet ended. 1145 | fn exit_code(&self) -> Option; 1146 | 1147 | /// Get the maximum "virtual memory size" of the job/step, in kibibytes. 1148 | /// 1149 | /// This quantity is not available (i.e., the function returns `None`) 1150 | /// until the job has finished running. 1151 | fn max_vm_size(&self) -> Option; 1152 | 1153 | /// Get the job/step's start time, or None if it has not yet started. 1154 | fn start_time(&self) -> Option>; 1155 | 1156 | /// Get the job/step's state. 1157 | fn state(&self) -> JobState; 1158 | 1159 | /// Get the wallclock time taken by the job/step: end time minus start time. 1160 | /// 1161 | /// Returns None if the job/step has not yet completed (or even started). 1162 | fn wallclock_duration(&self) -> Option; 1163 | } 1164 | 1165 | /// We implement the JobStepRecordSharedFields trait with a macro; that seems 1166 | /// like the easiest thing to do. 1167 | /// 1168 | /// The complete list of overlapping fields is: 1169 | /// 1170 | /// ```ignore 1171 | /// { 1172 | /// elapsed: u32, 1173 | /// end: i64, 1174 | /// exitcode: i32, 1175 | /// nodes: *mut c_char, 1176 | /// requid: u32, 1177 | /// start: i64, 1178 | /// state: u32, 1179 | /// stats: &slurm_sys::slurmdb_stats_t, 1180 | /// suspended: u32, 1181 | /// sys_cpu_sec: u32, 1182 | /// sys_cpu_usec: u32, 1183 | /// tot_cpu_sec: u32, 1184 | /// tot_cpu_usec: u32, 1185 | /// tres_alloc_str: *mut c_char, 1186 | /// user_cpu_sec: u32, 1187 | /// user_cpu_usec: u32, 1188 | /// } 1189 | /// ``` 1190 | macro_rules! impl_job_step_record_shared_fields { 1191 | ($type:path) => { 1192 | impl JobStepRecordSharedFields for $type { 1193 | fn end_time(&self) -> Option> { 1194 | match self.sys_data().end as i64 { 1195 | 0 => None, 1196 | t => Some(Utc.timestamp(t, 0)), 1197 | } 1198 | } 1199 | 1200 | fn exit_code(&self) -> Option { 1201 | match self.sys_data().end as i64 { 1202 | 0 => None, 1203 | _ => Some(self.sys_data().exitcode as i32), 1204 | } 1205 | } 1206 | 1207 | fn max_vm_size(&self) -> Option { 1208 | match self.sys_data().stats.vsize_max { 1209 | slurm_sys::SLURMRS_NO_VAL64 => None, 1210 | other => Some(other), 1211 | } 1212 | } 1213 | 1214 | fn start_time(&self) -> Option> { 1215 | match self.sys_data().start as i64 { 1216 | 0 => None, 1217 | t => Some(Utc.timestamp(t, 0)), 1218 | } 1219 | } 1220 | 1221 | fn state(&self) -> JobState { 1222 | JobState::from_slurm(self.sys_data().state).expect("unhandled job_state code") 1223 | } 1224 | 1225 | fn wallclock_duration(&self) -> Option { 1226 | match (self.start_time(), self.end_time()) { 1227 | (Some(start), Some(end)) => Some(end.signed_duration_since(start)), 1228 | _ => None, 1229 | } 1230 | } 1231 | } 1232 | }; 1233 | } 1234 | 1235 | impl_job_step_record_shared_fields!(JobRecord); 1236 | 1237 | impl JobRecord { 1238 | /// Get the unique identifier of the array group this job belonged to. 1239 | /// 1240 | /// Returns None if this job was not part of an array. 1241 | pub fn array_job_id(&self) -> Option { 1242 | match self.sys_data().array_job_id { 1243 | 0 => None, 1244 | other => Some(other), 1245 | } 1246 | } 1247 | 1248 | /// Get the job's "eligible" time, or None if the job is not yet eligible to run. 1249 | pub fn eligible_time(&self) -> Option> { 1250 | match self.sys_data().eligible as i64 { 1251 | 0 => None, 1252 | t => Some(Utc.timestamp(t, 0)), 1253 | } 1254 | } 1255 | 1256 | /// Get the job's ID number. 1257 | pub fn job_id(&self) -> JobId { 1258 | self.sys_data().jobid 1259 | } 1260 | 1261 | /// Get the job's name. 1262 | pub fn job_name(&self) -> Cow { 1263 | unsafe { CStr::from_ptr(self.sys_data().jobname) }.to_string_lossy() 1264 | } 1265 | 1266 | /// Get the job's submission time. 1267 | pub fn submit_time(&self) -> DateTime { 1268 | Utc.timestamp(self.sys_data().submit as i64, 0) 1269 | } 1270 | 1271 | /// Get the wallclock time spent waiting for the job to become eligible, 1272 | /// or None if the job has not yet become eligible to run. 1273 | pub fn eligible_wait_duration(&self) -> Option { 1274 | self.eligible_time() 1275 | .map(|t| t.signed_duration_since(self.submit_time())) 1276 | } 1277 | 1278 | /// Get the job's time limit in minutes. 1279 | pub fn time_limit(&self) -> u32 { 1280 | self.sys_data().timelimit 1281 | } 1282 | 1283 | /// Get the wallclock time spent waiting for the job to start, or None 1284 | /// if the job has not yet started. 1285 | /// 1286 | /// This includes time that the job spent waiting to become eligible to run. 1287 | pub fn wait_duration(&self) -> Option { 1288 | self.start_time() 1289 | .map(|t| t.signed_duration_since(self.submit_time())) 1290 | } 1291 | 1292 | /// Steps. 1293 | pub fn steps(&self) -> &SlurmList { 1294 | unsafe { SlurmList::transmute_ptr(&self.sys_data().steps) } 1295 | } 1296 | } 1297 | 1298 | make_slurm_wrap_struct!( 1299 | StepRecord, 1300 | slurm_sys::slurmdb_step_rec_t, 1301 | "\ 1302 | Accounting information about a step within a job. 1303 | 1304 | The following items in the Slurm API are *not* exposed in these Rust bindings: 1305 | 1306 | ```ignore 1307 | pub struct slurmdb_step_rec_t { 1308 | pub job_ptr: *mut slurmdb_job_rec_t, 1309 | pub nnodes: u32, 1310 | pub ntasks: u32, 1311 | pub pid_str: *mut c_char, 1312 | pub req_cpufreq_min: u32, 1313 | pub req_cpufreq_max: u32, 1314 | pub req_cpufreq_gov: u32, 1315 | pub task_dist: u32, 1316 | } 1317 | ``` 1318 | 1319 | (The above listing omits fields that would be handled by the 1320 | `JobStepRecordSharedFields` trait.) 1321 | " 1322 | ); 1323 | 1324 | impl_job_step_record_shared_fields!(StepRecord); 1325 | 1326 | impl StepRecord { 1327 | /// Get the step's ID. 1328 | pub fn step_id(&self) -> StepId { 1329 | self.sys_data().stepid 1330 | } 1331 | 1332 | /// Get the step's name. 1333 | pub fn step_name(&self) -> Cow { 1334 | unsafe { CStr::from_ptr(self.sys_data().stepname) }.to_string_lossy() 1335 | } 1336 | } 1337 | 1338 | make_slurm_wrap_struct!( 1339 | JobDescriptor, 1340 | slurm_sys::job_descriptor, 1341 | "\ 1342 | A description of a batch job to submit. 1343 | 1344 | The following items in the Slurm API are *not* exposed in these Rust bindings: 1345 | 1346 | ```ignore 1347 | pub struct job_descriptor { 1348 | pub account: *mut c_char, 1349 | pub acctg_freq: *mut c_char, 1350 | pub admin_comment: *mut c_char, 1351 | pub alloc_node: *mut c_char, 1352 | pub alloc_resp_port: u16, 1353 | pub alloc_sid: u32, 1354 | pub array_inx: *mut c_char, 1355 | pub array_bitmap: *mut c_void, 1356 | pub begin_time: time_t, 1357 | pub bitflags: u32, 1358 | pub burst_buffer: *mut c_char, 1359 | pub ckpt_interval: u16, 1360 | pub ckpt_dir: *mut c_char, 1361 | pub clusters: *mut c_char, 1362 | pub cluster_features: *mut c_char, 1363 | pub comment: *mut c_char, 1364 | pub contiguous: u16, 1365 | pub core_spec: u16, 1366 | pub cpu_bind: *mut c_char, 1367 | pub cpu_bind_type: u16, 1368 | pub cpu_freq_min: u32, 1369 | pub cpu_freq_max: u32, 1370 | pub cpu_freq_gov: u32, 1371 | pub deadline: time_t, 1372 | pub delay_boot: u32, 1373 | pub dependency: *mut c_char, 1374 | pub end_time: time_t, 1375 | pub environment: *mut *mut c_char, 1376 | pub env_size: u32, 1377 | pub extra: *mut c_char, 1378 | pub exc_nodes: *mut c_char, 1379 | pub features: *mut c_char, 1380 | pub fed_siblings_active: u64, 1381 | pub fed_siblings_viable: u64, 1382 | pub gres: *mut c_char, 1383 | pub immediate: u16, 1384 | pub job_id: u32, 1385 | pub job_id_str: *mut c_char, 1386 | pub kill_on_node_fail: u16, 1387 | pub licenses: *mut c_char, 1388 | pub mail_type: u16, 1389 | pub mail_user: *mut c_char, 1390 | pub mcs_label: *mut c_char, 1391 | pub mem_bind: *mut c_char, 1392 | pub mem_bind_type: u16, 1393 | pub network: *mut c_char, 1394 | pub nice: u32, 1395 | pub num_tasks: u32, 1396 | pub open_mode: u8, 1397 | pub origin_cluster: *mut c_char, 1398 | pub other_port: u16, 1399 | pub overcommit: u8, 1400 | pub pack_job_offset: u32, 1401 | pub plane_size: u16, 1402 | pub power_flags: u8, 1403 | pub priority: u32, 1404 | pub profile: u32, 1405 | pub qos: *mut c_char, 1406 | pub reboot: u16, 1407 | pub resp_host: *mut c_char, 1408 | pub restart_cnt: u16, 1409 | pub req_nodes: *mut c_char, 1410 | pub requeue: u16, 1411 | pub reservation: *mut c_char, 1412 | pub shared: u16, 1413 | pub spank_job_env: *mut *mut c_char, 1414 | pub spank_job_env_size: u32, 1415 | pub task_dist: u32, 1416 | pub time_min: u32, 1417 | pub wait_all_nodes: u16, 1418 | pub warn_flags: u16, 1419 | pub warn_signal: u16, 1420 | pub warn_time: u16, 1421 | pub cpus_per_task: u16, 1422 | pub min_cpus: u32, 1423 | pub max_cpus: u32, 1424 | pub min_nodes: u32, 1425 | pub max_nodes: u32, 1426 | pub boards_per_node: u16, 1427 | pub sockets_per_board: u16, 1428 | pub sockets_per_node: u16, 1429 | pub cores_per_socket: u16, 1430 | pub threads_per_core: u16, 1431 | pub ntasks_per_node: u16, 1432 | pub ntasks_per_socket: u16, 1433 | pub ntasks_per_core: u16, 1434 | pub ntasks_per_board: u16, 1435 | pub pn_min_cpus: u16, 1436 | pub pn_min_memory: u64, 1437 | pub pn_min_tmp_disk: u32, 1438 | pub geometry: [u16; 5], 1439 | pub conn_type: [u16; 5], 1440 | pub rotate: u16, 1441 | pub blrtsimage: *mut c_char, 1442 | pub linuximage: *mut c_char, 1443 | pub mloaderimage: *mut c_char, 1444 | pub ramdiskimage: *mut c_char, 1445 | pub req_switch: u32, 1446 | pub select_jobinfo: *mut dynamic_plugin_data_t, 1447 | pub tres_req_cnt: *mut u64, 1448 | pub wait4switch: u32, 1449 | pub wckey: *mut c_char, 1450 | pub x11: u16, 1451 | pub x11_magic_cookie: *mut c_char, 1452 | pub x11_target_port: u16, 1453 | } 1454 | ``` 1455 | 1456 | " 1457 | ); 1458 | 1459 | impl JobDescriptor { 1460 | /// Get the group ID associated with this job. 1461 | pub fn gid(&self) -> u32 { 1462 | self.sys_data().group_id 1463 | } 1464 | 1465 | /// Set the group ID associated with this job. 1466 | pub fn set_gid(&mut self, value: u32) -> &mut Self { 1467 | self.sys_data_mut().group_id = value; 1468 | self 1469 | } 1470 | 1471 | /// Set the group ID associated with this job to that of the current process. 1472 | pub fn set_gid_current(&mut self) -> &mut Self { 1473 | self.set_gid(unsafe { libc::getgid() }) 1474 | } 1475 | 1476 | /// Get this job's name. 1477 | pub fn name(&self) -> Cow { 1478 | unsafe { CStr::from_ptr(self.sys_data().name) }.to_string_lossy() 1479 | } 1480 | 1481 | /// Get the number of tasks within this job. 1482 | pub fn num_tasks(&self) -> u32 { 1483 | self.sys_data().num_tasks 1484 | } 1485 | 1486 | /// Set the number of tasks within this job. 1487 | pub fn set_num_tasks(&mut self, value: u32) -> &mut Self { 1488 | self.sys_data_mut().num_tasks = value; 1489 | self 1490 | } 1491 | 1492 | /// Get this job's assigned partition. 1493 | pub fn partition(&self) -> Cow { 1494 | unsafe { CStr::from_ptr(self.sys_data().partition) }.to_string_lossy() 1495 | } 1496 | 1497 | /// Get the contents of this job's batch wrapper script. 1498 | pub fn script(&self) -> Cow { 1499 | unsafe { CStr::from_ptr(self.sys_data().script) }.to_string_lossy() 1500 | } 1501 | 1502 | /// Get the path for this job's standard error stream. 1503 | pub fn stderr_path(&self) -> Cow { 1504 | unsafe { CStr::from_ptr(self.sys_data().std_err) }.to_string_lossy() 1505 | } 1506 | 1507 | /// Get the path for this job's standard input stream. 1508 | pub fn stdin_path(&self) -> Cow { 1509 | unsafe { CStr::from_ptr(self.sys_data().std_in) }.to_string_lossy() 1510 | } 1511 | 1512 | /// Get the path for this job's standard output stream. 1513 | pub fn stdout_path(&self) -> Cow { 1514 | unsafe { CStr::from_ptr(self.sys_data().std_out) }.to_string_lossy() 1515 | } 1516 | 1517 | /// Get the time limit associated with this job, measured in minutes. 1518 | pub fn time_limit(&self) -> u32 { 1519 | self.sys_data().time_limit 1520 | } 1521 | 1522 | /// Set the time limit associated with this job. 1523 | /// 1524 | /// The value is measured in minutes. 1525 | pub fn set_time_limit(&mut self, minutes: u32) -> &mut Self { 1526 | self.sys_data_mut().time_limit = minutes; 1527 | self 1528 | } 1529 | 1530 | /// Get the user ID associated with this job. 1531 | pub fn uid(&self) -> u32 { 1532 | self.sys_data().user_id 1533 | } 1534 | 1535 | /// Set the user ID associated with this job. 1536 | pub fn set_uid(&mut self, value: u32) -> &mut Self { 1537 | self.sys_data_mut().user_id = value; 1538 | self 1539 | } 1540 | 1541 | /// Set the user ID associated with this job to that of the current process. 1542 | pub fn set_uid_current(&mut self) -> &mut Self { 1543 | self.set_uid(unsafe { libc::getuid() }) 1544 | } 1545 | 1546 | /// Get the contents of this job's assigned working directory. 1547 | pub fn work_dir(&self) -> Cow { 1548 | unsafe { CStr::from_ptr(self.sys_data().work_dir) }.to_string_lossy() 1549 | } 1550 | 1551 | /// Submit this job to the batch processor. 1552 | /// 1553 | /// TODO? Handle server-side errors reported in the response. 1554 | pub fn submit_batch(&self) -> Result { 1555 | let mut msg = std::ptr::null_mut(); 1556 | ustry!(slurm_sys::slurm_submit_batch_job(self.0, &mut msg as _)); 1557 | Ok(unsafe { SubmitResponseMessageOwned::assume_ownership(msg as _) }) 1558 | } 1559 | } 1560 | 1561 | make_owned_version!(@customdrop JobDescriptor, JobDescriptorOwned, "An owned version of `JobDescriptor`."); 1562 | 1563 | impl JobDescriptorOwned { 1564 | /// Create a new, defaulted job descriptor. 1565 | pub fn new() -> Self { 1566 | let inst = unsafe { Self::alloc_zeroed() }; 1567 | unsafe { 1568 | slurm_sys::slurm_init_job_desc_msg((inst.0).0); 1569 | } 1570 | inst 1571 | } 1572 | 1573 | fn maybe_clear_argv(&mut self) { 1574 | let d = self.sys_data_mut(); 1575 | slurm_free_string_array(&mut d.argv, d.argc as usize); 1576 | d.argc = 0; 1577 | } 1578 | 1579 | /// Specify the command-line arguments of the job. 1580 | pub fn set_argv, S: AsRef>(&mut self, argv: I) -> &mut Self { 1581 | self.maybe_clear_argv(); 1582 | let (ptr, size) = slurm_alloc_utf8_string_array(argv); 1583 | { 1584 | let d = self.sys_data_mut(); 1585 | d.argv = ptr; 1586 | d.argc = size as u32; 1587 | } 1588 | self 1589 | } 1590 | 1591 | fn maybe_clear_environment(&mut self) { 1592 | let d = self.sys_data_mut(); 1593 | slurm_free_string_array(&mut d.environment, d.env_size as usize); 1594 | d.env_size = 0; 1595 | } 1596 | 1597 | /// Explicitly specify the UNIX environment of the job. 1598 | pub fn set_environment, S: AsRef>( 1599 | &mut self, 1600 | env: I, 1601 | ) -> &mut Self { 1602 | self.maybe_clear_environment(); 1603 | let (ptr, size) = slurm_alloc_utf8_string_array(env); 1604 | { 1605 | let d = self.sys_data_mut(); 1606 | d.environment = ptr; 1607 | d.env_size = size as u32; 1608 | } 1609 | self 1610 | } 1611 | 1612 | /// Set the UNIX environment of the job to match that of the current process. 1613 | /// 1614 | /// This will panic if any environment variables are not decodable as 1615 | /// Unicode. This limitation could be worked around with some developer 1616 | /// effort. 1617 | pub fn inherit_environment(&mut self) -> &mut Self { 1618 | self.set_environment(std::env::vars().map(|(key, val)| format!("{}={}", key, val))) 1619 | } 1620 | 1621 | /// Set this job's name. 1622 | pub fn set_name>(&mut self, name: S) -> &mut Self { 1623 | { 1624 | let d = self.sys_data_mut(); 1625 | slurm_free(&mut d.name); 1626 | d.name = slurm_alloc_utf8_string(name); 1627 | } 1628 | self 1629 | } 1630 | 1631 | /// Set this job's partition. 1632 | pub fn set_partition>(&mut self, partition: S) -> &mut Self { 1633 | { 1634 | let d = self.sys_data_mut(); 1635 | slurm_free(&mut d.partition); 1636 | d.partition = slurm_alloc_utf8_string(partition); 1637 | } 1638 | self 1639 | } 1640 | 1641 | /// Set the contents of this job's wrapper shell script. 1642 | /// 1643 | /// This is the textual content of a shell script that will be executed as 1644 | /// the batch job wrapper. It should start with a shebang (`#!`) line. 1645 | pub fn set_script>(&mut self, script: S) -> &mut Self { 1646 | { 1647 | let d = self.sys_data_mut(); 1648 | slurm_free(&mut d.script); 1649 | d.script = slurm_alloc_utf8_string(script); 1650 | } 1651 | self 1652 | } 1653 | 1654 | /// Set the path that will be used as this job's standard error stream. 1655 | pub fn set_stderr_path>(&mut self, path: S) -> &mut Self { 1656 | { 1657 | let d = self.sys_data_mut(); 1658 | slurm_free(&mut d.std_err); 1659 | d.std_err = slurm_alloc_utf8_string(path); 1660 | } 1661 | self 1662 | } 1663 | 1664 | /// Set the path that will be used as this job's standard input stream. 1665 | pub fn set_stdin_path>(&mut self, path: S) -> &mut Self { 1666 | { 1667 | let d = self.sys_data_mut(); 1668 | slurm_free(&mut d.std_in); 1669 | d.std_in = slurm_alloc_utf8_string(path); 1670 | } 1671 | self 1672 | } 1673 | 1674 | /// Set the path that will be used as this job's standard output stream. 1675 | pub fn set_stdout_path>(&mut self, path: S) -> &mut Self { 1676 | { 1677 | let d = self.sys_data_mut(); 1678 | slurm_free(&mut d.std_out); 1679 | d.std_out = slurm_alloc_utf8_string(path); 1680 | } 1681 | self 1682 | } 1683 | 1684 | /// Set this job's working directory. 1685 | /// 1686 | /// The working directory should be one that exists on all worker nodes of 1687 | /// the cluster. 1688 | pub fn set_work_dir>(&mut self, work_dir: S) -> &mut Self { 1689 | { 1690 | let d = self.sys_data_mut(); 1691 | slurm_free(&mut d.work_dir); 1692 | d.work_dir = slurm_alloc_utf8_string(work_dir); 1693 | } 1694 | self 1695 | } 1696 | 1697 | /// Set this job's working directory to the current process's current 1698 | /// directory. 1699 | /// 1700 | /// See `std::env::current_dir` for an explanation of the cases in which 1701 | /// this operation can fail. 1702 | pub fn set_work_dir_cwd(&mut self) -> Result<&mut Self, Error> { 1703 | Ok(self.set_work_dir( 1704 | std::env::current_dir()? 1705 | .to_str() 1706 | .ok_or(format_err!("could not express CWD as UTF8"))?, 1707 | )) 1708 | } 1709 | } 1710 | 1711 | impl Drop for JobDescriptorOwned { 1712 | fn drop(&mut self) { 1713 | self.maybe_clear_argv(); 1714 | self.maybe_clear_environment(); 1715 | 1716 | { 1717 | let d = self.sys_data_mut(); 1718 | slurm_free(&mut d.name); 1719 | slurm_free(&mut d.partition); 1720 | slurm_free(&mut d.script); 1721 | slurm_free(&mut d.std_err); 1722 | slurm_free(&mut d.std_in); 1723 | slurm_free(&mut d.std_out); 1724 | slurm_free(&mut d.work_dir); 1725 | } 1726 | 1727 | slurm_free(&mut (self.0).0); 1728 | } 1729 | } 1730 | 1731 | make_slurm_wrap_struct!( 1732 | SubmitResponseMessage, 1733 | slurm_sys::submit_response_msg, 1734 | "\ 1735 | Information returned by Slurm upon job submission. 1736 | " 1737 | ); 1738 | 1739 | impl SubmitResponseMessage { 1740 | /// Get the job ID of the new job. 1741 | /// 1742 | /// XXX: It looks like it is possible to have a non-zero `error_code` with 1743 | /// a non-zero job ID; I'm not sure in what cases that occurs. 1744 | pub fn job_id(&self) -> JobId { 1745 | self.sys_data().job_id 1746 | } 1747 | 1748 | /// Get the job-step ID of the new job. 1749 | /// 1750 | /// XXX: It looks like it is possible to have a non-zero `error_code` with 1751 | /// a non-zero job ID; I'm not sure in what cases that occurs. 1752 | pub fn step_id(&self) -> StepId { 1753 | self.sys_data().step_id 1754 | } 1755 | 1756 | /// Get the error code returned by the server. 1757 | pub fn error_code(&self) -> u32 { 1758 | self.sys_data().error_code 1759 | } 1760 | 1761 | /// Get the "user message" returned by the server. 1762 | /// 1763 | /// I think this is arbitrary text that should be shown to the user? 1764 | /// 1765 | /// This feature is not available in older versions of Slurm. (TBD: quantify). 1766 | #[cfg(slurm_api_submit_response_user_message)] 1767 | pub fn user_message(&self) -> Cow { 1768 | unsafe { CStr::from_ptr(self.sys_data().job_submit_user_msg) }.to_string_lossy() 1769 | } 1770 | } 1771 | 1772 | make_owned_version!(@customdrop SubmitResponseMessage, SubmitResponseMessageOwned, "An owned version of `SubmitResponseMessage`."); 1773 | 1774 | impl Drop for SubmitResponseMessageOwned { 1775 | fn drop(&mut self) { 1776 | unsafe { slurm_sys::slurm_free_submit_response_response_msg((self.0).0 as _) }; 1777 | } 1778 | } 1779 | -------------------------------------------------------------------------------- /src/colorio.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams 2 | // Licensed under the MIT License. 3 | 4 | /*! Colorized CLI output. 5 | 6 | There are a few common colorized output styles that we use. 7 | 8 | */ 9 | 10 | use failure::Error; 11 | use std::fmt; 12 | use std::io::Write; 13 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 14 | 15 | /// How to style some text to print. 16 | /// 17 | /// Instead of using this type directly, use the `cprint!` family of macros. 18 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 19 | pub enum Style { 20 | /// Style some text with a green color. 21 | Green, 22 | 23 | /// Style some text with a bold, bright color. 24 | Highlight, 25 | 26 | /// Style some text in the standard plain way. 27 | Plain, 28 | 29 | /// Style some text with a red color. 30 | Red, 31 | 32 | /// Style some text with a yellow color. 33 | Yellow, 34 | } 35 | 36 | /// How to style some text to print. 37 | /// 38 | /// Instead of using this type directly, use the `cprint!` family of macros. 39 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 40 | pub enum Stream { 41 | /// Print to standard error. 42 | Stderr, 43 | 44 | /// Print to standard output. 45 | Stdout, 46 | } 47 | 48 | macro_rules! cprint { 49 | ($cio:expr, green, $($fmt_args:expr),*) => {{ 50 | use $crate::colorio::{Stream, Style}; 51 | $cio.print_core(Stream::Stdout, Style::Green, format_args!($($fmt_args),*)) 52 | }}; 53 | 54 | ($cio:expr, hl, $($fmt_args:expr),*) => {{ 55 | use $crate::colorio::{Stream, Style}; 56 | $cio.print_core(Stream::Stdout, Style::Highlight, format_args!($($fmt_args),*)) 57 | }}; 58 | 59 | ($cio:expr, pl, $($fmt_args:expr),*) => {{ 60 | use $crate::colorio::{Stream, Style}; 61 | $cio.print_core(Stream::Stdout, Style::Plain, format_args!($($fmt_args),*)) 62 | }}; 63 | 64 | ($cio:expr, red, $($fmt_args:expr),*) => {{ 65 | use $crate::colorio::{Stream, Style}; 66 | $cio.print_core(Stream::Stdout, Style::Red, format_args!($($fmt_args),*)) 67 | }}; 68 | 69 | ($cio:expr, yellow, $($fmt_args:expr),*) => {{ 70 | use $crate::colorio::{Stream, Style}; 71 | $cio.print_core(Stream::Stdout, Style::Yellow, format_args!($($fmt_args),*)) 72 | }}; 73 | } 74 | 75 | macro_rules! cprintln { 76 | ($cio:expr, $style:ident, $($fmt_args:expr),*) => { 77 | cprint!($cio, $style, $($fmt_args),*); 78 | cprint!($cio, pl, "\n"); 79 | }; 80 | } 81 | 82 | macro_rules! ecprint { 83 | ($cio:expr, green, $($fmt_args:expr),*) => {{ 84 | use $crate::colorio::{Stream, Style}; 85 | $cio.print_core(Stream::Stderr, Style::Green, format_args!($($fmt_args),*)) 86 | }}; 87 | 88 | ($cio:expr, hl, $($fmt_args:expr),*) => {{ 89 | use $crate::colorio::{Stream, Style}; 90 | $cio.print_core(Stream::Stderr, Style::Highlight, format_args!($($fmt_args),*)) 91 | }}; 92 | 93 | ($cio:expr, pl, $($fmt_args:expr),*) => {{ 94 | use $crate::colorio::{Stream, Style}; 95 | $cio.print_core(Stream::Stderr, Style::Plain, format_args!($($fmt_args),*)) 96 | }}; 97 | 98 | ($cio:expr, red, $($fmt_args:expr),*) => {{ 99 | use $crate::colorio::{Stream, Style}; 100 | $cio.print_core(Stream::Stderr, Style::Red, format_args!($($fmt_args),*)) 101 | }}; 102 | 103 | ($cio:expr, yellow, $($fmt_args:expr),*) => {{ 104 | use $crate::colorio::{Stream, Style}; 105 | $cio.print_core(Stream::Stderr, Style::Yellow, format_args!($($fmt_args),*)) 106 | }}; 107 | } 108 | 109 | macro_rules! ecprintln { 110 | ($cio:expr, $style:ident, $($fmt_args:expr),*) => { 111 | ecprint!($cio, $style, $($fmt_args),*); 112 | ecprint!($cio, pl, "\n"); 113 | }; 114 | } 115 | 116 | /// State needed for our colorized I/O. 117 | pub struct ColorIo { 118 | stdout: StandardStream, 119 | stderr: StandardStream, 120 | red: ColorSpec, 121 | green: ColorSpec, 122 | highlight: ColorSpec, 123 | yellow: ColorSpec, 124 | } 125 | 126 | impl ColorIo { 127 | pub fn new() -> Self { 128 | let stdout = StandardStream::stdout(ColorChoice::Auto); 129 | let stderr = StandardStream::stderr(ColorChoice::Auto); 130 | 131 | let mut green = ColorSpec::new(); 132 | green.set_fg(Some(Color::Green)).set_bold(true); 133 | 134 | let mut highlight = ColorSpec::new(); 135 | highlight.set_bold(true); 136 | 137 | let mut red = ColorSpec::new(); 138 | red.set_fg(Some(Color::Red)).set_bold(true); 139 | 140 | let mut yellow = ColorSpec::new(); 141 | yellow.set_fg(Some(Color::Yellow)).set_bold(true); 142 | 143 | ColorIo { 144 | stdout, 145 | stderr, 146 | green, 147 | highlight, 148 | red, 149 | yellow, 150 | } 151 | } 152 | 153 | pub fn print_error(&mut self, err: Error) { 154 | let mut first = true; 155 | 156 | for cause in err.iter_chain() { 157 | if first { 158 | ecprint!(self, red, "error:"); 159 | ecprintln!(self, pl, " {}", cause); 160 | first = false; 161 | } else { 162 | ecprint!(self, pl, " "); 163 | ecprint!(self, red, "caused by:"); 164 | ecprintln!(self, pl, " {}", cause); 165 | } 166 | } 167 | } 168 | 169 | /// Print formatted arguments to the standard output stream. 170 | /// 171 | /// Use the `println_*!` macros instead of this function. 172 | #[inline(always)] 173 | pub fn print_core(&mut self, stream: Stream, style: Style, args: fmt::Arguments) { 174 | let stream = match stream { 175 | Stream::Stderr => &mut self.stderr, 176 | Stream::Stdout => &mut self.stdout, 177 | }; 178 | 179 | match style { 180 | Style::Green => { 181 | let _r = stream.set_color(&self.green); 182 | } 183 | 184 | Style::Highlight => { 185 | let _r = stream.set_color(&self.highlight); 186 | } 187 | 188 | Style::Plain => {} 189 | 190 | Style::Red => { 191 | let _r = stream.set_color(&self.red); 192 | } 193 | 194 | Style::Yellow => { 195 | let _r = stream.set_color(&self.yellow); 196 | } 197 | } 198 | 199 | let _r = write!(stream, "{}", args); 200 | 201 | match style { 202 | Style::Green | Style::Highlight | Style::Red | Style::Yellow => { 203 | let _r = stream.reset(); 204 | } 205 | 206 | Style::Plain => {} 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams 2 | // Licensed under the MIT License. 3 | 4 | //! The main CLI driver logic. 5 | 6 | extern crate chrono; 7 | extern crate failure; 8 | extern crate itertools; 9 | extern crate slurm; 10 | #[macro_use] 11 | extern crate structopt; 12 | extern crate termcolor; 13 | extern crate users; 14 | 15 | use failure::Error; 16 | use std::process; 17 | use structopt::StructOpt; 18 | 19 | #[macro_use] 20 | mod colorio; // keep first to get macros 21 | mod recent; 22 | mod status; 23 | mod util; 24 | 25 | use colorio::ColorIo; 26 | 27 | #[derive(Debug, StructOpt)] 28 | #[structopt( 29 | name = "slurmplus", 30 | about = "Better commands for interacting with Slurm." 31 | )] 32 | enum SlurmPlusCli { 33 | #[structopt(name = "recent")] 34 | /// Summarize recently-run jobs 35 | Recent(recent::RecentCommand), 36 | 37 | #[structopt(name = "status")] 38 | /// Get the status of a job 39 | Status(status::StatusCommand), 40 | } 41 | 42 | impl SlurmPlusCli { 43 | fn cli(self, cio: &mut ColorIo) -> Result { 44 | match self { 45 | SlurmPlusCli::Recent(cmd) => cmd.cli(cio), 46 | SlurmPlusCli::Status(cmd) => cmd.cli(cio), 47 | } 48 | } 49 | } 50 | 51 | fn main() { 52 | let program = SlurmPlusCli::from_args(); 53 | let mut cio = ColorIo::new(); 54 | 55 | process::exit(match program.cli(&mut cio) { 56 | Ok(code) => code, 57 | 58 | Err(e) => { 59 | cio.print_error(e); 60 | 1 61 | } 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/recent.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Make a list of recent jobs belonging to this user. 5 | */ 6 | 7 | use chrono::{DateTime, Duration, Utc}; 8 | use colorio::ColorIo; 9 | use failure::Error; 10 | use itertools::Itertools; 11 | use slurm::{self, JobState, JobStepRecordSharedFields}; 12 | use std::cmp; 13 | use std::collections::HashMap; 14 | use users; 15 | use util; 16 | 17 | #[derive(Debug, StructOpt)] 18 | pub struct RecentCommand { 19 | #[structopt(short = "s", long = "span", default_value = "7")] 20 | /// How many days back to query the accounting database. 21 | span_days: usize, 22 | 23 | #[structopt(short = "l", long = "limit", default_value = "30")] 24 | /// Limit the output to at most this number of recent jobs. 25 | limit: usize, 26 | } 27 | 28 | impl RecentCommand { 29 | pub fn cli(self, cio: &mut ColorIo) -> Result { 30 | let now = Utc::now(); 31 | let min_start = now - Duration::days(self.span_days as i64); 32 | 33 | // Note that the userid we have to filter on must be a string 34 | // representation of the numeric UID. 35 | let uid = users::get_current_uid(); 36 | let mut filter = slurm::JobFiltersOwned::default(); 37 | filter.userid_list_mut().append(format!("{}", uid)); 38 | filter.usage_start(min_start); 39 | 40 | let mut grouped = HashMap::new(); 41 | let db = slurm::DatabaseConnectionOwned::new()?; 42 | let jobs = db.get_jobs(&filter)?; 43 | let mut max_name_len = 0; 44 | let mut max_time_len = 0; 45 | 46 | for job in jobs.iter() { 47 | let group_id = job.group_id(); 48 | let group_info = grouped.entry(group_id).or_insert_with(|| { 49 | let info = JobGroupInfo::new(&job, &now); 50 | max_name_len = cmp::max(max_name_len, info.name.len()); 51 | max_time_len = cmp::max(max_time_len, info.submit_text.len()); 52 | info 53 | }); 54 | group_info.accumulate(&job); 55 | } 56 | 57 | let skip = if grouped.len() < self.limit { 58 | 0 59 | } else { 60 | grouped.len() - self.limit 61 | }; 62 | 63 | for group_info in grouped 64 | .values() 65 | .sorted_by_key(|gi| gi.submit_time) 66 | .skip(skip) 67 | { 68 | group_info.emit(cio, max_name_len, max_time_len); 69 | } 70 | 71 | Ok(0) 72 | } 73 | } 74 | 75 | trait JobRecordExt { 76 | fn group_id(&self) -> slurm::JobId; 77 | } 78 | 79 | impl JobRecordExt for slurm::JobRecord { 80 | fn group_id(&self) -> slurm::JobId { 81 | self.array_job_id().unwrap_or_else(|| self.job_id()) 82 | } 83 | } 84 | 85 | struct JobGroupInfo { 86 | id: slurm::JobId, 87 | name: String, 88 | submit_time: DateTime, 89 | submit_text: String, 90 | n_jobs: usize, 91 | states: HashMap, 92 | } 93 | 94 | impl JobGroupInfo { 95 | pub fn new(job: &slurm::JobRecord, now: &DateTime) -> Self { 96 | let submit_time = job.submit_time(); 97 | let submit_text = util::dur_to_text(&now.signed_duration_since(submit_time)); 98 | 99 | JobGroupInfo { 100 | id: job.group_id(), 101 | name: job.job_name().into_owned(), 102 | submit_time, 103 | submit_text, 104 | n_jobs: 0, 105 | states: HashMap::new(), 106 | } 107 | } 108 | 109 | pub fn accumulate(&mut self, job: &slurm::JobRecord) { 110 | self.n_jobs += 1; 111 | let slot = self.states.entry(job.state()).or_insert(0); 112 | *slot += 1; 113 | } 114 | 115 | pub fn emit(&self, cio: &mut ColorIo, max_name_len: usize, max_time_len: usize) { 116 | cprint!(cio, hl, "{}", self.id); 117 | cprint!(cio, pl, " {1:0$}", max_name_len, self.name); 118 | 119 | let stext = format!("{} ago", self.submit_text); 120 | cprint!(cio, pl, " {1:0$} ", max_time_len + 4, stext); 121 | 122 | if self.n_jobs == 1 { 123 | let state = self.states.keys().next().unwrap(); 124 | cprint!(cio, pl, " "); 125 | util::colorize_state(cio, *state); 126 | cprintln!(cio, pl, ""); 127 | } else { 128 | let seen_states = self.states.keys().sorted(); 129 | let mut first = true; 130 | 131 | for state in seen_states { 132 | if first { 133 | first = false; 134 | } else { 135 | cprint!(cio, pl, ","); 136 | } 137 | 138 | cprint!(cio, pl, " {} ", self.states.get(state).unwrap()); 139 | util::colorize_state(cio, *state); 140 | } 141 | 142 | cprintln!(cio, pl, " ({} total)", self.n_jobs); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/status.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Query the status of a job. 5 | 6 | This command is most similar to Slurm's `sacct` command. It works with both 7 | running and completed jobs. 8 | 9 | */ 10 | 11 | use chrono::{Duration, Utc}; 12 | use colorio::ColorIo; 13 | use failure::Error; 14 | use slurm::{self, JobStepRecordSharedFields}; 15 | use util; 16 | 17 | #[derive(Debug, StructOpt)] 18 | pub struct StatusCommand { 19 | #[structopt(help = "The ID of the job to query.")] 20 | jobid: slurm::JobId, 21 | } 22 | 23 | impl StatusCommand { 24 | pub fn cli(self, cio: &mut ColorIo) -> Result { 25 | let mut filter = slurm::JobFiltersOwned::default(); 26 | filter 27 | .step_list_mut() 28 | .append(slurm::JobStepFilterOwned::new(self.jobid)); 29 | 30 | let db = slurm::DatabaseConnectionOwned::new()?; 31 | let jobs = db.get_jobs(&filter)?; 32 | let now = Utc::now(); 33 | 34 | for job in jobs.iter() { 35 | cprint!(cio, hl, "{}", job.job_id()); 36 | cprint!(cio, pl, " {} ", job.job_name()); 37 | util::colorize_state(cio, job.state()); 38 | cprintln!(cio, pl, ""); 39 | 40 | if let Some(d) = job.eligible_wait_duration() { 41 | cprintln!( 42 | cio, 43 | pl, 44 | " time for job to become eligible to run: {} s", 45 | d.num_seconds() 46 | ); 47 | } else { 48 | let wait = now.signed_duration_since(job.submit_time()); 49 | cprintln!( 50 | cio, 51 | pl, 52 | " job not yet eligible to run; time since submission: {} s", 53 | wait.num_seconds() 54 | ); 55 | continue; 56 | } 57 | 58 | if let Some(d) = job.wait_duration() { 59 | cprintln!( 60 | cio, 61 | pl, 62 | " wait time after eligibility: {} s", 63 | d.num_seconds() 64 | ); 65 | } else if let Some(t_el) = job.eligible_time() { 66 | let wait = now.signed_duration_since(t_el); 67 | cprintln!( 68 | cio, 69 | pl, 70 | " job not yet started; time since eligibility: {} s", 71 | wait.num_seconds() 72 | ); 73 | continue; 74 | } 75 | 76 | if let Some(t_st) = job.start_time() { 77 | let t_limit = t_st + Duration::minutes(job.time_limit() as i64); 78 | let remaining = t_limit.signed_duration_since(now).num_minutes(); 79 | if remaining > 0 { 80 | cprintln!( 81 | cio, 82 | pl, 83 | " time left until job hits time limit: {} min", 84 | remaining 85 | ); 86 | } 87 | } 88 | 89 | for step in job.steps().iter() { 90 | cprint!(cio, hl, " step {}", step.step_id()); 91 | cprintln!(cio, pl, " {}", step.step_name()); 92 | 93 | if let Some(d) = step.wallclock_duration() { 94 | cprintln!(cio, pl, " wallclock runtime: {} s", d.num_seconds()); 95 | cprintln!(cio, pl, " exit code: {}", step.exit_code().unwrap()); 96 | } else if let Some(t_st) = step.start_time() { 97 | let wait = now.signed_duration_since(t_st); 98 | cprintln!( 99 | cio, 100 | pl, 101 | " step not yet finished; time since start: {} s", 102 | wait.num_seconds() 103 | ); 104 | } else { 105 | cprintln!(cio, pl, " step not yet finished"); 106 | } 107 | 108 | if let Some(b) = step.max_vm_size() { 109 | cprintln!(cio, pl, " max VM size: {:.2} MiB", (b as f64) / 1024.); 110 | } else { 111 | cprintln!( 112 | cio, 113 | pl, 114 | " max VM size not available (probably because step not finished)" 115 | ); 116 | } 117 | } 118 | } 119 | 120 | Ok(0) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Peter Williams and collaborators 2 | // Licensed under the MIT License 3 | 4 | /*! Miscellaneous utility functions. 5 | 6 | */ 7 | 8 | use chrono::Duration; 9 | use colorio::ColorIo; 10 | use slurm::JobState; 11 | 12 | /// Print out a shortcode for a job state with affective color. 13 | pub fn colorize_state(cio: &mut ColorIo, state: JobState) { 14 | match state { 15 | JobState::Pending => { 16 | cprint!(cio, pl, "{}", state.shortcode()); 17 | } 18 | 19 | JobState::Running => { 20 | cprint!(cio, hl, "{}", state.shortcode()); 21 | } 22 | 23 | JobState::Complete => { 24 | cprint!(cio, green, "{}", state.shortcode()); 25 | } 26 | 27 | JobState::Cancelled 28 | | JobState::Failed 29 | | JobState::NodeFail 30 | | JobState::BootFail 31 | | JobState::Deadline 32 | | JobState::OutOfMemory => { 33 | cprint!(cio, red, "{}", state.shortcode()); 34 | } 35 | 36 | JobState::Suspended | JobState::Timeout | JobState::Preempted => { 37 | cprint!(cio, yellow, "{}", state.shortcode()); 38 | } 39 | } 40 | } 41 | 42 | /// Express a duration in text, approximately. 43 | pub fn dur_to_text(dur: &Duration) -> String { 44 | if dur.num_days() > 2 { 45 | format!("{} days", dur.num_days()) 46 | } else if dur.num_hours() > 2 { 47 | format!("{} hours", dur.num_hours()) 48 | } else if dur.num_minutes() > 2 { 49 | format!("{} minutes", dur.num_minutes()) 50 | } else { 51 | format!("{} seconds", dur.num_seconds()) 52 | } 53 | } 54 | --------------------------------------------------------------------------------