Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nbping for openSUSE:Factory checked in at 2026-03-11 20:57:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nbping (Old) and /work/SRC/openSUSE:Factory/.nbping.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nbping" Wed Mar 11 20:57:16 2026 rev:2 rq:1338293 version:0.6.1 Changes: -------- --- /work/SRC/openSUSE:Factory/nbping/nbping.changes 2025-11-18 15:39:34.854799986 +0100 +++ /work/SRC/openSUSE:Factory/.nbping.new.8177/nbping.changes 2026-03-11 20:59:22.495954823 +0100 @@ -1,0 +2,13 @@ +Sun Jan 25 15:28:10 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 0.6.1: + * feat: update version to v0.6.1 and rename project to NBping. + * feat: nping rename to nbping (#105). + +------------------------------------------------------------------- +Tue Jan 6 11:02:17 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 0.6.0: + * feat: add prometheus exporter mode (#103) + +------------------------------------------------------------------- Old: ---- Nping-0.5.0.obscpio New: ---- Nping-0.6.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nbping.spec ++++++ --- /var/tmp/diff_new_pack.1WjwgP/_old 2026-03-11 20:59:23.567998973 +0100 +++ /var/tmp/diff_new_pack.1WjwgP/_new 2026-03-11 20:59:23.575999303 +0100 @@ -17,7 +17,7 @@ Name: nbping -Version: 0.5.0 +Version: 0.6.1 Release: 0 Summary: A ping tool with real-time data and visualizations License: MIT @@ -48,9 +48,6 @@ %install %{cargo_install} -# next version will be named "nbping" -# https://github.com/hanshuaikang/Nping/issues/62#issuecomment-3483448009 -mv %{buildroot}%{_bindir}/nping %{buildroot}%{_bindir}/nbping %check #%%{cargo_test} ++++++ Nping-0.5.0.obscpio -> Nping-0.6.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/.github/workflows/release.yml new/Nping-0.6.1/.github/workflows/release.yml --- old/Nping-0.5.0/.github/workflows/release.yml 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/.github/workflows/release.yml 2026-01-25 09:48:45.000000000 +0100 @@ -42,7 +42,7 @@ with: # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload. # Note that glob pattern is not supported yet. - bin: nping + bin: nbping # (optional) Target triple, default is host triple. target: ${{ matrix.target }} # (required) GitHub token for uploading assets to GitHub Releases. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/Cargo.lock new/Nping-0.6.1/Cargo.lock --- old/Nping-0.5.0/Cargo.lock 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/Cargo.lock 2026-01-25 09:48:45.000000000 +0100 @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -88,6 +88,12 @@ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -302,12 +308,52 @@ checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -325,12 +371,106 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] +name = "indexmap" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -465,12 +605,16 @@ ] [[package]] -name = "nping" -version = "0.5.0" +name = "nbping" +version = "0.6.1" dependencies = [ "anyhow", "clap", + "http-body-util", + "hyper", + "hyper-util", "pinger", + "prometheus", "ratatui", "tokio", ] @@ -526,13 +670,19 @@ checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] name = "pinger" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc53a927954d81a867bd7e9163a4b5f13babdf1627863d5b0ab52587fdb02ad" dependencies = [ "lazy-regex", - "thiserror", + "thiserror 2.0.9", "winping", ] @@ -556,6 +706,27 @@ ] [[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -691,6 +862,12 @@ ] [[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -753,11 +930,31 @@ [[package]] name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.9", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -801,6 +998,44 @@ ] [[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -842,6 +1077,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/Cargo.toml new/Nping-0.6.1/Cargo.toml --- old/Nping-0.5.0/Cargo.toml 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/Cargo.toml 2026-01-25 09:48:45.000000000 +0100 @@ -1,9 +1,9 @@ [package] -name = "nping" -version = "0.5.0" +name = "nbping" +version = "0.6.1" edition = "2021" license = "MIT License" -description = "Nping is a Ping tool developed in Rust. It supports concurrent Ping for multiple addresses, visual chart display, real-time data updates, and other features." +description = "NBping is a Ping tool developed in Rust. It supports concurrent Ping for multiple addresses, visual chart display, real-time data updates, and other features." authors = ["hanshuaikang https://github.com/hanshuaikang"] [dependencies] @@ -12,3 +12,7 @@ tokio = { version = "1.42.0", features = ["full"] } pinger="2.0.0" anyhow="1.0.89" +prometheus = "0.13" +hyper = { version = "1.0", features = ["full"] } +hyper-util = { version = "0.1", features = ["tokio", "server", "http1", "http2"] } +http-body-util = "0.1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/README.md new/Nping-0.6.1/README.md --- old/Nping-0.5.0/README.md 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/README.md 2026-01-25 09:48:45.000000000 +0100 @@ -1,10 +1,10 @@ -<h1 align="center"> 🏎 Nping </h1> +<h1 align="center"> 🏎 NBping </h1> <p align="center"> - <em>Nping is a Ping tool developed in Rust. It supports concurrent Ping for multiple addresses, visual chart display, real-time data updates, and other features.</em> + <em>NBping is a Ping tool developed in Rust. It supports concurrent Ping for multiple addresses, visual chart display, real-time data updates, and other features.</em> </p> <p align="center"> - <img src="docs/imgs/nb.gif" alt="Nping demo" width="30%"> + <img src="docs/imgs/nb.gif" alt="NBping demo" width="30%"> </p> <p align="center"> @@ -16,44 +16,77 @@ [中文文档](./README_ZH.md) +📢 **NBPing (formerly Nping)** + +> [!IMPORTANT] +> **Renaming Notice** +> +> This project has been officially renamed from **Nping** to **NBPing**. +> +> Please update your bookmarks, dependencies, and installation scripts accordingly. The old name is now deprecated and will no longer be maintained. +> ```bash +> nbping --help +> ``` + + +**[New Feature] 🛰️ NBping Prometheus Exporter Now Supported** + +Now, NBping supports exporting ping metrics to Prometheus format. You can use the exporter subcommand to start the exporter server. [Learn more](#exporter-mode) + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 +``` +Then, you can scrape the metrics from `http://localhost:9100/metrics` + **Graph View** <p align="center"> - <img src="docs/imgs/black.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/black.gif" alt="NBping demo" width="100%"> </p> **Table View** <p align="center"> - <img src="docs/imgs/table.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/table.gif" alt="NBping demo" width="100%"> </p> **Point View** <p align="center"> - <img src="docs/imgs/point.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/point.gif" alt="NBping demo" width="100%"> </p> **Sparkline View** <p align="center"> - <img src="docs/imgs/sparkline.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/sparkline.gif" alt="NBping demo" width="100%"> +</p> + + +#### Exporter Mode +Now NBping supports exporting ping metrics to Prometheus format. you can use exporter subcommand to start the exporter server. + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 +``` +Then, you can scrape the metrics from `http://localhost:9100/metrics` + +You can use grafana to visualize the data +<p align="center"> + <img src="docs/imgs/grafana.png" alt="NBping demo" width="100%"> </p> + ## Installation #### MacOS Homebrew ```bash -brew tap hanshuaikang/nping -brew install nping +brew tap hanshuaikang/nbping +brew install nbping -nping --help +nbping --help ``` ## Feature: -- Supports concurrent Ping for multiple addresses -- Supports visual latency display -- Real-time display of maximum, minimum, average latency, packet loss rate, and other metrics -- Support IpV4 and IpV6 -- Supports concurrent pinging of n ip's under one address. -- Support output results to files +- TCP Ping support +- IP range Ping support ## Roadmap: - Optimize UI interface, add more dynamic effects. @@ -61,13 +94,13 @@ ## Usage ```bash -nping www.baidu.com www.google.com www.apple.com www.sina.com -c 20 -i 2 +nbping www.baidu.com www.google.com www.apple.com www.sina.com -c 20 -i 2 -nping --help +nbping --help -🏎 Nping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations +🏎 NBping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations -Usage: nping [OPTIONS] <TARGET>... +Usage: nbping [OPTIONS] <TARGET>... Arguments: <TARGET>... target IP address or hostname to ping @@ -84,13 +117,33 @@ ``` +### Exporter Usage + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 + +./nbping exporter --help +Exporter mode for monitoring + +Usage: nbping exporter [OPTIONS] <TARGET>... + +Arguments: + <TARGET>... target IP addresses or hostnames to ping + +Options: + -i, --interval <INTERVAL> Interval in seconds between pings [default: 1] + -p, --port <PORT> Prometheus metrics HTTP port [default: 9090] + -h, --help Print help +``` + + ## Acknowledgements -Thanks to these people for their feedback and suggestions for 🏎Nping! +Thanks to these people for their feedback and suggestions for 🏎NBping! | [ThatFlower](https://github.com/ThatFlower) | [zx4i](https://github.com/zx4i) | [snail2sky](https://github.com/snail2sky) | [shenshouer](https://github.com/shenshouer) | [vnt-dev](https://github.com/vnt-dev) | [qingyuan0o0](https://github.com/qingyuan0o0) | [Onlywzr](https://github.com/Onlywzr) -Thanks to these self-media for reposting and paying attention to 🏎Nping! +Thanks to these self-media for reposting and paying attention to 🏎NBping! | [阮一峰的网络日志](https://www.ruanyifeng.com/blog/weekly/) |[Rust 中文社区](https://rustcc.cn/) | [公众号:奇妙的linux世界](https://mp.weixin.qq.com/s/lK_OqKp2yY8lDBoyLxtdGA) | [公众号:IT运维技术圈](https://mp.weixin.qq.com/s/bDJZ-H02dIKG3R7LQCeyaQ) | [X:@geekbb](https://x.com/geekbb/status/1875754541905539510) | [公众号:一飞开源](https://mp.weixin.qq.com/s/BZjr54h8dIQgzr8UW3fwOQ) | [公众号: 开源日记](https://mp.weixin.qq.com/s/uGtkD4x_XOFyKNbIy5pHYA) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/README_ZH.md new/Nping-0.6.1/README_ZH.md --- old/Nping-0.5.0/README_ZH.md 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/README_ZH.md 2026-01-25 09:48:45.000000000 +0100 @@ -1,38 +1,73 @@ -<h1 align="center"> 🏎 Nping </h1> +<h1 align="center"> 🏎 NBping </h1> <p align="center"> - <em>Nping 是一个基于 Rust 开发的终端可视化 Ping 工具, 支持多地址并发 Ping, 可视化图表展示, 数据实时更新等特性 </em> + <em>NBping 是一个基于 Rust 开发的终端可视化 Ping 工具, 支持多地址并发 Ping, 可视化图表展示, 数据实时更新等特性 </em> </p> <p align="center"> - <img src="docs/imgs/nb.gif" alt="Nping demo" width="30%"> + <img src="docs/imgs/nb.gif" alt="NBping demo" width="30%"> </p> <p align="center"> <a href="https://hellogithub.com/repository/21f5600774554866a3d686308df2dbf0" target="_blank"> <img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=21f5600774554866a3d686308df2dbf0&claim_uid=uT2Sc8Xli4PUA76&theme=neutral" alt="Featured|HelloGitHub" style="width: 200px; height: 60px;" width="250" height="60" /> </a> -<a href="https://trendshift.io/repositories/13472" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13472" alt="hanshuaikang%2FNping | Trendshift" style="width: 200px; height: 60px;" width="250" height="55"/></a> +<a href="https://trendshift.io/repositories/13472" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13472" alt="hanshuaikang%2FNBping | Trendshift" style="width: 200px; height: 60px;" width="250" height="55"/></a> </p> +📢 **Nping 现在已经更名为 NBPing(nbping)** + +> [!IMPORTANT] +> **更名通知** +> +> 本项目已从 **Nping** 正式更名为 **NBPing**。 +> +> 请相应更新您的书签、依赖项和安装脚本。旧名称现已弃用,不再维护。 +> ```bash +> nbping --help +> ``` + +**[新功能] 🛰️Exporter 模式** + +现在 NBping 支持通过将 Ping 指标数据通过 Prometheus 格式导出,你可以使用 Grafana 等工具进行可视化展示。 + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 +``` +Then, you can scrape the metrics from `http://localhost:9100/metrics` + + **图表视图** <p align="center"> - <img src="docs/imgs/black.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/black.gif" alt="NBping demo" width="100%"> </p> **表格视图** <p align="center"> - <img src="docs/imgs/table.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/table.gif" alt="NBping demo" width="100%"> </p> **点视图** <p align="center"> - <img src="docs/imgs/point.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/point.gif" alt="NBping demo" width="100%"> </p> **Sparkline 视图** <p align="center"> - <img src="docs/imgs/sparkline.gif" alt="Nping demo" width="100%"> + <img src="docs/imgs/sparkline.gif" alt="NBping demo" width="100%"> +</p> + +** Exporter 模式 ** +现在 NBping 支持通过将 Ping 指标数据通过 Prometheus 格式导出,你可以使用 Grafana 等工具进行可视化展示。 + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 +``` +然后你可以访问获取这些数据 `http://localhost:9100/metrics` + +你可以通过 Grafana 来可视化这些数据 +<p align="center"> + <img src="docs/imgs/grafana.png" alt="NBping demo" width="100%"> </p> @@ -40,20 +75,15 @@ #### MacOS Homebrew ```bash -brew tap hanshuaikang/nping -brew install nping +brew tap hanshuaikang/nbping +brew install nbping -nping --help +nbping --help ``` - ## Feature: -- 支持多地址并发同时 Ping -- 支持可视化延迟展示 -- 实时最大最小平均延迟丢包率等指标展示 -- 支持 IpV4 和 IpV6 -- 支持一个地址下并发 Ping n 个 ip -- 支持输出结果到文件 +- TCP Ping 支持 +- IP 段 Ping 支持 ## 后续的计划: - UI 界面优化, 增加更多的动态效果 @@ -61,13 +91,13 @@ ## Usage ```bash -nping www.baidu.com www.google.com www.apple.com www.sina.com -c 20 -i 2 +nbping www.baidu.com www.google.com www.apple.com www.sina.com -c 20 -i 2 -nping --help +nbping --help -🏎 Nping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations +🏎 NBping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations -Usage: nping [OPTIONS] <TARGET>... +Usage: nbping [OPTIONS] <TARGET>... Arguments: <TARGET>... target IP address or hostname to ping @@ -83,13 +113,33 @@ -V, --version Print version ``` + +### Exporter Usage + +```bash +nbping exporter www.baidu.com www.google.com -i 1 -p 9100 + +./nbping exporter --help +Exporter mode for monitoring + +Usage: nbping exporter [OPTIONS] <TARGET>... + +Arguments: + <TARGET>... target IP addresses or hostnames to ping + +Options: + -i, --interval <INTERVAL> Interval in seconds between pings [default: 1] + -p, --port <PORT> Prometheus metrics HTTP port [default: 9090] + -h, --help Print help +``` + ## 致谢 -感谢这些朋友对 Nping 提出的反馈和建议。 +感谢这些朋友对 NBping 提出的反馈和建议。 | [ThatFlower](https://github.com/ThatFlower) | [zx4i](https://github.com/zx4i) | [snail2sky](https://github.com/snail2sky) | [shenshouer](https://github.com/shenshouer) | [vnt-dev](https://github.com/vnt-dev) | [qingyuan0o0](https://github.com/qingyuan0o0) | [Onlywzr](https://github.com/Onlywzr) -感谢以下自媒体对 Nping 的关注和转发。 +感谢以下自媒体对 NBping 的关注和转发。 | [阮一峰的网络日志](https://www.ruanyifeng.com/blog/weekly/) |[Rust 中文社区](https://rustcc.cn/) | [公众号:奇妙的linux世界](https://mp.weixin.qq.com/s/lK_OqKp2yY8lDBoyLxtdGA) | [公众号:IT运维技术圈](https://mp.weixin.qq.com/s/bDJZ-H02dIKG3R7LQCeyaQ) | [X:@geekbb](https://x.com/geekbb/status/1875754541905539510) | [公众号:一飞开源](https://mp.weixin.qq.com/s/BZjr54h8dIQgzr8UW3fwOQ) | [公众号: 开源日记](https://mp.weixin.qq.com/s/uGtkD4x_XOFyKNbIy5pHYA) Binary files old/Nping-0.5.0/docs/imgs/grafana.png and new/Nping-0.6.1/docs/imgs/grafana.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/data_processor.rs new/Nping-0.6.1/src/data_processor.rs --- old/Nping-0.5.0/src/data_processor.rs 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/src/data_processor.rs 2026-01-25 09:48:45.000000000 +0100 @@ -120,4 +120,4 @@ } } }); -} \ No newline at end of file +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/exporter/metric.rs new/Nping-0.6.1/src/exporter/metric.rs --- old/Nping-0.5.0/src/exporter/metric.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/Nping-0.6.1/src/exporter/metric.rs 2026-01-25 09:48:45.000000000 +0100 @@ -0,0 +1,199 @@ +use prometheus::{CounterVec, HistogramVec, HistogramOpts, Opts, Registry, TextEncoder}; +use std::sync::Arc; + +/// Prometheus 指标收集器 +#[derive(Debug, Clone)] +pub struct PrometheusMetrics { + /// ping 延迟直方图指标 + ping_duration_histogram: HistogramVec, + /// ping 请求总数(按状态分组) + ping_requests_total: CounterVec, + /// Prometheus 注册表 + registry: Arc<Registry>, +} + +impl PrometheusMetrics { + /// 创建新的 Prometheus 指标收集器 + pub fn new() -> Result<Self, prometheus::Error> { + // 创建注册表 + let registry = Arc::new(Registry::new()); + + // 定义延迟的 buckets (秒): 1ms, 5ms, 10ms, 50ms, 100ms, 500ms, 1s, 5s, 10s, +Inf + let buckets = vec![ + 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0, + ]; + + // 创建直方图指标 + let ping_duration_histogram = HistogramVec::new( + HistogramOpts::new( + "nbping_ping_duration_seconds", + "Histogram of ping durations in seconds", + ) + .buckets(buckets), + &["target", "ip"], // label 名称 + )?; + + // 创建请求总数计数器 + let ping_requests_total = CounterVec::new( + Opts::new( + "nbping_ping_requests_total", + "Total number of ping requests", + ), + &["target", "ip", "status"], + )?; + + // 注册指标 + registry.register(Box::new(ping_duration_histogram.clone()))?; + registry.register(Box::new(ping_requests_total.clone()))?; + + Ok(Self { + ping_duration_histogram, + ping_requests_total, + registry, + }) + } + + /// 记录成功的 ping(记录到直方图) + pub fn record_ping_success(&self, target: &str, ip: &str, rtt_ms: f64) { + let rtt_seconds = rtt_ms / 1000.0; + + self.ping_requests_total + .with_label_values(&[target, ip, "success"]) + .inc(); + + // 为直方图添加 labels 并观察值 + self.ping_duration_histogram + .with_label_values(&[target, ip]) + .observe(rtt_seconds); + } + + /// 记录超时的 ping(不记录到直方图,但可以在这里添加其他指标) + pub fn record_ping_timeout(&self, target: &str, ip: &str) { + self.ping_requests_total + .with_label_values(&[target, ip, "timeout"]) + .inc(); + } + + /// 记录错误的 ping + pub fn record_ping_error(&self, target: &str, ip: &str) { + self.ping_requests_total + .with_label_values(&[target, ip, "error"]) + .inc(); + } + + /// 获取 Prometheus 格式的指标数据 + pub fn gather(&self) -> String { + let encoder = TextEncoder::new(); + let metric_families = self.registry.gather(); + + encoder.encode_to_string(&metric_families).unwrap_or_else(|e| { + eprintln!("Error encoding metrics: {}", e); + String::new() + }) + } + +} + +impl Default for PrometheusMetrics { + fn default() -> Self { + Self::new().expect("Failed to create PrometheusMetrics") + } +} + +/// HTTP 服务器,用于暴露 /metrics 端点 +pub mod http_server { + use super::*; + use hyper::service::service_fn; + use hyper::{Method, Request, Response, StatusCode}; + use hyper_util::rt::TokioIo; + use hyper_util::server::conn::auto::Builder; + use http_body_util::Full; + use hyper::body::Bytes; + use std::convert::Infallible; + use std::net::SocketAddr; + use std::sync::Arc; + use tokio::net::TcpListener; + + /// 启动 Prometheus metrics HTTP 服务器,支持优雅关闭 + pub async fn start_metrics_server( + metrics: Arc<PrometheusMetrics>, + addr: SocketAddr, + mut shutdown_rx: tokio::sync::oneshot::Receiver<()>, + ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + let listener = TcpListener::bind(addr).await?; + + loop { + tokio::select! { + // 接受新连接 + accept_result = listener.accept() => { + match accept_result { + Ok((stream, _)) => { + let metrics = metrics.clone(); + + tokio::task::spawn(async move { + let io = TokioIo::new(stream); + let service = service_fn(move |req| { + handle_request(req, metrics.clone()) + }); + + if let Err(err) = Builder::new(hyper_util::rt::TokioExecutor::new()) + .serve_connection(io, service) + .await + { + eprintln!("Error serving connection: {:?}", err); + } + }); + } + Err(e) => { + eprintln!("Failed to accept connection: {}", e); + } + } + } + // 接收关闭信号 + _ = &mut shutdown_rx => { + println!("Metrics server shutting down gracefully"); + break; + } + } + } + + Ok(()) + } + + /// 处理 HTTP 请求 + async fn handle_request( + req: Request<hyper::body::Incoming>, + metrics: Arc<PrometheusMetrics>, + ) -> Result<Response<Full<Bytes>>, Infallible> { + match (req.method(), req.uri().path()) { + (&Method::GET, "/metrics") => { + let metrics_output = metrics.gather(); + Ok(Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/plain; charset=utf-8") + .body(Full::new(Bytes::from(metrics_output))) + .unwrap()) + } + (&Method::GET, "/") => { + let body = r#"<html> +<head><title>NBPing Metrics</title></head> +<body> +<h1>NBPing Metrics</h1> +<p><a href='/metrics'>Metrics</a></p> +</body> +</html>"#; + Ok(Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/html") + .body(Full::new(Bytes::from(body))) + .unwrap()) + } + _ => { + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Not Found"))) + .unwrap()) + } + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/exporter/mod.rs new/Nping-0.6.1/src/exporter/mod.rs --- old/Nping-0.5.0/src/exporter/mod.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/Nping-0.6.1/src/exporter/mod.rs 2026-01-25 09:48:45.000000000 +0100 @@ -0,0 +1,6 @@ +mod metric; +mod runner; + +pub use metric::PrometheusMetrics; +pub use metric::http_server; +pub use runner::spawn_ping_workers; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/exporter/runner.rs new/Nping-0.6.1/src/exporter/runner.rs --- old/Nping-0.5.0/src/exporter/runner.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/Nping-0.6.1/src/exporter/runner.rs 2026-01-25 09:48:45.000000000 +0100 @@ -0,0 +1,70 @@ +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::thread; +use std::time::Duration; + +use pinger::{ping, PingOptions, PingResult}; + +use crate::exporter::PrometheusMetrics; + +pub fn spawn_ping_workers( + targets: Vec<(String, String)>, + interval: Duration, + running: Arc<AtomicBool>, + metrics: Arc<PrometheusMetrics>, +) -> Vec<thread::JoinHandle<()>> { + targets + .into_iter() + .map(|(addr, ip)| { + let running = running.clone(); + let metrics = metrics.clone(); + let interval = interval; + thread::spawn(move || run_ping_loop(addr, ip, interval, running, metrics)) + }) + .collect() +} + +fn run_ping_loop( + addr: String, + ip: String, + interval: Duration, + running: Arc<AtomicBool>, + metrics: Arc<PrometheusMetrics>, +) { + let options = PingOptions::new(ip.clone(), interval, None); + let stream = match ping(options) { + Ok(stream) => stream, + Err(err) => { + eprintln!("host({}) ping err, reason: ping init failed, err: {}", ip, err); + return; + } + }; + + while running.load(Ordering::Relaxed) { + match stream.recv() { + Ok(PingResult::Pong(duration, _size)) => { + let rtt_ms = duration.as_secs_f64() * 1000.0; + metrics.record_ping_success(&addr, &ip, rtt_ms); + } + Ok(PingResult::Timeout(_)) => { + metrics.record_ping_timeout(&addr, &ip); + } + Ok(PingResult::PingExited(status, err)) => { + if status.code() != Some(0) { + eprintln!( + "host({}) ping err, reason: ping exited, status: {} err: {}", + ip, err, status + ); + metrics.record_ping_error(&addr, &ip); + } + } + Ok(PingResult::Unknown(msg)) => { + eprintln!("host({}) ping err, reason: unknown, err: {}", ip, msg); + metrics.record_ping_error(&addr, &ip); + } + Err(err) => { + eprintln!("host({}) ping err, reason: recv failed, err: {}", ip, err); + metrics.record_ping_error(&addr, &ip); + } + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/main.rs new/Nping-0.6.1/src/main.rs --- old/Nping-0.5.0/src/main.rs 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/src/main.rs 2026-01-25 09:48:45.000000000 +0100 @@ -5,30 +5,51 @@ mod ui; mod ping_event; mod data_processor; +mod exporter; -use clap::Parser; +use clap::{Parser, Subcommand}; use std::collections::{HashSet, VecDeque}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use tokio::{task, runtime::Builder}; +use std::time::Duration; +use ratatui::crossterm::event::{self, Event, KeyCode, KeyModifiers}; +use ratatui::crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use tokio::{task, runtime::Builder, signal}; use crate::ip_data::IpData; use crate::ping_event::PingEvent; use crate::data_processor::start_data_processor; use std::sync::mpsc; use crate::network::send_ping; +use crate::exporter::{PrometheusMetrics, http_server, spawn_ping_workers}; + +struct RawModeGuard; + +impl RawModeGuard { + fn new() -> std::io::Result<Self> { + enable_raw_mode()?; + Ok(Self) + } +} + +impl Drop for RawModeGuard { + fn drop(&mut self) { + let _ = disable_raw_mode(); + } +} #[derive(Parser, Debug)] #[command( - version = "v0.5.0", + version = "v0.6.1", author = "hanshuaikang<https://github.com/hanshuaikang>", - about = "🏎 Nping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations" + about = "🏎 NBping mean NB Ping, A Ping Tool in Rust with Real-Time Data and Visualizations" )] struct Args { /// Target IP address or hostname to ping - #[arg(help = "target IP address or hostname to ping", required = true)] + #[arg(help = "target IP address or hostname to ping", required = false)] target: Vec<String>, /// Number of pings to send, when count is 0, the maximum number of pings per address is calculated - #[arg(short, long, default_value_t = 65535, help = "Number of pings to send")] + #[arg(short, long, default_value_t = 0, help = "Number of pings to send")] count: usize, /// Interval in seconds between pings @@ -51,6 +72,27 @@ #[arg(short = 'o', long = "output", help = "Output file to save ping results")] output: Option<String>, + + #[command(subcommand)] + command: Option<Commands>, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Exporter mode for monitoring + Exporter { + /// Target IP addresses or hostnames to ping + #[arg(help = "target IP addresses or hostnames to ping", required = true)] + target: Vec<String>, + + /// Interval in seconds between pings + #[arg(short, long, default_value_t = 1, help = "Interval in seconds between pings")] + interval: i32, + + /// Prometheus metrics HTTP port + #[arg(short, long, default_value_t = 9090, help = "Prometheus metrics HTTP port")] + port: u16, + }, } @@ -58,45 +100,69 @@ // parse command line arguments let args = Args::parse(); - // set Ctrl+C and q and esc to exit - let running = Arc::new(Mutex::new(true)); - - // check output file - if let Some(ref output_path) = args.output { - if std::path::Path::new(output_path).exists() { - eprintln!("Output file already exists: {}", output_path); - std::process::exit(1); - } - } - + match args.command { + Some(Commands::Exporter { target, interval, port }) => { + let worker_threads = (target.len() + 1).max(1); + // Create tokio runtime for Exporter mode + let rt = Builder::new_multi_thread() + .worker_threads(worker_threads) + .enable_all() + .build()?; + + let res = rt.block_on(run_exporter_mode(target, interval, port)); + + // if error print error message and exit + if let Err(err) = res { + eprintln!("{}", err); + std::process::exit(1); + } + }, + None => { + // Default ping mode + if args.target.is_empty() { + eprintln!("Error: target IP address or hostname is required"); + std::process::exit(1); + } + // set Ctrl+C and q and esc to exit + let running = Arc::new(Mutex::new(true)); - // after de-duplication, the original order is still preserved - let mut seen = HashSet::new(); - let targets: Vec<String> = args.target.into_iter() - .filter(|item| seen.insert(item.clone())) - .collect(); + // check output file + if let Some(ref output_path) = args.output { + if std::path::Path::new(output_path).exists() { + eprintln!("Output file already exists: {}", output_path); + std::process::exit(1); + } + } - // Calculate worker threads based on IP count - let ip_count = if targets.len() == 1 && args.multiple > 0 { - args.multiple as usize - } else { - targets.len() - }; - let worker_threads = (ip_count + 1).max(1); - - // Create tokio runtime with specific worker thread count - let rt = Builder::new_multi_thread() - .worker_threads(worker_threads) - .enable_all() - .build()?; - - let res = rt.block_on(run_app(targets, args.count, args.interval, running.clone(), args.force_ipv6, args.multiple, args.view_type, args.output)); - - // if error print error message and exit - if let Err(err) = res { - eprintln!("{}", err); - std::process::exit(1); + // after de-duplication, the original order is still preserved + let mut seen = HashSet::new(); + let targets: Vec<String> = args.target.into_iter() + .filter(|item| seen.insert(item.clone())) + .collect(); + + // Calculate worker threads based on IP count + let ip_count = if targets.len() == 1 && args.multiple > 0 { + args.multiple as usize + } else { + targets.len() + }; + let worker_threads = (ip_count + 1).max(1); + + // Create tokio runtime with specific worker thread count + let rt = Builder::new_multi_thread() + .worker_threads(worker_threads) + .enable_all() + .build()?; + + let res = rt.block_on(run_app(targets, args.count, args.interval, running.clone(), args.force_ipv6, args.multiple, args.view_type, args.output)); + + // if error print error message and exit + if let Err(err) = res { + eprintln!("{}", err); + std::process::exit(1); + } + } } Ok(()) } @@ -243,4 +309,144 @@ draw::restore_terminal(&mut terminal_guard.lock().unwrap().terminal.as_mut().unwrap())?; Ok(()) -} \ No newline at end of file +} + +async fn run_exporter_mode( + targets: Vec<String>, + interval: i32, + port: u16, +) -> Result<(), Box<dyn std::error::Error>> { + // 创建 Prometheus metrics 收集器 + let prometheus_metrics = Arc::new(PrometheusMetrics::new()?); + + // 创建信号处理通道 + let running = Arc::new(AtomicBool::new(true)); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + let shutdown_tx = Arc::new(Mutex::new(Some(shutdown_tx))); + + // 设置信号处理 + let running_for_signal = running.clone(); + let shutdown_tx_for_signal = shutdown_tx.clone(); + tokio::spawn(async move { + match signal::ctrl_c().await { + Ok(()) => { + println!("\nReceived Ctrl+C, shutting down gracefully..."); + running_for_signal.store(false, Ordering::Relaxed); + + // 发送关闭信号给 HTTP 服务器 + if let Some(tx) = shutdown_tx_for_signal.lock().unwrap().take() { + let _ = tx.send(()); + } + } + Err(err) => { + eprintln!("Unable to listen for shutdown signal: {}", err); + } + } + }); + + // 去重目标地址,同时保留原始顺序 + let mut seen = std::collections::HashSet::new(); + let targets: Vec<String> = targets.into_iter() + .filter(|item| seen.insert(item.clone())) + .collect(); + + if targets.is_empty() { + return Err("No valid targets provided".into()); + } + + // 解析目标地址为 IP 地址 + let mut target_pairs = Vec::new(); + for target in &targets { + let ip = network::get_host_ipaddr(target, false)?; + target_pairs.push((target.clone(), ip)); + } + + println!("🚀 NBPing Prometheus Exporter Mode Started"); + println!("┌─────────────────────────────────────────────────────────"); + println!("│ Targets : {} host(s)", targets.len()); + for (i, target) in targets.iter().enumerate() { + if i < 5 { + println!("│ : {}", target); + } else if i == 5 { + println!("│ : ... ({} more)", targets.len() - 5); + break; + } + } + println!("│ Interval : {} seconds", interval); + println!("│ Metrics port: {}", port); + println!("│ Metrics : http://0.0.0.0:{}/metrics", port); + println!("│ Actions : Press Ctrl+C or q to stop"); + println!("└─────────────────────────────────────────────────────────"); + + // 启动 HTTP metrics 服务器 + let metrics_addr = format!("0.0.0.0:{}", port).parse()?; + let metrics_for_server = prometheus_metrics.clone(); + let metrics_task = task::spawn(async move { + http_server::start_metrics_server( + metrics_for_server, + metrics_addr, + shutdown_rx, + ).await + }); + + let interval_ms = interval * 1000; + let ping_threads = spawn_ping_workers( + target_pairs, + Duration::from_millis(interval_ms as u64), + running.clone(), + prometheus_metrics.clone(), + ); + + // Listen for q/esc to exit (exporter mode only) + let running_for_key = running.clone(); + let shutdown_tx_for_key = shutdown_tx.clone(); + let key_listener = std::thread::spawn(move || { + let _raw_mode = match RawModeGuard::new() { + Ok(guard) => guard, + Err(_) => return, + }; + + while running_for_key.load(Ordering::Relaxed) { + if let Ok(true) = event::poll(Duration::from_millis(50)) { + if let Ok(Event::Key(key)) = event::read() { + match key.code { + KeyCode::Char('q') | KeyCode::Esc => { + running_for_key.store(false, Ordering::Relaxed); + if let Some(tx) = shutdown_tx_for_key.lock().unwrap().take() { + let _ = tx.send(()); + } + break; + } + KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { + running_for_key.store(false, Ordering::Relaxed); + if let Some(tx) = shutdown_tx_for_key.lock().unwrap().take() { + let _ = tx.send(()); + } + break; + } + _ => {} + } + } + } + } + }); + + // Wait for metrics server to shut down + let metrics_result = metrics_task.await?; + let metrics_error = metrics_result.err(); + + running.store(false, Ordering::Relaxed); + + // Wait for ping threads to complete + for handle in ping_threads { + let _ = handle.join(); + } + + let _ = key_listener.join(); + + if let Some(err) = metrics_error { + return Err(err); + } + + Ok(()) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/network.rs new/Nping-0.6.1/src/network.rs --- old/Nping-0.5.0/src/network.rs 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/src/network.rs 2026-01-25 09:48:45.000000000 +0100 @@ -95,11 +95,21 @@ // star ping let stream = ping(options)?; - for _ in 0..self.count { + let mut ping_count = 0; + loop { // if ctrl+c is pressed, break the loop if !*self.running.lock().unwrap() { break; } + + // if count is not 0, check if we've reached the limit + if self.count > 0 { + if ping_count >= self.count { + break; + } + ping_count += 1; + } + match stream.recv() { Ok(result) => { match result { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Nping-0.5.0/src/ui/point.rs new/Nping-0.6.1/src/ui/point.rs --- old/Nping-0.5.0/src/ui/point.rs 2025-09-10 17:15:13.000000000 +0200 +++ new/Nping-0.6.1/src/ui/point.rs 2026-01-25 09:48:45.000000000 +0100 @@ -39,7 +39,7 @@ // draw legend let legend = Line::from(vec