Showing preview only (864K chars total). Download the full file or copy to clipboard to get everything.
Repository: imsnif/bandwhich
Branch: main
Commit: cbcffe8816a8
Files: 124
Total size: 814.6 KB
Directory structure:
gitextract_5glekt08/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yaml
│ ├── publish-crate.yaml
│ ├── release.yaml
│ └── require-changelog-for-PRs.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Cross.toml
├── INSTALL.md
├── LICENSE.md
├── README.md
├── build.rs
├── rustfmt.toml
└── src/
├── cli.rs
├── display/
│ ├── components/
│ │ ├── display_bandwidth.rs
│ │ ├── header_details.rs
│ │ ├── help_text.rs
│ │ ├── layout.rs
│ │ ├── mod.rs
│ │ ├── snapshots/
│ │ │ └── bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap
│ │ └── table.rs
│ ├── mod.rs
│ ├── raw_terminal_backend.rs
│ ├── ui.rs
│ └── ui_state.rs
├── main.rs
├── network/
│ ├── connection.rs
│ ├── dns/
│ │ ├── client.rs
│ │ ├── mod.rs
│ │ └── resolver.rs
│ ├── mod.rs
│ ├── sniffer.rs
│ └── utilization.rs
├── os/
│ ├── errors.rs
│ ├── linux.rs
│ ├── lsof.rs
│ ├── lsof_utils.rs
│ ├── mod.rs
│ ├── shared.rs
│ └── windows.rs
└── tests/
├── cases/
│ ├── mod.rs
│ ├── raw_mode.rs
│ ├── snapshots/
│ │ ├── bandwhich__tests__cases__raw_mode__bi_directional_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_connections_from_remote_address.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_different_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_single_connection.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_processes_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__no_resolve_mode.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_ip_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_process_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes_bi_directional.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_one_process.snap
│ │ ├── bandwhich__tests__cases__raw_mode__traffic_with_host_names.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_addresses.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_connections.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_processes.snap
│ │ ├── bandwhich__tests__cases__ui__basic_processes_with_dns_queries.snap
│ │ ├── bandwhich__tests__cases__ui__basic_startup-2.snap
│ │ ├── bandwhich__tests__cases__ui__basic_startup.snap
│ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic-2.snap
│ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic.snap
│ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__ui__no_resolve_mode-2.snap
│ │ ├── bandwhich__tests__cases__ui__no_resolve_mode.snap
│ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic-2.snap
│ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__ui__pause_by_space-2.snap
│ │ ├── bandwhich__tests__cases__ui__pause_by_space.snap
│ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab-2.snap
│ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names-2.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event-2.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event.snap
│ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames-2.snap
│ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_addresses.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_connections.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_processes.snap
│ │ ├── bandwhich__tests__cases__ui__two_windows_split_horizontally.snap
│ │ └── bandwhich__tests__cases__ui__two_windows_split_vertically.snap
│ ├── test_utils.rs
│ └── ui.rs
├── fakes/
│ ├── fake_input.rs
│ ├── fake_output.rs
│ └── mod.rs
└── mod.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [imsnif]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 30
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 30
groups:
github-actions:
patterns:
- "*"
================================================
FILE: .github/workflows/ci.yaml
================================================
name: ci
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
jobs:
get-msrv:
name: Get declared MSRV from Cargo.toml
runs-on: ubuntu-latest
outputs:
msrv: ${{ steps.get_msrv.outputs.msrv }}
steps:
- name: Install ripgrep
run: sudo apt-get install -y ripgrep
- name: Checkout repository
uses: actions/checkout@v6
- name: Get MSRV
id: get_msrv
run: rg '^\s*rust-version\s*=\s*"(\d+(\.\d+){0,2})"' --replace 'msrv=$1' Cargo.toml >> "$GITHUB_OUTPUT"
check-fmt:
name: Check code formatting
runs-on: ubuntu-latest
needs: get-msrv
strategy:
fail-fast: false
matrix:
rust:
- ${{ needs.get-msrv.outputs.msrv }}
- stable
- nightly
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
test:
name: Test on each target
needs: get-msrv
env:
# use sccache
# It's too much of a hassle to set up sccache in cross.
# See https://github.com/cross-rs/cross/wiki/Recipes#sccache.
SCCACHE_GHA_ENABLED: ${{ matrix.cargo == 'cargo' && 'true' || 'false'}}
RUSTC_WRAPPER: ${{ matrix.cargo == 'cargo' && 'sccache' || '' }}
# Emit backtraces on panics.
RUST_BACKTRACE: 1
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
build:
- android-aarch64
- linux-aarch64-gnu
- linux-aarch64-musl
- linux-armv7-gnueabihf
- linux-armv7-musleabihf
- linux-x64-gnu
- linux-x64-musl
- macos-aarch64
- macos-x64
- windows-x64-msvc
rust:
- ${{ needs.get-msrv.outputs.msrv }}
- stable
- nightly
include:
- os: ubuntu-latest # default
- cargo: cargo # default; overwrite with `cross` if necessary
- build: android-aarch64
target: aarch64-linux-android
cargo: cross
- build: linux-aarch64-gnu
target: aarch64-unknown-linux-gnu
cargo: cross
- build: linux-aarch64-musl
target: aarch64-unknown-linux-musl
cargo: cross
- build: linux-armv7-gnueabihf
target: armv7-unknown-linux-gnueabihf
cargo: cross
- build: linux-armv7-musleabihf
target: armv7-unknown-linux-musleabihf
cargo: cross
- build: linux-x64-gnu
target: x86_64-unknown-linux-gnu
- build: linux-x64-musl
target: x86_64-unknown-linux-musl
- build: macos-aarch64
# Go back ot `macos-latest` after migration is complete
# See https://github.blog/changelog/2024-04-01-macos-14-sonoma-is-generally-available-and-the-latest-macos-runner-image/.
os: macos-14
target: aarch64-apple-darwin
- build: macos-x64
os: macos-14
target: x86_64-apple-darwin
- build: windows-x64-msvc
os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
components: clippy
- name: Set up sccache
# It's too much of a hassle to set up sccache in cross.
# See https://github.com/cross-rs/cross/wiki/Recipes#sccache.
if: matrix.cargo == 'cargo'
uses: mozilla-actions/sccache-action@v0.0.9
- name: Install cross
if: matrix.cargo == 'cross'
# The latest release of `cross` is not able to build/link for `aarch64-linux-android`
# See: https://github.com/cross-rs/cross/issues/1222
# This is fixed on `main` but not yet released. To avoid a breakage somewhen in the future
# pin the cross revision used to the latest HEAD at 04/2024.
# Go back to taiki-e/install-action once cross 0.3 is released.
uses: taiki-e/cache-cargo-install-action@v3
with:
tool: cross
git: https://github.com/cross-rs/cross.git
rev: 085092c
- name: Build
id: build
run: ${{ matrix.cargo }} build --verbose --target ${{ matrix.target }}
# This is useful for debugging problems when the expected build artifacts
# (like shell completions and man pages) aren't generated.
- name: Show build.rs stderr
shell: bash
run: |
# it's probably okay to assume no spaces?
STDERR_FILES=$(find "./target/debug" -name stderr | grep bandwhich || true)
for FILE in $STDERR_FILES; do
echo "::group::$FILE"
cat "$FILE"
echo "::endgroup::"
done
- name: Run clippy
run: ${{ matrix.cargo }} clippy --all-targets --target ${{ matrix.target }} -- -D warnings
- name: Install npcap on Windows
# PRs from other repositories cannot be trusted with repository secrets
if: matrix.os == 'windows-latest' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
env:
NPCAP_OEM_URL: ${{ secrets.NPCAP_OEM_URL }}
run: |
Invoke-WebRequest -Uri "$env:NPCAP_OEM_URL" -OutFile "$env:TEMP/npcap-oem.exe"
# for this ridiculous `&` syntax alone, I'd rather use COBOL than Powershell
# see https://stackoverflow.com/a/1674950/5637701
& "$env:TEMP/npcap-oem.exe" /S
- name: Run tests
id: run_tests
# npcap is needed to run tests on Windows, so unfortunately we cannot run tests
# on PRs from other repositories
if: matrix.os != 'windows-latest' || github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
env:
# make insta generate new snapshots in CI
INSTA_UPDATE: new
run: ${{ matrix.cargo }} test --all-targets --target ${{ matrix.target }}
- name: Upload snapshots of failed tests
if: ${{ failure() && steps.run_tests.outcome == 'failure' }}
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.os }}-${{ matrix.rust }}-failed_snapshots
path: '**/*.snap.new'
- name: Upload binaries
if: ${{ success() || steps.build.outcome == 'success' }}
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}-${{ matrix.rust }}
path: |
target/${{ matrix.target }}/debug/bandwhich
target/${{ matrix.target }}/debug/bandwhich.exe
target/${{ matrix.target }}/debug/bandwhich.pdb
================================================
FILE: .github/workflows/publish-crate.yaml
================================================
# This workflow triggers when a stable release is published on GitHub.
#
# The crates.io token used for publishing was created under the account of
# cyqsimon <28627918+cyqsimon@users.noreply.github.com> and was added to this
# repository's secrets by Aram Drevekenin <aram@poor.dev>.
name: publish-crate
on:
release:
types:
- released
workflow_dispatch:
jobs:
publish-to-crates-io:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Run cargo publish
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
================================================
FILE: .github/workflows/release.yaml
================================================
# The way this works is the following:
#
# - create-release job runs purely to initialize the GitHub release itself
# and to output upload_url for the following job.
#
# - build-release job runs only once create-release is finished. It gets
# the release upload URL from create-release job outputs, then builds
# the release executables for each supported platform and attaches them
# as release assets to the previously created release.
#
# Reference:
# - https://eugene-babichenko.github.io/blog/2020/05/09/github-actions-cross-platform-auto-releases/
#
# Currently this workflow only ever creates drafts; the draft should be checked
# and then released manually.
name: release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
jobs:
create-release:
name: create-release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: create_release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
# draft: ${{ github.event_name == 'workflow_dispatch' }}
draft: true
prerelease: false
build-release:
name: build-release
needs: create-release
runs-on: ${{ matrix.os }}
env:
# Emit backtraces on panics.
RUST_BACKTRACE: 1
BANDWHICH_GEN_DIR: assets
PKGDIR: github-actions-pkg
strategy:
matrix:
build:
- android-aarch64
- linux-aarch64-gnu
- linux-aarch64-musl
- linux-armv7-gnueabihf
- linux-armv7-musleabihf
- linux-x64-gnu
- linux-x64-musl
- macos-aarch64
- macos-x64
- windows-x64-msvc
include:
- os: ubuntu-latest # default
- cargo: cargo # default; overwrite with `cross` if necessary
- build: android-aarch64
target: aarch64-linux-android
cargo: cross
- build: linux-aarch64-gnu
target: aarch64-unknown-linux-gnu
cargo: cross
- build: linux-aarch64-musl
target: aarch64-unknown-linux-musl
cargo: cross
- build: linux-armv7-gnueabihf
target: armv7-unknown-linux-gnueabihf
cargo: cross
- build: linux-armv7-musleabihf
target: armv7-unknown-linux-musleabihf
cargo: cross
- build: linux-x64-gnu
target: x86_64-unknown-linux-gnu
- build: linux-x64-musl
target: x86_64-unknown-linux-musl
- build: macos-aarch64
# Go back ot `macos-latest` after migration is complete
# See https://github.blog/changelog/2024-04-01-macos-14-sonoma-is-generally-available-and-the-latest-macos-runner-image/.
os: macos-14
target: aarch64-apple-darwin
- build: macos-x64
os: macos-14
target: x86_64-apple-darwin
- build: windows-x64-msvc
os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: ${{ matrix.target }}
- name: Install cross
if: matrix.cargo == 'cross'
# The latest release of `cross` is not able to build/link for `aarch64-linux-android`
# See: https://github.com/cross-rs/cross/issues/1222
# This is fixed on `main` but not yet released. To avoid a breakage somewhen in the future
# pin the cross revision used to the latest HEAD at 04/2024.
# Go back to taiki-e/install-action once cross 0.3 is released.
uses: taiki-e/cache-cargo-install-action@v3
with:
tool: cross
git: https://github.com/cross-rs/cross.git
rev: 085092c
- name: Build release binary
shell: bash
env:
RUSTFLAGS: "-C strip=symbols"
run: |
mkdir -p "$BANDWHICH_GEN_DIR"
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
- name: Collect build artifacts
shell: bash
env:
BANDWHICH_BIN: ${{ contains(matrix.os, 'windows') && 'bandwhich.exe' || 'bandwhich' }}
run: |
mkdir "$PKGDIR"
mv "target/${{ matrix.target }}/release/$BANDWHICH_BIN" "$PKGDIR"
mv "$BANDWHICH_GEN_DIR" "$PKGDIR"
- name: Tar release (Unix)
if: ${{ !contains(matrix.os, 'windows') }}
working-directory: ${{ env.PKGDIR }}
run: tar cvfz bandwhich-${{ github.ref_name }}-${{ matrix.target }}.tar.gz *
- name: Zip release (Windows)
if: contains(matrix.os, 'windows')
working-directory: ${{ env.PKGDIR }}
run: Compress-Archive -Path * -DestinationPath bandwhich-${{ github.ref_name }}-${{ matrix.target }}.zip
- name: Upload release archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARCHIVE_EXT: ${{ contains(matrix.os, 'windows') && 'zip' || 'tar.gz' }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.PKGDIR }}/bandwhich-${{ github.ref_name }}-${{ matrix.target }}.${{ env.ARCHIVE_EXT }}
asset_name: bandwhich-${{ github.ref_name }}-${{ matrix.target }}.${{ env.ARCHIVE_EXT }}
asset_content_type: application/octet-stream
================================================
FILE: .github/workflows/require-changelog-for-PRs.yml
================================================
name: Changelog
on:
pull_request:
env:
PR_NUMBER: ${{ github.event.number }}
PR_BASE: ${{ github.base_ref }}
jobs:
get-submitter:
name: Get the username of the PR submitter
runs-on: ubuntu-latest
outputs:
submitter: ${{ steps.get-submitter.outputs.submitter }}
steps:
# cannot use `github.actor`: the triggering commit may be authored by a maintainer
- name: Get PR submitter
id: get-submitter
run: curl -sSfL https://api.github.com/repos/imsnif/bandwhich/pulls/${PR_NUMBER} | jq -r '"submitter=" + .user.login' | tee -a $GITHUB_OUTPUT
check-changelog:
name: Check for changelog entry
needs: get-submitter
env:
PR_SUBMITTER: ${{ needs.get-submitter.outputs.submitter }}
runs-on: ubuntu-latest
# allow dependabot PRs to have no changelog
if: ${{ needs.get-submitter.outputs.submitter != 'dependabot[bot]' }}
steps:
- uses: actions/checkout@v6
- name: Fetch PR base
run: git fetch --no-tags --prune --depth=1 origin
- name: Search for added line in changelog
run: |
ADDED=$(git diff -U0 "origin/${PR_BASE}" HEAD -- CHANGELOG.md | grep -P '^\+[^\+].+$')
echo "Added lines in CHANGELOG.md:"
echo "$ADDED"
echo "Grepping for PR info:"
grep -P "(#|pull/)${PR_NUMBER}\\b.*@${PR_SUBMITTER}\\b" <<< "$ADDED"
================================================
FILE: .gitignore
================================================
target/
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]
### Fixed
* Fix Ctrl+C handling to use SIGINT signal instead of keypress #491 - @chiranjeevi-max
* Update CONTRIBUTING information #438 - @YJDoc2 @cyqsimon
* Fix new clippy lint #457 - @cyqsimon
* Apply new clippy lints #468 - @cyqsimon
### Changed
* Bump msrv to 1.75.0 #439 - @YJDoc2
* Replace `derivative` with `derive_more` #439 - @YJDoc2
* Add build optimizations for release binary #434 - @pando85
* Minor cleanup and optimisations #435 - @cyqsimon
* Bump `pnet` & `packet-builder` #444 - @cyqsimon
* Switch from anyhow to eyre #450 - @cyqsimon
* Manually bump all dependencies #456 - @cyqsimon
* Bump MSRV to 1.82.0 - @cyqsimon
## [0.23.1] - 2024-10-09
### Fixed
* CI: Use Powershell Compress-Archive to create Windows binary zip #424 - @cyqsimon
* Exit gracefully when there is a broken pipe error #429 - @sigmaSd
* Fix breaking changes of sysinfo crate #431 - @cyqsimon
* Fix `clippy::needless_lifetimes` warnings on nightly #432 - @cyqsimon
## [0.23.0] - 2024-08-17
### Fixed
* Remove redundant imports #377 - @cyqsimon
* CI: use GitHub API to exempt dependabot from changelog requirement #378 - @cyqsimon
* Remove unnecessary logging synchronisation #381 - @cyqsimon
* Apply suggestions from new clippy lint clippy::assigning_clones #382 - @cyqsimon
* Fix IPv6 socket detect logic #383 - @cyqsimon
* Support build for `target_os` `android` #384 - @flxo
* Fix Windows FP discrepancy issue in test #400 - @cyqsimon
### Added
* CI: include generated assets in release archive #359 - @cyqsimon
* Add PID column to the process table #379 - @notjedi
* CI: add builds for target `aarch64-linux-android` #384 - @flxo
* CI: Keep GitHub Actions up to date with GitHub's Dependabot #403 - @cclauss
* CI: Enable more cross-compiled builds #401 - @cyqsimon
* CI: use sccache to speed up CI #408 - @cyqsimon
### Changed
* CI: strip release binaries for all targets #358 - @cyqsimon
* Bump MSRV to 1.74 (required by clap 4.5; see #373)
* CI: Configure dependabot grouping #395 - @cyqsimon
* CI refactor #399 - @cyqsimon
* CI: Temporarily disable UI tests #406 - @cyqsimon
* Update README #407 - @cyqsimon
* Update usage in README #409 - @cyqsimon
### Removed
* CI: Remove musl-tools install step #402 - @cyqsimon
## [0.22.2] - 2024-01-28
### Added
* Generate completion & manpage #357 - @cyqsimon
## [0.22.1] - 2024-01-28
### Fixed
* Hot fix a Windows compile issue #356 - @cyqsimon
## [0.22.0] - 2024-01-28
### Added
* Log unresolved processes in more detail + general refactor #318 - @cyqsimon
* Display bandwidth in different unit families #328 - @cyqsimon
* CI: ensure a changelog entry exists for each PR #331 - @cyqsimon
* Show interface names #340 - @ilyes-ced
### Changed
* Table formatting logic overhaul #305 - @cyqsimon
* Refactor OsInputOutput (combine interfaces & frames into single Vec) #310 - @cyqsimon
### Removed
* Reorganise & cleanup packaging code/resources #329 - @cyqsimon
### Fixed
* Make logging race-free using a global lock & macro #309 - @cyqsimon
* Use once_cell::sync::Lazy to make regex usage more ergonomic #313 - @cyqsimon
* Fix vague CLI option documentation; closes #314 #316 - @cyqsimon
## [0.21.1] - 2023-10-16
### Fixed
* Ignore connections that fail parsing instead of panicking on BSD (https://github.com/imsnif/bandwhich/pull/288) - [@cyqsimon](https://github.com/cyqsimon)
* Add missing version flag to CLI (https://github.com/imsnif/bandwhich/pull/290) - [@tranzystorek-io](https://github.com/tranzystorek-io)
* Various minor codestyle changes - [@cyqsimon](https://github.com/cyqsimon)
* Handle IPv4-mapped IPv6 addresses when resolving connection owner (https://github.com/imsnif/bandwhich/commit/76956cf) - [@cyqsimon](https://github.com/cyqsimon)
* Bump `rustix` dependencies to fix a memory leak (https://github.com/imsnif/bandwhich/commit/bc10c07) - [@cyqsimon](https://github.com/cyqsimon)
### Added
* Logging infrastrure (https://github.com/imsnif/bandwhich/pull/302) - [@cyqsimon](https://github.com/cyqsimon)
## [0.21.0] - 2023-09-19
### Fixed
* Fixed resolv.conf errors on systems with trust-ad (https://github.com/imsnif/bandwhich/pull/201) - [@JoshLambda](https://github.com/JoshLambda)
* Fixed build issues by updating various dependencies
* migrate out-of-date dependency `structopt` to `clap` (https://github.com/imsnif/bandwhich/pull/285) - [@Liyixin95](https://github.com/Liyixin95)
## [0.20.0] - 2020-10-15
### Added
* New command line argument to explicitly specify a DNS server to use (https://github.com/imsnif/bandwhich/pull/193) - [@imsnif](https://github.com/imsnif)
## [0.19.0] - 2020-09-29
### Fixed
* Fixed resolv.conf parsing for rDNS in some cases (https://github.com/imsnif/bandwhich/pull/184) - [@Ma27](https://github.com/Ma27)
* Cross platform window resizing (fixes momentary UI break when resizing window on Windows) (https://github.com/imsnif/bandwhich/pull/186) - [@remgodow](https://github.com/remgodow)
* CI: build binaries using github actions (https://github.com/imsnif/bandwhich/pull/181) - [@remgodow](https://github.com/remgodow)
* Fix build on FreeBSD (https://github.com/imsnif/bandwhich/pull/189) - [@imsnif](https://github.com/imsnif)
* Upgrade TUI to latest version (https://github.com/imsnif/bandwhich/pull/190) - [@imsnif](https://github.com/imsnif)
* Try to reconnect to disconnected interfaces (https://github.com/imsnif/bandwhich/pull/191) - [@thepacketgeek](https://github.com/thepacketgeek)
## [0.18.1] - 2020-09-11
* HOTFIX: do not build windows build-dependencies on other platforms
## [0.18.0] - 2020-09-11
### Added
* Future windows infrastructure support (should not have any user facing effect) (https://github.com/imsnif/bandwhich/pull/179) - [@remgodow](https://github.com/remgodow)
* Windows build and run support (https://github.com/imsnif/bandwhich/pull/180) - [@remgodow](https://github.com/remgodow)
### Fixed
* Update and improve MAN page (https://github.com/imsnif/bandwhich/pull/182) - [@Nudin](https://github.com/Nudin)
## [0.17.0] - 2020-09-02
### Added
* Add delimiters between refreshes in raw mode for easier parsing (https://github.com/imsnif/bandwhich/pull/175) - [@sigmaSd](https://github.com/sigmaSd)
### Fixed
* Truncate Chinese characters properly (https://github.com/imsnif/bandwhich/pull/177) - [@zxlzy](https://github.com/zxlzy)
* Moved to mebi/gibi/tibi bytes to improve bandwidth accuracy and reduce ambiguity (https://github.com/imsnif/bandwhich/pull/178) - [@imsnif](https://github.com/imsnif)
## [0.16.0] - 2020-07-13
### Fixed
* Allow filtering by processes/connections/remote-ips in raw-mode (https://github.com/imsnif/bandwhich/pull/174) - [@sigmaSd](https://github.com/sigmaSd)
* Changed repository trunk branch to "main" instead of "master".
## [0.15.0] - 2020-05-23
### Added
* Ability to change the window layout with <TAB> (https://github.com/imsnif/bandwhich/pull/118) - [@Louis-Lesage](https://github.com/Louis-Lesage)
* Show duration of current capture when running in "total utilization" mode. - [@Eosis](https://github.com/Eosis)
### Fixed
* Add terabytes as a display unit (for cumulative mode) (https://github.com/imsnif/bandwhich/pull/168) - [@TheLostLambda](https://github.com/TheLostLambda)
## [0.14.0] - 2020-05-03
### Fixed
* HOTFIX: remove pnet_bandwhich_fork dependency and upgrade to working version of pnet + packet_builder instead (this should hopefully not change anything)
## [0.13.0] - 2020-04-05
### Added
* Hide DNS queries by default. This can be overridden with `-s, --show-dns` (https://github.com/imsnif/bandwhich/pull/161) - [@olesh0](https://github.com/olehs0)
* Show cumulative utilization in "total utilization" mode. Trigger with `-t, --total-utilization` (https://github.com/imsnif/bandwhich/pull/155) - [@TheLostLambda](https://github.com/TheLostLambda)
### Fixed
* Fix the loss of large, merged packets (https://github.com/imsnif/bandwhich/pull/158) - [@TheLostLambda](https://github.com/TheLostLambda)
## [0.12.0] - 2020-03-01
### Added
* Add custom error handling (https://github.com/imsnif/bandwhich/pull/104) - [@captain-yossarian](https://github.com/captain-yossarian)
## [0.11.0] - 2020-01-25
### Added
* List unknown processes in processes table as well (https://github.com/imsnif/bandwhich/pull/132) - [@jcfvalente](https://github.com/jcfvalente)
* New layout (https://github.com/imsnif/bandwhich/pull/139) - [@imsnif](https://github.com/imsnif)
## [0.10.0] - 2020-01-18
### Added
* Support Ipv6 (https://github.com/imsnif/bandwhich/pull/70) - [@zhangxp1998](https://github.com/zhangxp1998)
* Select tables to render from the CLI (https://github.com/imsnif/bandwhich/pull/107) - [@chobeat](https://github.com/chobeat)
### Fixed
* VPN traffic sniffing on mac (https://github.com/imsnif/bandwhich/pull/129) - [@zhangxp1998](https://github.com/zhangxp1998)
## [0.9.0] - 2020-01-14
### Added
* Paused UI by pressing <SPACE> key. Does not affect raw mode. (https://github.com/imsnif/bandwhich/pull/106) - [@zhangxp1998](https://github.com/zhangxp1998)
* Mention setcap option in linux permission error. (https://github.com/imsnif/bandwhich/pull/108) - [@Ma27](https://github.com/Ma27)
* Display weighted average bandwidth for the past 5 seconds. (https://github.com/imsnif/bandwhich/pull/77) - [@zhangxp1998](https://github.com/zhangxp1998) + [@imsnif](https://github.com/imsnif)
* FreeBSD support. (https://github.com/imsnif/bandwhich/pull/110) - [@Erk-](https://github.com/Erk-)
* Pause help text. (https://github.com/imsnif/bandwhich/pull/111) - [@imsnif](https://github.com/imsnif)
### Fixed
* Upgrade trust-dns-resolver. (https://github.com/imsnif/bandwhich/pull/105) - [@bigtoast](https://github.com/bigtoast)
* Do not listen on inactive interfaces. (https://github.com/imsnif/bandwhich/pull/116) - [@zhangxp1998](https://github.com/zhangxp1998)
## [0.8.0] - 2020-01-09
### Added
- Brew formula and installation instructions for macOS (https://github.com/imsnif/bandwhich/pull/75) - [@imbsky](https://github.com/imbsky)
- UI change: add spacing between up and down rates for readability (https://github.com/imsnif/bandwhich/pull/58) - [@Calinou](https://github.com/Calinou)
- Support for wireguard interfaces (eg. for VPNs) (https://github.com/imsnif/bandwhich/pull/98) - [@Ma27](https://github.com/Ma27)
- Void linux installation instructions (https://github.com/imsnif/bandwhich/pull/102) - [@jcgruenhage](https://github.com/jcgruenhage)
- Arch installation with pacman (https://github.com/imsnif/bandwhich/pull/103) - [@kpcyrd](https://github.com/kpcyrd)
### Fixed
- Fix string conversion error on macOS (https://github.com/imsnif/bandwhich/pull/79) - [@zhangxp1998](https://github.com/zhangxp1998)
- Proper fix for macos no-screen-of-death (https://github.com/imsnif/bandwhich/pull/83) - [@zhangxp1998](https://github.com/zhangxp1998) + [@imsnif](https://github.com/imsnif)
- Fix UDP traffic not displayed issue #81 with (https://github.com/imsnif/bandwhich/pull/82) - [@zhangxp1998](https://github.com/zhangxp1998)
- Fix mac build (https://github.com/imsnif/bandwhich/pull/93) - [@imsnif](https://github.com/imsnif)
- Better procfs error handling (https://github.com/imsnif/bandwhich/pull/88) - [@zhangxp1998](https://github.com/zhangxp1998)
## [0.7.0] - 2020-01-05
### Added
- Running instructions (sudo) for a cargo installation (https://github.com/imsnif/bandwhich/pull/42) - [@LoyVanBeek](https://github.com/LoyVanBeek) + [@filalex77](https://github.com/filalex77)
- `setcap` instructions for linux instead of using sudo (https://github.com/imsnif/bandwhich/pull/57) - [@Calinou](https://github.com/Calinou)
- Installation instructions for Nix/NixOS (https://github.com/imsnif/bandwhich/pull/32) - [@filalex77](https://github.com/filalex77)
- MSRV and cargo installation instructions (https://github.com/imsnif/bandwhich/pull/66) - [@ebroto](https://github.com/ebroto)
### Fixed
- Repository URLs in Cargo.toml (https://github.com/imsnif/bandwhich/pull/43) - [@MatthieuBizien](https://github.com/MatthieuBizien)
- Skip interfaces with error (https://github.com/imsnif/bandwhich/pull/49) - [@Grishy](https://github.com/Grishy)
- MacOS no-screen-of-death workaround (https://github.com/imsnif/bandwhich/pull/56) - [@zhangxp1998](https://github.com/zhangxp1998)
- Reduce CPU utilization on linux (https://github.com/imsnif/bandwhich/pull/68) - [@ebroto](https://github.com/ebroto)
- Informative sudo error message (https://github.com/imsnif/bandwhich/pull/67) - [@Tobbeman](https://github.com/Tobbeman)
- Foreground text color for non-black terminals (https://github.com/imsnif/bandwhich/pull/65) - [@niiiil](https://github.com/niiiil)
- Do not truncate process names on MacOS (https://github.com/imsnif/bandwhich/pull/63) - [@zhangxp1998](https://github.com/zhangxp1998)
- Refactor tests into shared functionality (https://github.com/imsnif/bandwhich/pull/55) - [@chobeat](https://github.com/chobeat)
- Error on 0 interfaces found (https://github.com/imsnif/bandwhich/pull/69) - [@imsnif](https://github.com/imsnif)
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at aram@poor.dev. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CONTRIBUTING.md
================================================
Contributions of any kind are very welcome. If you'd like a new feature (or found a bug), please open an issue or a PR.
To set up your development environment:
1. Clone the project
2. `cargo run`, or if you prefer `cargo run -- -i <network interface name>` (you can often find out the name with `ifconfig` or `iwconfig`). You might need root privileges to run this application, so be sure to use (for example) sudo.
To run tests: `cargo test`
After tests, check the formatting: `cargo fmt -- --check`
Note that at the moment the tests do not test the os layer (anything in the `os` folder).
If you are stuck, unsure about how to approach an issue or would like some guidance,
you are welcome to [open an issue](https://github.com/imsnif/bandwhich/issues/new);
================================================
FILE: Cargo.toml
================================================
[package]
name = "bandwhich"
version = "0.23.1"
authors = [
"Aram Drevekenin <aram@poor.dev>",
"Eduardo Toledo <etoledom@icloud.com>",
"Eduardo Broto <ebroto@tutanota.com>",
"Kelvin Zhang <zhangxp1998@gmail.com>",
"Brooks Rady <b.j.rady@gmail.com>",
"cyqsimon <28627918+cyqsimon@users.noreply.github.com>",
]
categories = ["network-programming", "command-line-utilities"]
edition = "2021"
exclude = ["src/tests/*", "demo.gif"]
homepage = "https://github.com/imsnif/bandwhich"
keywords = ["networking", "utilization", "cli"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/imsnif/bandwhich"
rust-version = "1.82.0"
description = "Display current network utilization by process, connection and remote IP/hostname"
[features]
default = []
# UI tests temporarily disabled by default, until big refactor is done
ui_test = []
[dependencies]
async-trait = "0.1.88"
chrono = "0.4"
clap-verbosity-flag = "3.0.3"
clap = { version = "4.5.41", features = ["derive"] }
crossterm = "0.29.0"
ctrlc = "3.4"
derive_more = { version = "2.0.1", features = ["debug"] }
eyre = "0.6.12"
itertools = "0.14.0"
log = "0.4.27"
once_cell = "1.21.3"
pnet = "0.35.0"
pnet_macros_support = "0.35.0"
ratatui = "0.29.0"
resolv-conf = "0.7.4"
simplelog = "0.12.2"
thiserror = "2.0.12"
tokio = { version = "1.46", features = ["rt", "sync"] }
trust-dns-resolver = "0.23.2"
unicode-width = "0.2.0"
strum = { version = "0.27.1", features = ["derive"] }
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
procfs = "0.17.0"
[target.'cfg(any(target_os = "macos", target_os = "freebsd"))'.dependencies]
regex = "1.11.1"
[target.'cfg(target_os = "windows")'.dependencies]
netstat2 = "0.11.1"
sysinfo = "0.35.2"
[dev-dependencies]
insta = "1.43.1"
packet-builder = { version = "0.7.0", git = "https://github.com/cyqsimon/packet_builder.git", branch = "patch-pnet-0.35" }
pnet_base = "0.35.0"
regex = "1.11.1"
rstest = "0.25.0"
[build-dependencies]
clap = { version = "4.5.41", features = ["derive"] }
clap-verbosity-flag = "3.0.3"
clap_complete = "4.5.55"
clap_mangen = "0.2.28"
derive_more = { version = "2.0.1", features = ["debug"] }
eyre = "0.6.12"
strum = { version = "0.27.1", features = ["derive"] }
[target.'cfg(target_os = "windows")'.build-dependencies]
http_req = "0.14.0"
zip = "2.4.2"
[profile.release]
codegen-units = 1
opt-level = 3
lto = "fat"
panic = "abort"
strip = "symbols"
================================================
FILE: Cross.toml
================================================
[build.env]
passthrough = ["BANDWHICH_GEN_DIR"]
================================================
FILE: INSTALL.md
================================================
# Installation
- [Installation](#installation)
- [Arch Linux](#arch-linux)
- [Exherbo Linux](#exherbo-linux)
- [Nix/NixOS](#nixnixos)
- [Void Linux](#void-linux)
- [Fedora](#fedora)
- [macOS/Linux (using Homebrew)](#macoslinux-using-homebrew)
- [macOS (using MacPorts)](#macos-using-macports)
- [FreeBSD](#freebsd)
- [Cargo](#cargo)
## Arch Linux
```
pacman -S bandwhich
```
## Exherbo Linux
`bandwhich` is available in [rust repository](https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/bandwhich), and can be installed via `cave`:
```
cave resolve -x repository/rust
cave resolve -x bandwhich
```
## Nix/NixOS
`bandwhich` is available in [`nixpkgs`](https://github.com/nixos/nixpkgs/blob/master/pkgs/tools/networking/bandwhich/default.nix), and can be installed, for example, with `nix-env`:
```
nix-env -iA nixpkgs.bandwhich
```
## Void Linux
```
xbps-install -S bandwhich
```
## Fedora
`bandwhich` is available in [COPR](https://copr.fedorainfracloud.org/coprs/atim/bandwhich/), and can be installed via DNF:
```
sudo dnf copr enable atim/bandwhich -y && sudo dnf install bandwhich
```
## macOS/Linux (using Homebrew)
```
brew install bandwhich
```
## macOS (using MacPorts)
```
sudo port selfupdate
sudo port install bandwhich
```
## FreeBSD
```
pkg install bandwhich
```
or
```
cd /usr/ports/net-mgmt/bandwhich && make install clean
```
## Cargo
Regardless of OS, you can always fallback to the Rust package manager, `cargo`.
For installation instructions of the Rust toolchain, see [here](https://www.rust-lang.org/tools/install).
```
cargo install bandwhich
```
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2019 Aram Drevekenin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# bandwhich

This is a CLI utility for displaying current network utilization by process, connection and remote IP/hostname
## Table of contents
- [bandwhich](#bandwhich)
- [Table of contents](#table-of-contents)
- [Project status](#project-status)
- [How does it work?](#how-does-it-work)
- [Installation](#installation)
- [Downstream packaging status](#downstream-packaging-status)
- [Download a prebuilt binary](#download-a-prebuilt-binary)
- [Building from source](#building-from-source)
- [Cross-compiling](#cross-compiling)
- [Android](#android)
- [Post install (Linux)](#post-install-linux)
- [1. `setcap`](#1-setcap)
- [Capabilities explained](#capabilities-explained)
- [2. `sudo` (or alternative)](#2-sudo-or-alternative)
- [Post install (Windows)](#post-install-windows)
- [Usage](#usage)
- [Contributing](#contributing)
- [License](#license)
## Project status
This project is in passive maintenance. Critical issues will be addressed, but
no new features are being worked on. However, this is due to a lack of funding
and/or manpower more than anything else, so pull requests are more than welcome.
In addition, if you are able and willing to contribute to this project long-term,
we would like to invite you to apply for co-maintainership.
For more details, see [The Future of Bandwhich #275](https://github.com/imsnif/bandwhich/issues/275).
## How does it work?
`bandwhich` sniffs a given network interface and records IP packet size, cross referencing it with the `/proc` filesystem on linux, `lsof` on macOS, or using WinApi on windows. It is responsive to the terminal window size, displaying less info if there is no room for it. It will also attempt to resolve ips to their host name in the background using reverse DNS on a best effort basis.
## Installation
### Downstream packaging status
For detailed instructions for each platform, see [INSTALL.md](INSTALL.md).
<a href="https://repology.org/project/bandwhich/versions">
<img src="https://repology.org/badge/vertical-allrepos/bandwhich.svg?columns=3" alt="Packaging status">
</a>
### Download a prebuilt binary
We offer several generic binaries in [releases](https://github.com/imsnif/bandwhich/releases) for various OSes.
<table>
<thead>
<th>OS</th><th>Architecture</th><th>Support</th><th>Usage</th>
</thead>
<tbody>
<tr>
<td>Android</td><td>aarch64</td><td>Best effort</td>
<td>
<p>All modern Android devices.</p>
<p>Note that this is a pure binary file, not an APK suitable for general usage.</p>
</td>
</tr>
<tr>
<td rowspan="3">Linux</td><td>aarch64</td><td>Full</td>
<td>64-bit ARMv8+ (servers, some modern routers, RPi-4+).</td>
</tr>
<tr>
<td>armv7hf</td><td>Best effort</td><td>32-bit ARMv7 (older routers, pre-RPi-4).</td>
</tr>
<tr>
<td>x64</td><td>Full</td>
<td>Most Linux desktops & servers.</td>
</tr>
<tr>
<td rowspan="2">MacOS</td><td>aarch64</td><td rowspan="2">Full</td>
<td>Apple silicon Macs (2021+).</td>
</tr>
<tr>
<td>x64</td>
<td>Intel Macs (pre-2021).</td>
</tr>
<tr>
<td>Windows</td><td>x64</td><td>Full</td>
<td>Most Windows desktops & servers.</td>
</tr>
</tbody>
</table>
## Building from source
```sh
git clone https://github.com/imsnif/bandwhich.git
cd bandwhich
cargo build --release
```
For the up-to-date minimum supported Rust version, please refer to the `rust-version` field in [Cargo.toml](Cargo.toml).
### Cross-compiling
Cross-compiling for alternate targets is supported via [cross](https://github.com/cross-rs/cross). Here's the rough procedure:
1. Check the target architecture. If on Linux, you can use `uname -m`.
2. Lookup [rustc platform support](https://doc.rust-lang.org/rustc/platform-support.html) for the corresponding target triple.
3. [Install `cross`](https://github.com/cross-rs/cross#installation).
4. Run `cross build --release --target <TARGET_TRIPLE>`.
#### Android
Until [cross-rs/cross#1222](https://github.com/cross-rs/cross/issues/1222) is solved, use the latest HEAD:
```sh
cargo install --git https://github.com/cross-rs/cross.git cross
cross build --release --target aarch64-linux-android
```
## Post install (Linux)
Since `bandwhich` sniffs network packets, it requires elevated privileges.
On Linux, there are two main ways to accomplish this:
### 1. `setcap`
- Permanently allow the `bandwhich` binary its required privileges (called "capabilities" in Linux).
- Do this if you want to give all unprivileged users full access to bandwhich's monitoring capabilities.
- This is the **recommended** setup **for single user machines**, or **if all users are trusted**.
- This is **not recommended** if you want to **ensure users cannot see others' traffic**.
```sh
# assign capabilities
sudo setcap cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep $(command -v bandwhich)
# run as unprivileged user
bandwhich
```
#### Capabilities explained
- `cap_sys_ptrace,cap_dac_read_search`: allow access to `/proc/<pid>/fd/`, so that `bandwhich` can determine which open port belongs to which process.
- `cap_net_raw,cap_net_admin`: allow capturing packets on your system.
### 2. `sudo` (or alternative)
- Require privilege escalation every time.
- Do this if you are an administrator of a multi-user environment.
```sh
sudo bandwhich
```
Note that if your installation method installed `bandwhich` to somewhere in
your home directory (you can check with `command -v bandwhich`), you may get a
`command not found` error. This is because in many distributions, `sudo` by
default does not keep your user's `$PATH` for safety concerns.
To overcome this, you can do any one of the following:
1. [make `sudo` preserve your `$PATH` environment variable](https://unix.stackexchange.com/q/83191/375550);
2. explicitly set `$PATH` while running `bandwhich`: `sudo env "PATH=$PATH" bandwhich`;
3. pass the full path to `sudo`: `sudo $(command -v bandwhich)`.
## Post install (Windows)
You might need to first install [npcap](https://npcap.com/#download) for capturing packets on Windows.
## Usage
```
Usage: bandwhich [OPTIONS]
Options:
-i, --interface <INTERFACE> The network interface to listen on, eg. eth0
-r, --raw Machine friendlier output
-n, --no-resolve Do not attempt to resolve IPs to their hostnames
-s, --show-dns Show DNS queries
-d, --dns-server <DNS_SERVER> A dns server ip to use instead of the system default
--log-to <LOG_TO> Enable debug logging to a file
-v, --verbose... Increase logging verbosity
-q, --quiet... Decrease logging verbosity
-p, --processes Show processes table only
-c, --connections Show connections table only
-a, --addresses Show remote addresses table only
-u, --unit-family <UNIT_FAMILY> Choose a specific family of units [default: bin-bytes] [possible values: bin-bytes, bin-bits, si-bytes, si-bits]
-t, --total-utilization Show total (cumulative) usages
-h, --help Print help (see more with '--help')
-V, --version Print version
```
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
MIT
================================================
FILE: build.rs
================================================
use std::{env, fs::File};
use clap::CommandFactory;
use clap_complete::Shell;
use clap_mangen::Man;
use eyre::eyre;
fn main() {
build_completion_manpage().unwrap();
#[cfg(target_os = "windows")]
download_windows_npcap_sdk().unwrap();
}
include!("src/cli.rs");
fn build_completion_manpage() -> eyre::Result<()> {
let mut cmd = Opt::command();
// build into `BANDWHICH_GEN_DIR` with a fallback to `OUT_DIR`
let gen_dir: PathBuf = env::var_os("BANDWHICH_GEN_DIR")
.or_else(|| env::var_os("OUT_DIR"))
.ok_or(eyre!("OUT_DIR is unset"))?
.into();
// completion
for &shell in Shell::value_variants() {
clap_complete::generate_to(shell, &mut cmd, "bandwhich", &gen_dir)?;
}
// manpage
let mut manpage_out = File::create(gen_dir.join("bandwhich.1"))?;
let manpage = Man::new(cmd);
manpage.render(&mut manpage_out)?;
Ok(())
}
#[cfg(target_os = "windows")]
fn download_windows_npcap_sdk() -> eyre::Result<()> {
use std::{
fs,
io::{self, Write},
};
use http_req::request;
use zip::ZipArchive;
println!("cargo:rerun-if-changed=build.rs");
// get npcap SDK
const NPCAP_SDK: &str = "npcap-sdk-1.13.zip";
let npcap_sdk_download_url = format!("https://npcap.com/dist/{NPCAP_SDK}");
let cache_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("target");
let npcap_sdk_cache_path = cache_dir.join(NPCAP_SDK);
let npcap_zip = match fs::read(&npcap_sdk_cache_path) {
// use cached
Ok(zip_data) => {
eprintln!("Found cached npcap SDK");
zip_data
}
// download SDK
Err(_) => {
eprintln!("Downloading npcap SDK");
// download
let mut zip_data = vec![];
let _res = request::get(npcap_sdk_download_url, &mut zip_data)?;
// write cache
fs::create_dir_all(cache_dir)?;
let mut cache = fs::File::create(npcap_sdk_cache_path)?;
cache.write_all(&zip_data)?;
zip_data
}
};
// extract DLL
let lib_path = if cfg!(target_arch = "aarch64") {
"Lib/ARM64/Packet.lib"
} else if cfg!(target_arch = "x86_64") {
"Lib/x64/Packet.lib"
} else if cfg!(target_arch = "x86") {
"Lib/Packet.lib"
} else {
panic!("Unsupported target!")
};
let mut archive = ZipArchive::new(io::Cursor::new(npcap_zip))?;
let mut npcap_lib = archive.by_name(lib_path)?;
// write DLL
let lib_dir = PathBuf::from(env::var("OUT_DIR")?).join("npcap_sdk");
let lib_path = lib_dir.join("Packet.lib");
fs::create_dir_all(&lib_dir)?;
let mut lib_file = fs::File::create(lib_path)?;
io::copy(&mut npcap_lib, &mut lib_file)?;
println!(
"cargo:rustc-link-search=native={}",
lib_dir
.to_str()
.ok_or(eyre!("{lib_dir:?} is not valid UTF-8"))?
);
Ok(())
}
================================================
FILE: rustfmt.toml
================================================
================================================
FILE: src/cli.rs
================================================
use std::{net::Ipv4Addr, path::PathBuf};
use clap::{Args, Parser, ValueEnum, ValueHint};
use clap_verbosity_flag::{InfoLevel, Verbosity};
use derive_more::Debug;
use strum::EnumIter;
#[derive(Clone, Debug, Parser, Default)]
#[command(name = "bandwhich", version)]
pub struct Opt {
#[arg(short, long)]
/// The network interface to listen on, eg. eth0
pub interface: Option<String>,
#[arg(short, long)]
/// Machine friendlier output
pub raw: bool,
#[arg(short, long)]
/// Do not attempt to resolve IPs to their hostnames
pub no_resolve: bool,
#[arg(short, long)]
/// Show DNS queries
pub show_dns: bool,
#[arg(short, long)]
/// A dns server ip to use instead of the system default
pub dns_server: Option<Ipv4Addr>,
#[arg(long, value_hint = ValueHint::FilePath)]
/// Enable debug logging to a file
pub log_to: Option<PathBuf>,
#[command(flatten)]
pub verbosity: Verbosity<InfoLevel>,
#[command(flatten)]
pub render_opts: RenderOpts,
}
#[derive(Copy, Clone, Debug, Default, Args)]
pub struct RenderOpts {
#[arg(short, long)]
/// Show processes table only
pub processes: bool,
#[arg(short, long)]
/// Show connections table only
pub connections: bool,
#[arg(short, long)]
/// Show remote addresses table only
pub addresses: bool,
#[arg(short, long, value_enum, default_value_t)]
/// Choose a specific family of units
pub unit_family: UnitFamily,
#[arg(short, long)]
/// Show total (cumulative) usages
pub total_utilization: bool,
}
// IMPRV: it would be nice if we can `#[cfg_attr(not(build), derive(strum::EnumIter))]` this
// unfortunately there is no configuration option for build script detection
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, ValueEnum, EnumIter)]
pub enum UnitFamily {
#[default]
/// bytes, in powers of 2^10
BinBytes,
/// bits, in powers of 2^10
BinBits,
/// bytes, in powers of 10^3
SiBytes,
/// bits, in powers of 10^3
SiBits,
}
================================================
FILE: src/display/components/display_bandwidth.rs
================================================
use std::fmt;
use derive_more::Debug;
use crate::cli::UnitFamily;
#[derive(Copy, Clone, Debug)]
pub struct DisplayBandwidth {
// Custom format for reduced precision.
// Workaround for FP calculation discrepancy between Unix and Windows.
// See https://github.com/rust-lang/rust/issues/111405#issuecomment-2055964223.
#[debug("{bandwidth:.10e}")]
pub bandwidth: f64,
pub unit_family: BandwidthUnitFamily,
}
impl fmt::Display for DisplayBandwidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (div, suffix) = self.unit_family.get_unit_for(self.bandwidth);
write!(f, "{:.2}{suffix}", self.bandwidth / div)
}
}
/// Type wrapper around [`UnitFamily`] to provide extra functionality.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[debug("{_0:?}")]
pub struct BandwidthUnitFamily(UnitFamily);
impl From<UnitFamily> for BandwidthUnitFamily {
fn from(value: UnitFamily) -> Self {
Self(value)
}
}
impl BandwidthUnitFamily {
#[inline]
/// Returns an array of tuples, corresponding to the steps of this unit family.
///
/// Each step contains a divisor, an upper bound, and a unit suffix.
fn steps(&self) -> [(f64, f64, &'static str); 6] {
/// The fraction of the next unit the value has to meet to step up.
const STEP_UP_FRAC: f64 = 0.95;
/// Binary base: 2^10.
const BB: f64 = 1024.0;
use UnitFamily as F;
// probably could macro this stuff, but I'm too lazy
match self.0 {
F::BinBytes => [
(1.0, BB * STEP_UP_FRAC, "B"),
(BB, BB.powi(2) * STEP_UP_FRAC, "KiB"),
(BB.powi(2), BB.powi(3) * STEP_UP_FRAC, "MiB"),
(BB.powi(3), BB.powi(4) * STEP_UP_FRAC, "GiB"),
(BB.powi(4), BB.powi(5) * STEP_UP_FRAC, "TiB"),
(BB.powi(5), f64::MAX, "PiB"),
],
F::BinBits => [
(1.0 / 8.0, BB / 8.0 * STEP_UP_FRAC, "b"),
(BB / 8.0, BB.powi(2) / 8.0 * STEP_UP_FRAC, "Kib"),
(BB.powi(2) / 8.0, BB.powi(3) / 8.0 * STEP_UP_FRAC, "Mib"),
(BB.powi(3) / 8.0, BB.powi(4) / 8.0 * STEP_UP_FRAC, "Gib"),
(BB.powi(4) / 8.0, BB.powi(5) / 8.0 * STEP_UP_FRAC, "Tib"),
(BB.powi(5) / 8.0, f64::MAX, "Pib"),
],
F::SiBytes => [
(1.0, 1e3 * STEP_UP_FRAC, "B"),
(1e3, 1e6 * STEP_UP_FRAC, "kB"),
(1e6, 1e9 * STEP_UP_FRAC, "MB"),
(1e9, 1e12 * STEP_UP_FRAC, "GB"),
(1e12, 1e15 * STEP_UP_FRAC, "TB"),
(1e15, f64::MAX, "PB"),
],
F::SiBits => [
(1.0 / 8.0, 1e3 / 8.0 * STEP_UP_FRAC, "b"),
(1e3 / 8.0, 1e6 / 8.0 * STEP_UP_FRAC, "kb"),
(1e6 / 8.0, 1e9 / 8.0 * STEP_UP_FRAC, "Mb"),
(1e9 / 8.0, 1e12 / 8.0 * STEP_UP_FRAC, "Gb"),
(1e12 / 8.0, 1e15 / 8.0 * STEP_UP_FRAC, "Tb"),
(1e15 / 8.0, f64::MAX, "Pb"),
],
}
}
/// Select a unit for a given value, returning its divisor and suffix.
fn get_unit_for(&self, bytes: f64) -> (f64, &'static str) {
let Some((div, _, suffix)) = self
.steps()
.into_iter()
.find(|&(_, bound, _)| bound >= bytes)
else {
panic!("Cannot select an appropriate unit for {bytes:.2}B.")
};
(div, suffix)
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use insta::assert_snapshot;
use itertools::Itertools;
use strum::IntoEnumIterator;
use crate::{cli::UnitFamily, display::DisplayBandwidth};
#[test]
fn bandwidth_formatting() {
let test_bandwidths_formatted = UnitFamily::iter()
.map_into()
.cartesian_product(
// I feel like this is a decent selection of values
(-6..60)
.map(|exp| 2f64.powi(exp))
.chain((-5..45).map(|exp| 2.5f64.powi(exp)))
.chain((-4..38).map(|exp| 3f64.powi(exp)))
.chain((-3..26).map(|exp| 5f64.powi(exp))),
)
.map(|(unit_family, bandwidth)| DisplayBandwidth {
bandwidth,
unit_family,
})
.fold(String::new(), |mut buf, b| {
let _ = writeln!(buf, "{b:?}: {b}");
buf
});
assert_snapshot!(test_bandwidths_formatted);
}
}
================================================
FILE: src/display/components/header_details.rs
================================================
use std::time::{Duration, Instant};
use ratatui::{
layout::{Alignment, Rect},
style::{Color, Modifier, Style},
text::Span,
widgets::Paragraph,
Frame,
};
use unicode_width::UnicodeWidthStr;
use crate::display::{DisplayBandwidth, UIState};
pub fn elapsed_time(last_start_time: Instant, cumulative_time: Duration, paused: bool) -> Duration {
if paused {
cumulative_time
} else {
cumulative_time + last_start_time.elapsed()
}
}
fn format_duration(d: Duration) -> String {
let s = d.as_secs();
let days = match s / 86400 {
0 => "".to_string(),
1 => "1 day, ".to_string(),
n => format!("{n} days, "),
};
format!(
"{days}{:02}:{:02}:{:02}",
(s / 3600) % 24,
(s / 60) % 60,
s % 60,
)
}
pub struct HeaderDetails<'a> {
pub state: &'a UIState,
pub elapsed_time: Duration,
pub paused: bool,
}
impl HeaderDetails<'_> {
pub fn render(&self, frame: &mut Frame, rect: Rect) {
let bandwidth = self.bandwidth_string();
let color = if self.paused {
Color::Yellow
} else {
Color::Green
};
// do not render time in tests, otherwise the output becomes non-deterministic
// see: https://github.com/imsnif/bandwhich/issues/303
if cfg!(not(test)) && self.state.cumulative_mode {
let elapsed_time = format_duration(self.elapsed_time);
// only render if there is enough width
if bandwidth.width() + 1 + elapsed_time.width() <= rect.width as usize {
self.render_elapsed_time(frame, rect, &elapsed_time, color);
}
}
self.render_bandwidth(frame, rect, &bandwidth, color);
}
fn render_bandwidth(&self, frame: &mut Frame, rect: Rect, bandwidth: &str, color: Color) {
let bandwidth_text = Span::styled(
bandwidth,
Style::default().fg(color).add_modifier(Modifier::BOLD),
);
let paragraph = Paragraph::new(bandwidth_text).alignment(Alignment::Left);
frame.render_widget(paragraph, rect);
}
fn bandwidth_string(&self) -> String {
let intrf = self.state.interface_name.as_deref().unwrap_or("all");
let t = if self.state.cumulative_mode {
"Data"
} else {
"Rate"
};
let unit_family = self.state.unit_family;
let up = DisplayBandwidth {
bandwidth: self.state.total_bytes_uploaded as f64,
unit_family,
};
let down = DisplayBandwidth {
bandwidth: self.state.total_bytes_downloaded as f64,
unit_family,
};
let paused = if self.paused { " [PAUSED]" } else { "" };
format!("IF: {intrf} | Total {t} (Up / Down): {up} / {down}{paused}")
}
fn render_elapsed_time(&self, frame: &mut Frame, rect: Rect, elapsed_time: &str, color: Color) {
let elapsed_time_text = Span::styled(
elapsed_time,
Style::default().fg(color).add_modifier(Modifier::BOLD),
);
let paragraph = Paragraph::new(elapsed_time_text).alignment(Alignment::Right);
frame.render_widget(paragraph, rect);
}
}
================================================
FILE: src/display/components/help_text.rs
================================================
use ratatui::{
layout::{Alignment, Rect},
style::{Modifier, Style},
text::Span,
widgets::Paragraph,
Frame,
};
pub struct HelpText {
pub paused: bool,
pub show_dns: bool,
}
const FIRST_WIDTH_BREAKPOINT: u16 = 76;
const SECOND_WIDTH_BREAKPOINT: u16 = 54;
const TEXT_WHEN_PAUSED: &str = " Press <SPACE> to resume.";
const TEXT_WHEN_NOT_PAUSED: &str = " Press <SPACE> to pause.";
const TEXT_WHEN_DNS_NOT_SHOWN: &str = " (DNS queries hidden).";
const TEXT_WHEN_DNS_SHOWN: &str = " (DNS queries shown).";
const TEXT_TAB_TIP: &str = " Use <TAB> to rearrange tables.";
impl HelpText {
pub fn render(&self, frame: &mut Frame, rect: Rect) {
let pause_content = if self.paused {
TEXT_WHEN_PAUSED
} else {
TEXT_WHEN_NOT_PAUSED
};
let dns_content = if rect.width <= FIRST_WIDTH_BREAKPOINT {
""
} else if self.show_dns {
TEXT_WHEN_DNS_SHOWN
} else {
TEXT_WHEN_DNS_NOT_SHOWN
};
let tab_text = if rect.width <= SECOND_WIDTH_BREAKPOINT {
""
} else {
TEXT_TAB_TIP
};
let text = Span::styled(
[pause_content, tab_text, dns_content].concat(),
Style::default().add_modifier(Modifier::BOLD),
);
let paragraph = Paragraph::new(text).alignment(Alignment::Left);
frame.render_widget(paragraph, rect);
}
}
================================================
FILE: src/display/components/layout.rs
================================================
use ratatui::{
layout::{Constraint, Direction, Rect},
Frame,
};
use crate::display::{HeaderDetails, HelpText, Table};
const FIRST_HEIGHT_BREAKPOINT: u16 = 30;
const FIRST_WIDTH_BREAKPOINT: u16 = 120;
fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) {
let parts = ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
[
Constraint::Length(1),
Constraint::Length(rect.height - 2),
Constraint::Length(1),
]
.as_ref(),
)
.split(rect);
(parts[0], parts[1], parts[2])
}
pub struct Layout<'a> {
pub header: HeaderDetails<'a>,
pub children: Vec<Table>,
pub footer: HelpText,
}
impl Layout<'_> {
fn progressive_split(&self, rect: Rect, splits: Vec<Direction>) -> Vec<Rect> {
splits
.into_iter()
.fold(vec![rect], |mut layout, direction| {
let last_rect = layout.pop().unwrap();
let halves = ratatui::layout::Layout::default()
.direction(direction)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(last_rect);
layout.append(&mut halves.to_vec());
layout
})
}
fn build_two_children_layout(&self, rect: Rect) -> Vec<Rect> {
// if there are two elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
// if the space is not enough, we drop one element
vec![rect]
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
} else {
// by default we display two elements splitting vertically
self.progressive_split(rect, vec![Direction::Horizontal])
}
}
fn build_three_children_layout(&self, rect: Rect) -> Vec<Rect> {
// if there are three elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
//if the space is not enough, we drop two elements
vec![rect]
} else if rect.height < FIRST_HEIGHT_BREAKPOINT {
// if the vertical space is not enough, we drop one element and we split vertically
self.progressive_split(rect, vec![Direction::Horizontal])
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
} else {
// default layout
let halves = ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(rect);
let top_quarters = ratatui::layout::Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(halves[0]);
vec![top_quarters[0], top_quarters[1], halves[1]]
}
}
fn build_layout(&self, rect: Rect) -> Vec<Rect> {
if self.children.len() == 1 {
// if there's only one element to render, it can take the whole frame
vec![rect]
} else if self.children.len() == 2 {
self.build_two_children_layout(rect)
} else {
self.build_three_children_layout(rect)
}
}
pub fn render(&self, frame: &mut Frame, rect: Rect, table_cycle_offset: usize) {
let (top, app, bottom) = top_app_and_bottom_split(rect);
let layout_slots = self.build_layout(app);
for i in 0..layout_slots.len() {
if let Some(rect) = layout_slots.get(i) {
if let Some(child) = self
.children
.get((i + table_cycle_offset) % self.children.len())
{
child.render(frame, *rect);
}
}
}
self.header.render(frame, top);
self.footer.render(frame, bottom);
}
}
================================================
FILE: src/display/components/mod.rs
================================================
mod display_bandwidth;
mod header_details;
mod help_text;
mod layout;
mod table;
pub use display_bandwidth::*;
pub use header_details::*;
pub use help_text::*;
pub use layout::*;
pub use table::*;
================================================
FILE: src/display/components/snapshots/bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap
================================================
---
source: src/display/components/display_bandwidth.rs
expression: test_bandwidths_formatted
---
DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: BinBytes }: 0.02B
DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: BinBytes }: 0.03B
DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: BinBytes }: 0.06B
DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: BinBytes }: 0.12B
DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: BinBytes }: 0.25B
DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: BinBytes }: 0.50B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B
DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: BinBytes }: 2.00B
DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: BinBytes }: 4.00B
DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: BinBytes }: 8.00B
DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: BinBytes }: 16.00B
DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: BinBytes }: 32.00B
DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: BinBytes }: 64.00B
DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: BinBytes }: 128.00B
DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: BinBytes }: 256.00B
DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: BinBytes }: 512.00B
DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: BinBytes }: 1.00KiB
DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: BinBytes }: 2.00KiB
DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: BinBytes }: 4.00KiB
DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: BinBytes }: 8.00KiB
DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: BinBytes }: 16.00KiB
DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: BinBytes }: 32.00KiB
DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: BinBytes }: 64.00KiB
DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: BinBytes }: 128.00KiB
DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: BinBytes }: 256.00KiB
DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: BinBytes }: 512.00KiB
DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: BinBytes }: 1.00MiB
DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: BinBytes }: 2.00MiB
DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: BinBytes }: 4.00MiB
DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: BinBytes }: 8.00MiB
DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: BinBytes }: 16.00MiB
DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: BinBytes }: 32.00MiB
DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: BinBytes }: 64.00MiB
DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: BinBytes }: 128.00MiB
DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: BinBytes }: 256.00MiB
DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: BinBytes }: 512.00MiB
DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: BinBytes }: 1.00GiB
DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: BinBytes }: 2.00GiB
DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: BinBytes }: 4.00GiB
DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: BinBytes }: 8.00GiB
DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: BinBytes }: 16.00GiB
DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: BinBytes }: 32.00GiB
DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: BinBytes }: 64.00GiB
DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: BinBytes }: 128.00GiB
DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: BinBytes }: 256.00GiB
DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: BinBytes }: 512.00GiB
DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: BinBytes }: 1.00TiB
DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: BinBytes }: 2.00TiB
DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: BinBytes }: 4.00TiB
DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: BinBytes }: 8.00TiB
DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: BinBytes }: 16.00TiB
DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: BinBytes }: 32.00TiB
DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: BinBytes }: 64.00TiB
DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: BinBytes }: 128.00TiB
DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: BinBytes }: 256.00TiB
DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: BinBytes }: 512.00TiB
DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: BinBytes }: 1.00PiB
DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: BinBytes }: 2.00PiB
DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: BinBytes }: 4.00PiB
DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: BinBytes }: 8.00PiB
DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: BinBytes }: 16.00PiB
DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: BinBytes }: 32.00PiB
DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: BinBytes }: 64.00PiB
DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: BinBytes }: 128.00PiB
DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: BinBytes }: 256.00PiB
DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: BinBytes }: 512.00PiB
DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: BinBytes }: 0.01B
DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: BinBytes }: 0.03B
DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: BinBytes }: 0.06B
DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: BinBytes }: 0.16B
DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: BinBytes }: 0.40B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B
DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: BinBytes }: 2.50B
DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: BinBytes }: 6.25B
DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: BinBytes }: 15.62B
DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: BinBytes }: 39.06B
DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: BinBytes }: 97.66B
DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: BinBytes }: 244.14B
DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: BinBytes }: 610.35B
DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: BinBytes }: 1.49KiB
DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: BinBytes }: 3.73KiB
DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: BinBytes }: 9.31KiB
DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: BinBytes }: 23.28KiB
DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: BinBytes }: 58.21KiB
DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: BinBytes }: 145.52KiB
DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: BinBytes }: 363.80KiB
DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: BinBytes }: 909.49KiB
DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: BinBytes }: 2.22MiB
DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: BinBytes }: 5.55MiB
DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: BinBytes }: 13.88MiB
DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: BinBytes }: 34.69MiB
DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: BinBytes }: 86.74MiB
DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: BinBytes }: 216.84MiB
DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: BinBytes }: 542.10MiB
DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: BinBytes }: 1.32GiB
DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: BinBytes }: 3.31GiB
DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: BinBytes }: 8.27GiB
DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: BinBytes }: 20.68GiB
DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: BinBytes }: 51.70GiB
DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: BinBytes }: 129.25GiB
DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: BinBytes }: 323.12GiB
DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: BinBytes }: 807.79GiB
DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: BinBytes }: 1.97TiB
DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: BinBytes }: 4.93TiB
DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: BinBytes }: 12.33TiB
DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: BinBytes }: 30.81TiB
DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: BinBytes }: 77.04TiB
DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: BinBytes }: 192.59TiB
DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: BinBytes }: 481.48TiB
DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: BinBytes }: 1.18PiB
DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: BinBytes }: 2.94PiB
DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: BinBytes }: 7.35PiB
DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: BinBytes }: 18.37PiB
DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: BinBytes }: 45.92PiB
DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: BinBytes }: 114.79PiB
DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: BinBytes }: 286.99PiB
DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: BinBytes }: 0.01B
DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: BinBytes }: 0.04B
DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: BinBytes }: 0.11B
DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: BinBytes }: 0.33B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B
DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: BinBytes }: 3.00B
DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: BinBytes }: 9.00B
DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: BinBytes }: 27.00B
DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: BinBytes }: 81.00B
DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: BinBytes }: 243.00B
DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: BinBytes }: 729.00B
DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: BinBytes }: 2.14KiB
DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: BinBytes }: 6.41KiB
DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: BinBytes }: 19.22KiB
DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: BinBytes }: 57.67KiB
DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: BinBytes }: 173.00KiB
DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: BinBytes }: 518.99KiB
DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: BinBytes }: 1.52MiB
DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: BinBytes }: 4.56MiB
DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: BinBytes }: 13.68MiB
DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: BinBytes }: 41.05MiB
DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: BinBytes }: 123.16MiB
DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: BinBytes }: 369.47MiB
DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: BinBytes }: 1.08GiB
DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: BinBytes }: 3.25GiB
DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: BinBytes }: 9.74GiB
DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: BinBytes }: 29.23GiB
DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: BinBytes }: 87.68GiB
DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: BinBytes }: 263.03GiB
DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: BinBytes }: 789.10GiB
DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: BinBytes }: 2.31TiB
DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: BinBytes }: 6.94TiB
DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: BinBytes }: 20.81TiB
DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: BinBytes }: 62.42TiB
DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: BinBytes }: 187.26TiB
DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: BinBytes }: 561.77TiB
DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: BinBytes }: 1.65PiB
DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: BinBytes }: 4.94PiB
DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: BinBytes }: 14.81PiB
DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: BinBytes }: 44.44PiB
DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: BinBytes }: 133.31PiB
DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: BinBytes }: 399.93PiB
DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: BinBytes }: 0.01B
DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: BinBytes }: 0.04B
DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: BinBytes }: 0.20B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B
DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: BinBytes }: 5.00B
DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: BinBytes }: 25.00B
DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: BinBytes }: 125.00B
DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: BinBytes }: 625.00B
DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: BinBytes }: 3.05KiB
DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: BinBytes }: 15.26KiB
DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: BinBytes }: 76.29KiB
DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: BinBytes }: 381.47KiB
DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: BinBytes }: 1.86MiB
DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: BinBytes }: 9.31MiB
DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: BinBytes }: 46.57MiB
DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: BinBytes }: 232.83MiB
DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: BinBytes }: 1.14GiB
DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: BinBytes }: 5.68GiB
DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: BinBytes }: 28.42GiB
DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: BinBytes }: 142.11GiB
DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: BinBytes }: 710.54GiB
DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: BinBytes }: 3.47TiB
DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: BinBytes }: 17.35TiB
DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: BinBytes }: 86.74TiB
DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: BinBytes }: 433.68TiB
DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: BinBytes }: 2.12PiB
DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: BinBytes }: 10.59PiB
DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: BinBytes }: 52.94PiB
DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: BinBytes }: 264.70PiB
DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: BinBits }: 0.12b
DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: BinBits }: 0.25b
DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: BinBits }: 0.50b
DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: BinBits }: 1.00b
DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: BinBits }: 2.00b
DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: BinBits }: 4.00b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b
DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: BinBits }: 16.00b
DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: BinBits }: 32.00b
DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: BinBits }: 64.00b
DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: BinBits }: 128.00b
DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: BinBits }: 256.00b
DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: BinBits }: 512.00b
DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: BinBits }: 1.00Kib
DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: BinBits }: 2.00Kib
DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: BinBits }: 4.00Kib
DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: BinBits }: 8.00Kib
DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: BinBits }: 16.00Kib
DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: BinBits }: 32.00Kib
DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: BinBits }: 64.00Kib
DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: BinBits }: 128.00Kib
DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: BinBits }: 256.00Kib
DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: BinBits }: 512.00Kib
DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: BinBits }: 1.00Mib
DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: BinBits }: 2.00Mib
DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: BinBits }: 4.00Mib
DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: BinBits }: 8.00Mib
DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: BinBits }: 16.00Mib
DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: BinBits }: 32.00Mib
DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: BinBits }: 64.00Mib
DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: BinBits }: 128.00Mib
DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: BinBits }: 256.00Mib
DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: BinBits }: 512.00Mib
DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: BinBits }: 1.00Gib
DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: BinBits }: 2.00Gib
DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: BinBits }: 4.00Gib
DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: BinBits }: 8.00Gib
DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: BinBits }: 16.00Gib
DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: BinBits }: 32.00Gib
DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: BinBits }: 64.00Gib
DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: BinBits }: 128.00Gib
DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: BinBits }: 256.00Gib
DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: BinBits }: 512.00Gib
DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: BinBits }: 1.00Tib
DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: BinBits }: 2.00Tib
DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: BinBits }: 4.00Tib
DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: BinBits }: 8.00Tib
DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: BinBits }: 16.00Tib
DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: BinBits }: 32.00Tib
DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: BinBits }: 64.00Tib
DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: BinBits }: 128.00Tib
DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: BinBits }: 256.00Tib
DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: BinBits }: 512.00Tib
DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: BinBits }: 1.00Pib
DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: BinBits }: 2.00Pib
DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: BinBits }: 4.00Pib
DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: BinBits }: 8.00Pib
DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: BinBits }: 16.00Pib
DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: BinBits }: 32.00Pib
DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: BinBits }: 64.00Pib
DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: BinBits }: 128.00Pib
DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: BinBits }: 256.00Pib
DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: BinBits }: 512.00Pib
DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: BinBits }: 1024.00Pib
DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: BinBits }: 2048.00Pib
DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: BinBits }: 4096.00Pib
DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: BinBits }: 0.08b
DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: BinBits }: 0.20b
DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: BinBits }: 0.51b
DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: BinBits }: 1.28b
DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: BinBits }: 3.20b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b
DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: BinBits }: 20.00b
DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: BinBits }: 50.00b
DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: BinBits }: 125.00b
DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: BinBits }: 312.50b
DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: BinBits }: 781.25b
DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: BinBits }: 1.91Kib
DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: BinBits }: 4.77Kib
DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: BinBits }: 11.92Kib
DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: BinBits }: 29.80Kib
DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: BinBits }: 74.51Kib
DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: BinBits }: 186.26Kib
DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: BinBits }: 465.66Kib
DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: BinBits }: 1.14Mib
DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: BinBits }: 2.84Mib
DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: BinBits }: 7.11Mib
DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: BinBits }: 17.76Mib
DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: BinBits }: 44.41Mib
DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: BinBits }: 111.02Mib
DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: BinBits }: 277.56Mib
DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: BinBits }: 693.89Mib
DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: BinBits }: 1.69Gib
DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: BinBits }: 4.24Gib
DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: BinBits }: 10.59Gib
DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: BinBits }: 26.47Gib
DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: BinBits }: 66.17Gib
DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: BinBits }: 165.44Gib
DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: BinBits }: 413.59Gib
DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: BinBits }: 1.01Tib
DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: BinBits }: 2.52Tib
DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: BinBits }: 6.31Tib
DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: BinBits }: 15.78Tib
DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: BinBits }: 39.44Tib
DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: BinBits }: 98.61Tib
DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: BinBits }: 246.52Tib
DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: BinBits }: 616.30Tib
DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: BinBits }: 1.50Pib
DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: BinBits }: 3.76Pib
DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: BinBits }: 9.40Pib
DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: BinBits }: 23.51Pib
DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: BinBits }: 58.77Pib
DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: BinBits }: 146.94Pib
DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: BinBits }: 367.34Pib
DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: BinBits }: 918.35Pib
DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: BinBits }: 2295.89Pib
DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: BinBits }: 0.10b
DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: BinBits }: 0.30b
DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: BinBits }: 0.89b
DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: BinBits }: 2.67b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b
DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: BinBits }: 24.00b
DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: BinBits }: 72.00b
DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: BinBits }: 216.00b
DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: BinBits }: 648.00b
DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: BinBits }: 1.90Kib
DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: BinBits }: 5.70Kib
DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: BinBits }: 17.09Kib
DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: BinBits }: 51.26Kib
DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: BinBits }: 153.77Kib
DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: BinBits }: 461.32Kib
DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: BinBits }: 1.35Mib
DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: BinBits }: 4.05Mib
DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: BinBits }: 12.16Mib
DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: BinBits }: 36.49Mib
DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: BinBits }: 109.47Mib
DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: BinBits }: 328.42Mib
DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: BinBits }: 0.96Gib
DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: BinBits }: 2.89Gib
DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: BinBits }: 8.66Gib
DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: BinBits }: 25.98Gib
DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: BinBits }: 77.94Gib
DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: BinBits }: 233.81Gib
DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: BinBits }: 701.42Gib
DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: BinBits }: 2.05Tib
DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: BinBits }: 6.16Tib
DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: BinBits }: 18.49Tib
DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: BinBits }: 55.48Tib
DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: BinBits }: 166.45Tib
DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: BinBits }: 499.35Tib
DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: BinBits }: 1.46Pib
DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: BinBits }: 4.39Pib
DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: BinBits }: 13.17Pib
DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: BinBits }: 39.50Pib
DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: BinBits }: 118.50Pib
DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: BinBits }: 355.50Pib
DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: BinBits }: 1066.49Pib
DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: BinBits }: 3199.46Pib
DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: BinBits }: 0.06b
DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: BinBits }: 0.32b
DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: BinBits }: 1.60b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b
DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: BinBits }: 40.00b
DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: BinBits }: 200.00b
DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: BinBits }: 0.98Kib
DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: BinBits }: 4.88Kib
DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: BinBits }: 24.41Kib
DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: BinBits }: 122.07Kib
DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: BinBits }: 610.35Kib
DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: BinBits }: 2.98Mib
DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: BinBits }: 14.90Mib
DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: BinBits }: 74.51Mib
DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: BinBits }: 372.53Mib
DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: BinBits }: 1.82Gib
DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: BinBits }: 9.09Gib
DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: BinBits }: 45.47Gib
DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: BinBits }: 227.37Gib
DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: BinBits }: 1.11Tib
DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: BinBits }: 5.55Tib
DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: BinBits }: 27.76Tib
DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: BinBits }: 138.78Tib
DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: BinBits }: 693.89Tib
DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: BinBits }: 3.39Pib
DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: BinBits }: 16.94Pib
DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: BinBits }: 84.70Pib
DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: BinBits }: 423.52Pib
DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: BinBits }: 2117.58Pib
DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: SiBytes }: 0.02B
DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: SiBytes }: 0.03B
DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: SiBytes }: 0.06B
DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: SiBytes }: 0.12B
DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: SiBytes }: 0.25B
DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: SiBytes }: 0.50B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B
DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: SiBytes }: 2.00B
DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: SiBytes }: 4.00B
DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: SiBytes }: 8.00B
DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: SiBytes }: 16.00B
DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: SiBytes }: 32.00B
DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: SiBytes }: 64.00B
DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: SiBytes }: 128.00B
DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: SiBytes }: 256.00B
DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: SiBytes }: 512.00B
DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: SiBytes }: 1.02kB
DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: SiBytes }: 2.05kB
DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: SiBytes }: 4.10kB
DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: SiBytes }: 8.19kB
DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: SiBytes }: 16.38kB
DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: SiBytes }: 32.77kB
DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: SiBytes }: 65.54kB
DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: SiBytes }: 131.07kB
DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: SiBytes }: 262.14kB
DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: SiBytes }: 524.29kB
DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: SiBytes }: 1.05MB
DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: SiBytes }: 2.10MB
DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: SiBytes }: 4.19MB
DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: SiBytes }: 8.39MB
DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: SiBytes }: 16.78MB
DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: SiBytes }: 33.55MB
DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: SiBytes }: 67.11MB
DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: SiBytes }: 134.22MB
DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: SiBytes }: 268.44MB
DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: SiBytes }: 536.87MB
DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: SiBytes }: 1.07GB
DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: SiBytes }: 2.15GB
DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: SiBytes }: 4.29GB
DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: SiBytes }: 8.59GB
DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: SiBytes }: 17.18GB
DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: SiBytes }: 34.36GB
DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: SiBytes }: 68.72GB
DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: SiBytes }: 137.44GB
DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: SiBytes }: 274.88GB
DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: SiBytes }: 549.76GB
DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: SiBytes }: 1.10TB
DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: SiBytes }: 2.20TB
DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: SiBytes }: 4.40TB
DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: SiBytes }: 8.80TB
DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: SiBytes }: 17.59TB
DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: SiBytes }: 35.18TB
DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: SiBytes }: 70.37TB
DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: SiBytes }: 140.74TB
DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: SiBytes }: 281.47TB
DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: SiBytes }: 562.95TB
DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: SiBytes }: 1.13PB
DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: SiBytes }: 2.25PB
DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: SiBytes }: 4.50PB
DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: SiBytes }: 9.01PB
DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: SiBytes }: 18.01PB
DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: SiBytes }: 36.03PB
DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: SiBytes }: 72.06PB
DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: SiBytes }: 144.12PB
DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: SiBytes }: 288.23PB
DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: SiBytes }: 576.46PB
DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: SiBytes }: 0.01B
DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: SiBytes }: 0.03B
DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: SiBytes }: 0.06B
DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: SiBytes }: 0.16B
DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: SiBytes }: 0.40B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B
DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: SiBytes }: 2.50B
DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: SiBytes }: 6.25B
DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: SiBytes }: 15.62B
DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: SiBytes }: 39.06B
DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: SiBytes }: 97.66B
DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: SiBytes }: 244.14B
DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: SiBytes }: 610.35B
DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: SiBytes }: 1.53kB
DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: SiBytes }: 3.81kB
DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: SiBytes }: 9.54kB
DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: SiBytes }: 23.84kB
DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: SiBytes }: 59.60kB
DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: SiBytes }: 149.01kB
DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: SiBytes }: 372.53kB
DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: SiBytes }: 931.32kB
DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: SiBytes }: 2.33MB
DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: SiBytes }: 5.82MB
DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: SiBytes }: 14.55MB
DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: SiBytes }: 36.38MB
DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: SiBytes }: 90.95MB
DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: SiBytes }: 227.37MB
DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: SiBytes }: 568.43MB
DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: SiBytes }: 1.42GB
DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: SiBytes }: 3.55GB
DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: SiBytes }: 8.88GB
DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: SiBytes }: 22.20GB
DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: SiBytes }: 55.51GB
DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: SiBytes }: 138.78GB
DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: SiBytes }: 346.94GB
DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: SiBytes }: 867.36GB
DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: SiBytes }: 2.17TB
DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: SiBytes }: 5.42TB
DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: SiBytes }: 13.55TB
DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: SiBytes }: 33.88TB
DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: SiBytes }: 84.70TB
DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: SiBytes }: 211.76TB
DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: SiBytes }: 529.40TB
DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: SiBytes }: 1.32PB
DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: SiBytes }: 3.31PB
DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: SiBytes }: 8.27PB
DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: SiBytes }: 20.68PB
DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: SiBytes }: 51.70PB
DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: SiBytes }: 129.25PB
DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: SiBytes }: 323.12PB
DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: SiBytes }: 0.01B
DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: SiBytes }: 0.04B
DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: SiBytes }: 0.11B
DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: SiBytes }: 0.33B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B
DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: SiBytes }: 3.00B
DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: SiBytes }: 9.00B
DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: SiBytes }: 27.00B
DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: SiBytes }: 81.00B
DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: SiBytes }: 243.00B
DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: SiBytes }: 729.00B
DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: SiBytes }: 2.19kB
DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: SiBytes }: 6.56kB
DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: SiBytes }: 19.68kB
DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: SiBytes }: 59.05kB
DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: SiBytes }: 177.15kB
DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: SiBytes }: 531.44kB
DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: SiBytes }: 1.59MB
DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: SiBytes }: 4.78MB
DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: SiBytes }: 14.35MB
DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: SiBytes }: 43.05MB
DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: SiBytes }: 129.14MB
DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: SiBytes }: 387.42MB
DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: SiBytes }: 1.16GB
DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: SiBytes }: 3.49GB
DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: SiBytes }: 10.46GB
DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: SiBytes }: 31.38GB
DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: SiBytes }: 94.14GB
DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: SiBytes }: 282.43GB
DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: SiBytes }: 847.29GB
DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: SiBytes }: 2.54TB
DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: SiBytes }: 7.63TB
DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: SiBytes }: 22.88TB
DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: SiBytes }: 68.63TB
DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: SiBytes }: 205.89TB
DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: SiBytes }: 617.67TB
DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: SiBytes }: 1.85PB
DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: SiBytes }: 5.56PB
DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: SiBytes }: 16.68PB
DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: SiBytes }: 50.03PB
DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: SiBytes }: 150.09PB
DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: SiBytes }: 450.28PB
DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: SiBytes }: 0.01B
DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: SiBytes }: 0.04B
DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: SiBytes }: 0.20B
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B
DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: SiBytes }: 5.00B
DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: SiBytes }: 25.00B
DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: SiBytes }: 125.00B
DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: SiBytes }: 625.00B
DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: SiBytes }: 3.12kB
DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: SiBytes }: 15.62kB
DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: SiBytes }: 78.12kB
DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: SiBytes }: 390.62kB
DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: SiBytes }: 1.95MB
DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: SiBytes }: 9.77MB
DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: SiBytes }: 48.83MB
DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: SiBytes }: 244.14MB
DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: SiBytes }: 1.22GB
DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: SiBytes }: 6.10GB
DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: SiBytes }: 30.52GB
DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: SiBytes }: 152.59GB
DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: SiBytes }: 762.94GB
DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: SiBytes }: 3.81TB
DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: SiBytes }: 19.07TB
DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: SiBytes }: 95.37TB
DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: SiBytes }: 476.84TB
DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: SiBytes }: 2.38PB
DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: SiBytes }: 11.92PB
DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: SiBytes }: 59.60PB
DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: SiBytes }: 298.02PB
DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: SiBits }: 0.12b
DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: SiBits }: 0.25b
DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: SiBits }: 0.50b
DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: SiBits }: 1.00b
DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: SiBits }: 2.00b
DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: SiBits }: 4.00b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b
DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: SiBits }: 16.00b
DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: SiBits }: 32.00b
DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: SiBits }: 64.00b
DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: SiBits }: 128.00b
DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: SiBits }: 256.00b
DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: SiBits }: 512.00b
DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: SiBits }: 1.02kb
DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: SiBits }: 2.05kb
DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: SiBits }: 4.10kb
DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: SiBits }: 8.19kb
DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: SiBits }: 16.38kb
DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: SiBits }: 32.77kb
DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: SiBits }: 65.54kb
DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: SiBits }: 131.07kb
DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: SiBits }: 262.14kb
DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: SiBits }: 524.29kb
DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: SiBits }: 1.05Mb
DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: SiBits }: 2.10Mb
DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: SiBits }: 4.19Mb
DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: SiBits }: 8.39Mb
DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: SiBits }: 16.78Mb
DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: SiBits }: 33.55Mb
DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: SiBits }: 67.11Mb
DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: SiBits }: 134.22Mb
DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: SiBits }: 268.44Mb
DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: SiBits }: 536.87Mb
DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: SiBits }: 1.07Gb
DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: SiBits }: 2.15Gb
DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: SiBits }: 4.29Gb
DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: SiBits }: 8.59Gb
DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: SiBits }: 17.18Gb
DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: SiBits }: 34.36Gb
DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: SiBits }: 68.72Gb
DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: SiBits }: 137.44Gb
DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: SiBits }: 274.88Gb
DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: SiBits }: 549.76Gb
DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: SiBits }: 1.10Tb
DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: SiBits }: 2.20Tb
DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: SiBits }: 4.40Tb
DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: SiBits }: 8.80Tb
DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: SiBits }: 17.59Tb
DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: SiBits }: 35.18Tb
DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: SiBits }: 70.37Tb
DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: SiBits }: 140.74Tb
DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: SiBits }: 281.47Tb
DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: SiBits }: 562.95Tb
DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: SiBits }: 1.13Pb
DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: SiBits }: 2.25Pb
DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: SiBits }: 4.50Pb
DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: SiBits }: 9.01Pb
DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: SiBits }: 18.01Pb
DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: SiBits }: 36.03Pb
DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: SiBits }: 72.06Pb
DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: SiBits }: 144.12Pb
DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: SiBits }: 288.23Pb
DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: SiBits }: 576.46Pb
DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: SiBits }: 1152.92Pb
DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: SiBits }: 2305.84Pb
DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: SiBits }: 4611.69Pb
DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: SiBits }: 0.08b
DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: SiBits }: 0.20b
DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: SiBits }: 0.51b
DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: SiBits }: 1.28b
DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: SiBits }: 3.20b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b
DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: SiBits }: 20.00b
DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: SiBits }: 50.00b
DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: SiBits }: 125.00b
DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: SiBits }: 312.50b
DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: SiBits }: 781.25b
DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: SiBits }: 1.95kb
DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: SiBits }: 4.88kb
DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: SiBits }: 12.21kb
DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: SiBits }: 30.52kb
DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: SiBits }: 76.29kb
DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: SiBits }: 190.73kb
DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: SiBits }: 476.84kb
DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: SiBits }: 1.19Mb
DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: SiBits }: 2.98Mb
DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: SiBits }: 7.45Mb
DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: SiBits }: 18.63Mb
DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: SiBits }: 46.57Mb
DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: SiBits }: 116.42Mb
DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: SiBits }: 291.04Mb
DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: SiBits }: 727.60Mb
DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: SiBits }: 1.82Gb
DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: SiBits }: 4.55Gb
DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: SiBits }: 11.37Gb
DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: SiBits }: 28.42Gb
DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: SiBits }: 71.05Gb
DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: SiBits }: 177.64Gb
DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: SiBits }: 444.09Gb
DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: SiBits }: 1.11Tb
DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: SiBits }: 2.78Tb
DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: SiBits }: 6.94Tb
DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: SiBits }: 17.35Tb
DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: SiBits }: 43.37Tb
DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: SiBits }: 108.42Tb
DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: SiBits }: 271.05Tb
DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: SiBits }: 677.63Tb
DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: SiBits }: 1.69Pb
DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: SiBits }: 4.24Pb
DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: SiBits }: 10.59Pb
DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: SiBits }: 26.47Pb
DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: SiBits }: 66.17Pb
DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: SiBits }: 165.44Pb
DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: SiBits }: 413.59Pb
DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: SiBits }: 1033.98Pb
DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: SiBits }: 2584.94Pb
DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: SiBits }: 0.10b
DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: SiBits }: 0.30b
DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: SiBits }: 0.89b
DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: SiBits }: 2.67b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b
DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: SiBits }: 24.00b
DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: SiBits }: 72.00b
DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: SiBits }: 216.00b
DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: SiBits }: 648.00b
DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: SiBits }: 1.94kb
DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: SiBits }: 5.83kb
DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: SiBits }: 17.50kb
DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: SiBits }: 52.49kb
DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: SiBits }: 157.46kb
DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: SiBits }: 472.39kb
DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: SiBits }: 1.42Mb
DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: SiBits }: 4.25Mb
DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: SiBits }: 12.75Mb
DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: SiBits }: 38.26Mb
DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: SiBits }: 114.79Mb
DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: SiBits }: 344.37Mb
DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: SiBits }: 1.03Gb
DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: SiBits }: 3.10Gb
DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: SiBits }: 9.30Gb
DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: SiBits }: 27.89Gb
DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: SiBits }: 83.68Gb
DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: SiBits }: 251.05Gb
DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: SiBits }: 753.15Gb
DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: SiBits }: 2.26Tb
DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: SiBits }: 6.78Tb
DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: SiBits }: 20.33Tb
DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: SiBits }: 61.00Tb
DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: SiBits }: 183.01Tb
DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: SiBits }: 549.04Tb
DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: SiBits }: 1.65Pb
DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: SiBits }: 4.94Pb
DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: SiBits }: 14.82Pb
DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: SiBits }: 44.47Pb
DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: SiBits }: 133.42Pb
DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: SiBits }: 400.25Pb
DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: SiBits }: 1200.76Pb
DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: SiBits }: 3602.27Pb
DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: SiBits }: 0.06b
DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: SiBits }: 0.32b
DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: SiBits }: 1.60b
DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b
DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: SiBits }: 40.00b
DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: SiBits }: 200.00b
DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: SiBits }: 1.00kb
DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: SiBits }: 5.00kb
DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: SiBits }: 25.00kb
DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: SiBits }: 125.00kb
DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: SiBits }: 625.00kb
DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: SiBits }: 3.12Mb
DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: SiBits }: 15.62Mb
DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: SiBits }: 78.12Mb
DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: SiBits }: 390.62Mb
DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: SiBits }: 1.95Gb
DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: SiBits }: 9.77Gb
DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: SiBits }: 48.83Gb
DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: SiBits }: 244.14Gb
DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: SiBits }: 1.22Tb
DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: SiBits }: 6.10Tb
DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: SiBits }: 30.52Tb
DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: SiBits }: 152.59Tb
DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: SiBits }: 762.94Tb
DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: SiBits }: 3.81Pb
DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: SiBits }: 19.07Pb
DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: SiBits }: 95.37Pb
DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: SiBits }: 476.84Pb
DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: SiBits }: 2384.19Pb
================================================
FILE: src/display/components/table.rs
================================================
use std::{collections::HashMap, net::IpAddr, ops::Index, rc::Rc};
use derive_more::Debug;
use itertools::Itertools;
use ratatui::{
layout::{Constraint, Rect},
style::{Color, Style},
widgets::{Block, Borders, Row},
Frame,
};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::{
display::{Bandwidth, BandwidthUnitFamily, DisplayBandwidth, UIState},
network::{display_connection_string, display_ip_or_host},
};
/// The displayed layout choice of a table.
/// Each value in the array is the width of each column.
///
/// Note that this only determines how a table is displayed, not what data it contains.
///
/// If we intend to display different number of columns in the future,
/// then new variants should be added.
#[derive(Copy, Clone, Debug)]
pub enum DisplayLayout {
/// Show 2 columns.
C2([u16; 2]),
/// Show 3 columns.
C3([u16; 3]),
/// Show 4 columns.
C4([u16; 4]),
}
impl Index<usize> for DisplayLayout {
type Output = u16;
fn index(&self, i: usize) -> &Self::Output {
match self {
Self::C2(arr) => &arr[i],
Self::C3(arr) => &arr[i],
Self::C4(arr) => &arr[i],
}
}
}
impl DisplayLayout {
#[inline]
fn columns_count(&self) -> usize {
match self {
Self::C2(_) => 2,
Self::C3(_) => 3,
Self::C4(_) => 4,
}
}
#[inline]
fn iter(&self) -> impl Iterator<Item = &u16> {
match self {
Self::C2(ws) => ws.iter(),
Self::C3(ws) => ws.iter(),
Self::C4(ws) => ws.iter(),
}
}
#[inline]
fn widths_sum(&self) -> u16 {
self.iter().sum()
}
/// Returns the computed actual width and the spacer width.
///
/// See [`Table`] for layout rules.
fn compute_actual_widths(&self, available: u16) -> (Self, u16) {
let columns_count = self.columns_count() as u16;
let desired_min = self.widths_sum();
// spacer max width is 2
let spacer = if available > desired_min {
((available - desired_min) / (columns_count - 1)).min(2)
} else {
0
};
let available_without_spacers = available - spacer * (columns_count - 1);
// multiplier
let m = available_without_spacers as f64 / desired_min as f64;
// remainder width is arbitrarily given to column 0
let computed = match *self {
Self::C2([_w0, w1]) => {
let w1_new = (w1 as f64 * m).trunc() as u16;
Self::C2([available_without_spacers - w1_new, w1_new])
}
Self::C3([_w0, w1, w2]) => {
let w1_new = (w1 as f64 * m).trunc() as u16;
let w2_new = (w2 as f64 * m).trunc() as u16;
Self::C3([available_without_spacers - w1_new - w2_new, w1_new, w2_new])
}
Self::C4([_w0, w1, w2, w3]) => {
let w1_new = (w1 as f64 * m).trunc() as u16;
let w2_new = (w2 as f64 * m).trunc() as u16;
let w3_new = (w3 as f64 * m).trunc() as u16;
Self::C4([
available_without_spacers - w1_new - w2_new - w3_new,
w1_new,
w2_new,
w3_new,
])
}
};
(computed, spacer)
}
}
/// All data of a table.
///
/// If tables with different number of columns are added in the future,
/// then new variants should be added.
#[derive(Clone, Debug)]
enum TableData {
/// A table with 3 columns.
C3(NColsTableData<3>),
/// A table with 4 columns.
C4(NColsTableData<4>),
}
impl From<NColsTableData<3>> for TableData {
fn from(data: NColsTableData<3>) -> Self {
Self::C3(data)
}
}
impl From<NColsTableData<4>> for TableData {
fn from(data: NColsTableData<4>) -> Self {
Self::C4(data)
}
}
impl TableData {
fn column_names(&self) -> &[&str] {
match self {
Self::C3(inner) => &inner.column_names,
Self::C4(inner) => &inner.column_names,
}
}
fn rows(&self) -> Vec<&[String]> {
match self {
Self::C3(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(),
Self::C4(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(),
}
}
fn column_selector(&self) -> &dyn Fn(&DisplayLayout) -> Vec<usize> {
match self {
Self::C3(inner) => inner.column_selector.as_ref(),
Self::C4(inner) => inner.column_selector.as_ref(),
}
}
}
/// All data of a table with `C` columns.
///
/// Note that the number of columns here is independent of the number of columns
/// being actually shown. If width-constrained, we might only show some of the columns.
#[derive(Clone, Debug)]
struct NColsTableData<const C: usize> {
/// The name of each column.
column_names: [&'static str; C],
/// All rows of data.
rows: Vec<[String; C]>,
/// Function to determine which columns to show for a given layout.
///
/// This function should return a vector of column indices.
/// The indices should be less than `C`; otherwise this will cause a runtime panic.
#[debug("Rc</* function pointer */>")]
column_selector: Rc<ColumnSelectorFn>,
}
/// Clippy wanted me to write this. 💢
type ColumnSelectorFn = dyn Fn(&DisplayLayout) -> Vec<usize>;
/// A table displayed by bandwhich.
#[derive(Clone, Debug)]
pub struct Table {
title: &'static str,
/// A layout mapping between minimum available width and the width of each column.
///
/// Note that the width of each column here is the "desired minimum width".
///
/// - Wt = available width of table
/// - Wd = sum of desired minimum width of each column
///
/// - If `Wt >= Wd`, spacers with a maximum width of `2` will be inserted
/// between columns; and then the columns will proportionally expand.
/// - If `Wt < Wd`, columns will proportionally shrink.
width_cutoffs: Vec<(u16, DisplayLayout)>,
data: TableData,
}
impl Table {
pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap<IpAddr, String>) -> Self {
use DisplayLayout as D;
let title = "Utilization by connection";
let width_cutoffs = vec![
(0, D::C2([32, 18])),
(80, D::C3([36, 12, 18])),
(100, D::C3([54, 18, 22])),
(120, D::C3([72, 24, 22])),
];
let column_names = [
"Connection",
"Process",
if state.cumulative_mode {
"Data (Up / Down)"
} else {
"Rate (Up / Down)"
},
];
let rows = state
.connections
.iter()
.map(|(connection, connection_data)| {
[
display_connection_string(
connection,
ip_to_host,
&connection_data.interface_name,
),
connection_data.process_name.to_string(),
display_upload_and_download(
connection_data,
state.unit_family,
state.cumulative_mode,
),
]
})
.collect();
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 2],
D::C3(_) => vec![0, 1, 2],
D::C4(_) => unreachable!(),
});
Table {
title,
width_cutoffs,
data: NColsTableData {
column_names,
rows,
column_selector,
}
.into(),
}
}
pub fn create_processes_table(state: &UIState) -> Self {
use DisplayLayout as D;
let title = "Utilization by process name";
let width_cutoffs = vec![
(0, D::C2([16, 18])),
(50, D::C3([16, 12, 20])),
(60, D::C3([24, 12, 20])),
(80, D::C4([28, 12, 12, 24])),
];
let column_names = [
"Process",
"PID",
"Connections",
if state.cumulative_mode {
"Data (Up / Down)"
} else {
"Rate (Up / Down)"
},
];
let rows = state
.processes
.iter()
.map(|(proc_info, data_for_process)| {
[
proc_info.name.to_string(),
proc_info.pid.to_string(),
data_for_process.connection_count.to_string(),
display_upload_and_download(
data_for_process,
state.unit_family,
state.cumulative_mode,
),
]
})
.collect();
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 3],
D::C3(_) => vec![0, 2, 3],
D::C4(_) => vec![0, 1, 2, 3],
});
Table {
title,
width_cutoffs,
data: NColsTableData {
column_names,
rows,
column_selector,
}
.into(),
}
}
pub fn create_remote_addresses_table(
state: &UIState,
ip_to_host: &HashMap<IpAddr, String>,
) -> Self {
use DisplayLayout as D;
let title = "Utilization by remote address";
let width_cutoffs = vec![
(0, D::C2([16, 16])),
(40, D::C2([20, 16])),
(60, D::C3([24, 10, 20])),
(100, D::C3([54, 16, 24])),
];
let column_names = [
"Remote Address",
"Connections",
if state.cumulative_mode {
"Data (Up / Down)"
} else {
"Rate (Up / Down)"
},
];
let rows = state
.remote_addresses
.iter()
.map(|(remote_address, data_for_remote_address)| {
let remote_address = display_ip_or_host(*remote_address, ip_to_host);
[
remote_address,
data_for_remote_address.connection_count.to_string(),
display_upload_and_download(
data_for_remote_address,
state.unit_family,
state.cumulative_mode,
),
]
})
.collect();
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 2],
D::C3(_) => vec![0, 1, 2],
D::C4(_) => unreachable!(),
});
Table {
title,
width_cutoffs,
data: NColsTableData {
column_names,
rows,
column_selector,
}
.into(),
}
}
/// See [`Table`] for layout rules.
pub fn render(&self, frame: &mut Frame, rect: Rect) {
let (computed_layout, spacer_width) = {
// pick the largest possible layout, constrained by the available width
let &(_, layout) = self
.width_cutoffs
.iter()
.rev()
.find(|(cutoff, _)| rect.width > *cutoff)
.unwrap(); // all cutoff tables have a 0-width entry
layout.compute_actual_widths(rect.width)
};
let columns_to_show = self.data.column_selector()(&computed_layout);
let column_names: Vec<_> = columns_to_show
.iter()
.copied()
.map(|i| self.data.column_names()[i])
.collect();
// text needs to react to column widths
let tui_rows_iter = self
.data
.rows()
.into_iter()
.map(|row_data| {
let shown_columns_data = columns_to_show.iter().copied().map(|i| &row_data[i]);
let column_widths = computed_layout.iter().copied();
shown_columns_data
.zip_eq(column_widths)
.map(|(text, width)| truncate_middle(text, width))
.collect::<Vec<_>>()
})
.map(Row::new);
let widths_constraints: Vec<_> = computed_layout
.iter()
.copied()
.map(Constraint::Length)
.collect();
let table = ratatui::widgets::Table::new(tui_rows_iter, widths_constraints)
.block(Block::default().title(self.title).borders(Borders::ALL))
.header(Row::new(column_names).style(Style::default().fg(Color::Yellow)))
.flex(ratatui::layout::Flex::Legacy)
.column_spacing(spacer_width);
frame.render_widget(table, rect);
}
}
fn display_upload_and_download(
bandwidth: &impl Bandwidth,
unit_family: BandwidthUnitFamily,
_cumulative: bool,
) -> String {
let up = DisplayBandwidth {
bandwidth: bandwidth.get_total_bytes_uploaded() as f64,
unit_family,
};
let down = DisplayBandwidth {
bandwidth: bandwidth.get_total_bytes_downloaded() as f64,
unit_family,
};
format!("{up} / {down}")
}
fn collect_to_unicode_width<T>(iter: impl Iterator<Item = char>, width: usize) -> T
where
T: FromIterator<char>,
{
let mut chunk_width = 0;
iter.take_while(|ch| {
chunk_width += ch.width().unwrap_or(0);
chunk_width <= width
})
.collect()
}
fn truncate_middle(row: &str, max_len: u16) -> String {
const ELLIPSIS: &str = "..";
if max_len < 6 {
collect_to_unicode_width(row.chars(), max_len as usize)
} else if row.width() as u16 > max_len {
let suffix_len = (max_len as usize - ELLIPSIS.len()) / 2;
// remainder length arbitrarily given to prefix
let prefix_len = max_len as usize - ELLIPSIS.len() - suffix_len;
let prefix: String = collect_to_unicode_width(row.chars(), prefix_len);
let suffix: String = collect_to_unicode_width::<Vec<_>>(row.chars().rev(), suffix_len)
.into_iter()
.rev()
.collect();
format!("{prefix}{ELLIPSIS}{suffix}")
} else {
row.to_string()
}
}
================================================
FILE: src/display/mod.rs
================================================
mod components;
mod raw_terminal_backend;
mod ui;
mod ui_state;
pub use components::*;
pub use raw_terminal_backend::*;
pub use ui::*;
pub use ui_state::*;
================================================
FILE: src/display/raw_terminal_backend.rs
================================================
// this is a bit of a hack:
// the TUI backend used by this app changes stdout to raw byte mode.
// this is not desired when we do not use it (in our --raw mode),
// since it makes writing to stdout overly complex
//
// so what we do here is provide a fake backend (RawTerminalBackend)
// that implements the Backend TUI trait, but does nothing
// this way, we don't need to create the TermionBackend
// and thus skew our stdout when we don't need it
use std::io;
use ratatui::{
backend::{Backend, WindowSize},
buffer::Cell,
layout::{Position, Size},
};
pub struct RawTerminalBackend {}
impl Backend for RawTerminalBackend {
fn clear(&mut self) -> io::Result<()> {
Ok(())
}
fn hide_cursor(&mut self) -> io::Result<()> {
Ok(())
}
fn show_cursor(&mut self) -> io::Result<()> {
Ok(())
}
fn get_cursor_position(&mut self) -> io::Result<Position> {
Ok(Position::new(0, 0))
}
fn set_cursor_position<P: Into<Position>>(&mut self, _position: P) -> io::Result<()> {
Ok(())
}
fn draw<'a, I>(&mut self, _content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
Ok(())
}
fn size(&self) -> io::Result<Size> {
Ok(Size::new(0, 0))
}
fn window_size(&mut self) -> io::Result<WindowSize> {
Ok(WindowSize {
columns_rows: Size::default(),
pixels: Size::default(),
})
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
================================================
FILE: src/display/ui.rs
================================================
use std::{collections::HashMap, net::IpAddr, time::Duration};
use chrono::prelude::*;
use ratatui::{backend::Backend, Terminal};
use crate::{
cli::{Opt, RenderOpts},
display::{
components::{HeaderDetails, HelpText, Layout, Table},
UIState,
},
network::{display_connection_string, display_ip_or_host, LocalSocket, Utilization},
os::ProcessInfo,
};
pub struct Ui<B>
where
B: Backend,
{
terminal: Terminal<B>,
state: UIState,
ip_to_host: HashMap<IpAddr, String>,
opts: RenderOpts,
}
impl<B> Ui<B>
where
B: Backend,
{
pub fn new(terminal_backend: B, opts: &Opt) -> Self {
let mut terminal = Terminal::new(terminal_backend).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
let state = {
let mut state = UIState::default();
state.interface_name.clone_from(&opts.interface);
state.unit_family = opts.render_opts.unit_family.into();
state.cumulative_mode = opts.render_opts.total_utilization;
state.show_dns = opts.show_dns;
state
};
Ui {
terminal,
state,
ip_to_host: Default::default(),
opts: opts.render_opts,
}
}
pub fn output_text(&mut self, write_to_stdout: &mut (dyn FnMut(&str) + Send)) {
let state = &self.state;
let ip_to_host = &self.ip_to_host;
let local_time: DateTime<Local> = Local::now();
let timestamp = local_time.timestamp();
let mut no_traffic = true;
let output_process_data = |write_to_stdout: &mut (dyn FnMut(&str) + Send),
no_traffic: &mut bool| {
for (proc_info, process_network_data) in &state.processes {
write_to_stdout(&format!(
"process: <{timestamp}> \"{}\" up/down Bps: {}/{} connections: {}",
proc_info.name,
process_network_data.total_bytes_uploaded,
process_network_data.total_bytes_downloaded,
process_network_data.connection_count
));
*no_traffic = false;
}
};
let output_connections_data =
|write_to_stdout: &mut (dyn FnMut(&str) + Send), no_traffic: &mut bool| {
for (connection, connection_network_data) in &state.connections {
write_to_stdout(&format!(
"connection: <{timestamp}> {} up/down Bps: {}/{} process: \"{}\"",
display_connection_string(
connection,
ip_to_host,
&connection_network_data.interface_name,
),
connection_network_data.total_bytes_uploaded,
connection_network_data.total_bytes_downloaded,
connection_network_data.process_name
));
*no_traffic = false;
}
};
let output_adressess_data = |write_to_stdout: &mut (dyn FnMut(&str) + Send),
no_traffic: &mut bool| {
for (remote_address, remote_address_network_data) in &state.remote_addresses {
write_to_stdout(&format!(
"remote_address: <{timestamp}> {} up/down Bps: {}/{} connections: {}",
display_ip_or_host(*remote_address, ip_to_host),
remote_address_network_data.total_bytes_uploaded,
remote_address_network_data.total_bytes_downloaded,
remote_address_network_data.connection_count
));
*no_traffic = false;
}
};
// header
write_to_stdout("Refreshing:");
// body1
if self.opts.processes {
output_process_data(write_to_stdout, &mut no_traffic);
}
if self.opts.connections {
output_connections_data(write_to_stdout, &mut no_traffic);
}
if self.opts.addresses {
output_adressess_data(write_to_stdout, &mut no_traffic);
}
if !(self.opts.processes || self.opts.connections || self.opts.addresses) {
output_process_data(write_to_stdout, &mut no_traffic);
output_connections_data(write_to_stdout, &mut no_traffic);
output_adressess_data(write_to_stdout, &mut no_traffic);
}
// body2: In case no traffic is detected
if no_traffic {
write_to_stdout("<NO TRAFFIC>");
}
// footer
write_to_stdout("");
}
pub fn draw(&mut self, paused: bool, elapsed_time: Duration, table_cycle_offset: usize) {
let layout = Layout {
header: HeaderDetails {
state: &self.state,
elapsed_time,
paused,
},
children: self.get_tables_to_display(),
footer: HelpText {
paused,
show_dns: self.state.show_dns,
},
};
self.terminal
.draw(|frame| layout.render(frame, frame.area(), table_cycle_offset))
.unwrap();
}
fn get_tables_to_display(&self) -> Vec<Table> {
let opts = &self.opts;
let mut children: Vec<Table> = Vec::new();
if opts.processes {
children.push(Table::create_processes_table(&self.state));
}
if opts.addresses {
children.push(Table::create_remote_addresses_table(
&self.state,
&self.ip_to_host,
));
}
if opts.connections {
children.push(Table::create_connections_table(
&self.state,
&self.ip_to_host,
));
}
if !(opts.processes || opts.addresses || opts.connections) {
children = vec![
Table::create_processes_table(&self.state),
Table::create_remote_addresses_table(&self.state, &self.ip_to_host),
Table::create_connections_table(&self.state, &self.ip_to_host),
];
}
children
}
pub fn get_table_count(&self) -> usize {
self.get_tables_to_display().len()
}
pub fn update_state(
&mut self,
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
utilization: Utilization,
ip_to_host: HashMap<IpAddr, String>,
) {
self.state.update(connections_to_procs, utilization);
self.ip_to_host.extend(ip_to_host);
}
pub fn end(&mut self) {
self.terminal.show_cursor().unwrap();
}
}
================================================
FILE: src/display/ui_state.rs
================================================
use std::{
cmp,
collections::{HashMap, HashSet, VecDeque},
hash::Hash,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use log::warn;
use crate::{
display::BandwidthUnitFamily,
network::{Connection, LocalSocket, Utilization},
os::ProcessInfo,
};
static RECALL_LENGTH: usize = 5;
static MAX_BANDWIDTH_ITEMS: usize = 1000;
pub trait Bandwidth {
fn get_total_bytes_downloaded(&self) -> u128;
fn get_total_bytes_uploaded(&self) -> u128;
fn combine_bandwidth(&mut self, other: &Self);
fn divide_by(&mut self, amount: u128);
}
#[derive(Clone, Default)]
pub struct NetworkData {
pub total_bytes_downloaded: u128,
pub total_bytes_uploaded: u128,
pub connection_count: u128,
}
#[derive(Clone, Default)]
pub struct ConnectionData {
pub total_bytes_downloaded: u128,
pub total_bytes_uploaded: u128,
pub process_name: String,
pub interface_name: String,
}
impl Bandwidth for NetworkData {
fn get_total_bytes_downloaded(&self) -> u128 {
self.total_bytes_downloaded
}
fn get_total_bytes_uploaded(&self) -> u128 {
self.total_bytes_uploaded
}
fn combine_bandwidth(&mut self, other: &NetworkData) {
self.total_bytes_downloaded += other.get_total_bytes_downloaded();
self.total_bytes_uploaded += other.get_total_bytes_uploaded();
self.connection_count = other.connection_count;
}
fn divide_by(&mut self, amount: u128) {
self.total_bytes_downloaded /= amount;
self.total_bytes_uploaded /= amount;
}
}
impl Bandwidth for ConnectionData {
fn get_total_bytes_downloaded(&self) -> u128 {
self.total_bytes_downloaded
}
fn get_total_bytes_uploaded(&self) -> u128 {
self.total_bytes_uploaded
}
fn combine_bandwidth(&mut self, other: &ConnectionData) {
self.total_bytes_downloaded += other.get_total_bytes_downloaded();
self.total_bytes_uploaded += other.get_total_bytes_uploaded();
}
fn divide_by(&mut self, amount: u128) {
self.total_bytes_downloaded /= amount;
self.total_bytes_uploaded /= amount;
}
}
pub struct UtilizationData {
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
network_utilization: Utilization,
}
#[derive(Default)]
pub struct UIState {
/// The interface name in single-interface mode. `None` means all interfaces.
pub interface_name: Option<String>,
pub processes: Vec<(ProcessInfo, NetworkData)>,
pub remote_addresses: Vec<(IpAddr, NetworkData)>,
pub connections: Vec<(Connection, ConnectionData)>,
pub total_bytes_downloaded: u128,
pub total_bytes_uploaded: u128,
pub cumulative_mode: bool,
pub show_dns: bool,
pub unit_family: BandwidthUnitFamily,
pub utilization_data: VecDeque<UtilizationData>,
pub processes_map: HashMap<ProcessInfo, NetworkData>,
pub remote_addresses_map: HashMap<IpAddr, NetworkData>,
pub connections_map: HashMap<Connection, ConnectionData>,
/// Used for reducing logging noise.
known_orphan_sockets: VecDeque<LocalSocket>,
}
impl UIState {
pub fn update(
&mut self,
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
network_utilization: Utilization,
) {
self.utilization_data.push_back(UtilizationData {
connections_to_procs,
network_utilization,
});
if self.utilization_data.len() > RECALL_LENGTH {
self.utilization_data.pop_front();
}
let mut processes: HashMap<ProcessInfo, NetworkData> = HashMap::new();
let mut remote_addresses: HashMap<IpAddr, NetworkData> = HashMap::new();
let mut connections: HashMap<Connection, ConnectionData> = HashMap::new();
let mut total_bytes_downloaded: u128 = 0;
let mut total_bytes_uploaded: u128 = 0;
let mut seen_connections = HashSet::new();
for state in self.utilization_data.iter().rev() {
let connections_to_procs = &state.connections_to_procs;
let network_utilization = &state.network_utilization;
for (connection, connection_info) in &network_utilization.connections {
let connection_previously_seen = !seen_connections.insert(connection);
let connection_data = connections.entry(*connection).or_default();
let data_for_remote_address = remote_addresses
.entry(connection.remote_socket.ip)
.or_default();
connection_data.total_bytes_downloaded += connection_info.total_bytes_downloaded;
connection_data.total_bytes_uploaded += connection_info.total_bytes_uploaded;
connection_data
.interface_name
.clone_from(&connection_info.interface_name);
data_for_remote_address.total_bytes_downloaded +=
connection_info.total_bytes_downloaded;
data_for_remote_address.total_bytes_uploaded +=
connection_info.total_bytes_uploaded;
if !connection_previously_seen {
data_for_remote_address.connection_count += 1;
}
total_bytes_downloaded += connection_info.total_bytes_downloaded;
total_bytes_uploaded += connection_info.total_bytes_uploaded;
let data_for_process = {
let local_socket = connection.local_socket;
let proc_info = get_proc_info(connections_to_procs, &local_socket);
// only log each orphan connection once
if proc_info.is_none() && !self.known_orphan_sockets.contains(&local_socket) {
// newer connections go in the front so that searches are faster
// basically recency bias
self.known_orphan_sockets.push_front(local_socket);
self.known_orphan_sockets.truncate(10_000); // arbitrary maximum backlog
match connections_to_procs
.iter()
.find(|(&LocalSocket { port, protocol, .. }, _)| {
port == local_socket.port && protocol == local_socket.protocol
})
.and_then(|(local_conn_lookalike, info)| {
network_utilization
.connections
.keys()
.find(|conn| &conn.local_socket == local_conn_lookalike)
.map(|conn| (conn, info))
}) {
Some((lookalike, proc_info)) => {
warn!(
r#""{0}" owns a similar looking connection, but its local ip doesn't match."#,
proc_info.name
);
warn!("Looking for: {connection:?}; found: {lookalike:?}");
}
None => {
warn!("Cannot determine which process owns {connection:?}");
}
};
}
let proc_info = proc_info
.cloned()
.unwrap_or_else(|| ProcessInfo::new("<UNKNOWN>", 0));
connection_data.process_name.clone_from(&proc_info.name);
processes.entry(proc_info).or_default()
};
data_for_process.total_bytes_downloaded += connection_info.total_bytes_downloaded;
data_for_process.total_bytes_uploaded += connection_info.total_bytes_uploaded;
if !connection_previously_seen {
data_for_process.connection_count += 1;
}
}
}
let divide_by = if self.utilization_data.is_empty() {
1_u128
} else {
self.utilization_data.len() as u128
};
for (_, network_data) in processes.iter_mut() {
network_data.divide_by(divide_by)
}
for (_, network_data) in remote_addresses.iter_mut() {
network_data.divide_by(divide_by)
}
for (_, connection_data) in connections.iter_mut() {
connection_data.divide_by(divide_by)
}
if self.cumulative_mode {
merge_bandwidth(&mut self.processes_map, processes);
merge_bandwidth(&mut self.remote_addresses_map, remote_addresses);
merge_bandwidth(&mut self.connections_map, connections);
self.total_bytes_downloaded += total_bytes_downloaded / divide_by;
self.total_bytes_uploaded += total_bytes_uploaded / divide_by;
} else {
self.processes_map = processes;
self.remote_addresses_map = remote_addresses;
self.connections_map = connections;
self.total_bytes_downloaded = total_bytes_downloaded / divide_by;
self.total_bytes_uploaded = total_bytes_uploaded / divide_by;
}
self.processes = sort_and_prune(&mut self.processes_map);
self.remote_addresses = sort_and_prune(&mut self.remote_addresses_map);
self.connections = sort_and_prune(&mut self.connections_map);
}
}
fn get_proc_info<'a>(
connections_to_procs: &'a HashMap<LocalSocket, ProcessInfo>,
local_socket: &LocalSocket,
) -> Option<&'a ProcessInfo> {
connections_to_procs
// direct match
.get(local_socket)
// IPv4-mapped IPv6 addresses
.or_else(|| {
let swapped: IpAddr = match local_socket.ip {
IpAddr::V4(v4) => v4.to_ipv6_mapped().into(),
IpAddr::V6(v6) => v6.to_ipv4_mapped()?.into(),
};
connections_to_procs.get(&LocalSocket {
ip: swapped,
..*local_socket
})
})
// address unspecified
.or_else(|| {
connections_to_procs.get(&LocalSocket {
ip: Ipv4Addr::UNSPECIFIED.into(),
..*local_socket
})
})
.or_else(|| {
connections_to_procs.get(&LocalSocket {
ip: Ipv6Addr::UNSPECIFIED.into(),
..*local_socket
})
})
}
fn merge_bandwidth<K, V>(self_map: &mut HashMap<K, V>, other_map: HashMap<K, V>)
where
K: Eq + Hash,
V: Bandwidth,
{
for (key, b_other) in other_map {
self_map
.entry(key)
.and_modify(|b_self| b_self.combine_bandwidth(&b_other))
.or_insert(b_other);
}
}
fn sort_and_prune<K, V>(map: &mut HashMap<K, V>) -> Vec<(K, V)>
where
K: Eq + Hash + Clone,
V: Bandwidth + Clone,
{
let mut bandwidth_list = Vec::from_iter(map.clone());
bandwidth_list.sort_by_key(|(_, b)| {
cmp::Reverse(b.get_total_bytes_downloaded() + b.get_total_bytes_uploaded())
});
if bandwidth_list.len() > MAX_BANDWIDTH_ITEMS {
for (key, _) in &bandwidth_list[MAX_BANDWIDTH_ITEMS..] {
map.remove(key);
}
}
bandwidth_list
}
================================================
FILE: src/main.rs
================================================
#![deny(clippy::enum_glob_use)]
mod cli;
mod display;
mod network;
mod os;
#[cfg(test)]
mod tests;
use std::{
collections::HashMap,
fs::File,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
thread::{self, park_timeout},
time::{Duration, Instant},
};
use clap::Parser;
use crossterm::{
event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
terminal,
};
use display::{elapsed_time, RawTerminalBackend, Ui};
use eyre::bail;
use network::{
dns::{self, IpTable},
LocalSocket, Sniffer, Utilization,
};
use pnet::datalink::{DataLinkReceiver, NetworkInterface};
use ratatui::backend::{Backend, CrosstermBackend};
use simplelog::WriteLogger;
use crate::cli::Opt;
use crate::os::ProcessInfo;
const DISPLAY_DELTA: Duration = Duration::from_millis(1000);
fn main() -> eyre::Result<()> {
let opts = Opt::parse();
// init logging
if let Some(ref log_path) = opts.log_to {
let log_file = File::options()
.write(true)
.create_new(true)
.open(log_path)?;
WriteLogger::init(
opts.verbosity.log_level_filter(),
Default::default(),
log_file,
)?;
}
let os_input = os::get_input(opts.interface.as_deref(), !opts.no_resolve, opts.dns_server)?;
if opts.raw {
let terminal_backend = RawTerminalBackend {};
start(terminal_backend, os_input, opts);
} else {
let Ok(()) = terminal::enable_raw_mode() else {
bail!(
"Failed to get stdout: if you are trying to pipe 'bandwhich' you should use the --raw flag"
)
};
let mut stdout = std::io::stdout();
// Ignore enteralternatescreen error
let _ = crossterm::execute!(&mut stdout, terminal::EnterAlternateScreen);
let terminal_backend = CrosstermBackend::new(stdout);
start(terminal_backend, os_input, opts);
// Ensure terminal is restored after exit (handles SIGINT case).
// These operations are idempotent, so safe to call even if 'q' already cleaned up.
let _ = terminal::disable_raw_mode();
let _ = crossterm::execute!(std::io::stdout(), terminal::LeaveAlternateScreen);
}
Ok(())
}
pub struct OpenSockets {
sockets_to_procs: HashMap<LocalSocket, ProcessInfo>,
}
pub struct OsInputOutput {
pub interfaces_with_frames: Vec<(NetworkInterface, Box<dyn DataLinkReceiver>)>,
pub get_open_sockets: fn() -> OpenSockets,
pub terminal_events: Box<dyn Iterator<Item = Event> + Send>,
pub dns_client: Option<dns::Client>,
pub write_to_stdout: Box<dyn FnMut(&str) + Send>,
}
pub fn start<B>(terminal_backend: B, os_input: OsInputOutput, opts: Opt)
where
B: Backend + Send + 'static,
{
let running = Arc::new(AtomicBool::new(true));
let paused = Arc::new(AtomicBool::new(false));
let last_start_time = Arc::new(RwLock::new(Instant::now()));
let cumulative_time = Arc::new(RwLock::new(Duration::new(0, 0)));
let table_cycle_offset = Arc::new(AtomicUsize::new(0));
// handle SIGINT properly instead of as a keypress
// see https://github.com/imsnif/bandwhich/issues/487
#[cfg(not(test))]
{
let running = running.clone();
ctrlc::set_handler(move || {
running.store(false, Ordering::Release);
})
.expect("failed to set SIGINT handler");
}
let mut active_threads = vec![];
let terminal_events = os_input.terminal_events;
let get_open_sockets = os_input.get_open_sockets;
let mut write_to_stdout = os_input.write_to_stdout;
let mut dns_client = os_input.dns_client;
let raw_mode = opts.raw;
let network_utilization = Arc::new(Mutex::new(Utilization::new()));
let ui = Arc::new(Mutex::new(Ui::new(terminal_backend, &opts)));
let display_handler = thread::Builder::new()
.name("display_handler".to_string())
.spawn({
let running = running.clone();
let paused = paused.clone();
let table_cycle_offset = table_cycle_offset.clone();
let network_utilization = network_utilization.clone();
let last_start_time = last_start_time.clone();
let cumulative_time = cumulative_time.clone();
let ui = ui.clone();
move || {
while running.load(Ordering::Acquire) {
let render_start_time = Instant::now();
let utilization = network_utilization.lock().unwrap().clone_and_reset();
let OpenSockets { sockets_to_procs } = get_open_sockets();
let mut ip_to_host = IpTable::new();
if let Some(dns_client) = dns_client.as_mut() {
ip_to_host = dns_client.cache();
let unresolved_ips = utilization
.connections
.keys()
.filter(|conn| !ip_to_host.contains_key(&conn.remote_socket.ip))
.map(|conn| conn.remote_socket.ip)
.collect::<Vec<_>>();
dns_client.resolve(unresolved_ips);
}
{
let mut ui = ui.lock().unwrap();
let paused = paused.load(Ordering::SeqCst);
let table_cycle_offset = table_cycle_offset.load(Ordering::SeqCst);
if !paused {
ui.update_state(sockets_to_procs, utilization, ip_to_host);
}
let elapsed_time = elapsed_time(
*last_start_time.read().unwrap(),
*cumulative_time.read().unwrap(),
paused,
);
if raw_mode {
ui.output_text(&mut write_to_stdout);
} else {
ui.draw(paused, elapsed_time, table_cycle_offset);
}
}
let render_duration = render_start_time.elapsed();
if render_duration < DISPLAY_DELTA {
park_timeout(DISPLAY_DELTA - render_duration);
}
}
if !raw_mode {
let mut ui = ui.lock().unwrap();
ui.end();
}
}
})
.unwrap();
let terminal_event_handler = thread::Builder::new()
.name("terminal_events_handler".to_string())
.spawn({
let running = running.clone();
let display_handler = display_handler.thread().clone();
move || {
let mut terminal_events = terminal_events;
while running.load(Ordering::Acquire) {
let Some(evt) = terminal_events.next() else {
continue;
};
let mut ui = ui.lock().unwrap();
match evt {
Event::Resize(_x, _y) if !raw_mode => {
let paused = paused.load(Ordering::SeqCst);
ui.draw(
paused,
elapsed_time(
*last_start_time.read().unwrap(),
*cumulative_time.read().unwrap(),
paused,
),
table_cycle_offset.load(Ordering::SeqCst),
);
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char('q'),
kind: KeyEventKind::Press,
..
}) => {
running.store(false, Ordering::Release);
display_handler.unpark();
match terminal::disable_raw_mode() {
Ok(_) => {}
Err(_) => println!("Error could not disable raw input"),
}
let mut stdout = std::io::stdout();
if crossterm::execute!(&mut stdout, terminal::LeaveAlternateScreen)
.is_err()
{
println!("Error could not leave alternte screen");
};
break;
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char(' '),
kind: KeyEventKind::Press,
..
}) => {
let restarting = paused.fetch_xor(true, Ordering::SeqCst);
if restarting {
*last_start_time.write().unwrap() = Instant::now();
} else {
let last_start_time_copy = *last_start_time.read().unwrap();
let current_cumulative_time_copy = *cumulative_time.read().unwrap();
let new_cumulative_time =
current_cumulative_time_copy + last_start_time_copy.elapsed();
*cumulative_time.write().unwrap() = new_cumulative_time;
}
display_handler.unpark();
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Tab,
kind: KeyEventKind::Press,
..
}) => {
let paused = paused.load(Ordering::SeqCst);
let elapsed_time = elapsed_time(
*last_start_time.read().unwrap(),
*cumulative_time.read().unwrap(),
paused,
);
let table_count = ui.get_table_count();
let new = table_cycle_offset.load(Ordering::SeqCst) + 1 % table_count;
table_cycle_offset.store(new, Ordering::SeqCst);
ui.draw(paused, elapsed_time, new);
}
_ => (),
};
}
}
})
.unwrap();
active_threads.push(display_handler);
active_threads.push(terminal_event_handler);
let sniffer_threads = os_input
.interfaces_with_frames
.into_iter()
.map(|(iface, frames)| {
let name = format!("sniffing_handler_{}", iface.name);
let running = running.clone();
let show_dns = opts.show_dns;
let network_utilization = network_utilization.clone();
thread::Builder::new()
.name(name)
.spawn(move || {
let mut sniffer = Sniffer::new(iface, frames, show_dns);
while running.load(Ordering::Acquire) {
if let Some(segment) = sniffer.next() {
network_utilization.lock().unwrap().ingest(segment);
}
}
})
.unwrap()
})
.collect::<Vec<_>>();
active_threads.extend(sniffer_threads);
for thread_handler in active_threads {
thread_handler.join().unwrap()
}
}
================================================
FILE: src/network/connection.rs
================================================
use std::{
collections::HashMap,
fmt,
net::{IpAddr, SocketAddr},
};
#[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Debug, Copy)]
pub enum Protocol {
Tcp,
Udp,
}
impl Protocol {
#[allow(dead_code)]
pub fn from_str(string: &str) -> Option<Self> {
match string {
"TCP" => Some(Protocol::Tcp),
"UDP" => Some(Protocol::Udp),
_ => None,
}
}
}
impl fmt::Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Protocol::Tcp => write!(f, "tcp"),
Protocol::Udp => write!(f, "udp"),
}
}
}
#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Hash, Copy)]
pub struct Socket {
pub ip: IpAddr,
pub port: u16,
}
impl fmt::Debug for Socket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Socket { ip, port } = self;
match ip {
IpAddr::V4(v4) => write!(f, "{v4}:{port}"),
IpAddr::V6(v6) => write!(f, "[{v6}]:{port}"),
}
}
}
#[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Copy)]
pub struct LocalSocket {
pub ip: IpAddr,
pub port: u16,
pub protocol: Protocol,
}
impl fmt::Debug for LocalSocket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let LocalSocket { ip, port, protocol } = self;
match ip {
IpAddr::V4(v4) => write!(f, "{protocol}://{v4}:{port}"),
IpAddr::V6(v6) => write!(f, "{protocol}://[{v6}]:{port}"),
}
}
}
#[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Copy)]
pub struct Connection {
pub remote_socket: Socket,
pub local_socket: LocalSocket,
}
impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Connection {
remote_socket,
local_socket,
} = self;
write!(f, "{local_socket:?} => {remote_socket:?}")
}
}
pub fn display_ip_or_host(ip: IpAddr, ip_to_host: &HashMap<IpAddr, String>) -> String {
match ip_to_host.get(&ip) {
Some(host) => host.clone(),
None => ip.to_string(),
}
}
pub fn display_connection_string(
connection: &Connection,
ip_to_host: &HashMap<IpAddr, String>,
interface_name: &str,
) -> String {
format!(
"<{interface_name}>:{} => {}:{} ({})",
connection.local_socket.port,
display_ip_or_host(connection.remote_socket.ip, ip_to_host),
connection.remote_socket.port,
connection.local_socket.protocol,
)
}
impl Connection {
pub fn new(
remote_socket: SocketAddr,
local_ip: IpAddr,
local_port: u16,
protocol: Protocol,
) -> Self {
Connection {
remote_socket: Socket {
ip: remote_socket.ip(),
port: remote_socket.port(),
},
local_socket: LocalSocket {
ip: local_ip,
port: local_port,
protocol,
},
}
}
}
================================================
FILE: src/network/dns/client.rs
================================================
use std::{
collections::HashSet,
net::IpAddr,
sync::{Arc, Mutex},
thread::{Builder, JoinHandle},
};
use tokio::{
runtime::Runtime,
sync::mpsc::{self, Sender},
};
use crate::network::dns::{resolver::Lookup, IpTable};
type PendingAddrs = HashSet<IpAddr>;
const CHANNEL_SIZE: usize = 1_000;
pub struct Client {
cache: Arc<Mutex<IpTable>>,
pending: Arc<Mutex<PendingAddrs>>,
tx: Option<Sender<Vec<IpAddr>>>,
handle: Option<JoinHandle<()>>,
}
impl Client {
pub fn new<R>(resolver: R, runtime: Runtime) -> eyre::Result<Self>
where
R: Lookup + Send + Sync + 'static,
{
let cache = Arc::new(Mutex::new(IpTable::new()));
let pending = Arc::new(Mutex::new(PendingAddrs::new()));
let (tx, mut rx) = mpsc::channel::<Vec<IpAddr>>(CHANNEL_SIZE);
let handle = Builder::new().name("resolver".into()).spawn({
let cache = cache.clone();
let pending = pending.clone();
move || {
runtime.block_on(async {
let resolver = Arc::new(resolver);
while let Some(ips) = rx.recv().await {
for ip in ips {
tokio::spawn({
let resolver = resolver.clone();
let cache = cache.clone();
let pending = pending.clone();
async move {
if let Some(name) = resolver.lookup(ip).await {
cache.lock().unwrap().insert(ip, name);
}
pending.lock().unwrap().remove(&ip);
}
});
}
}
});
}
})?;
Ok(Self {
cache,
pending,
tx: Some(tx),
handle: Some(handle),
})
}
pub fn resolve(&mut self, ips: Vec<IpAddr>) {
// Remove ips that are already being resolved
let ips = ips
.into_iter()
.filter(|ip| self.pending.lock().unwrap().insert(*ip))
.collect::<Vec<_>>();
if !ips.is_empty() {
// Discard the message if the channel is full; it will be retried eventually
let _ = self.tx.as_mut().unwrap().try_send(ips);
}
}
pub fn cache(&mut self) -> IpTable {
let cache = self.cache.lock().unwrap();
cache.clone()
}
}
impl Drop for Client {
fn drop(&mut self) {
// Do the Option dance to be able to drop the sender so that the receiver finishes and the thread can be joined
drop(self.tx.take().unwrap());
self.handle.take().unwrap().join().unwrap();
}
}
================================================
FILE: src/network/dns/mod.rs
================================================
use std::{collections::HashMap, net::IpAddr};
mod client;
mod resolver;
pub use client::*;
pub use resolver::*;
pub type IpTable = HashMap<IpAddr, String>;
================================================
FILE: src/network/dns/resolver.rs
================================================
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use async_trait::async_trait;
use trust_dns_resolver::{
config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts},
error::ResolveErrorKind,
TokioAsyncResolver,
};
#[async_trait]
pub trait Lookup {
async fn lookup(&self, ip: IpAddr) -> Option<String>;
}
pub struct Resolver(TokioAsyncResolver);
impl Resolver {
pub async fn new(dns_server: Option<Ipv4Addr>) -> eyre::Result<Self> {
let resolver = match dns_server {
Some(dns_server_address) => {
let mut config = ResolverConfig::new();
let options = ResolverOpts::default();
let socket = SocketAddr::V4(SocketAddrV4::new(dns_server_address, 53));
let nameserver_config = NameServerConfig {
socket_addr: socket,
protocol: Protocol::Udp,
tls_dns_name: None,
trust_negative_responses: false,
bind_addr: None,
};
config.add_name_server(nameserver_config);
TokioAsyncResolver::tokio(config, options)
}
None => TokioAsyncResolver::tokio_from_system_conf()?,
};
Ok(Self(resolver))
}
}
#[async_trait]
impl Lookup for Resolver {
async fn lookup(&self, ip: IpAddr) -> Option<String> {
let lookup_future = self.0.reverse_lookup(ip);
match lookup_future.await {
Ok(names) => {
// Take the first result and convert it to a string
names.into_iter().next().map(|name| name.to_string())
}
Err(e) => match e.kind() {
// If the IP is not associated with a hostname, store the IP
// so that we don't retry indefinitely
ResolveErrorKind::NoRecordsFound { .. } => Some(ip.to_string()),
_ => None,
},
}
}
}
================================================
FILE: src/network/mod.rs
================================================
mod connection;
pub mod dns;
mod sniffer;
mod utilization;
pub use connection::*;
pub use sniffer::*;
pub use utilization::*;
================================================
FILE: src/network/sniffer.rs
================================================
use std::{
io::{self, Result},
net::{IpAddr, SocketAddr},
thread::park_timeout,
time::Duration,
};
use pnet::{
datalink::{DataLinkReceiver, NetworkInterface},
ipnetwork::IpNetwork,
packet::{
ethernet::{EtherTypes, EthernetPacket},
ip::{IpNextHeaderProtocol, IpNextHeaderProtocols},
ipv4::Ipv4Packet,
ipv6::Ipv6Packet,
tcp::TcpPacket,
udp::UdpPacket,
Packet,
},
};
use crate::{
network::{Connection, Protocol},
os::shared::get_datalink_channel,
};
const PACKET_WAIT_TIMEOUT: Duration = Duration::from_millis(10);
const CHANNEL_RESET_DELAY: Duration = Duration::from_millis(1000);
#[derive(Debug)]
pub struct Segment {
pub interface_name: String,
pub connection: Connection,
pub direction: Direction,
pub data_length: u128,
}
#[derive(PartialEq, Hash, Eq, Debug, Clone, PartialOrd)]
pub enum Direction {
Download,
Upload,
}
impl Direction {
pub fn new(network_interface_ips: &[IpNetwork], source: IpAddr) -> Self {
if network_interface_ips
.iter()
.any(|ip_network| ip_network.ip() == source)
{
Direction::Upload
} else {
Direction::Download
}
}
}
trait NextLevelProtocol {
fn get_next_level_protocol(&self) -> IpNextHeaderProtocol;
}
impl NextLevelProtocol for Ipv6Packet<'_> {
fn get_next_level_protocol(&self) -> IpNextHeaderProtocol {
self.get_next_header()
}
}
macro_rules! extract_transport_protocol {
( $ip_packet: ident ) => {{
match $ip_packet.get_next_level_protocol() {
IpNextHeaderProtocols::Tcp => {
let message = TcpPacket::new($ip_packet.payload())?;
(
Protocol::Tcp,
message.get_source(),
message.get_destination(),
$ip_packet.payload().len() as u128,
)
}
IpNextHeaderProtocols::Udp => {
let datagram = UdpPacket::new($ip_packet.payload())?;
(
Protocol::Udp,
datagram.get_source(),
datagram.get_destination(),
$ip_packet.payload().len() as u128,
)
}
_ => return None,
}
}};
}
pub struct Sniffer {
network_interface: NetworkInterface,
network_frames: Box<dyn DataLinkReceiver>,
show_dns: bool,
}
impl Sniffer {
pub fn new(
network_interface: NetworkInterface,
network_frames: Box<dyn DataLinkReceiver>,
show_dns: bool,
) -> Self {
Sniffer {
network_interface,
network_frames,
show_dns,
}
}
pub fn next(&mut self) -> Option<Segment> {
let bytes = match self.network_frames.next() {
Ok(bytes) => bytes,
Err(err) => match err.kind() {
std::io::ErrorKind::TimedOut => {
park_timeout(PACKET_WAIT_TIMEOUT);
return None;
}
_ => {
park_timeout(CHANNEL_RESET_DELAY);
self.reset_channel().ok();
return None;
}
},
};
// See https://github.com/libpnet/libpnet/blob/master/examples/packetdump.rs
// VPN interfaces (such as utun0, utun1, etc) have POINT_TO_POINT bit set to 1
let payload_offset = if (self.network_interface.is_loopback()
|| self.network_interface.is_point_to_point())
&& cfg!(target_os = "macos")
{
// The pnet code for BPF loopback adds a zero'd out Ethernet header
14
} else {
0
};
let ip_packet = Ipv4Packet::new(&bytes[payload_offset..])?;
let version = ip_packet.get_version();
match version {
4 => Self::handle_v4(ip_packet, &self.network_interface, self.show_dns),
6 => Self::handle_v6(
Ipv6Packet::new(&bytes[payload_offset..])?,
&self.network_interface,
),
_ => {
let pkg = EthernetPacket::new(bytes)?;
match pkg.get_ethertype() {
EtherTypes::Ipv4 => Self::handle_v4(
Ipv4Packet::new(pkg.payload())?,
&self.network_interface,
self.show_dns,
),
EtherTypes::Ipv6 => {
Self::handle_v6(Ipv6Packet::new(pkg.payload())?, &self.network_interface)
}
_ => None,
}
}
}
}
pub fn reset_channel(&mut self) -> Result<()> {
self.network_frames = get_datalink_channel(&self.network_interface)
.map_err(|_| io::Error::other("Interface not available"))?;
Ok(())
}
fn handle_v6(ip_packet: Ipv6Packet, network_interface: &NetworkInterface) -> Option<Segment> {
let (protocol, source_port, destination_port, data_length) =
extract_transport_protocol!(ip_packet);
let interface_name = network_interface.name.clone();
let direction = Direction::new(&network_interface.ips, ip_packet.get_source().into());
let from = SocketAddr::new(ip_packet.get_source().into(), source_port);
let to = SocketAddr::new(ip_packet.get_destination().into(), destination_port);
let connection = match direction {
Direction::Download => Connection::new(from, to.ip(), destination_port, protocol),
Direction::Upload => Connection::new(to, from.ip(), source_port, protocol),
};
Some(Segment {
interface_name,
connection,
data_length,
direction,
})
}
fn handle_v4(
ip_packet: Ipv4Packet,
network_interface: &NetworkInterface,
show_dns: bool,
) -> Option<Segment> {
let (protocol, source_port, destination_port, data_length) =
extract_transport_protocol!(ip_packet);
let interface_name = network_interface.name.clone();
let direction = Direction::new(&network_interface.ips, ip_packet.get_source().into());
let from = SocketAddr::new(ip_packet.get_source().into(), source_port);
let to = SocketAddr::new(ip_packet.get_destination().into(), destination_port);
let connection = match direction {
Direction::Download => Connection::new(from, to.ip(), destination_port, protocol),
Direction::Upload => Connection::new(to, from.ip(), source_port, protocol),
};
if !show_dns && connection.remote_socket.port == 53 {
return None;
}
Some(Segment {
interface_name,
connection,
data_length,
direction,
})
}
}
================================================
FILE: src/network/utilization.rs
================================================
use std::collections::HashMap;
use crate::network::{Connection, Direction, Segment};
#[derive(Clone)]
pub struct ConnectionInfo {
pub interface_name: String,
pub total_bytes_downloaded: u128,
pub total_bytes_uploaded: u128,
}
#[derive(Clone)]
pub struct Utilization {
pub connections: HashMap<Connection, ConnectionInfo>,
}
impl Utilization {
pub fn new() -> Self {
let connections = HashMap::new();
Utilization { connections }
}
pub fn clone_and_reset(&mut self) -> Self {
let clone = self.clone();
self.connections.clear();
clone
}
pub fn ingest(&mut self, seg: Segment) {
let total_bandwidth = self
.connections
.entry(seg.connection)
.or_insert(ConnectionInfo {
interface_name: seg.interface_name,
total_bytes_downloaded: 0,
total_bytes_uploaded: 0,
});
match seg.direction {
Direction::Download => {
total_bandwidth.total_bytes_downloaded += seg.data_length;
}
Direction::Upload => {
total_bandwidth.total_bytes_uploaded += seg.data_length;
}
}
}
}
================================================
FILE: src/os/errors.rs
================================================
#[derive(Clone, Eq, PartialEq, Debug, thiserror::Error)]
pub enum GetInterfaceError {
#[error("Permission error: {0}")]
PermissionError(String),
#[error("Other error: {0}")]
OtherError(String),
}
================================================
FILE: src/os/linux.rs
================================================
use std::collections::HashMap;
use procfs::process::FDTarget;
use crate::{
network::{LocalSocket, Protocol},
os::ProcessInfo,
OpenSockets,
};
pub(crate) fn get_open_sockets() -> OpenSockets {
let mut open_sockets = HashMap::new();
let mut inode_to_proc = HashMap::new();
if let Ok(all_procs) = procfs::process::all_processes() {
for process in all_procs.filter_map(|res| res.ok()) {
let Ok(fds) = process.fd() else { continue };
let Ok(stat) = process.stat() else { continue };
let proc_name = stat.comm;
let proc_info = ProcessInfo::new(&proc_name, stat.pid as u32);
for fd in fds.filter_map(|res| res.ok()) {
if let FDTarget::Socket(inode) = fd.target {
inode_to_proc.insert(inode, proc_info.clone());
}
}
}
}
macro_rules! insert_proto {
($source: expr, $proto: expr) => {
let entries = $source.into_iter().filter_map(|res| res.ok()).flatten();
for entry in entries {
if let Some(proc_info) = inode_to_proc.get(&entry.inode) {
let socket = LocalSocket {
ip: entry.local_address.ip(),
port: entry.local_address.port(),
protocol: $proto,
};
open_sockets.insert(socket, proc_info.clone());
}
}
};
}
insert_proto!([procfs::net::tcp(), procfs::net::tcp6()], Protocol::Tcp);
insert_proto!([procfs::net::udp(), procfs::net::udp6()], Protocol::Udp);
OpenSockets {
sockets_to_procs: open_sockets,
}
}
================================================
FILE: src/os/lsof.rs
================================================
use crate::{os::lsof_utils::get_connections, OpenSockets};
pub(crate) fn get_open_sockets() -> OpenSockets {
let sockets_to_procs = get_connections()
.filter_map(|raw| raw.as_local_socket().map(|s| (s, raw.proc_info)))
.collect();
OpenSockets { sockets_to_procs }
}
================================================
FILE: src/os/lsof_utils.rs
================================================
use std::{ffi::OsStr, net::IpAddr, process::Command};
use log::warn;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::{
network::{LocalSocket, Protocol},
os::ProcessInfo,
};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RawConnection {
remote_ip: String,
local_ip: String,
local_port: String,
remote_port: String,
protocol: String,
pub proc_info: ProcessInfo,
}
fn get_null_addr(ip_type: &str) -> &str {
if ip_type.contains('4') {
"0.0.0.0"
} else {
"::0"
}
}
impl RawConnection {
pub fn new(raw_line: &str) -> Option<RawConnection> {
// Example row
// com.apple 664 user 198u IPv4 0xeb179a6650592b8d 0t0 TCP 192.168.1.187:58535->1.2.3.4:443 (ESTABLISHED)
let columns: Vec<&str> = raw_line.split_ascii_whitespace().collect();
if columns.len() < 9 {
return None;
}
let process_name = columns[0].replace("\\x20", " ");
let pid = columns[1].parse().ok()?;
let proc_info = ProcessInfo::new(&process_name, pid);
// Unneeded
// let username = columns[2];
// let fd = columns[3];
// IPv4 or IPv6
let ip_type = columns[4];
// let device = columns[5];
// let size = columns[6];
// UDP/TCP
let protocol = columns[7].to_ascii_uppercase();
if protocol != "TCP" && protocol != "UDP" {
return None;
}
let connection_str = columns[8];
// "(LISTEN)" or "(ESTABLISHED)", this column may or may not be present
// let connection_state = columns[9];
static CONNECTION_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\[?([^\s\]]*)\]?:(\d+)->\[?([^\s\]]*)\]?:(\d+)").unwrap());
static LISTEN_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\[?([^\s\[\]]*)\]?:(.*)").unwrap());
// If this socket is in a "connected" state
if let Some(caps) = CONNECTION_REGEX.captures(connection_str) {
// Example
// 192.168.1.187:64230->0.1.2.3:5228
// *:*
// *:4567
let local_ip = String::from(caps.get(1).unwrap().as_str());
let local_port = String::from(caps.get(2).unwrap().as_str());
let remote_ip = String::from(caps.get(3).unwrap().as_str());
let remote_port = String::from(caps.get(4).unwrap().as_str());
let connection = RawConnection {
local_ip,
local_port,
remote_ip,
remote_port,
protocol,
proc_info,
};
Some(connection)
} else if let Some(caps) = LISTEN_REGEX.captures(connection_str) {
let local_ip = if caps.get(1).unwrap().as_str() == "*" {
get_null_addr(ip_type)
} else {
caps.get(1).unwrap().as_str()
};
let local_ip = String::from(local_ip);
let local_port = String::from(if caps.get(2).unwrap().as_str() == "*" {
"0"
} else {
caps.get(2).unwrap().as_str()
});
let remote_ip = String::from(get_null_addr(ip_type));
let remote_port = String::from("0");
let connection = RawConnection {
local_ip,
local_port,
remote_ip,
remote_port,
protocol,
proc_info,
};
Some(connection)
} else {
None
}
}
pub fn get_protocol(&self) -> Option<Protocol> {
Protocol::from_str(&self.protocol)
}
pub fn get_local_ip(&self) -> Option<IpAddr> {
self.local_ip.parse().ok()
}
pub fn get_local_port(&self) -> Option<u16> {
self.local_port.parse::<u16>().ok()
}
pub fn as_local_socket(&self) -> Option<LocalSocket> {
let process = &self.proc_info.name;
let Some(ip) = self.get_local_ip() else {
warn!(r#"Failed to get the local IP of a connection belonging to "{process}"."#);
return None;
};
let Some(port) = self.get_local_port() else {
warn!(r#"Failed to get the local port of a connection belonging to "{process}"."#);
return None;
};
let Some(protocol) = self.get_protocol() else {
warn!(r#"Failed to get the protocol of a connection belonging to "{process}"."#);
return None;
};
Some(LocalSocket { ip, port, protocol })
}
}
pub fn get_connections() -> RawConnections {
let content = run(["-n", "-P", "-i4", "-i6", "+c", "0"]);
RawConnections::new(content)
}
fn run<I, S>(args: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output = Command::new("lsof")
.args(args)
.output()
.expect("failed to execute process");
String::from_utf8_lossy(&output.stdout).into_owned()
}
pub struct RawConnections {
content: Vec<RawConnection>,
}
impl RawConnections {
pub fn new(content: String) -> RawConnections {
let lines: Vec<RawConnection> = content.lines().flat_map(RawConnection::new).collect();
RawConnections { content: lines }
}
}
impl Iterator for RawConnections {
type Item = RawConnection;
fn next(&mut self) -> Option<Self::Item> {
self.content.pop()
}
}
#[cfg(test)]
mod tests {
use super::*;
const IPV6_LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 9u IPv6 0x5d53dfe5445cee01 0t0 UDP [fe80:4::aede:48ff:fe00:1122]:1111->[fe80:4::aede:48ff:fe33:4455]:2222";
const LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 39u IPv4 0x28ffb9c0021196bf 0t0 UDP 192.168.0.1:1111->198.252.206.25:2222";
const FULL_RAW_OUTPUT: &str = r#"
com.apple 590 etoledom 193u IPv4 0x28ffb9c041115627
gitextract_5glekt08/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yaml
│ ├── publish-crate.yaml
│ ├── release.yaml
│ └── require-changelog-for-PRs.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Cross.toml
├── INSTALL.md
├── LICENSE.md
├── README.md
├── build.rs
├── rustfmt.toml
└── src/
├── cli.rs
├── display/
│ ├── components/
│ │ ├── display_bandwidth.rs
│ │ ├── header_details.rs
│ │ ├── help_text.rs
│ │ ├── layout.rs
│ │ ├── mod.rs
│ │ ├── snapshots/
│ │ │ └── bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap
│ │ └── table.rs
│ ├── mod.rs
│ ├── raw_terminal_backend.rs
│ ├── ui.rs
│ └── ui_state.rs
├── main.rs
├── network/
│ ├── connection.rs
│ ├── dns/
│ │ ├── client.rs
│ │ ├── mod.rs
│ │ └── resolver.rs
│ ├── mod.rs
│ ├── sniffer.rs
│ └── utilization.rs
├── os/
│ ├── errors.rs
│ ├── linux.rs
│ ├── lsof.rs
│ ├── lsof_utils.rs
│ ├── mod.rs
│ ├── shared.rs
│ └── windows.rs
└── tests/
├── cases/
│ ├── mod.rs
│ ├── raw_mode.rs
│ ├── snapshots/
│ │ ├── bandwhich__tests__cases__raw_mode__bi_directional_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_connections_from_remote_address.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_different_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_single_connection.snap
│ │ ├── bandwhich__tests__cases__raw_mode__multiple_processes_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__no_resolve_mode.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_ip_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__raw_mode__one_process_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes_bi_directional.snap
│ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_one_process.snap
│ │ ├── bandwhich__tests__cases__raw_mode__traffic_with_host_names.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_addresses.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_connections.snap
│ │ ├── bandwhich__tests__cases__ui__basic_only_processes.snap
│ │ ├── bandwhich__tests__cases__ui__basic_processes_with_dns_queries.snap
│ │ ├── bandwhich__tests__cases__ui__basic_startup-2.snap
│ │ ├── bandwhich__tests__cases__ui__basic_startup.snap
│ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic-2.snap
│ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic.snap
│ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-draw_events.snap
│ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-events.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__ui__no_resolve_mode-2.snap
│ │ ├── bandwhich__tests__cases__ui__no_resolve_mode.snap
│ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic-2.snap
│ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic.snap
│ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections-2.snap
│ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections.snap
│ │ ├── bandwhich__tests__cases__ui__pause_by_space-2.snap
│ │ ├── bandwhich__tests__cases__ui__pause_by_space.snap
│ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab-2.snap
│ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total-2.snap
│ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names-2.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event-2.snap
│ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event.snap
│ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames-2.snap
│ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_addresses.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_connections.snap
│ │ ├── bandwhich__tests__cases__ui__two_packets_only_processes.snap
│ │ ├── bandwhich__tests__cases__ui__two_windows_split_horizontally.snap
│ │ └── bandwhich__tests__cases__ui__two_windows_split_vertically.snap
│ ├── test_utils.rs
│ └── ui.rs
├── fakes/
│ ├── fake_input.rs
│ ├── fake_output.rs
│ └── mod.rs
└── mod.rs
SYMBOL INDEX (284 symbols across 28 files)
FILE: build.rs
function main (line 8) | fn main() {
function build_completion_manpage (line 17) | fn build_completion_manpage() -> eyre::Result<()> {
function download_windows_npcap_sdk (line 40) | fn download_windows_npcap_sdk() -> eyre::Result<()> {
FILE: src/cli.rs
type Opt (line 10) | pub struct Opt {
type RenderOpts (line 43) | pub struct RenderOpts {
type UnitFamily (line 68) | pub enum UnitFamily {
FILE: src/display/components/display_bandwidth.rs
type DisplayBandwidth (line 8) | pub struct DisplayBandwidth {
method fmt (line 18) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type BandwidthUnitFamily (line 27) | pub struct BandwidthUnitFamily(UnitFamily);
method from (line 29) | fn from(value: UnitFamily) -> Self {
method steps (line 38) | fn steps(&self) -> [(f64, f64, &'static str); 6] {
method get_unit_for (line 83) | fn get_unit_for(&self, bytes: f64) -> (f64, &'static str) {
function bandwidth_formatting (line 107) | fn bandwidth_formatting() {
FILE: src/display/components/header_details.rs
function elapsed_time (line 14) | pub fn elapsed_time(last_start_time: Instant, cumulative_time: Duration,...
function format_duration (line 22) | fn format_duration(d: Duration) -> String {
type HeaderDetails (line 37) | pub struct HeaderDetails<'a> {
function render (line 44) | pub fn render(&self, frame: &mut Frame, rect: Rect) {
function render_bandwidth (line 65) | fn render_bandwidth(&self, frame: &mut Frame, rect: Rect, bandwidth: &st...
function bandwidth_string (line 75) | fn bandwidth_string(&self) -> String {
function render_elapsed_time (line 95) | fn render_elapsed_time(&self, frame: &mut Frame, rect: Rect, elapsed_tim...
FILE: src/display/components/help_text.rs
type HelpText (line 9) | pub struct HelpText {
method render (line 24) | pub fn render(&self, frame: &mut Frame, rect: Rect) {
constant FIRST_WIDTH_BREAKPOINT (line 14) | const FIRST_WIDTH_BREAKPOINT: u16 = 76;
constant SECOND_WIDTH_BREAKPOINT (line 15) | const SECOND_WIDTH_BREAKPOINT: u16 = 54;
constant TEXT_WHEN_PAUSED (line 17) | const TEXT_WHEN_PAUSED: &str = " Press <SPACE> to resume.";
constant TEXT_WHEN_NOT_PAUSED (line 18) | const TEXT_WHEN_NOT_PAUSED: &str = " Press <SPACE> to pause.";
constant TEXT_WHEN_DNS_NOT_SHOWN (line 19) | const TEXT_WHEN_DNS_NOT_SHOWN: &str = " (DNS queries hidden).";
constant TEXT_WHEN_DNS_SHOWN (line 20) | const TEXT_WHEN_DNS_SHOWN: &str = " (DNS queries shown).";
constant TEXT_TAB_TIP (line 21) | const TEXT_TAB_TIP: &str = " Use <TAB> to rearrange tables.";
FILE: src/display/components/layout.rs
constant FIRST_HEIGHT_BREAKPOINT (line 8) | const FIRST_HEIGHT_BREAKPOINT: u16 = 30;
constant FIRST_WIDTH_BREAKPOINT (line 9) | const FIRST_WIDTH_BREAKPOINT: u16 = 120;
function top_app_and_bottom_split (line 11) | fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) {
type Layout (line 27) | pub struct Layout<'a> {
function progressive_split (line 34) | fn progressive_split(&self, rect: Rect, splits: Vec<Direction>) -> Vec<R...
function build_two_children_layout (line 49) | fn build_two_children_layout(&self, rect: Rect) -> Vec<Rect> {
function build_three_children_layout (line 63) | fn build_three_children_layout(&self, rect: Rect) -> Vec<Rect> {
function build_layout (line 91) | fn build_layout(&self, rect: Rect) -> Vec<Rect> {
function render (line 102) | pub fn render(&self, frame: &mut Frame, rect: Rect, table_cycle_offset: ...
FILE: src/display/components/table.rs
type DisplayLayout (line 26) | pub enum DisplayLayout {
type Output (line 36) | type Output = u16;
method index (line 38) | fn index(&self, i: usize) -> &Self::Output {
method columns_count (line 49) | fn columns_count(&self) -> usize {
method iter (line 58) | fn iter(&self) -> impl Iterator<Item = &u16> {
method widths_sum (line 67) | fn widths_sum(&self) -> u16 {
method compute_actual_widths (line 74) | fn compute_actual_widths(&self, available: u16) -> (Self, u16) {
type TableData (line 122) | enum TableData {
method from (line 130) | fn from(data: NColsTableData<3>) -> Self {
method from (line 136) | fn from(data: NColsTableData<4>) -> Self {
method column_names (line 142) | fn column_names(&self) -> &[&str] {
method rows (line 149) | fn rows(&self) -> Vec<&[String]> {
method column_selector (line 156) | fn column_selector(&self) -> &dyn Fn(&DisplayLayout) -> Vec<usize> {
type NColsTableData (line 169) | struct NColsTableData<const C: usize> {
type ColumnSelectorFn (line 183) | type ColumnSelectorFn = dyn Fn(&DisplayLayout) -> Vec<usize>;
type Table (line 187) | pub struct Table {
method create_connections_table (line 204) | pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap<...
method create_processes_table (line 261) | pub fn create_processes_table(state: &UIState) -> Self {
method create_remote_addresses_table (line 316) | pub fn create_remote_addresses_table(
method render (line 374) | pub fn render(&self, frame: &mut Frame, rect: Rect) {
function display_upload_and_download (line 423) | fn display_upload_and_download(
function collect_to_unicode_width (line 439) | fn collect_to_unicode_width<T>(iter: impl Iterator<Item = char>, width: ...
function truncate_middle (line 451) | fn truncate_middle(row: &str, max_len: u16) -> String {
FILE: src/display/raw_terminal_backend.rs
type RawTerminalBackend (line 19) | pub struct RawTerminalBackend {}
method clear (line 22) | fn clear(&mut self) -> io::Result<()> {
method hide_cursor (line 26) | fn hide_cursor(&mut self) -> io::Result<()> {
method show_cursor (line 30) | fn show_cursor(&mut self) -> io::Result<()> {
method get_cursor_position (line 34) | fn get_cursor_position(&mut self) -> io::Result<Position> {
method set_cursor_position (line 38) | fn set_cursor_position<P: Into<Position>>(&mut self, _position: P) -> io...
method draw (line 42) | fn draw<'a, I>(&mut self, _content: I) -> io::Result<()>
method size (line 49) | fn size(&self) -> io::Result<Size> {
method window_size (line 53) | fn window_size(&mut self) -> io::Result<WindowSize> {
method flush (line 60) | fn flush(&mut self) -> io::Result<()> {
FILE: src/display/ui.rs
type Ui (line 16) | pub struct Ui<B>
function new (line 30) | pub fn new(terminal_backend: B, opts: &Opt) -> Self {
function output_text (line 49) | pub fn output_text(&mut self, write_to_stdout: &mut (dyn FnMut(&str) + S...
function draw (line 130) | pub fn draw(&mut self, paused: bool, elapsed_time: Duration, table_cycle...
function get_tables_to_display (line 148) | fn get_tables_to_display(&self) -> Vec<Table> {
function get_table_count (line 176) | pub fn get_table_count(&self) -> usize {
function update_state (line 180) | pub fn update_state(
function end (line 189) | pub fn end(&mut self) {
FILE: src/display/ui_state.rs
type Bandwidth (line 19) | pub trait Bandwidth {
method get_total_bytes_downloaded (line 20) | fn get_total_bytes_downloaded(&self) -> u128;
method get_total_bytes_uploaded (line 21) | fn get_total_bytes_uploaded(&self) -> u128;
method combine_bandwidth (line 22) | fn combine_bandwidth(&mut self, other: &Self);
method divide_by (line 23) | fn divide_by(&mut self, amount: u128);
method get_total_bytes_downloaded (line 42) | fn get_total_bytes_downloaded(&self) -> u128 {
method get_total_bytes_uploaded (line 45) | fn get_total_bytes_uploaded(&self) -> u128 {
method combine_bandwidth (line 48) | fn combine_bandwidth(&mut self, other: &NetworkData) {
method divide_by (line 53) | fn divide_by(&mut self, amount: u128) {
method get_total_bytes_downloaded (line 60) | fn get_total_bytes_downloaded(&self) -> u128 {
method get_total_bytes_uploaded (line 63) | fn get_total_bytes_uploaded(&self) -> u128 {
method combine_bandwidth (line 66) | fn combine_bandwidth(&mut self, other: &ConnectionData) {
method divide_by (line 70) | fn divide_by(&mut self, amount: u128) {
type NetworkData (line 27) | pub struct NetworkData {
type ConnectionData (line 34) | pub struct ConnectionData {
type UtilizationData (line 76) | pub struct UtilizationData {
type UIState (line 82) | pub struct UIState {
method update (line 102) | pub fn update(
function get_proc_info (line 230) | fn get_proc_info<'a>(
function merge_bandwidth (line 263) | fn merge_bandwidth<K, V>(self_map: &mut HashMap<K, V>, other_map: HashMa...
function sort_and_prune (line 276) | fn sort_and_prune<K, V>(map: &mut HashMap<K, V>) -> Vec<(K, V)>
FILE: src/main.rs
constant DISPLAY_DELTA (line 39) | const DISPLAY_DELTA: Duration = Duration::from_millis(1000);
function main (line 41) | fn main() -> eyre::Result<()> {
type OpenSockets (line 82) | pub struct OpenSockets {
type OsInputOutput (line 86) | pub struct OsInputOutput {
function start (line 94) | pub fn start<B>(terminal_backend: B, os_input: OsInputOutput, opts: Opt)
FILE: src/network/connection.rs
type Protocol (line 8) | pub enum Protocol {
method from_str (line 15) | pub fn from_str(string: &str) -> Option<Self> {
method fmt (line 25) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
type Socket (line 34) | pub struct Socket {
method fmt (line 40) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type LocalSocket (line 50) | pub struct LocalSocket {
method fmt (line 57) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Connection (line 67) | pub struct Connection {
method fmt (line 73) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method new (line 104) | pub fn new(
function display_ip_or_host (line 82) | pub fn display_ip_or_host(ip: IpAddr, ip_to_host: &HashMap<IpAddr, Strin...
function display_connection_string (line 89) | pub fn display_connection_string(
FILE: src/network/dns/client.rs
type PendingAddrs (line 15) | type PendingAddrs = HashSet<IpAddr>;
constant CHANNEL_SIZE (line 17) | const CHANNEL_SIZE: usize = 1_000;
type Client (line 19) | pub struct Client {
method new (line 27) | pub fn new<R>(resolver: R, runtime: Runtime) -> eyre::Result<Self>
method resolve (line 70) | pub fn resolve(&mut self, ips: Vec<IpAddr>) {
method cache (line 83) | pub fn cache(&mut self) -> IpTable {
method drop (line 90) | fn drop(&mut self) {
FILE: src/network/dns/mod.rs
type IpTable (line 9) | pub type IpTable = HashMap<IpAddr, String>;
FILE: src/network/dns/resolver.rs
type Lookup (line 11) | pub trait Lookup {
method lookup (line 12) | async fn lookup(&self, ip: IpAddr) -> Option<String>;
method lookup (line 42) | async fn lookup(&self, ip: IpAddr) -> Option<String> {
type Resolver (line 15) | pub struct Resolver(TokioAsyncResolver);
method new (line 18) | pub async fn new(dns_server: Option<Ipv4Addr>) -> eyre::Result<Self> {
FILE: src/network/sniffer.rs
constant PACKET_WAIT_TIMEOUT (line 27) | const PACKET_WAIT_TIMEOUT: Duration = Duration::from_millis(10);
constant CHANNEL_RESET_DELAY (line 28) | const CHANNEL_RESET_DELAY: Duration = Duration::from_millis(1000);
type Segment (line 31) | pub struct Segment {
type Direction (line 39) | pub enum Direction {
method new (line 45) | pub fn new(network_interface_ips: &[IpNetwork], source: IpAddr) -> Self {
type NextLevelProtocol (line 57) | trait NextLevelProtocol {
method get_next_level_protocol (line 58) | fn get_next_level_protocol(&self) -> IpNextHeaderProtocol;
method get_next_level_protocol (line 62) | fn get_next_level_protocol(&self) -> IpNextHeaderProtocol {
type Sniffer (line 93) | pub struct Sniffer {
method new (line 100) | pub fn new(
method next (line 111) | pub fn next(&mut self) -> Option<Segment> {
method reset_channel (line 162) | pub fn reset_channel(&mut self) -> Result<()> {
method handle_v6 (line 167) | fn handle_v6(ip_packet: Ipv6Packet, network_interface: &NetworkInterfa...
method handle_v4 (line 187) | fn handle_v4(
FILE: src/network/utilization.rs
type ConnectionInfo (line 6) | pub struct ConnectionInfo {
type Utilization (line 13) | pub struct Utilization {
method new (line 18) | pub fn new() -> Self {
method clone_and_reset (line 22) | pub fn clone_and_reset(&mut self) -> Self {
method ingest (line 27) | pub fn ingest(&mut self, seg: Segment) {
FILE: src/os/errors.rs
type GetInterfaceError (line 2) | pub enum GetInterfaceError {
FILE: src/os/linux.rs
function get_open_sockets (line 11) | pub(crate) fn get_open_sockets() -> OpenSockets {
FILE: src/os/lsof.rs
function get_open_sockets (line 3) | pub(crate) fn get_open_sockets() -> OpenSockets {
FILE: src/os/lsof_utils.rs
type RawConnection (line 14) | pub struct RawConnection {
method new (line 32) | pub fn new(raw_line: &str) -> Option<RawConnection> {
method get_protocol (line 110) | pub fn get_protocol(&self) -> Option<Protocol> {
method get_local_ip (line 114) | pub fn get_local_ip(&self) -> Option<IpAddr> {
method get_local_port (line 118) | pub fn get_local_port(&self) -> Option<u16> {
method as_local_socket (line 122) | pub fn as_local_socket(&self) -> Option<LocalSocket> {
function get_null_addr (line 23) | fn get_null_addr(ip_type: &str) -> &str {
function get_connections (line 142) | pub fn get_connections() -> RawConnections {
function run (line 147) | fn run<I, S>(args: I) -> String
type RawConnections (line 160) | pub struct RawConnections {
method new (line 165) | pub fn new(content: String) -> RawConnections {
type Item (line 173) | type Item = RawConnection;
method next (line 175) | fn next(&mut self) -> Option<Self::Item> {
constant IPV6_LINE_RAW_OUTPUT (line 184) | const IPV6_LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 9u IP...
constant LINE_RAW_OUTPUT (line 185) | const LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 39u IPv4 0x28ff...
constant FULL_RAW_OUTPUT (line 186) | const FULL_RAW_OUTPUT: &str = r#"
function test_iterator_multiline (line 194) | fn test_iterator_multiline() {
function test_raw_connection_is_created_from_raw_output_ipv4 (line 201) | fn test_raw_connection_is_created_from_raw_output_ipv4() {
function test_raw_connection_is_created_from_raw_output_ipv6 (line 205) | fn test_raw_connection_is_created_from_raw_output_ipv6() {
function test_raw_connection_is_created_from_raw_output (line 208) | fn test_raw_connection_is_created_from_raw_output(raw_output: &str) {
function test_raw_connection_is_not_created_from_wrong_raw_output (line 214) | fn test_raw_connection_is_not_created_from_wrong_raw_output() {
function test_raw_connection_parse_local_port_ipv4 (line 220) | fn test_raw_connection_parse_local_port_ipv4() {
function test_raw_connection_parse_local_port_ipv6 (line 224) | fn test_raw_connection_parse_local_port_ipv6() {
function test_raw_connection_parse_local_port (line 227) | fn test_raw_connection_parse_local_port(raw_output: &str) {
function test_raw_connection_parse_protocol_ipv4 (line 233) | fn test_raw_connection_parse_protocol_ipv4() {
function test_raw_connection_parse_protocol_ipv6 (line 237) | fn test_raw_connection_parse_protocol_ipv6() {
function test_raw_connection_parse_protocol (line 240) | fn test_raw_connection_parse_protocol(raw_line: &str) {
function test_raw_connection_parse_process_name_ipv4 (line 246) | fn test_raw_connection_parse_process_name_ipv4() {
function test_raw_connection_parse_process_name_ipv6 (line 250) | fn test_raw_connection_parse_process_name_ipv6() {
function test_raw_connection_parse_process_name (line 253) | fn test_raw_connection_parse_process_name(raw_line: &str) {
FILE: src/os/shared.rs
type ProcessInfo (line 24) | pub struct ProcessInfo {
method new (line 30) | pub fn new(name: &str, pid: u32) -> Self {
constant POLL_TIMEOUT (line 41) | const POLL_TIMEOUT: Duration = Duration::from_millis(100);
type TerminalEvents (line 43) | pub struct TerminalEvents;
type Item (line 46) | type Item = Event;
method next (line 52) | fn next(&mut self) -> Option<Event> {
function get_datalink_channel (line 60) | pub(crate) fn get_datalink_channel(
function get_interface (line 87) | fn get_interface(interface_name: &str) -> Option<NetworkInterface> {
function create_write_to_stdout (line 93) | fn create_write_to_stdout() -> Box<dyn FnMut(&str) + Send> {
function get_input (line 108) | pub fn get_input(
function eperm_message (line 234) | fn eperm_message() -> &'static str {
function eperm_message (line 240) | fn eperm_message() -> &'static str {
function eperm_message (line 254) | fn eperm_message() -> &'static str {
FILE: src/os/windows.rs
function get_open_sockets (line 12) | pub(crate) fn get_open_sockets() -> OpenSockets {
FILE: src/tests/cases/raw_mode.rs
function build_ip_tcp_packet (line 25) | fn build_ip_tcp_packet(
function format_raw_stdout (line 42) | fn format_raw_stdout(raw: &Mutex<Vec<u8>>) -> String {
function one_ip_packet_of_traffic (line 51) | fn one_ip_packet_of_traffic() {
function one_packet_of_traffic (line 68) | fn one_packet_of_traffic() {
function bi_directional_traffic (line 85) | fn bi_directional_traffic() {
function multiple_packets_of_traffic_from_different_connections (line 111) | fn multiple_packets_of_traffic_from_different_connections() {
function multiple_packets_of_traffic_from_single_connection (line 137) | fn multiple_packets_of_traffic_from_single_connection() {
function one_process_with_multiple_connections (line 163) | fn one_process_with_multiple_connections() {
function multiple_processes_with_multiple_connections (line 189) | fn multiple_processes_with_multiple_connections() {
function multiple_connections_from_remote_address (line 229) | fn multiple_connections_from_remote_address() {
function sustained_traffic_from_one_process (line 256) | fn sustained_traffic_from_one_process() {
function sustained_traffic_from_multiple_processes (line 284) | fn sustained_traffic_from_multiple_processes() {
function sustained_traffic_from_multiple_processes_bi_directional (line 326) | fn sustained_traffic_from_multiple_processes_bi_directional() {
function traffic_with_host_names (line 396) | fn traffic_with_host_names() {
function no_resolve_mode (line 479) | fn no_resolve_mode() {
FILE: src/tests/cases/test_utils.rs
function sleep_and_quit_events (line 25) | pub fn sleep_and_quit_events(sleep_num: usize) -> Box<TerminalEvents> {
function sleep_resize_and_quit_events (line 35) | pub fn sleep_resize_and_quit_events(sleep_num: usize) -> Box<TerminalEve...
function build_tcp_packet (line 48) | pub fn build_tcp_packet(
function sample_frames_short (line 67) | pub fn sample_frames_short() -> Vec<Box<dyn DataLinkReceiver>> {
function sample_frames_sustained_one_process (line 94) | pub fn sample_frames_sustained_one_process() -> Vec<Box<dyn DataLinkRece...
function sample_frames_sustained_multiple_processes (line 115) | pub fn sample_frames_sustained_multiple_processes() -> Vec<Box<dyn DataL...
function sample_frames_sustained_long (line 150) | pub fn sample_frames_sustained_long() -> Vec<Box<dyn DataLinkReceiver>> {
function os_input_output (line 212) | pub fn os_input_output(
function os_input_output_stdout (line 223) | pub fn os_input_output_stdout(
function os_input_output_dns (line 236) | pub fn os_input_output_dns(
function os_input_output_factory (line 250) | pub fn os_input_output_factory(
function opts_raw (line 277) | pub fn opts_raw() -> Opt {
function opts_ui (line 284) | pub fn opts_ui() -> Opt {
type BackendWithStreams (line 291) | type BackendWithStreams = (
function test_backend_factory (line 296) | pub fn test_backend_factory(w: u16, h: u16) -> BackendWithStreams {
FILE: src/tests/cases/ui.rs
constant SNAPSHOT_SECTION_SEPARATOR (line 27) | const SNAPSHOT_SECTION_SEPARATOR: &str = "\n--- SECTION SEPARATOR ---\n";
function basic_startup (line 30) | fn basic_startup() {
function pause_by_space (line 48) | fn pause_by_space() {
function rearranged_by_tab (line 95) | fn rearranged_by_tab() {
function basic_only_processes (line 142) | fn basic_only_processes() {
function basic_processes_with_dns_queries (line 165) | fn basic_processes_with_dns_queries() {
function basic_only_connections (line 189) | fn basic_only_connections() {
function basic_only_addresses (line 212) | fn basic_only_addresses() {
function two_packets_only_processes (line 235) | fn two_packets_only_processes(frames: Vec<Box<dyn DataLinkReceiver>>) {
function two_packets_only_connections (line 254) | fn two_packets_only_connections(frames: Vec<Box<dyn DataLinkReceiver>>) {
function two_packets_only_addresses (line 273) | fn two_packets_only_addresses(frames: Vec<Box<dyn DataLinkReceiver>>) {
function two_windows_split_horizontally (line 292) | fn two_windows_split_horizontally() {
function two_windows_split_vertically (line 316) | fn two_windows_split_vertically() {
function one_packet_of_traffic (line 340) | fn one_packet_of_traffic() {
function bi_directional_traffic (line 361) | fn bi_directional_traffic(frames: Vec<Box<dyn DataLinkReceiver>>) {
function multiple_packets_of_traffic_from_different_connections (line 375) | fn multiple_packets_of_traffic_from_different_connections() {
function multiple_packets_of_traffic_from_single_connection (line 406) | fn multiple_packets_of_traffic_from_single_connection() {
function one_process_with_multiple_connections (line 437) | fn one_process_with_multiple_connections() {
function multiple_processes_with_multiple_connections (line 468) | fn multiple_processes_with_multiple_connections() {
function multiple_connections_from_remote_address (line 513) | fn multiple_connections_from_remote_address() {
function sustained_traffic_from_one_process (line 544) | fn sustained_traffic_from_one_process(frames: Vec<Box<dyn DataLinkReceiv...
function sustained_traffic_from_one_process_total (line 559) | fn sustained_traffic_from_one_process_total(frames: Vec<Box<dyn DataLink...
function sustained_traffic_from_multiple_processes (line 575) | fn sustained_traffic_from_multiple_processes(frames: Vec<Box<dyn DataLin...
function sustained_traffic_from_multiple_processes_total (line 590) | fn sustained_traffic_from_multiple_processes_total(frames: Vec<Box<dyn D...
function sustained_traffic_from_multiple_processes_bi_directional (line 606) | fn sustained_traffic_from_multiple_processes_bi_directional(
function sustained_traffic_from_multiple_processes_bi_directional_total (line 623) | fn sustained_traffic_from_multiple_processes_bi_directional_total(
function traffic_with_host_names (line 641) | fn traffic_with_host_names(network_frames: Vec<Box<dyn DataLinkReceiver>...
function truncate_long_hostnames (line 680) | fn truncate_long_hostnames(network_frames: Vec<Box<dyn DataLinkReceiver>...
function no_resolve_mode (line 719) | fn no_resolve_mode(network_frames: Vec<Box<dyn DataLinkReceiver>>) {
function traffic_with_winch_event (line 758) | fn traffic_with_winch_event() {
function layout (line 796) | fn layout(#[case] name: &str, #[case] width: u16, #[case] height: u16) {
FILE: src/tests/fakes/fake_input.rs
type TerminalEvents (line 25) | pub struct TerminalEvents {
method new (line 30) | pub fn new(mut events: Vec<Option<Event>>) -> Self {
type Item (line 36) | type Item = Event;
method next (line 37) | fn next(&mut self) -> Option<Event> {
type NetworkFrames (line 51) | pub struct NetworkFrames {
method new (line 57) | pub fn new(packets: Vec<Option<Vec<u8>>>) -> Box<Self> {
method next_packet (line 63) | fn next_packet(&mut self) -> Option<&[u8]> {
method next (line 70) | fn next(&mut self) -> Result<&[u8], std::io::Error> {
function get_open_sockets (line 92) | pub fn get_open_sockets() -> OpenSockets {
function get_interfaces (line 152) | pub fn get_interfaces() -> Vec<NetworkInterface> {
function get_interfaces_with_frames (line 166) | pub fn get_interfaces_with_frames(
function create_fake_dns_client (line 172) | pub fn create_fake_dns_client(ips_to_hosts: HashMap<IpAddr, String>) -> ...
type FakeResolver (line 178) | struct FakeResolver(HashMap<IpAddr, String>);
method lookup (line 182) | async fn lookup(&self, ip: IpAddr) -> Option<String> {
FILE: src/tests/fakes/fake_output.rs
type TerminalEvent (line 14) | pub enum TerminalEvent {
type TestBackend (line 23) | pub struct TestBackend {
method new (line 31) | pub fn new(
type Point (line 47) | struct Point {
method clear (line 53) | fn clear(&mut self) -> io::Result<()> {
method hide_cursor (line 58) | fn hide_cursor(&mut self) -> io::Result<()> {
method show_cursor (line 63) | fn show_cursor(&mut self) -> io::Result<()> {
method get_cursor_position (line 68) | fn get_cursor_position(&mut self) -> io::Result<Position> {
method set_cursor_position (line 73) | fn set_cursor_position<P: Into<Position>>(&mut self, _position: P) -> io...
method draw (line 77) | fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
method size (line 109) | fn size(&self) -> io::Result<Size> {
method window_size (line 116) | fn window_size(&mut self) -> io::Result<WindowSize> {
method flush (line 126) | fn flush(&mut self) -> io::Result<()> {
Condensed preview — 124 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (858K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 734,
"preview": "# These are supported funding model platforms\n\ngithub: [imsnif]\npatreon: # Replace with a single Patreon username\nopen_c"
},
{
"path": ".github/dependabot.yml",
"chars": 408,
"preview": "version: 2\nupdates:\n - package-ecosystem: cargo\n directory: \"/\"\n schedule:\n interval: monthly\n open-pull-"
},
{
"path": ".github/workflows/ci.yaml",
"chars": 7067,
"preview": "name: ci\non:\n pull_request:\n push:\n branches:\n - main\n workflow_dispatch:\njobs:\n get-msrv:\n name: Get dec"
},
{
"path": ".github/workflows/publish-crate.yaml",
"chars": 799,
"preview": "# This workflow triggers when a stable release is published on GitHub.\n#\n# The crates.io token used for publishing was c"
},
{
"path": ".github/workflows/release.yaml",
"chars": 5696,
"preview": "# The way this works is the following:\n#\n# - create-release job runs purely to initialize the GitHub release itself\n# an"
},
{
"path": ".github/workflows/require-changelog-for-PRs.yml",
"chars": 1385,
"preview": "name: Changelog\n\non:\n pull_request:\n\nenv:\n PR_NUMBER: ${{ github.event.number }}\n PR_BASE: ${{ github.base_ref }}\n\njo"
},
{
"path": ".gitignore",
"chars": 8,
"preview": "target/\n"
},
{
"path": "CHANGELOG.md",
"chars": 13165,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3345,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 765,
"preview": "Contributions of any kind are very welcome. If you'd like a new feature (or found a bug), please open an issue or a PR.\n"
},
{
"path": "Cargo.toml",
"chars": 2421,
"preview": "[package]\nname = \"bandwhich\"\nversion = \"0.23.1\"\nauthors = [\n \"Aram Drevekenin <aram@poor.dev>\",\n \"Eduardo Toledo <etol"
},
{
"path": "Cross.toml",
"chars": 48,
"preview": "[build.env]\npassthrough = [\"BANDWHICH_GEN_DIR\"]\n"
},
{
"path": "INSTALL.md",
"chars": 1635,
"preview": "# Installation\n\n- [Installation](#installation)\n - [Arch Linux](#arch-linux)\n - [Exherbo Linux](#exherbo-linux)\n - [N"
},
{
"path": "LICENSE.md",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2019 Aram Drevekenin\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 7424,
"preview": "# bandwhich\n\n\n\nThis is a CLI utility for displaying current network utilization by process, connect"
},
{
"path": "build.rs",
"chars": 2974,
"preview": "use std::{env, fs::File};\n\nuse clap::CommandFactory;\nuse clap_complete::Shell;\nuse clap_mangen::Man;\nuse eyre::eyre;\n\nfn"
},
{
"path": "rustfmt.toml",
"chars": 0,
"preview": ""
},
{
"path": "src/cli.rs",
"chars": 2056,
"preview": "use std::{net::Ipv4Addr, path::PathBuf};\n\nuse clap::{Args, Parser, ValueEnum, ValueHint};\nuse clap_verbosity_flag::{Info"
},
{
"path": "src/display/components/display_bandwidth.rs",
"chars": 4573,
"preview": "use std::fmt;\n\nuse derive_more::Debug;\n\nuse crate::cli::UnitFamily;\n\n#[derive(Copy, Clone, Debug)]\npub struct DisplayBan"
},
{
"path": "src/display/components/header_details.rs",
"chars": 3235,
"preview": "use std::time::{Duration, Instant};\n\nuse ratatui::{\n layout::{Alignment, Rect},\n style::{Color, Modifier, Style},\n"
},
{
"path": "src/display/components/help_text.rs",
"chars": 1443,
"preview": "use ratatui::{\n layout::{Alignment, Rect},\n style::{Modifier, Style},\n text::Span,\n widgets::Paragraph,\n "
},
{
"path": "src/display/components/layout.rs",
"chars": 4492,
"preview": "use ratatui::{\n layout::{Constraint, Direction, Rect},\n Frame,\n};\n\nuse crate::display::{HeaderDetails, HelpText, T"
},
{
"path": "src/display/components/mod.rs",
"chars": 198,
"preview": "mod display_bandwidth;\nmod header_details;\nmod help_text;\nmod layout;\nmod table;\n\npub use display_bandwidth::*;\npub use "
},
{
"path": "src/display/components/snapshots/bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap",
"chars": 58851,
"preview": "---\nsource: src/display/components/display_bandwidth.rs\nexpression: test_bandwidths_formatted\n---\nDisplayBandwidth { ban"
},
{
"path": "src/display/components/table.rs",
"chars": 14497,
"preview": "use std::{collections::HashMap, net::IpAddr, ops::Index, rc::Rc};\n\nuse derive_more::Debug;\nuse itertools::Itertools;\nuse"
},
{
"path": "src/display/mod.rs",
"chars": 157,
"preview": "mod components;\nmod raw_terminal_backend;\nmod ui;\nmod ui_state;\n\npub use components::*;\npub use raw_terminal_backend::*;"
},
{
"path": "src/display/raw_terminal_backend.rs",
"chars": 1539,
"preview": "// this is a bit of a hack:\n// the TUI backend used by this app changes stdout to raw byte mode.\n// this is not desired "
},
{
"path": "src/display/ui.rs",
"chars": 6766,
"preview": "use std::{collections::HashMap, net::IpAddr, time::Duration};\n\nuse chrono::prelude::*;\nuse ratatui::{backend::Backend, T"
},
{
"path": "src/display/ui_state.rs",
"chars": 11310,
"preview": "use std::{\n cmp,\n collections::{HashMap, HashSet, VecDeque},\n hash::Hash,\n net::{IpAddr, Ipv4Addr, Ipv6Addr}"
},
{
"path": "src/main.rs",
"chars": 12016,
"preview": "#![deny(clippy::enum_glob_use)]\n\nmod cli;\nmod display;\nmod network;\nmod os;\n#[cfg(test)]\nmod tests;\n\nuse std::{\n coll"
},
{
"path": "src/network/connection.rs",
"chars": 3047,
"preview": "use std::{\n collections::HashMap,\n fmt,\n net::{IpAddr, SocketAddr},\n};\n\n#[derive(PartialEq, Hash, Eq, Clone, Pa"
},
{
"path": "src/network/dns/client.rs",
"chars": 2878,
"preview": "use std::{\n collections::HashSet,\n net::IpAddr,\n sync::{Arc, Mutex},\n thread::{Builder, JoinHandle},\n};\n\nuse"
},
{
"path": "src/network/dns/mod.rs",
"chars": 159,
"preview": "use std::{collections::HashMap, net::IpAddr};\n\nmod client;\nmod resolver;\n\npub use client::*;\npub use resolver::*;\n\npub t"
},
{
"path": "src/network/dns/resolver.rs",
"chars": 1980,
"preview": "use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};\n\nuse async_trait::async_trait;\nuse trust_dns_resolver::{\n "
},
{
"path": "src/network/mod.rs",
"chars": 127,
"preview": "mod connection;\npub mod dns;\nmod sniffer;\nmod utilization;\n\npub use connection::*;\npub use sniffer::*;\npub use utilizati"
},
{
"path": "src/network/sniffer.rs",
"chars": 6981,
"preview": "use std::{\n io::{self, Result},\n net::{IpAddr, SocketAddr},\n thread::park_timeout,\n time::Duration,\n};\n\nuse "
},
{
"path": "src/network/utilization.rs",
"chars": 1238,
"preview": "use std::collections::HashMap;\n\nuse crate::network::{Connection, Direction, Segment};\n\n#[derive(Clone)]\npub struct Conne"
},
{
"path": "src/os/errors.rs",
"chars": 212,
"preview": "#[derive(Clone, Eq, PartialEq, Debug, thiserror::Error)]\npub enum GetInterfaceError {\n #[error(\"Permission error: {0}"
},
{
"path": "src/os/linux.rs",
"chars": 1717,
"preview": "use std::collections::HashMap;\n\nuse procfs::process::FDTarget;\n\nuse crate::{\n network::{LocalSocket, Protocol},\n o"
},
{
"path": "src/os/lsof.rs",
"chars": 292,
"preview": "use crate::{os::lsof_utils::get_connections, OpenSockets};\n\npub(crate) fn get_open_sockets() -> OpenSockets {\n let so"
},
{
"path": "src/os/lsof_utils.rs",
"chars": 8691,
"preview": "use std::{ffi::OsStr, net::IpAddr, process::Command};\n\nuse log::warn;\nuse once_cell::sync::Lazy;\nuse regex::Regex;\n\nuse "
},
{
"path": "src/os/mod.rs",
"chars": 307,
"preview": "#[cfg(any(target_os = \"android\", target_os = \"linux\"))]\nmod linux;\n\n#[cfg(any(target_os = \"macos\", target_os = \"freebsd\""
},
{
"path": "src/os/shared.rs",
"chars": 8458,
"preview": "use std::{\n io::{self, ErrorKind, Write},\n net::Ipv4Addr,\n time::{self, Duration},\n};\n\nuse crossterm::event::{p"
},
{
"path": "src/os/windows.rs",
"chars": 1881,
"preview": "use std::collections::HashMap;\n\nuse netstat2::*;\nuse sysinfo::{Pid, ProcessesToUpdate, System};\n\nuse crate::{\n networ"
},
{
"path": "src/tests/cases/mod.rs",
"chars": 78,
"preview": "pub mod raw_mode;\npub mod test_utils;\n#[cfg(feature = \"ui_test\")]\npub mod ui;\n"
},
{
"path": "src/tests/cases/raw_mode.rs",
"chars": 16015,
"preview": "use std::{\n collections::HashMap,\n net::IpAddr,\n sync::{Arc, Mutex},\n};\n\nuse insta::assert_snapshot;\nuse once_c"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__bi_directional_traffic.snap",
"chars": 359,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_connections_from_remote_address.snap",
"chars": 463,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_different_connections.snap",
"chars": 530,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_single_connection.snap",
"chars": 356,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_processes_with_multiple_connections.snap",
"chars": 1107,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__no_resolve_mode.snap",
"chars": 1131,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_ip_packet_of_traffic.snap",
"chars": 356,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_packet_of_traffic.snap",
"chars": 356,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_process_with_multiple_connections.snap",
"chars": 463,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes.snap",
"chars": 1119,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes_bi_directional.snap",
"chars": 1131,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_one_process.snap",
"chars": 619,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__traffic_with_host_names.snap",
"chars": 1179,
"preview": "---\nsource: src/tests/cases/raw_mode.rs\nexpression: formatted\n---\nRefreshing:\n<NO TRAFFIC>\n\nRefreshing:\nprocess: <TIMEST"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_addresses.snap",
"chars": 9671,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_connections.snap",
"chars": 9671,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_processes.snap",
"chars": 9557,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_processes_with_dns_queries.snap",
"chars": 9556,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_startup-2.snap",
"chars": 177,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_startup.snap",
"chars": 9557,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__bi_directional_traffic-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__bi_directional_traffic.snap",
"chars": 15027,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-full-width-under-30-height-draw_events.snap",
"chars": 7005,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-full-width-under-30-height-events.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-full-height-draw_events.snap",
"chars": 9853,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-full-height-events.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-draw_events.snap",
"chars": 4447,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-events.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-draw_events.snap",
"chars": 4272,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-events.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-draw_events.snap",
"chars": 2659,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-events.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_connections_from_remote_address-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_connections_from_remote_address.snap",
"chars": 15217,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections.snap",
"chars": 15217,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection.snap",
"chars": 15026,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections.snap",
"chars": 15599,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__no_resolve_mode-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__no_resolve_mode.snap",
"chars": 24791,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_packet_of_traffic-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_packet_of_traffic.snap",
"chars": 15026,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_process_with_multiple_connections-2.snap",
"chars": 214,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_process_with_multiple_connections.snap",
"chars": 15217,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__pause_by_space-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__pause_by_space.snap",
"chars": 19274,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__rearranged_by_tab-2.snap",
"chars": 325,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__rearranged_by_tab.snap",
"chars": 43729,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes.snap",
"chars": 24790,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional.snap",
"chars": 24791,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap",
"chars": 24791,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total.snap",
"chars": 24789,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process.snap",
"chars": 24599,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total.snap",
"chars": 24599,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_host_names-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_host_names.snap",
"chars": 24791,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_winch_event-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_winch_event.snap",
"chars": 19274,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__truncate_long_hostnames-2.snap",
"chars": 251,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_events.lock().unwrap().as_slice()\n---\n[\n Clear,\n HideCursor"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__truncate_long_hostnames.snap",
"chars": 24791,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_addresses.snap",
"chars": 19248,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_connections.snap",
"chars": 19248,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_processes.snap",
"chars": 10419,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_windows_split_horizontally.snap",
"chars": 6248,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_windows_split_vertically.snap",
"chars": 9671,
"preview": "---\nsource: src/tests/cases/ui.rs\nexpression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR)\n---\n"
},
{
"path": "src/tests/cases/test_utils.rs",
"chars": 8128,
"preview": "#![cfg_attr(not(feature = \"ui_test\"), allow(dead_code))]\n\nuse std::{\n collections::HashMap,\n io::Write,\n iter,\n"
},
{
"path": "src/tests/cases/ui.rs",
"chars": 25545,
"preview": "use std::{collections::HashMap, net::IpAddr};\n\nuse crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};\nuse insta"
},
{
"path": "src/tests/fakes/fake_input.rs",
"chars": 5206,
"preview": "use std::{\n collections::HashMap,\n net::{IpAddr, Ipv4Addr, SocketAddr},\n thread, time,\n};\n\nuse async_trait::asy"
},
{
"path": "src/tests/fakes/fake_output.rs",
"chars": 3586,
"preview": "use std::{\n collections::HashMap,\n io,\n sync::{Arc, Mutex},\n};\n\nuse ratatui::{\n backend::{Backend, WindowSiz"
},
{
"path": "src/tests/fakes/mod.rs",
"chars": 81,
"preview": "mod fake_input;\nmod fake_output;\n\npub use fake_input::*;\npub use fake_output::*;\n"
},
{
"path": "src/tests/mod.rs",
"chars": 30,
"preview": "pub mod cases;\npub mod fakes;\n"
}
]
About this extraction
This page contains the full source code of the imsnif/bandwhich GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 124 files (814.6 KB), approximately 105.7k tokens, and a symbol index with 284 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.