diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 3ea08526..00000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -target/ -.git/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee4ff037..b4c70155 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,35 +90,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - test_alpine: - name: Test in Alpine - runs-on: ubuntu-latest - container: - image: alpine:latest - steps: - - uses: actions/checkout@master - - run: apk add libgcc gcc musl-dev - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - # Caching: - - name: Cache cargo registry - uses: actions/cache@v3 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@v3 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test cross_builds: name: Cross-build diff --git a/Cargo.lock b/Cargo.lock index d270fd26..3683d113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,239 +11,18 @@ dependencies = [ "memchr", ] -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "cc" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" -dependencies = [ - "jobserver", -] - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time 0.1.45", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "clap" -version = "4.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" -dependencies = [ - "bitflags", - "clap_derive", - "clap_lex", - "is-terminal", - "once_cell", - "strsim", - "termcolor", -] - -[[package]] -name = "clap_derive" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - -[[package]] -name = "const_format" -version = "0.2.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" -dependencies = [ - "winapi", -] - -[[package]] -name = "cxx" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "dns-lookup" version = "1.0.8" @@ -256,182 +35,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "git2" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", -] - -[[package]] -name = "gping" -version = "1.8.0" -dependencies = [ - "anyhow", - "chrono", - "clap", - "const_format", - "crossterm 0.26.1", - "dns-lookup", - "itertools", - "pinger", - "read_color", - "shadow-rs", - "tui", -] - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] - -[[package]] -name = "is_debug" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" - -[[package]] -name = "jobserver" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -444,55 +47,6 @@ version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" -[[package]] -name = "libgit2-sys" -version = "0.14.2+1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -508,52 +62,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" - [[package]] name = "os_info" version = "3.6.0" @@ -565,41 +73,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - [[package]] name = "pinger" version = "0.8.0" @@ -613,36 +86,6 @@ dependencies = [ "winping", ] -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.50" @@ -661,21 +104,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "read_color" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f4c8858baa4ad3c8bcc156ae91a0ffe22b76a3975c40c49b4f04c15c6bce0da" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.7.1" @@ -693,32 +121,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "rustix" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" - [[package]] name = "serde" version = "1.0.152" @@ -739,55 +141,6 @@ dependencies = [ "syn", ] -[[package]] -name = "shadow-rs" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942b9991e2cfab3d5059e508d517c66bc460b302a2a8187a90d0aae4f08531d" -dependencies = [ - "const_format", - "git2", - "is_debug", - "time 0.3.17", - "tzdb", -] - -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - [[package]] name = "socket2" version = "0.4.7" @@ -804,12 +157,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "syn" version = "1.0.107" @@ -821,15 +168,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.38" @@ -850,221 +188,12 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" -dependencies = [ - "itoa", - "libc", - "num_threads", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" -dependencies = [ - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tui" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" -dependencies = [ - "bitflags", - "cassowary", - "crossterm 0.25.0", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "tz-rs" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" -dependencies = [ - "const_fn", -] - -[[package]] -name = "tzdb" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b882d864be6a5d7c3c916719944458b1e03c85f86dbc825ec98155117c4408" -dependencies = [ - "iana-time-zone", - "tz-rs", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" - [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - [[package]] name = "winapi" version = "0.3.9" @@ -1081,15 +210,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1106,63 +226,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - [[package]] name = "winping" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 71146e25..65c24c8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ - "gping", "pinger" ] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4dce996a..00000000 --- a/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM rust as builder -WORKDIR /usr/src/gping -COPY src/ src/ -COPY Cargo.* ./ -RUN cargo install --path . - -FROM debian:buster-slim -RUN apt-get update && apt-get install -y inetutils-ping && rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/local/cargo/bin/gping /usr/local/bin/gping -ENTRYPOINT ["gping"] diff --git a/gping.1 b/gping.1 deleted file mode 100644 index 5f02998a..00000000 --- a/gping.1 +++ /dev/null @@ -1,103 +0,0 @@ -.\" Automatically generated by Pandoc 2.17.1.1 -.\" -.\" Define V font for inline verbatim, using C font in formats -.\" that render this, and otherwise B font. -.ie "\f[CB]x\f[]"x" \{\ -. ftr V B -. ftr VI BI -. ftr VB B -. ftr VBI BI -.\} -.el \{\ -. ftr V CR -. ftr VI CI -. ftr VB CB -. ftr VBI CBI -.\} -.TH "gping" "utils" "\[lq]January 11 2023\[rq]" "" "User Commands" -.hy -.SH NAME -.PP -gping - ping(1), but with a graph -.SH SYNOPSIS -.PP -\f[B]gping\f[R] \f[B]OPTIONS\f[R] \f[B][HOSTS]\f[R] -.PP -\f[B]gping\f[R] ccc.de -.PP -\f[B]gping\f[R] \f[B]-s\f[R] \f[B]-b 20\f[R] ccc.de -.SH DESCRIPTION -.PP -This manual page documents briefly the \f[B]gping\f[R] commmand. -.PP -This manual page was written for the Debian distribution because the -original program does not have a manual page. -.PP -\f[B]gping\f[R] is essentially ping(1), but with a TUI graph. -.SH OPTIONS -.TP -\f[B]--cmd\f[R] -Graph the execution time for a list of commands rather than pinging -hosts -.TP -\f[B]-h\f[R], \f[B]--help\f[R] -Show summary of options. -.TP -\f[B]-V\f[R], \f[B]--version\f[R] -Print version information -.TP -\f[B]-n\f[R], \f[B]--watch-interval\f[R] -Watch interval seconds (provide partial seconds like `0.5'). -Default for ping is 0.2, -default for cmd is 0.5. -.TP -\f[B]-b\f[R], \f[B]--buffer\f[R] -Determines the number of seconds to display in the graph. -[default: 30] -.TP -\f[B]-4\f[R] -Resolve ping targets to IPv4 address -.TP -\f[B]-6\f[R] -Resolve ping targets to IPv6 address -.TP -\f[B]-i\f[R], \f[B]--interface\f[R] -Interface to use when pinging. -?? -.TP -\f[B]-s\f[R], \f[B]--simple-graphics\f[R] -Uses dot characters instead of braille -.TP -\f[B]--vertical-margin\f[R] -Vertical margin around the graph (top and bottom) [default: 1] -.TP -\f[B]--horizontal-margin\f[R] -Horizontal margin around the graph (left and right) [default: 0] -.TP -\f[B]-c\f[R], \f[B]\[en]color\f[R] -Assign color to a graph entry. -This option can be defined more than once as a comma separated string, -and the order which the colors are provided will be matched against the -hosts or commands passed to gping. -Hexadecimal RGB color codes are accepted in the form of `#RRGGBB' or the -following color names: `black', `red', `green', `yellow', `blue', -`magenta',`cyan', `gray', `dark-gray', `light-red', `light-green', -`light-yellow', -`light-blue', `light-magenta', `light-cyan', and `white' -.SH AUTHOR -.TP -Matthias Geiger -Wrote this manpage for the Debian system. -.SH COPYRIGHT -.PP -Copyright \[co] 2023 Matthias Geiger -.PP -This manual page was written for the Debian system (and may be used by -others). -.PP -Permission is granted to copy, distribute and/or modify this document -under the terms of the GNU General Public License, Version 2 or (at your -option) any later version published by the Free Software Foundation. -.PP -On Debian systems, the complete text of the GNU General Public License -can be found in /usr/share/common-licenses/GPL. diff --git a/gping/Cargo.toml b/gping/Cargo.toml deleted file mode 100644 index f5813d74..00000000 --- a/gping/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "gping" -version = "1.8.0" -authors = ["Tom Forbes "] -edition = "2018" -repository = "https://github.com/orf/gping" -license = "MIT" -description = "Ping, but with a graph." -build = "build.rs" -readme = "../readme.md" - -[dependencies] -pinger = { version = "^0.8.0", path = "../pinger" } -tui = { version = "0.19.0", features = ["crossterm"], default_features = false } -crossterm = "0.26.1" -anyhow = "1.0.69" -dns-lookup = "1.0.8" -chrono = "0.4.23" -itertools = "0.10.5" -shadow-rs = "0.20.1" -const_format = "0.2.30" -read_color = "1.0.0" -clap = { version = "4.1.6", features = ["derive"] } - -[build-dependencies] -shadow-rs = "0.20.1" diff --git a/gping/build.rs b/gping/build.rs deleted file mode 100644 index 4a0dfc45..00000000 --- a/gping/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> shadow_rs::SdResult<()> { - shadow_rs::new() -} diff --git a/gping/src/colors.rs b/gping/src/colors.rs deleted file mode 100644 index ff6f637b..00000000 --- a/gping/src/colors.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{iter::Iterator, ops::RangeFrom}; - -use anyhow::{anyhow, Result}; -use read_color::rgb; -use tui::style::Color; - -pub struct Colors { - already_used: Vec, - color_names: T, - indices: RangeFrom, -} - -impl From for Colors { - fn from(color_names: T) -> Self { - Self { - already_used: Vec::new(), - color_names, - indices: 2.., - } - } -} - -impl<'a, T> Iterator for Colors -where - T: Iterator, -{ - type Item = Result; - - fn next(&mut self) -> Option { - match self.color_names.next() { - Some(name) => match try_color_from_string(name) { - Ok(color) => { - if !self.already_used.contains(&color) { - self.already_used.push(color); - } - Some(Ok(color)) - } - error => Some(error), - }, - None => loop { - let index = unsafe { self.indices.next().unwrap_unchecked() }; - let color = Color::Indexed(index); - if !self.already_used.contains(&color) { - self.already_used.push(color); - break Some(Ok(color)); - } - }, - } - } -} - -fn try_color_from_string(string: &str) -> Result { - let mut characters = string.chars(); - - let color = if let Some('#') = characters.next() { - match rgb(&mut characters) { - Some([r, g, b]) => Color::Rgb(r, g, b), - None => return Err(anyhow!("Invalid color code: `{}`", string)), - } - } else { - use Color::*; - match string.to_lowercase().as_str() { - "black" => Black, - "red" => Red, - "green" => Green, - "yellow" => Yellow, - "blue" => Blue, - "magenta" => Magenta, - "cyan" => Cyan, - "gray" => Gray, - "dark-gray" => DarkGray, - "light-red" => LightRed, - "light-green" => LightGreen, - "light-yellow" => LightYellow, - "light-blue" => LightBlue, - "light-magenta" => LightMagenta, - "light-cyan" => LightCyan, - "white" => White, - invalid => return Err(anyhow!("Invalid color name: `{}`", invalid)), - } - }; - - Ok(color) -} diff --git a/gping/src/lib.rs b/gping/src/lib.rs deleted file mode 100644 index 19323ea3..00000000 --- a/gping/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod region_map; diff --git a/gping/src/main.rs b/gping/src/main.rs deleted file mode 100644 index f645423b..00000000 --- a/gping/src/main.rs +++ /dev/null @@ -1,540 +0,0 @@ -use crate::plot_data::PlotData; -use anyhow::{anyhow, Result}; -use chrono::prelude::*; -use clap::Parser; -use crossterm::event::KeyModifiers; -use crossterm::{ - event::{self, Event as CEvent, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, SetSize}, -}; -use dns_lookup::lookup_host; -use pinger::{ping_with_interval, PingResult}; -use std::io; -use std::io::BufWriter; -use std::iter; -use std::net::IpAddr; -use std::ops::Add; -use std::process::{Command, ExitStatus, Stdio}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::Sender; -use std::sync::{mpsc, Arc}; -use std::thread; -use std::thread::{sleep, JoinHandle}; -use std::time::{Duration, Instant}; -use tui::backend::{Backend, CrosstermBackend}; -use tui::layout::{Constraint, Direction, Layout}; -use tui::style::{Color, Style}; -use tui::text::Span; -use tui::widgets::{Axis, Block, Borders, Chart, Dataset}; -use tui::Terminal; - -mod colors; -mod plot_data; -mod region_map; - -use colors::Colors; -use shadow_rs::{formatcp, shadow}; - -shadow!(build); - -const VERSION_INFO: &str = formatcp!( - r#"{} -commit_hash: {} -build_time: {} -build_env: {},{}"#, - build::PKG_VERSION, - build::SHORT_COMMIT, - build::BUILD_TIME, - build::RUST_VERSION, - build::RUST_CHANNEL -); - -#[derive(Parser, Debug)] -#[command(author, name = "gping", about = "Ping, but with a graph.", version = VERSION_INFO)] -struct Args { - #[arg( - long, - help = "Graph the execution time for a list of commands rather than pinging hosts" - )] - cmd: bool, - #[arg( - short = 'n', - long, - help = "Watch interval seconds (provide partial seconds like '0.5'). Default for ping is 0.2, default for cmd is 0.5." - )] - watch_interval: Option, - #[arg( - help = "Hosts or IPs to ping, or commands to run if --cmd is provided. Can use cloud shorthands like aws:eu-west-1." - )] - hosts_or_commands: Vec, - #[arg( - short, - long, - default_value = "30", - help = "Determines the number of seconds to display in the graph." - )] - buffer: u64, - /// Resolve ping targets to IPv4 address - #[arg(short = '4', conflicts_with = "ipv6")] - ipv4: bool, - /// Resolve ping targets to IPv6 address - #[arg(short = '6', conflicts_with = "ipv4")] - ipv6: bool, - /// Interface to use when pinging. - #[arg(short = 'i', long)] - interface: Option, - #[arg(short = 's', long, help = "Uses dot characters instead of braille")] - simple_graphics: bool, - #[arg( - long, - help = "Vertical margin around the graph (top and bottom)", - default_value = "1" - )] - vertical_margin: u16, - #[arg( - long, - help = "Horizontal margin around the graph (left and right)", - default_value = "0" - )] - horizontal_margin: u16, - #[arg( - name = "color", - short = 'c', - long = "color", - use_value_delimiter = true, - value_delimiter = ',', - help = "\ - Assign color to a graph entry. This option can be defined more than \ - once as a comma separated string, and the order which the colors are \ - provided will be matched against the hosts or commands passed to gping. \ - Hexadecimal RGB color codes are accepted in the form of '#RRGGBB' or the \ - following color names: 'black', 'red', 'green', 'yellow', 'blue', 'magenta',\ - 'cyan', 'gray', 'dark-gray', 'light-red', 'light-green', 'light-yellow', \ - 'light-blue', 'light-magenta', 'light-cyan', and 'white'\ - " - )] - color_codes_or_names: Vec, -} - -struct App { - data: Vec, - display_interval: chrono::Duration, - started: chrono::DateTime, -} - -impl App { - fn new(data: Vec, buffer: u64) -> Self { - App { - data, - display_interval: chrono::Duration::from_std(Duration::from_secs(buffer)).unwrap(), - started: Local::now(), - } - } - - fn update(&mut self, host_idx: usize, item: Option) { - let host = &mut self.data[host_idx]; - host.update(item); - } - - fn y_axis_bounds(&self) -> [f64; 2] { - // Find the Y axis bounds for our chart. - // This is trickier than the x-axis. We iterate through all our PlotData structs - // and find the min/max of all the values. Then we add a 10% buffer to them. - let iter = self - .data - .iter() - .flat_map(|b| b.data.as_slice()) - .map(|v| v.1); - let min = iter.clone().fold(f64::INFINITY, |a, b| a.min(b)); - let max = iter.fold(0f64, |a, b| a.max(b)); - // Add a 10% buffer to the top and bottom - let max_10_percent = (max * 10_f64) / 100_f64; - let min_10_percent = (min * 10_f64) / 100_f64; - [min - min_10_percent, max + max_10_percent] - } - - fn x_axis_bounds(&self) -> [f64; 2] { - let now = Local::now(); - let now_idx; - let before_idx; - if (now - self.started) < self.display_interval { - now_idx = (self.started + self.display_interval).timestamp_millis() as f64 / 1_000f64; - before_idx = self.started.timestamp_millis() as f64 / 1_000f64; - } else { - now_idx = now.timestamp_millis() as f64 / 1_000f64; - let before = now - self.display_interval; - before_idx = before.timestamp_millis() as f64 / 1_000f64; - } - - [before_idx, now_idx] - } - - fn x_axis_labels(&self, bounds: [f64; 2]) -> Vec { - let lower_utc = NaiveDateTime::from_timestamp_opt(bounds[0] as i64, 0) - .expect("Error parsing x-axis bounds 0"); - let upper_utc = NaiveDateTime::from_timestamp_opt(bounds[1] as i64, 0) - .expect("Error parsing x-asis bounds 1"); - let lower = Local::from_utc_datetime(&Local, &lower_utc); - let upper = Local::from_utc_datetime(&Local, &upper_utc); - let diff = (upper - lower) / 2; - let midpoint = lower + diff; - vec![ - Span::raw(format!("{:?}", lower.time())), - Span::raw(format!("{:?}", midpoint.time())), - Span::raw(format!("{:?}", upper.time())), - ] - } - - fn y_axis_labels(&self, bounds: [f64; 2]) -> Vec { - // Create 7 labels for our y axis, based on the y-axis bounds we computed above. - let min = bounds[0]; - let max = bounds[1]; - - let difference = max - min; - let num_labels = 7; - // Split difference into one chunk for each of the 7 labels - let increment = Duration::from_micros((difference / num_labels as f64) as u64); - let duration = Duration::from_micros(min as u64); - - (0..num_labels) - .map(|i| Span::raw(format!("{:?}", duration.add(increment * i)))) - .collect() - } -} - -#[derive(Debug)] -enum Update { - Result(Duration), - Timeout, - Unknown, - Terminated(ExitStatus, String), -} - -impl From for Update { - fn from(result: PingResult) -> Self { - match result { - PingResult::Pong(duration, _) => Update::Result(duration), - PingResult::Timeout(_) => Update::Timeout, - PingResult::Unknown(_) => Update::Unknown, - PingResult::PingExited(e, stderr) => Update::Terminated(e, stderr), - } - } -} - -#[derive(Debug)] -enum Event { - Update(usize, Update), - Terminate, - Render, -} - -fn start_render_thread( - kill_event: Arc, - cmd_tx: Sender, -) -> JoinHandle> { - thread::spawn(move || { - while !kill_event.load(Ordering::Acquire) { - sleep(Duration::from_millis(250)); - cmd_tx.send(Event::Render)?; - } - Ok(()) - }) -} - -fn start_cmd_thread( - watch_cmd: &str, - host_id: usize, - watch_interval: Option, - cmd_tx: Sender, - kill_event: Arc, -) -> JoinHandle> { - let mut words = watch_cmd.split_ascii_whitespace(); - let cmd = words - .next() - .expect("Must specify a command to watch") - .to_string(); - let cmd_args = words.map(|w| w.to_string()).collect::>(); - - let interval = Duration::from_millis((watch_interval.unwrap_or(0.5) * 1000.0) as u64); - - // Pump cmd watches into the queue - thread::spawn(move || -> Result<()> { - while !kill_event.load(Ordering::Acquire) { - let start = Instant::now(); - let mut child = Command::new(&cmd) - .args(&cmd_args) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn()?; - let status = child.wait()?; - let duration = start.elapsed(); - let update = if status.success() { - Update::Result(duration) - } else { - Update::Timeout - }; - cmd_tx.send(Event::Update(host_id, update))?; - sleep(interval); - } - Ok(()) - }) -} - -fn start_ping_thread( - host: String, - host_id: usize, - watch_interval: Option, - ping_tx: Sender, - kill_event: Arc, - interface: Option, -) -> Result>> { - let interval = Duration::from_millis((watch_interval.unwrap_or(0.2) * 1000.0) as u64); - // Pump ping messages into the queue - let stream = ping_with_interval(host, interval, interface)?; - Ok(thread::spawn(move || -> Result<()> { - while !kill_event.load(Ordering::Acquire) { - match stream.recv() { - Ok(v) => { - ping_tx.send(Event::Update(host_id, v.into()))?; - } - Err(_) => { - // Stream closed, just break - return Ok(()); - } - } - } - Ok(()) - })) -} - -fn get_host_ipaddr(host: &str, force_ipv4: bool, force_ipv6: bool) -> Result { - let ipaddr: Vec = match lookup_host(host) { - Ok(ip) => ip, - Err(e) => return Err(anyhow!("Could not resolve hostname {}", host).context(e)), - }; - let ipaddr = if force_ipv4 { - ipaddr - .iter() - .find(|ip| matches!(ip, IpAddr::V4(_))) - .ok_or_else(|| anyhow!("Could not resolve '{}' to IPv4", host)) - } else if force_ipv6 { - ipaddr - .iter() - .find(|ip| matches!(ip, IpAddr::V6(_))) - .ok_or_else(|| anyhow!("Could not resolve '{}' to IPv6", host)) - } else { - ipaddr - .first() - .ok_or_else(|| anyhow!("Could not resolve '{}' to IP", host)) - }; - Ok(ipaddr?.to_string()) -} - -fn main() -> Result<()> { - let args: Args = Args::parse(); - - if args.hosts_or_commands.is_empty() { - return Err(anyhow!("At least one host or command must be given (i.e gping google.com). Use --help for a full list of arguments.")); - } - - let mut data = vec![]; - - let colors = Colors::from(args.color_codes_or_names.iter()); - let hosts_or_commands: Vec = args - .hosts_or_commands - .clone() - .into_iter() - .map(|s| match region_map::try_host_from_cloud_region(&s) { - None => s, - Some(new_domain) => new_domain, - }) - .collect(); - - for (host_or_cmd, color) in hosts_or_commands.iter().zip(colors) { - let color = color?; - let display = match args.cmd { - true => host_or_cmd.to_string(), - false => format!( - "{} ({})", - host_or_cmd, - get_host_ipaddr(host_or_cmd, args.ipv4, args.ipv6)? - ), - }; - data.push(PlotData::new( - display, - args.buffer, - Style::default().fg(color), - args.simple_graphics, - )); - } - - let (key_tx, rx) = mpsc::channel(); - - let mut threads = vec![]; - - let killed = Arc::new(AtomicBool::new(false)); - - for (host_id, host_or_cmd) in hosts_or_commands.iter().cloned().enumerate() { - if args.cmd { - let cmd_thread = start_cmd_thread( - &host_or_cmd, - host_id, - args.watch_interval, - key_tx.clone(), - std::sync::Arc::clone(&killed), - ); - threads.push(cmd_thread); - } else { - threads.push(start_ping_thread( - host_or_cmd, - host_id, - args.watch_interval, - key_tx.clone(), - std::sync::Arc::clone(&killed), - args.interface.clone(), - )?); - } - } - threads.push(start_render_thread( - std::sync::Arc::clone(&killed), - key_tx.clone(), - )); - - let mut app = App::new(data, args.buffer); - enable_raw_mode()?; - let stdout = io::stdout(); - let mut backend = CrosstermBackend::new(BufWriter::with_capacity(1024 * 1024 * 4, stdout)); - let rect = backend.size()?; - execute!(backend, SetSize(rect.width, rect.height),)?; - - let mut terminal = Terminal::new(backend)?; - terminal.clear()?; - - // Pump keyboard messages into the queue - let killed_thread = std::sync::Arc::clone(&killed); - thread::spawn(move || -> Result<()> { - while !killed_thread.load(Ordering::Acquire) { - if event::poll(Duration::from_secs(5))? { - if let CEvent::Key(key) = event::read()? { - match key.code { - KeyCode::Char('q') | KeyCode::Esc => { - key_tx.send(Event::Terminate)?; - break; - } - KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { - key_tx.send(Event::Terminate)?; - break; - } - _ => {} - } - } - } - } - Ok(()) - }); - - loop { - match rx.recv()? { - Event::Update(host_id, update) => { - match update { - Update::Result(duration) => app.update(host_id, Some(duration)), - Update::Timeout => app.update(host_id, None), - Update::Unknown => (), - Update::Terminated(e, _) if e.success() => { - break; - } - Update::Terminated(e, stderr) => { - eprintln!("There was an error running ping: {e}\nStderr: {stderr}\n"); - break; - } - }; - } - Event::Render => { - terminal.draw(|f| { - let chunks = Layout::default() - .direction(Direction::Vertical) - .vertical_margin(args.vertical_margin) - .horizontal_margin(args.horizontal_margin) - .constraints( - iter::repeat(Constraint::Length(1)) - .take(app.data.len()) - .chain(iter::once(Constraint::Percentage(10))) - .collect::>() - .as_ref(), - ) - .split(f.size()); - - let total_chunks = chunks.len(); - - let header_chunks = &chunks[0..total_chunks - 1]; - let chart_chunk = &chunks[total_chunks - 1]; - - for (plot_data, chunk) in app.data.iter().zip(header_chunks) { - let header_layout = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(30), - Constraint::Percentage(10), - Constraint::Percentage(10), - Constraint::Percentage(10), - Constraint::Percentage(10), - Constraint::Percentage(10), - Constraint::Percentage(10), - Constraint::Percentage(10), - ] - .as_ref(), - ) - .split(*chunk); - - for (area, paragraph) in - header_layout.into_iter().zip(plot_data.header_stats()) - { - f.render_widget(paragraph, area); - } - } - - let datasets: Vec = app.data.iter().map(|d| d.into()).collect(); - - let y_axis_bounds = app.y_axis_bounds(); - let x_axis_bounds = app.x_axis_bounds(); - - let chart = Chart::new(datasets) - .block(Block::default().borders(Borders::NONE)) - .x_axis( - Axis::default() - .style(Style::default().fg(Color::Gray)) - .bounds(x_axis_bounds) - .labels(app.x_axis_labels(x_axis_bounds)), - ) - .y_axis( - Axis::default() - .style(Style::default().fg(Color::Gray)) - .bounds(y_axis_bounds) - .labels(app.y_axis_labels(y_axis_bounds)), - ); - - f.render_widget(chart, *chart_chunk) - })?; - } - Event::Terminate => { - killed.store(true, Ordering::Release); - break; - } - } - } - killed.store(true, Ordering::Relaxed); - - disable_raw_mode()?; - execute!(terminal.backend_mut())?; - terminal.show_cursor()?; - - let new_size = terminal.size()?; - terminal.set_cursor(new_size.width, new_size.height)?; - for thread in threads { - thread.join().unwrap()?; - } - - Ok(()) -} diff --git a/gping/src/plot_data.rs b/gping/src/plot_data.rs deleted file mode 100644 index 6cf5e77e..00000000 --- a/gping/src/plot_data.rs +++ /dev/null @@ -1,110 +0,0 @@ -use chrono::prelude::*; -use core::option::Option; -use core::option::Option::{None, Some}; -use core::time::Duration; -use itertools::Itertools; -use tui::style::Style; -use tui::symbols; -use tui::widgets::{Dataset, GraphType, Paragraph}; - -pub struct PlotData { - pub display: String, - pub data: Vec<(f64, f64)>, - pub style: Style, - buffer: chrono::Duration, - simple_graphics: bool, -} - -impl PlotData { - pub fn new(display: String, buffer: u64, style: Style, simple_graphics: bool) -> PlotData { - PlotData { - display, - data: Vec::with_capacity(150), // ringbuffer::FixedRingBuffer::new(capacity), - style, - buffer: chrono::Duration::seconds(buffer as i64), - simple_graphics, - } - } - pub fn update(&mut self, item: Option) { - let now = Local::now(); - let idx = now.timestamp_millis() as f64 / 1_000f64; - match item { - Some(dur) => self.data.push((idx, dur.as_micros() as f64)), - None => self.data.push((idx, f64::NAN)), - } - // Find the last index that we should remove. - let earliest_timestamp = (now - self.buffer).timestamp_millis() as f64 / 1_000f64; - let last_idx = self - .data - .iter() - .enumerate() - .filter(|(_, (timestamp, _))| *timestamp < earliest_timestamp) - .map(|(idx, _)| idx) - .last(); - if let Some(idx) = last_idx { - self.data.drain(0..idx).for_each(drop) - } - } - - pub fn header_stats(&self) -> Vec { - let ping_header = Paragraph::new(self.display.clone()).style(self.style); - let items: Vec<&f64> = self - .data - .iter() - .filter(|(_, x)| !x.is_nan()) - .sorted_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()) - .map(|(_, v)| v) - .collect(); - if items.is_empty() { - return vec![ping_header]; - } - - let min = **items.first().unwrap(); - let max = **items.last().unwrap(); - let avg = items.iter().fold(0f64, |sum, &item| sum + item) / items.len() as f64; - let jtr = items.iter().enumerate().fold(0f64, |sum, (idx, &item)| { - sum + (*items.get(idx + 1).unwrap_or(&item) - item).abs() - }) / (items.len() - 1) as f64; - - let percentile_position = 0.95 * items.len() as f32; - let rounded_position = percentile_position.round() as usize; - let p95 = items.get(rounded_position).map(|i| **i).unwrap_or(0f64); - - // count timeouts - let to = self.data.iter().filter(|(_, x)| x.is_nan()).count(); - - let last = self.data.last().unwrap_or(&(0f64, 0f64)).1; - - vec![ - ping_header, - Paragraph::new(format!("last {:?}", Duration::from_micros(last as u64))) - .style(self.style), - Paragraph::new(format!("min {:?}", Duration::from_micros(min as u64))) - .style(self.style), - Paragraph::new(format!("max {:?}", Duration::from_micros(max as u64))) - .style(self.style), - Paragraph::new(format!("avg {:?}", Duration::from_micros(avg as u64))) - .style(self.style), - Paragraph::new(format!("jtr {:?}", Duration::from_micros(jtr as u64))) - .style(self.style), - Paragraph::new(format!("p95 {:?}", Duration::from_micros(p95 as u64))) - .style(self.style), - Paragraph::new(format!("t/o {to:?}")).style(self.style), - ] - } -} - -impl<'a> From<&'a PlotData> for Dataset<'a> { - fn from(plot: &'a PlotData) -> Self { - let slice = plot.data.as_slice(); - Dataset::default() - .marker(if plot.simple_graphics { - symbols::Marker::Dot - } else { - symbols::Marker::Braille - }) - .style(plot.style) - .graph_type(GraphType::Line) - .data(slice) - } -} diff --git a/gping/src/region_map.rs b/gping/src/region_map.rs deleted file mode 100644 index 3a84a55c..00000000 --- a/gping/src/region_map.rs +++ /dev/null @@ -1,31 +0,0 @@ -type Host = String; - -pub fn try_host_from_cloud_region(query: &str) -> Option { - match query.split_once(':') { - Some(("aws", region)) => Some(format!("ec2.{region}.amazonaws.com")), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_host_from_aws() { - assert_eq!( - try_host_from_cloud_region("aws:eu-west-1"), - Some("ec2.eu-west-1.amazonaws.com".to_string()) - ); - } - - #[test] - fn test_host_from_foo() { - assert_eq!(try_host_from_cloud_region("foo:bar"), None); - } - - #[test] - fn test_invalid_input() { - assert_eq!(try_host_from_cloud_region("foo"), None); - } -} diff --git a/images/readme-example.gif b/images/readme-example.gif deleted file mode 100644 index 0c3ebb13..00000000 Binary files a/images/readme-example.gif and /dev/null differ diff --git a/pinger/Cargo.toml b/pinger/Cargo.toml index 47cd7eb3..a5e1e86e 100644 --- a/pinger/Cargo.toml +++ b/pinger/Cargo.toml @@ -12,6 +12,9 @@ anyhow = "1.0.69" regex = "1.7.1" lazy_static = "1.4.0" thiserror = "1.0.37" +async-process = "1.6.0" +tokio = { version = "1", features = ["full"] } +futures = "0.3.26" [target.'cfg(windows)'.dependencies] winping = "0.10.1" diff --git a/pinger/src/bsd.rs b/pinger/src/bsd.rs deleted file mode 100644 index 622077b9..00000000 --- a/pinger/src/bsd.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::{Parser, PingResult, Pinger}; -use regex::Regex; -use std::time::Duration; - -lazy_static! { - static ref RE: Regex = Regex::new(r"time=(?:(?P[0-9]+).(?P[0-9]+)\s+ms)").unwrap(); -} - -#[derive(Default)] -pub struct BSDPinger { - interval: Duration, - interface: Option, -} - -impl Pinger for BSDPinger { - fn set_interval(&mut self, interval: Duration) { - self.interval = interval; - } - - fn set_interface(&mut self, interface: Option) { - self.interface = interface; - } - - fn ping_args(&self, target: String) -> (&str, Vec) { - let mut args = vec![format!( - "-i{:.1}", - self.interval.as_millis() as f32 / 1_000_f32 - )]; - if let Some(interface) = &self.interface { - args.push("-I".into()); - args.push(interface.clone()); - } - args.push(target); - ("ping", args) - } -} - -#[derive(Default)] -pub struct BSDParser {} - -impl Parser for BSDParser { - fn parse(&self, line: String) -> Option { - if line.starts_with("PING ") { - return None; - } - if line.starts_with("Request timeout") { - return Some(PingResult::Timeout(line)); - } - self.extract_regex(&RE, line) - } -} diff --git a/pinger/src/lib.rs b/pinger/src/lib.rs index c9e55d8f..25c8ee16 100644 --- a/pinger/src/lib.rs +++ b/pinger/src/lib.rs @@ -1,5 +1,5 @@ #[cfg(unix)] -use crate::linux::{detect_linux_ping, LinuxPingType}; +use crate::linux::{detect_linux_ping}; /// Pinger /// This crate exposes a simple function to ping remote hosts across different operating systems. /// Example: @@ -10,101 +10,62 @@ use crate::linux::{detect_linux_ping, LinuxPingType}; /// for message in stream { /// match message { /// PingResult::Pong(duration, line) => println!("{:?} (line: {})", duration, line), -/// PingResult::Timeout(_) => println!("Timeout!"), -/// PingResult::Unknown(line) => println!("Unknown line: {}", line), -/// PingResult::PingExited(_code, _stderr) => {} +/// PingResult::Failed(_,_) => println!("Failed!"), /// } /// } /// ``` -use anyhow::{Context, Result}; +use anyhow::Result; use regex::Regex; use std::fmt::Formatter; -use std::io::{BufRead, BufReader}; -use std::process::{Child, Command, ExitStatus, Stdio}; use std::sync::mpsc; use std::time::Duration; -use std::{fmt, thread}; +use std::fmt; use thiserror::Error; +use std::thread::JoinHandle; +use tokio::sync::oneshot; #[macro_use] extern crate lazy_static; +extern crate core; pub mod linux; -// pub mod alpine' -pub mod macos; + #[cfg(windows)] pub mod windows; -mod bsd; + #[cfg(test)] mod test; -pub fn run_ping(cmd: &str, args: Vec) -> Result { - Command::new(cmd) - .args(&args) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - // Required to ensure that the output is formatted in the way we expect, not - // using locale specific delimiters. - .env("LANG", "C") - .env("LC_ALL", "C") - .spawn() - .with_context(|| format!("Failed to run ping with args {:?}", &args)) +pub struct Pinger { + pub channel: mpsc::Receiver, + ping_thread: Option<(oneshot::Sender<()>, JoinHandle<()>)>, } -pub trait Pinger: Default { - fn start

