A menu-bar system monitor for macOS, written in Rust with gpui-kit. Tidegauge groups processes by their owning app and presents system activity in a compact panel.
Download a release DMG, open it, and drag Tidegauge.app onto the Applications
shortcut. Launch Tidegauge from Applications. The release DMG is signed and
notarized; a locally generated image is not automatically notarized.
Overview in Tidegauge 0.3.3, dark appearance, captured on a virtual Mac. The virtual Mac has no battery and no readable GPU usage, so those read as unavailable; readings describe that environment, not physical hardware.
| Light appearance | Dark appearance |
|---|---|
![]() |
![]() |
![]() |
![]() |
The screenshot archive records how each capture was made.
See the changelog for published releases and unreleased changes, including work still in review. Repository features are not necessarily present in the latest downloadable release.
Tidegauge is an early development build. This repository contains the app source and recovered development notes. Some controls remain incomplete, while local .app packaging and a fail-closed production release helper are available. Production signing and notarization require the credentials described in Releasing. Collector regression tests cover the fixes described in CONTRIBUTING. See the limitations below before relying on a metric or action.
Requirements:
- macOS. The app uses Mach, libproc, IOKit, CoreAudio, and AppKit directly.
- A Rust toolchain with Cargo and the macOS developer tools installed.
- The macOS utilities
lsof,nettop,system_profiler, andosascriptavailable to the process.
Run a development build from the repository root:
cargo runFor an optimized build:
cargo build --release
./target/release/tidegaugeThe app opens a resizable window and adds one item to the menu bar. It runs without a Dock icon. A left click on the menu-bar item opens a small panel with live readings, AI plans and the busiest apps, and a button that opens the window; a right or Control click opens a menu with Open Dashboard, Check for Updates, Settings, and Quit. Closing the window hides it while collection continues. The window's sidebar lists every page and ends with Settings. Command-comma also opens Settings, and Command-Q quits while the window is focused. A per-user lock prevents duplicate instances; launching again leaves the existing app running.
Create an ad-hoc preview bundle after the optimized build:
packaging/package-app.sh previewThe packaging modes, signing prerequisites, and required production notarization flow are documented in Releasing. Production packaging fails closed when its signing credentials are unavailable.
Create a local drag-and-drop disk image from the same app bundle:
packaging/create-dmg.sh build/preview/Tidegauge.app build/Tidegauge.dmgThe image contains only Tidegauge.app, an Applications shortcut, and the
committed Finder layout. Only the release pipeline notarizes and staples its
DMG; the helper optionally signs with DEVELOPER_ID_APPLICATION.
The packaged app carries a WidgetKit extension (Contents/PlugIns/TidegaugeWidgets.appex,
sources in widgets/). Add it with Edit Widgets on the desktop or in Notification Center:
Tidegauge Reading (small; right-click, Edit Widget, to pick CPU, Memory, GPU, Battery,
Network or Disk) and Tidegauge Overview (medium and large). Tidegauge stays local-only: it
writes a small JSON file of the latest readings into an app group container
(~/Library/Group Containers/45AD7E7G5G.no.ogard.tidegauge/) every 12 seconds, and
only while a widget exists. The widget reads that file; neither side uses the network.
macOS decides when a widget is redrawn, so the figures lag the menu bar by minutes, not
seconds. Without recent readings (Tidegauge not running for more than 2 minutes) a widget
shows "Open Tidegauge to start". Widgets work in builds signed with the Developer ID
identity. A preview or QA bundle is ad-hoc signed, and macOS may not list its widgets.
TIDEGAUGE_SKIP_WIDGETS=1 packaging/package-app.sh preview leaves the extension out
(it needs Xcode).
| Area | Current implementation |
|---|---|
| Menu bar | One Tidegauge icon by default; optional labeled readings share the same native item |
| Menu-bar panel | Your choice of live tiles (CPU, memory, network, disk, GPU, battery, temperature, fans, power) with recent curves, AI plans, the busiest apps, and quick actions; optionally opened from any app with ⌃⌥⌘T or ⌃⌥⌘M (Settings → General) |
| Overview | Favorite metric cards (including temperature, fans and power), busiest apps, memory and power breakdowns, optional compact layout, and configurable sidebar order |
| Battery health | Capacity health and cycle count kept once a day beyond History's 30 days, charted per day on the Battery page |
| Cores and clocks | Per-core use grouped by core type (Super, Performance, Efficiency), each group's clock speed, and the GPU's core count and clock |
| Metric pages | CPU, Memory, Disk, Network (with the link type, addresses, Wi-Fi signal and whether the Mac is online), GPU, Battery, Sound, Bluetooth, and stored History, each with notes behind ⓘ |
| Memory pressure | macOS's own pressure level (normal, warning, critical) and the share of memory it counts as available, on the Memory page and Overview |
| Agents | Running coding-agent sessions with what they started and Quit, AI plan usage read from OpenQuota's saved data, and development servers |
| Device telemetry | Read-only temperature/fan channels, per-drive capacity/APFS/activity/health, and battery health/charging detail where available |
| App grouping | CPU, memory, disk counters, and processor power grouped by executable bundle or parent app |
| Sound | Choose the output and input device; volume and mute for both where the device supports them; per-app volume and mute (macOS 14.2+) with a level meter |
| Bluetooth | Connected devices and available battery levels from system_profiler |
| Dev servers | Shown in Agents: owned development listeners, explicit process/project/port confirmation, signal-time eligibility checks, and asynchronous Stop outcomes |
| History | Local 1-hour, 12-hour, 24-hour, 7-day, and 30-day system/app trends (including chip, SSD and battery temperatures) with downsampled averages and visible gaps; an expanded app in Apps & processes shows its own last hour; Export CSV saves the samples to Downloads |
| Alerts | Opt-in CPU, used-memory, memory-pressure, disk, network, network lost and back, single-app CPU and memory, low startup-disk space and overheating alerts; configurable CPU/memory/app thresholds and independent cooldowns |
| Keep awake | Native idle-system-sleep prevention for 15 minutes, 30 minutes, 1 hour, or until stopped |
| Apps & processes | Every app with its processes, filter and sort, observed ownership, and confirmed Quit and Force Quit for an app, a process, or what an app left running |
| Desktop widgets | Small (one reading, chosen when you edit the widget), medium (four readings with sparklines) and large (all six) WidgetKit widgets for the desktop and Notification Center; macOS 14 or later, packaged builds only |
| Language | English or Norwegian Bokmål, following macOS or chosen in Settings |
| Updates | Signed automatic updates, with Check for Updates in Settings, the panel and the menu |
| Tools · Startup | Bounded legacy registration inventory, explicit override evidence, Tidegauge login preference and System Settings handoff |
| Last minute | Apps & processes view of recent per-process CPU, disk I/O and wakeups with observed coverage, not an energy score |
| Tools · Compare | Frozen 5-, 15- or 30-minute system summaries and equal-length, non-overlapping comparisons |
| Tools · Storage | Cancellable metadata-only selected-folder scan, allocated/logical sizes, largest files and Reveal in Finder |
| Tools · Apps & Packages | Explicit source-filtered app/package inventory and single-item package-manager actions where ownership can be verified |
- Project Stop rechecks ownership, start time, project eligibility, and a confirmed listening port. It requests termination with SIGTERM and reports whether the original process exited; it never escalates to force-kill. As with any PID-based API, validation and signaling are not an atomic operation.
- Process signing metadata is fetched only on request and is not signature validation or a safety verdict. It never changes Quit/Stop eligibility. See process semantics.
- Device controls apply to the default media output and input. Per-app volume passes that app's sound through Tidegauge (macOS 14.2+, one audio-capture permission); nothing is recorded or saved, and at 100% or when Tidegauge quits the app plays on its own. See audio capabilities.
- The Neural Engine is not shown: on a Mac17,7 (M5 Max, macOS 27.0.1) neither macOS's energy counters nor
powermetrics(even as root) report it. GPU and battery detail depend on hardware and available system properties. GPU power is measured from macOS's GPU energy counter; system power comes from the power controller and is reported only on Macs with a battery. Per-app watts are the processor energy macOS accounts to each process (Apple silicon), from its energy model rather than a meter. GPU, display, radio and work done by system processes on an app's behalf are not included, so graphics-heavy apps read low. - Alerts default to off, including when older settings are migrated. Enable each category in Settings. CPU/memory must exceed the chosen threshold for 20 seconds; disk/network for 30 seconds; one app's CPU (100% is one core, up to 400%) for 5 minutes, each app with its own cooldown; one app's memory above the chosen 1–64 GB for 5 minutes; startup-disk space must stay under 10 GB free for a minute; overheating fires when macOS reports a serious or critical thermal state for 10 seconds; network lost fires when macOS reports no usable network path for 10 seconds, and "back" follows when it returns (macOS's path monitor is read; nothing is sent over the network); memory pressure when macOS reports warning or critical for 20 seconds. While pressure is warning or critical, the menu-bar memory reading turns orange or red. An episode alerts only once until recovery, with a separate ten-minute cooldown for each category. Notification permission, Focus, and macOS notification settings still govern delivery.
- Launch at login is
available on macOS 13+ only for a bundle installed in
/Applicationsor~/Applications; approval may be required in System Settings > General > Login Items. Started from anywhere else, Tidegauge offers once to move itself into Applications (the old copy goes to the Trash) and relaunch. Closing the dashboard hides it while collection continues. - Live module charts show the current session; the History tab reads persisted SQLite data off the UI thread. Retention targets 30 days with hourly pruning while running. Legacy placeholders and the selection of recorded apps have important limitations; see history semantics.
- Keep awake prevents idle system sleep only. Displays may sleep, and closing the lid or explicitly sleeping still takes precedence. It releases on expiry, Stop, or app exit and does not resume automatically after restart.
- Dashboard reset affects only tab order, favorites, and compact layout. It does not reset menu-bar readings, appearance, login, or alert preferences. Overview and Settings remain reachable.
- Startup inventory is partial. Modern login/background items are managed through System Settings. Third-party disable, removal and restoration are deliberately unavailable until managed/protected registration eligibility can be established safely; a plist or an absent launchctl override does not establish effective state.
- Resource activity is a recent observed window, not lifetime consumption, watts, an energy-impact score or predicted battery savings. Counter resets, process replacement, missing observations and long gaps reset coverage; wakeups are reported interrupt counters only, and package-idle wakeups remain unavailable.
- Baselines compare system metrics only, require 80% coverage for percentage changes, and never divide by a zero baseline. A saved summary survives History pruning. Power-source/thermal context is not recorded, and differences do not prove causation.
- Storage scans never delete files or calculate guaranteed reclaimable space. APFS sharing and snapshots affect totals. Cloud placeholders, links, other volumes and inaccessible files are skipped. Bounds on entries, recursion and queued directories can produce explicitly partial results.
- Apps & Packages covers detected installations only. Linked packages, missing registry provenance, ambiguous versions and unsupported installer artifacts remain read-only. Manager actions can run lifecycle scripts and change dependencies; they are not atomic and have no automatic rollback. Removal requires a successful command and a complete reread of the same manager source before success is reported. No elevated privileges, forced removal or leftover-data cleanup is used. Application metadata is read from
Contents/Info.plistwithplutil; whole-file JSON conversion cannot represent<data>or<date>values, so a plist containing either falls back to per-key extraction of the bundle name and version. A bundle with no readable Info.plist is reported as a warning rather than silently dropped.
Projects requires a working directory within four levels of a Git root or a recognized manifest (package.json, Cargo.toml, pyproject.toml, go.mod, Gemfile, or composer.json). Known development runtimes and custom executables inside that project qualify. Desktop app bundles, other users' processes, root/home directories, and listeners without a project marker are excluded. Servers launched from unrelated folders will not appear.
| Data | macOS location |
|---|---|
| Settings | ~/Library/Application Support/no.ogard.tidegauge/settings.json |
| History | ~/Library/Application Support/no.ogard.tidegauge/history.db |
| Saved system baseline | ~/Library/Application Support/no.ogard.tidegauge/system-baseline-v1.json |
| Cached app icons | ~/Library/Caches/no.ogard.tidegauge/icons/ |
Open Settings from the sidebar, the menu-bar panel, the menu, or Command-comma. Each metric can be Off, Icon, Value, Graph, or Value + graph. Optional readings appear together in one native item, with labels for values and graphs, consistent spacing, and fixed widths for changing values. Changes are saved immediately and applied on the next refresh. Use Reset to icon only to clear extra readings. Existing preferences are preserved on upgrade.
You can hide the Tidegauge icon while a metric is enabled; it stays visible if every metric is Off so Settings and Quit remain reachable. Graphs show a dash until enough samples are available. K/s, M/s, G/s, and T/s are byte rates using powers of 1024.
The app uses defaults when the settings file is absent or cannot be parsed. You can also edit the file directly:
{
"dark": null,
"show_app_icon": true,
"menu_items": [],
"alert_cpu_pct": 90.0,
"alert_mem_pct": 92.0,
"launch_at_login": false
}Available metric identifiers are cpu, memory, network, disk, battery, gpu, and power. The menu-bar pump reloads this file while the app runs. Appearance can be set to System, Light, or Dark for the dashboard.
History includes app names and resource usage. The sampler records headline metrics and the app groups using the most memory, CPU, disk and network approximately every five sampling passes. Sampling starts a new process and chart baseline on each launch; renaming the storage namespace does not migrate older settings or history.
The sampler runs on a background thread. It publishes each completed Snapshot for the panel and status-bar pump to read without waiting for the collectors. Per-app network rates persist between network sampling passes. Each pass waits two seconds after its work completes; external utilities can lengthen that interval. Bluetooth collection runs every ten passes, per-app network collection every other pass, and history persistence every five passes.
| File | Responsibility |
|---|---|
src/main.rs |
Application entry point, panel creation, status-bar pump |
src/instance.rs |
Per-user process lock preventing duplicate monitors |
src/app.rs |
Sampler lifecycle, shared panel handle, notifications |
src/engine.rs |
Counter deltas, snapshot model, in-memory chart histories |
src/ffi.rs |
Raw macOS C API declarations and layouts |
src/metrics.rs |
System CPU, memory, disk, network, GPU, battery, and chip collectors |
src/processes.rs |
Process enumeration and app grouping |
src/projects.rs |
Listening ports, project grouping, idle tracking, per-app network collection |
src/audio.rs |
CoreAudio device and process properties |
src/bluetooth.rs |
Bluetooth JSON parsing and cache |
src/history.rs, src/history_browser.rs |
SQLite persistence and read-only range queries |
src/baselines.rs, src/resource_activity.rs |
Frozen time-weighted summaries and identity-aware recent process activity |
src/startup.rs, src/packages.rs, src/storage.rs |
Bounded inventories and source-specific safety checks |
src/tools_panel.rs |
Tools navigation and asynchronous inventory/scan/comparison controls |
src/alerts.rs |
Sustained alert thresholds, recovery, and cooldowns |
src/keepawake.rs |
Owned native idle-sleep assertions and expiry |
src/controls.rs |
Settings, history, keep-awake, and output-device controls |
src/settings.rs |
Settings model, defaults, and JSON persistence |
src/statusbar.rs |
Native menu-bar items and sparklines |
src/ui.rs |
Panel navigation, tab content, and charts |
src/theme.rs, src/icons.rs |
Design tokens and app-icon caching |
- Roadmap and implementation issues: priorities, acceptance criteria, and verification steps.
- Artwork: logo files and generation prompt.
- Contributing: checks, manual verification, and review expectations.
- Design: colors, layout, and menu-bar behavior.
- Releasing: bundle modes, signing prerequisites, and update acceptance.
- Signing-key recovery: portable backups, restore checks, and hosting migration.
- 0.1.0 release notes: included capabilities and remaining acceptance checks.
- Recovery manifest: provenance of the recovered files.
- Historical findings and historical test plan: evidence from an earlier environment, not validation of the current checkout.
Tidegauge is licensed under the MIT License.
Copyright (c) 2026 Henrik Øgård.





