Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Huskarl AI colleagues on your own Mac Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Elyra VM Virtual machines for macOS, Linux and Windows on your Mac Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Licence & updates
Licensing
Updates
Elyra
Measurement

Measurement

What a number in Starf means, and when there is deliberately no number.

Unavailable is not zero

macOS shares a process's detailed counters only when it chooses to. On an ordinary desktop about a quarter of processes deny them — on the machine this was built on, 341 of 1,245. Two calls can be refused independently, so a process carries two flags:

Flag Set when Guards
accessible proc_pidinfo(PROC_PIDTASKINFO) answered CPU, CPU time, memory, threads
ioAccessible proc_pid_rusage(RUSAGE_INFO_V4) answered bytes read and written, wake-ups, physical footprint

A counter behind a flag that is false was never measured. It renders as —, exports as an empty CSV cell and a JSON null, and sorts below every real value in both directions — because an unavailable counter is not a small one.

A measured zero is a measurement. It renders as 0, exports as 0, and sorts with the other numbers.

Refused is not gone

Two different things stop Starf reading a process, and the kernel distinguishes them:

Kernel answer Meaning Reported as
EPERM the process exists; macOS will not describe it "macOS does not share this process's details."
ESRCH no such process "This process has exited."

Conflating them would be the easiest mistake in the codebase to make, and it was made once: the first per-thread probe against WindowServer reported it as exited when it was running perfectly well. sys::process::identity now returns Same, Gone or Denied, and every message downstream says which.

Identity

A PID on its own is not an identity — macOS reuses them. Everything that has to survive across samples is keyed by PID and start time together:

  • rate baselines, so a new process never inherits the previous holder's counters
  • GPU observed time
  • tree expansion state, so a recycled PID does not arrive collapsed
  • per-thread baselines

Before Starf sends a signal to a process, or reads a diagnostics page, it re-checks that the PID still names the same process, with the same start time and owner. Diagnostics checks again after the read, so a process that exits mid-walk cannot have its successor's files attributed to it.

Rates

Every rate is a difference between two readings divided by the time actually measured between them, never by the interval that was asked for.

Three rules apply everywhere:

  • A first reading has no rate. It establishes a baseline and reports nothing. A process, processor, thread or GPU client seen for the first time shows —, not 0.
  • A counter that went backwards is a reset. It reports nothing rather than a negative rate or a wrapped-around spike.
  • A gap too long to describe one interval is not differenced. Per-thread collection refuses a gap over thirty seconds, which is what a paused monitor or a sleeping machine produces.

Host CPU tick counters are 32 bits and wrap. The difference is taken with wrapping subtraction, which is the real elapsed count as long as fewer than 2³² ticks passed.

Scales

Two CPU scales exist, and mixing them up would make every number wrong by the processor count:

Where Scale On a 16-processor machine
Process list, per-thread charts 100% is one logical processor a process on four processors reads 400%
CPU card, breakdown, per-processor tiles 100% is the whole machine user + system + idle is always 100

A per-processor tile is 0–100% of that one processor, whatever the machine's count.

GPU percentages are driver-reported GPU time over elapsed time. Overlapping work can exceed 100%.

Totals that do not add up, and why that is correct

Host CPU against the process column. The host counters include kernel work and the processes that deny their own counters. The visible process column does not sum to the host total, and forcing it to would mean inventing the difference.

Subtree totals against each other. A parent's Σ includes its children's, so adding the visible rows double-counts. The overview charts are what measure the machine.

Memory against the machine. Process memory counters are summed as reported. Shared mappings overlap between processes, so a subtree's memory is not unique physical RAM and not what quitting it would free.

Processes against the disk. Process disk counters and the disk driver's own are counted at different layers and moments. They can differ either way, so neither is shown as a share of the other.

Resident against virtual. Resident pages are part of a virtual mapping. The two are selectable measures, never stacked.

Bounds and coverage

A subtree total tracks how many of its members reported the counter:

Shown Meaning
a number every member reported; this is the total
≥ and a number some member did not report, or the sum reached an arithmetic limit; this is a lower bound
— no member reported

Above 2⁵³ a f64 no longer represents every integer, so a sum past that point is flagged as a bound rather than passed off as exact. No real counter comes close; the guard exists so a corrupt one cannot slip through.

Steady growth

A process is tagged growing when its memory footprint has climbed steadily for at least five minutes. Starf never calls this a leak: a leak looks like this, but so does a cache that is still filling or a document that is still getting bigger, and nothing the kernel reports tells them apart. The tag says what was measured — the rate, the span, from and to — and leaves the verdict to the person who knows what the program is doing.

The footprint is read every ten seconds and kept for fifteen minutes. Four conditions must all hold before a process is flagged:

Condition Why
a least-squares slope above zero over at least five minutes a trend, not a moment
at least 64 MB of growth smaller growth is not worth anyone's attention
at least 10 % of where it started 100 MB on a 4 GB process is noise
a rise in at least 75 % of the steps between readings a sawtooth of allocate-and-free rises about half the time and never qualifies

A process's first five minutes are not judged. Every program grows from almost nothing to its working size when it starts; counting that would flag every app someone had just opened. A process whose footprint is not readable adds nothing to its history rather than a zero, and the history follows PID and start time, so a reused PID starts afresh.

Retention

Histories keep fifteen minutes. Per-processor and per-thread series keep at most 901 points each under a shared budget of 131,072 points, so a machine with very many processors keeps a shorter history rather than an unbounded one. An unavailable reading is stored as a gap, and the chart breaks its line there instead of drawing through time it did not measure.