A native macOS menu-bar utility that tells you, second by second, which apps are using your bandwidth and how much. No accounts, no telemetry, no background services beyond the menu-bar app itself. All data lives on your machine.
- Live total speed.
↓ 1.2 MB/s | ↑ 305 KB/s, refreshed every second. - Left-click opens the popover. Right-click brings up Launch-at-Login + Quit.
- One row per app, sorted by cross-session lifetime bytes. Each row reads as 3 lines on each side:
- Left: name + code-signature trust icon · System/User chip + PIDs · last-active time
- Right: total bytes · ↓ in / ↑ out · current speed (when transmitting)
- Live green dot before any app currently transmitting.
- Active-count badge in the header doubles as a filter — click to show only currently-active apps, click again to show all.
- Search bar (always visible) filters across app names, PIDs, ports, and services. Results group into four collapsible sections with colored match-type chips. Just start typing anywhere in the popover — the first printable keystroke jumps you straight into search.
- Right-click any row:
- Hide from List — single ignore
- Select Multiple to Hide… — enters multi-select mode with a top action bar; click rows to toggle (no checkboxes), then Hide Selected
- Reset All-Time Data — wipes the persistent lifetime store
- Gear menu: restore ignored apps, close all chart windows (when ≥ 2 are pinned), reset all-time data, quit.
A floating panel opens beside the popover with a per-app history graph and a port/service breakdown.
- 3-line header mirroring the row layout: pulsing activity dot + name + trust · origin + PIDs + status · live ↓/↑.
- Date-range picker: Live (1 min) / 5 min / 15 min / 1 hour / 24 hours / 7 days / 30 days.
- Trading-chart crosshair on hover — dashed vertical at the snapped time + two horizontal guides (blue ↓, green ↑) reading off the y-axis, plus a floating value box showing the exact numbers. Cursor turns to a crosshair over the plot.
- Port / service breakdown under the chart: every service the app talks to (443 → https, 53 → dns, …) with ↓/↑ totals. Click any port to expand the distinct remote IPs behind it.
- Resizable graph / port-service split with a draggable divider that highlights on hover. Per-pane minimums prevent collapse.
- Movable, resizable window. Drag from any non-interactive area; resize from edges + corners.
- Pin to keep it open. Up to 3 pinned panels can be open simultaneously — useful for comparing two or three apps' traffic patterns live. Unpinned chart auto-closes when you click anywhere in the main popover or outside both windows. Pinned charts survive until you close them explicitly.
The app spawns /usr/bin/nettop -P -n -L 1 once per second, parses the CSV output, and computes per-PID byte deltas. PIDs are grouped by bundle identifier so multi-process apps (Chrome helpers, Electron renderers) collapse to a single row. The previous architecture tried to keep one long-lived nettop process, which doesn't work — nettop block-buffers stdout when piped and only emits CSV when given -L. Per-cycle spawning costs ~60 ms each tick (idle CPU stays near 0%).
Code signatures are checked once per bundle path via SecStaticCodeCheckValidity and cached. Lifetime byte counts are accumulated into a JSON file under ~/Library/Application Support/NetworkUsageMonitor/lifetime.json and saved every 30 seconds plus on quit. Speed history is tiered: 1 Hz samples for the last hour in memory, per-minute averages for 24 hours, and per-hour averages for 30 days — both averaged tiers persisted to speed-history.json.
| macOS | 13 Ventura or later (Swift Charts) |
| CPU | Intel or Apple Silicon |
| Toolchain (for building) | Swift 5.9+ (Xcode 15 or Command Line Tools) |
No external Swift packages. The app is not sandboxed — it needs to invoke nettop and read installed-application metadata via NSWorkspace, both of which the App Sandbox blocks. That's why this can't ship on the Mac App Store; download from GitHub Releases (or Homebrew) instead.
brew install --cask light-house-group/taps/network-monitorGrab NetworkUsageMonitor.app.zip from the Releases page, unzip, and drag NetworkUsageMonitor.app into /Applications.
First launch: right-click the app → Open → confirm. (Standard Gatekeeper prompt for unnotarized builds; the app is ad-hoc-signed.)
git clone https://github.com/Light-House-Group/Network-Speed.git
cd Network-Speed
./Scripts/build_app.sh --run # builds + launches
# or
./Scripts/build_app.sh --install # builds + copies to /Applications + launchesThe script handles compilation, bundle assembly, ad-hoc signing, and (optionally) installation.
| Action | Result |
|---|---|
| Left-click menu bar | Open / close popover |
| Right-click menu bar | Launch-at-Login + Quit menu |
| Type anywhere in popover | Auto-enter search; characters seed the field |
| Click row | Open chart for that app |
| Click same row again (chart open) | Close that chart |
| Click anywhere in popover (chart open, unpinned) | Close transient chart |
| Pin button in chart header | Promote chart to standalone window (survives outside clicks) |
| Drag chart panel body | Move window |
| Drag chart edges/corners | Resize window |
| Drag chart divider | Resize graph vs. port-service split |
| Hover plot | Crosshair with ↓ / ↑ / time at snapped sample |
| Right-click row → Select Multiple to Hide… | Enter multi-select mode |
Network-Speed/
├── Package.swift
├── Scripts/
│ ├── build_app.sh # build + sign + (optional) install/run
│ ├── Info.plist # bundle metadata
│ └── NetworkUsageMonitor.entitlements
├── Sources/NetworkUsageMonitor/
│ ├── NetworkUsageMonitorApp.swift # @main + AppDelegate + popover plumbing
│ ├── Managers/
│ │ ├── NetworkMonitor.swift # per-cycle nettop spawner
│ │ ├── NettopLineParser.swift # pure CSV-row parser
│ │ ├── NetworkViewModel.swift # SwiftUI publishers
│ │ ├── ProcessTracker.swift # per-PID → per-bundle aggregation
│ │ ├── ProcessClassifier.swift # System/User + Trust verdicts
│ │ ├── LifetimeUsageStore.swift # persistent all-time totals
│ │ ├── SpeedHistoryStore.swift # tiered history: 1h raw + 24h/30d on disk
│ │ ├── LatestSnapshotStore.swift # per-id live snapshot cache
│ │ ├── IgnoreListManager.swift # user-hidden apps
│ │ ├── BudgetManager.swift # per-app session limits (no current UI)
│ │ ├── BudgetNotifier.swift # threshold-crossed notifications
│ │ ├── ServiceDirectory.swift # port → service name lookup
│ │ └── LaunchAtLoginManager.swift
│ ├── Models/
│ │ ├── AppNetworkUsage.swift
│ │ └── NetworkSpeed.swift
│ ├── Utilities/FormatUtility.swift
│ └── Views/
│ ├── PopoverRootView.swift # popover + search + multi-select
│ └── GraphPanel.swift # chart panel + multi-pin controller
└── Tests/NetworkUsageMonitorTests/
├── NettopLineParserTests.swift
├── ProcessTrackerTests.swift
├── BudgetManagerTests.swift
├── FormatUtilityTests.swift
└── LifetimeUsageStoreTests.swift
- 30-day history with downsampling. Raw 1 Hz samples for the last hour (in memory), per-minute averages for 24 hours, per-hour averages for 30 days. Long ranges are averaged, not raw — a 30-day view shows hourly means.
- No retroactive counters. macOS doesn't expose per-process bytes the kernel counted before our app launched, so All-Time totals only include bytes observed while the monitor was running.
- Cold-start cost. First snapshot after launch hashes each running binary's signature. Expect a brief ~0.3-1 second CPU spike, then idle.
- Not in the Mac App Store. See Requirements — the sandbox blocks
nettopinvocation.
Bug reports, fixes, and feature ideas are all welcome. Please open an issue first for anything larger than a small fix so we can sort scope. See CONTRIBUTING.md for the basics.
If you're a maintainer (or planning to fork-and-ship), read DISTRIBUTION.md — covers Apple Developer ID signing, notarization for Gatekeeper, GitHub Releases automation, and submitting a Homebrew Cask formula.
MIT — see LICENSE.