(&self, target: String) -> Result> - where - P: Parser, - { - let (tx, rx) = mpsc::channel(); - let (cmd, args) = self.ping_args(target); - let mut child = run_ping(cmd, args)?; - let stdout = child.stdout.take().context("child did not have a stdout")?; - - thread::spawn(move || { - let parser = P::default(); - let reader = BufReader::new(stdout).lines(); - for line in reader { - match line { - Ok(msg) => { - if let Some(result) = parser.parse(msg) { - if tx.send(result).is_err() { - break; - } - } - } - Err(_) => break, - } - } - let result = child.wait_with_output().expect("Child wasn't started?"); - let decoded_stderr = String::from_utf8(result.stderr).expect("Error decoding stderr"); - let _ = tx.send(PingResult::PingExited(result.status, decoded_stderr)); - }); - - Ok(rx) +impl Drop for Pinger { + fn drop(&mut self) { + if let Some((notify_exit_sender, thread)) = self.ping_thread.take() { + notify_exit_sender.send(()).unwrap(); + thread.join().unwrap(); + } } +} - fn set_interval(&mut self, interval: Duration); - fn set_interface(&mut self, interface: Option); +pub trait PingerTrait: Default { + fn start(&self, target: String) -> Result; - fn ping_args(&self, target: String) -> (&str, Vec) { - ("ping", vec![target]) - } -} -// Default empty implementation of a pinger. -#[derive(Default)] -pub struct SimplePinger {} + fn set_interval(&mut self, interval: Duration); -impl Pinger for SimplePinger { - fn set_interval(&mut self, _interval: Duration) {} + fn set_interface(&mut self, interface: Option); - fn set_interface(&mut self, _interface: Option) {} + fn ping_args(&self, target: String) -> (String, Vec) { + ("ping".to_string(), vec![target]) + } } -pub trait Parser: Default { +pub trait Parser: Default + Send { fn parse(&self, line: String) -> Option; fn extract_regex(&self, regex: &Regex, line: String) -> Option { @@ -132,18 +93,14 @@ pub trait Parser: Default { #[derive(Debug)] pub enum PingResult { Pong(Duration, String), - Timeout(String), - Unknown(String), - PingExited(ExitStatus, String), + Failed(String, String), } impl fmt::Display for PingResult { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match &self { PingResult::Pong(duration, _) => write!(f, "{duration:?}"), - PingResult::Timeout(_) => write!(f, "Timeout"), - PingResult::Unknown(_) => write!(f, "Unknown"), - PingResult::PingExited(status, stderr) => write!(f, "Exited({status}, {stderr})"), + PingResult::Failed(status, stderr) => write!(f, "Exited({status}, {stderr})"), } } } @@ -171,7 +128,7 @@ pub enum PingError { } /// Start pinging a an address. The address can be either a hostname or an IP address. -pub fn ping(addr: String, interface: Option) -> Result> { +pub fn ping(addr: String, interface: Option) -> Result { ping_with_interval(addr, Duration::from_millis(200), interface) } @@ -180,46 +137,53 @@ pub fn ping_with_interval( addr: String, interval: Duration, interface: Option, -) -> Result> { +) -> Result { #[cfg(windows)] { let mut p = windows::WindowsPinger::default(); p.set_interval(interval); p.set_interface(interface); - return p.start::(addr); + p.start::(addr) } #[cfg(unix)] { - if cfg!(target_os = "freebsd") - || cfg!(target_os = "dragonfly") - || cfg!(target_os = "openbsd") - || cfg!(target_os = "netbsd") - { - let mut p = bsd::BSDPinger::default(); - p.set_interval(interval); - p.set_interface(interface); - p.start::(addr) - } else if cfg!(target_os = "macos") { - let mut p = macos::MacOSPinger::default(); - p.set_interval(interval); - p.set_interface(interface); - p.start::(addr) - } else { - match detect_linux_ping() { - Ok(LinuxPingType::IPTools) => { - let mut p = linux::LinuxPinger::default(); - p.set_interval(interval); - p.set_interface(interface); - p.start::(addr) - } - Ok(LinuxPingType::BusyBox) => { - let mut p = linux::AlpinePinger::default(); - p.set_interval(interval); - p.set_interface(interface); - p.start::(addr) - } - Err(e) => Err(PingError::UnsupportedPing(e))?, + match detect_linux_ping() { + Ok(_) => { + let mut p = linux::LinuxPinger::default(); + p.set_interval(interval); + p.set_interface(interface); + p.start::(addr) } + Err(e) => Err(PingError::UnsupportedPing(e))?, } } } + +// #[cfg(test)] +// mod tests { +// use std::thread::sleep; +// +// #[test] +// fn test() { +// use super::*; +// let ping_channel = ping_with_interval( +// "8.8.8.9".to_string(), +// Duration::from_millis(200), +// None, +// ).unwrap(); +// let mut counter = 0; +// loop { +// if let Ok(result) = ping_channel.channel.try_recv() { +// match result { +// PingResult::Pong(duration, _) => println!("{:?}", duration.as_millis()), +// PingResult::Failed(exit_status, err) => println!("{} - {}", exit_status, err) +// } +// } +// counter+=1; +// if counter == 10 { +// break; +// } +// sleep(Duration::from_millis(200)); +// } +// } +// } diff --git a/pinger/src/linux.rs b/pinger/src/linux.rs index 87f1f743..0d5839ce 100644 --- a/pinger/src/linux.rs +++ b/pinger/src/linux.rs @@ -1,30 +1,30 @@ -use crate::{run_ping, Parser, PingDetectionError, PingResult, Pinger}; -use anyhow::Context; +use crate::{Parser, PingDetectionError, PingResult, PingerTrait, Pinger}; +use anyhow::{Context, Result}; use regex::Regex; -use std::time::Duration; +use std::{time::Duration, thread, sync::mpsc, process::Output}; +use async_process::Command; +use futures::executor; +use tokio::{sync::oneshot, time}; -#[derive(Debug, Eq, PartialEq)] -pub enum LinuxPingType { - BusyBox, - IPTools, +pub async fn run_ping(cmd: &str, args: Vec) -> Result { + Command::new(cmd) + .args(&args) + // Required to ensure that the output is formatted in the way we expect, not + // using locale specific delimiters. + .env("LANG", "C") + .env("LC_ALL", "C") + .output().await + .with_context(|| format!("Failed to run ping with args {:?}", &args)) } -pub fn detect_linux_ping() -> Result { - let child = run_ping("ping", vec!["-V".to_string()])?; - let output = child - .wait_with_output() - .context("Error getting ping stdout/stderr")?; +pub fn detect_linux_ping() -> Result<(), PingDetectionError> { + let output = executor::block_on(run_ping("ping", vec!["-V".to_string()]))?; + let stdout = String::from_utf8(output.stdout).context("Error decoding ping stdout")?; let stderr = String::from_utf8(output.stderr).context("Error decoding ping stderr")?; - if stderr.contains("BusyBox") { - Ok(LinuxPingType::BusyBox) - } else if stdout.contains("iputils") { - Ok(LinuxPingType::IPTools) - } else if stdout.contains("inetutils") { - Err(PingDetectionError::NotSupported { - alternative: "Please use iputils ping, not inetutils.".to_string(), - }) + if stdout.contains("iputils") { + Ok(()) } else { let first_two_lines_stderr: Vec = stderr.lines().take(2).map(str::to_string).collect(); @@ -43,45 +43,84 @@ pub struct LinuxPinger { interface: Option, } -impl Pinger for LinuxPinger { +impl PingerTrait for LinuxPinger { + fn start

(&self, target: String) -> Result + where + P: Parser, + { + let args = self.ping_args(target); + let interval = self.interval; + + let (tx, rx) = mpsc::channel(); + + let (notify_exit_sender, exit_receiver) = oneshot::channel(); + let ping_thread = Some((notify_exit_sender, + thread::spawn({ + let (cmd, args) = args; + move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + runtime.spawn(async move { + loop { + let parser = P::default(); + match run_ping(cmd.as_str(), args.clone()).await { + Ok(output) => { + if output.status.success() { + if let Some(result) = parser.parse(String::from_utf8(output.stdout.clone()).expect("Error decoding stdout")) { + if tx.send(result).is_err() { + break; + } + } + } else { + tx.send(PingResult::Failed(output.status.to_string(), "Timeout reached".to_string())).ok(); + } + } + Err(e) => { + panic!("Ping command failed - this should not happen, please verify the integrity of the ping command: {}", e.to_string()) + } + }; + time::sleep(interval).await; + } + }); + + runtime.block_on(async move { + let _ = exit_receiver.await; + }); + } + }) + )); + Ok(Pinger { + channel: rx, + ping_thread, + }) + } + fn set_interval(&mut self, interval: Duration) { self.interval = interval; } + fn set_interface(&mut self, interface: Option) { self.interface = interface; } - fn ping_args(&self, target: String) -> (&str, Vec) { - // The -O flag ensures we "no answer yet" messages from ping - // See https://superuser.com/questions/270083/linux-ping-show-time-out + fn ping_args(&self, target: String) -> (String, Vec) { + // timeout of 1 second let mut args = vec![ - "-O".to_string(), - format!("-i{:.1}", self.interval.as_millis() as f32 / 1_000_f32), + "-c".to_string(), + "1".to_string(), + "-W".to_string(), + "1.0".to_string(), ]; if let Some(interface) = &self.interface { args.push("-I".into()); args.push(interface.clone()); } args.push(target); - ("ping", args) - } -} - -#[derive(Default)] -pub struct AlpinePinger { - interval: Duration, - interface: Option, -} - -// Alpine doesn't support timeout notifications, so we don't add the -O flag here -impl Pinger for AlpinePinger { - fn set_interval(&mut self, interval: Duration) { - self.interval = interval; - } - - fn set_interface(&mut self, interface: Option) { - self.interface = interface; + ("ping".to_string(), args) } } @@ -94,32 +133,12 @@ lazy_static! { pub struct LinuxParser {} impl Parser for LinuxParser { - fn parse(&self, line: String) -> Option { - if line.starts_with("64 bytes from") { - return self.extract_regex(&UBUNTU_RE, line); - } else if line.starts_with("no answer yet") { - return Some(PingResult::Timeout(line)); - } - None - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(target_os = "linux")] - fn test_linux_detection() { - use super::*; - use os_info::Type; - let ping_type = detect_linux_ping().expect("Error getting ping"); - match os_info::get().os_type() { - Type::Alpine => { - assert_eq!(ping_type, LinuxPingType::BusyBox) - } - Type::Ubuntu => { - assert_eq!(ping_type, LinuxPingType::IPTools) + fn parse(&self, lines: String) -> Option { + for line in lines.lines() { + if line.starts_with("64 bytes from") { + return self.extract_regex(&UBUNTU_RE, line.to_string()); } - _ => {} } + return Some(PingResult::Failed("1".to_string(), format!("Failed to parse: {lines}"))); } } diff --git a/pinger/src/macos.rs b/pinger/src/macos.rs deleted file mode 100644 index cb1f6a8d..00000000 --- a/pinger/src/macos.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{Parser, PingResult, Pinger}; -use regex::Regex; -use std::net::Ipv6Addr; -use std::time::Duration; - -lazy_static! { - static ref RE: Regex = Regex::new(r"time=(?:(?P[0-9]+).(?P[0-9]+)\s+ms)").unwrap(); -} - -#[derive(Default)] -pub struct MacOSPinger { - interval: Duration, - interface: Option, -} - -impl Pinger for MacOSPinger { - fn set_interval(&mut self, interval: Duration) { - self.interval = interval; - } - - fn set_interface(&mut self, interface: Option) { - self.interface = interface; - } - - fn ping_args(&self, target: String) -> (&str, Vec) { - let cmd = match target.parse::() { - Ok(_) => "ping6", - Err(_) => "ping", - }; - let mut args = vec![ - format!("-i{:.1}", self.interval.as_millis() as f32 / 1_000_f32), - target, - ]; - if let Some(interface) = &self.interface { - args.push("-b".into()); - args.push(interface.clone()); - } - - (cmd, args) - } -} - -#[derive(Default)] -pub struct MacOSParser {} - -impl Parser for MacOSParser { - fn parse(&self, line: String) -> Option { - if line.starts_with("PING ") { - return None; - } - if line.starts_with("Request timeout") { - return Some(PingResult::Timeout(line)); - } - self.extract_regex(&RE, line) - } -} diff --git a/pinger/src/test.rs b/pinger/src/test.rs index c4be0acf..e4489c7c 100644 --- a/pinger/src/test.rs +++ b/pinger/src/test.rs @@ -1,8 +1,6 @@ #[cfg(test)] mod tests { - use crate::bsd::BSDParser; use crate::linux::LinuxParser; - use crate::macos::MacOSParser; use crate::{Parser, PingResult}; #[cfg(windows)] @@ -14,64 +12,25 @@ mod tests { { let parser = T::default(); let test_file: Vec<&str> = contents.split("-----").collect(); - let input = test_file[0].trim().split('\n'); - let expected: Vec<&str> = test_file[1].trim().split('\n').collect(); - let parsed: Vec> = input.map(|l| parser.parse(l.to_string())).collect(); - - assert_eq!( - parsed.len(), - expected.len(), - "Parsed: {:?}, Expected: {:?}", - &parsed, - &expected - ); - - for (idx, (output, expected)) in parsed.into_iter().zip(expected).enumerate() { - if let Some(value) = output { - assert_eq!( - format!("{value}").trim(), - expected.trim(), - "Failed at idx {idx}" - ) - } else { - assert_eq!("None", expected.trim(), "Failed at idx {idx}") - } + let input = test_file[0].trim(); + let expected: &str = test_file[1].trim(); + let parsed: Option = parser.parse(input.to_string()); + + if let Some(value) = parsed { + assert_eq!( + format!("{value}").trim(), + expected.trim(), + "Failed" + ) + } else { + panic!("Could not parse input file") } - } - - #[test] - fn macos() { - test_parser::(include_str!("tests/macos.txt")); - } - - #[test] - fn freebsd() { - test_parser::(include_str!("tests/bsd.txt")); - } - - #[test] - fn dragonfly() { - test_parser::(include_str!("tests/bsd.txt")); - } - #[test] - fn openbsd() { - test_parser::(include_str!("tests/bsd.txt")); - } - - #[test] - fn netbsd() { - test_parser::(include_str!("tests/bsd.txt")); - } - - #[test] - fn ubuntu() { - test_parser::(include_str!("tests/ubuntu.txt")); } #[test] - fn debian() { - test_parser::(include_str!("tests/debian.txt")); + fn linux() { + test_parser::(include_str!("tests/linux.txt")); } #[cfg(windows)] @@ -80,13 +39,6 @@ mod tests { test_parser::(include_str!("tests/windows.txt")); } - #[test] - fn android() { - test_parser::(include_str!("tests/android.txt")); - } - #[test] - fn alpine() { - test_parser::(include_str!("tests/alpine.txt")); - } + } diff --git a/pinger/src/tests/alpine.txt b/pinger/src/tests/alpine.txt deleted file mode 100644 index 55e02483..00000000 --- a/pinger/src/tests/alpine.txt +++ /dev/null @@ -1,12 +0,0 @@ -PING google.com (142.250.178.14): 56 data bytes -64 bytes from 142.250.178.14: seq=0 ttl=37 time=19.236 ms -64 bytes from 142.250.178.14: seq=1 ttl=37 time=19.319 ms -64 bytes from 142.250.178.14: seq=2 ttl=37 time=17.944 ms -ping: sendto: Network unreachable ------ - -None -19.236ms -19.319ms -17.944ms -None diff --git a/pinger/src/tests/android.txt b/pinger/src/tests/android.txt deleted file mode 100644 index a319c8b2..00000000 --- a/pinger/src/tests/android.txt +++ /dev/null @@ -1,25 +0,0 @@ -PING google.com (172.217.173.46) 56(84) bytes of data. -64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=1 ttl=110 time=106 ms -64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=2 ttl=110 time=142 ms -64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=3 ttl=110 time=244 ms -64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=4 ttl=110 time=120 ms -64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=5 ttl=110 time=122 ms -64 bytes from 172.217.173.46: icmp_seq=6 ttl=110 time=246 ms - ---- google.com ping statistics --- -6 packets transmitted, 6 received, 0% packet loss, time 5018ms -rtt min/avg/max/mdev = 106.252/163.821/246.851/58.823 ms - ------ - -None -106ms -142ms -244ms -120ms -122ms -246ms -None -None -None -None \ No newline at end of file diff --git a/pinger/src/tests/bsd.txt b/pinger/src/tests/bsd.txt deleted file mode 100644 index d0987af8..00000000 --- a/pinger/src/tests/bsd.txt +++ /dev/null @@ -1,13 +0,0 @@ -PING google.com (216.58.198.174): 56 data bytes -64 bytes from 96.47.72.84: icmp_seq=0 ttl=50 time=111.525 ms -ping: sendto: Host is down -64 bytes from 96.47.72.84: icmp_seq=1 ttl=50 time=110.395 ms -ping: sendto: No route to host - ------ - -None -111.525ms -None -110.395ms -None diff --git a/pinger/src/tests/debian.txt b/pinger/src/tests/debian.txt deleted file mode 100644 index d2abf6eb..00000000 --- a/pinger/src/tests/debian.txt +++ /dev/null @@ -1,15 +0,0 @@ -PING google.com (216.58.209.78): 56 data bytes -64 bytes from 216.58.209.78: icmp_seq=0 ttl=37 time=21.308 ms -64 bytes from 216.58.209.78: icmp_seq=1 ttl=37 time=15.769 ms -^C--- google.com ping statistics --- -8 packets transmitted, 8 packets received, 0% packet loss -round-trip min/avg/max/stddev = 15.282/20.347/41.775/8.344 ms - ------ - -None -21.308ms -15.769ms -None -None -None diff --git a/pinger/src/tests/linux.txt b/pinger/src/tests/linux.txt new file mode 100644 index 00000000..6803b9c9 --- /dev/null +++ b/pinger/src/tests/linux.txt @@ -0,0 +1,10 @@ +PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. +64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=2.69 ms + +--- 8.8.8.8 ping statistics --- +1 packets transmitted, 1 received, 0% packet loss, time 0ms +rtt min/avg/max/mdev = 2.689/2.689/2.689/0.000 ms + + +----- +2.69ms diff --git a/pinger/src/tests/macos.txt b/pinger/src/tests/macos.txt deleted file mode 100644 index 895966b2..00000000 --- a/pinger/src/tests/macos.txt +++ /dev/null @@ -1,25 +0,0 @@ -PING google.com (216.58.209.78): 56 data bytes -64 bytes from 216.58.209.78: icmp_seq=0 ttl=119 time=14.621 ms -64 bytes from 216.58.209.78: icmp_seq=1 ttl=119 time=33.898 ms -64 bytes from 216.58.209.78: icmp_seq=2 ttl=119 time=17.305 ms -64 bytes from 216.58.209.78: icmp_seq=3 ttl=119 time=24.235 ms -64 bytes from 216.58.209.78: icmp_seq=4 ttl=119 time=15.242 ms -64 bytes from 216.58.209.78: icmp_seq=5 ttl=119 time=16.639 ms -Request timeout for icmp_seq 19 -Request timeout for icmp_seq 20 -Request timeout for icmp_seq 21 -64 bytes from 216.58.209.78: icmp_seq=30 ttl=119 time=16.943 ms - ------ - -None -14.621ms -33.898ms -17.305ms -24.235ms -15.242ms -16.639ms -Timeout -Timeout -Timeout -16.943ms diff --git a/pinger/src/tests/ubuntu.txt b/pinger/src/tests/ubuntu.txt deleted file mode 100644 index fcb8ec0f..00000000 --- a/pinger/src/tests/ubuntu.txt +++ /dev/null @@ -1,29 +0,0 @@ -PING google.com (216.58.209.78) 56(84) bytes of data. -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=1 ttl=37 time=25.1 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=37 time=19.4 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=3 ttl=37 time=14.9 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=4 ttl=37 time=22.8 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=5 ttl=37 time=13.9 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=6 ttl=37 time=77.6 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=7 ttl=37 time=158 ms -no answer yet for icmp_seq=8 -no answer yet for icmp_seq=9 -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=18 ttl=37 time=357 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=19 ttl=37 time=85.2 ms -64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=20 ttl=37 time=17.8 ms - ------ - -None -25.1ms -19.4ms -14.9ms -22.8ms -13.9ms -77.6ms -158ms -Timeout -Timeout -357ms -85.2ms -17.8ms diff --git a/pinger/src/windows.rs b/pinger/src/windows.rs index 883009a3..0bc3e727 100644 --- a/pinger/src/windows.rs +++ b/pinger/src/windows.rs @@ -1,12 +1,11 @@ -use crate::{Parser, PingError, PingResult, Pinger}; +use crate::{Parser, PingError, PingResult, Pinger, PingerTrait}; use anyhow::Result; use dns_lookup::lookup_host; use regex::Regex; -use std::net::IpAddr; -use std::sync::mpsc; -use std::thread; -use std::time::Duration; -use winping::{Buffer, Pinger as WinPinger}; +use std::{time::Duration, thread, sync::mpsc, net::IpAddr}; +use winping::{Buffer, AsyncPinger as WinPinger}; +use tokio::{time, sync::oneshot}; + lazy_static! { static ref RE: Regex = Regex::new(r"(?ix-u)time=(?P\d+)(?:\.(?P\d+))?").unwrap(); @@ -18,10 +17,10 @@ pub struct WindowsPinger { interface: Option, } -impl Pinger for WindowsPinger { - fn start

(&self, target: String) -> Result> - where - P: Parser, +impl PingerTrait for WindowsPinger { + fn start

(&self, target: String) -> Result + where + P: Parser, { let interval = self.interval; let parsed_ip: IpAddr = match target.parse() { @@ -37,35 +36,50 @@ impl Pinger for WindowsPinger { }?; let (tx, rx) = mpsc::channel(); + let (notify_exit_sender, exit_receiver) = oneshot::channel(); + let ping_thread = Some((notify_exit_sender, + thread::spawn({ + move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); - thread::spawn(move || { - let pinger = WinPinger::new().expect("Failed to create a WinPinger instance"); - let mut buffer = Buffer::new(); - loop { - match pinger.send(parsed_ip.clone(), &mut buffer) { - Ok(rtt) => { - if tx - .send(PingResult::Pong( - Duration::from_millis(rtt as u64), - "".to_string(), - )) - .is_err() - { - break; - } - } - Err(_) => { - // Fuck it. All errors are timeouts. Why not. - if tx.send(PingResult::Timeout("".to_string())).is_err() { - break; - } - } - } - thread::sleep(interval); - } - }); + runtime.spawn(async move { + let pinger = WinPinger::new(); + loop { + let buffer = Buffer::new(); + match pinger.send(parsed_ip, buffer).await.result { + Ok(rtt) => { + if tx + .send(PingResult::Pong( + Duration::from_millis(rtt as u64), + "".to_string(), + )) + .is_err() + { + break; + } + } + Err(e) => { + // Fuck it. All errors are timeouts. Why not. + tx.send(PingResult::Failed("-1".to_string(), e.to_string())).ok(); + } + } + time::sleep(interval).await; + } + }); - Ok(rx) + runtime.block_on(async move { + let _ = exit_receiver.await; + }); + } + }) + )); + Ok(Pinger { + channel: rx, + ping_thread, + }) } fn set_interval(&mut self, interval: Duration) { @@ -83,7 +97,7 @@ pub struct WindowsParser {} impl Parser for WindowsParser { fn parse(&self, line: String) -> Option { if line.contains("timed out") || line.contains("failure") { - return Some(PingResult::Timeout(line)); + return Some(PingResult::Failed("1".to_string(), line)); } self.extract_regex(&RE, line) } diff --git a/readme.md b/readme.md deleted file mode 100644 index 3b3f23c0..00000000 --- a/readme.md +++ /dev/null @@ -1,96 +0,0 @@ -# gping 🚀 - -[![Crates.io](https://img.shields.io/crates/v/gping.svg)](https://crates.io/crates/gping) -[![Actions Status](https://github.com/orf/gping/workflows/CI/badge.svg)](https://github.com/orf/gping/actions) - -Ping, but with a graph. - -![](./images/readme-example.gif) - -Comes with the following super-powers: -* Graph the ping time for multiple hosts -* Graph the _execution time_ for commands via the `--cmd` flag -* Custom colours -* Windows, Mac and Linux support - -Table of Contents -================= - - * [Install :cd:](#install-cd) - * [Usage :saxophone:](#usage-saxophone) - - - Packaging status - - -# Install :cd: - -* macOS - * [Homebrew](https://formulae.brew.sh/formula/gping#default): `brew install gping` - * [MacPorts](https://ports.macports.org/port/gping/): `sudo port install gping` -* Linux (Homebrew): `brew install gping` -* CentOS (and other distributions with an old glibc): Download the MUSL build from the latest release -* Windows/ARM: - * Scoop: `scoop install gping` - * Chocolatey: `choco install gping` - * Download the latest release from [the github releases page](https://github.com/orf/gping/releases) -* Fedora ([COPR](https://copr.fedorainfracloud.org/coprs/atim/gping/)): `sudo dnf copr enable atim/gping -y && sudo dnf install gping` -* Cargo (**This requires `rustc` version 1.44.0 or greater**): `cargo install gping` -* Arch Linux: `pacman -S gping` -* Ubuntu/Debian ([Azlux's repo](http://packages.azlux.fr/)): -```bash -echo "deb http://packages.azlux.fr/debian/ buster main" | sudo tee /etc/apt/sources.list.d/azlux.list -wget -qO - https://azlux.fr/repo.gpg.key | sudo apt-key add - -sudo apt update -sudo apt install gping -``` -* Gentoo ([dm9pZCAq overlay](https://github.com/gentoo-mirror/dm9pZCAq)): -```sh -sudo eselect repository enable dm9pZCAq -sudo emerge --sync dm9pZCAq -sudo emerge net-misc/gping::dm9pZCAq -``` -* FreeBSD: - * [pkg](https://www.freshports.org/net-mgmt/gping/): `pkg install gping` - * [ports](https://cgit.freebsd.org/ports/tree/net-mgmt/gping) `cd /usr/ports/net-mgmt/gping; make install clean` - -# Usage :saxophone: - -Just run `gping [host]`. `host` can be a command like `curl google.com` if the `--cmd` flag is used. You can also use -shorthands like `aws:eu-west-1` or `aws:ca-central-1` to ping specific cloud regions. Only `aws` is currently supported. - -```bash -$ gping --help -Ping, but with a graph. - -Usage: gping [OPTIONS] [HOSTS_OR_COMMANDS]... - -Arguments: - [HOSTS_OR_COMMANDS]... Hosts or IPs to ping, or commands to run if --cmd is provided. Can use cloud shorthands like aws:eu-west-1. - -Options: - --cmd - Graph the execution time for a list of commands rather than pinging hosts - -n, --watch-interval - Watch interval seconds (provide partial seconds like '0.5'). Default for ping is 0.2, default for cmd is 0.5. - -b, --buffer - Determines the number of seconds to display in the graph. [default: 30] - -4 - Resolve ping targets to IPv4 address - -6 - Resolve ping targets to IPv6 address - -i, --interface - Interface to use when pinging - -s, --simple-graphics - Uses dot characters instead of braille - --vertical-margin - Vertical margin around the graph (top and bottom) [default: 1] - --horizontal-margin - Horizontal margin around the graph (left and right) [default: 0] - -c, --color - Assign color to a graph entry. This option can be defined more than once as a comma separated string, and the order which the colors are provided will be matched against the hosts or commands passed to gping. Hexadecimal RGB color codes are accepted in the form of '#RRGGBB' or the following color names: 'black', 'red', 'green', 'yellow', 'blue', 'magenta','cyan', 'gray', 'dark-gray', 'light-red', 'light-green', 'light-yellow', 'light-blue', 'light-magenta', 'light-cyan', and 'white' - -h, --help - Print help information - -V, --version - Print version information -```