API Traffic
Who is spending an API's budget right now — mnml's panes, the statusline poller, your own loops and scripts — read from the files every process on the machine writes.
Jira and Bitbucket give each account a budget of requests an hour. On a machine running mnml's panes, its statusline poller, a fleet of loops and the odd script, they all spend the same budget, and when it runs out every one of them slows down at once. The integration's statusline chip says that the budget is low. The API TRAFFIC pane says who is spending it.
Open it with view.api_traffic — space i a in the vim profile,
ctrl+k i a (or space i a) in the standard one — from View → API
traffic, or from the command palette. Its activity-bar row (a gauge,
under Scripts) starts hidden: the gear's Show hidden sections ▸ puts
it on the bar, and ui.rail.hidden is where that is kept. It opens below the active pane; running the command again
brings the same pane back and reads the files again.
The pane only reads files. It never sends a request, so opening it in the middle of a throttled morning costs nothing.
Where the numbers come from
Three files per service, each a documented contract that any program can write to (the SDK reference has the exact shapes):
- The draws file,
<service>-draws.jsonlbeside the shared bucket. Every process that takes a token from the bucket appends one line: when, its pid, its program name, the reason it gave, how long it waited and how many tokens were left. mnml's integrations write it, and so can anything else — a Python loop, a shell script — by appending the same line. This is the file that makes traffic from outside mnml visible, so when it exists the timeline and the WHO table count it. - mnml's own request log,
<data root>/requests/<service>.jsonl. Only mnml's integrations write it, but it records what the draws file cannot: the status each request came back with, and the requests a local cache answered without spending a token. The header's304share and its 429 count come from here. For a service with no draws file, the request log is what the pane counts instead. - The shared bucket,
<service>-ratelimit.json: the tokens left, the refill rate, a cooldown after a 429, and the time of the last one. Bitbucket counts its limit per token, so a client that names its token keeps a bucket of its own beside the shared one,<service>-ratelimit-<id>.json, and its draw lines carry thattoken_id. mnml's Bitbucket integration is one of those clients.
Both logs rotate at 4 MB and keep one older generation; the pane reads the older generation when it opens and follows the live file from there, so a rotation in the middle of a session loses nothing. It keeps seven days, and at most fifty thousand lines per log.
The files live where the SDK resolves them: $MNML_SHARED_STATE_DIR
when it is set, otherwise mnml's own data root. Point every program on
the machine that agrees to the format at the same directory and they
all land in one view.
Reading the pane
A tab per service that has any of the files. The busiest service this
hour opens first; Tab (and Shift+Tab) walks the tabs.
The header
BITBUCKET · last 1h · 412 requests · 89 % 304 · 2 429 window: 1hThe window's requests, the share of mnml's answered requests that came
back 304 Not Modified (an unchanged listing — nearly free), the 429s
and the cache hits. The window: chip walks the last hour, day and
week; a right-click lists all three. w does the same from the keyboard,
and 1 / 2 / 3 jump straight to one.
NOW
bucket — one row per bucket file: the tokens left, refilled to this second at the rate in the file, out of the bucket's capacity; the refill rate; a cooldown after a 429 and how long it has left; when the last 429 was, and how many the file has counted. With only the shared file the row ends in
(shared). With a token's file as well, each row starts with its name, the shared one first:bucket shared 7.5/40 tokens · 1.20/s · cooling 44s · last 429 4m ago · 4 throttles token 0123… 2.0/40 tokens · 0.60/s · no cooldown · last 429 30s ago · 9 throttlesEach row's hover names its file.
hour — requests in the last hour by every program, against the hourly limit: the bucket's refill rate times 3600, the same figure an integration's budget chip paces itself to. With more than one bucket the hour is split by the bucket each draw came out of (a draw line's
token_id; a line without one spent the shared bucket), each against the whole limit, because each token has a limit of its own:hour shared 12 (0 %) · token 0123… 300 (7 %) of 4320 limit each.mnml todayis the budget's day tally, which counts mnml's own integrations only.broker — whether the broker is up (this mnml hosting it, or another process) and how many requests are queued in each class: interactive, refresh, warm, batch. Down means every program takes tokens first come, first served.
feed — the integration's event feed, when its config names one: live while something has written the file inside its staleness limit, stale once it has gone quiet and the pane is polling again, polling when no feed is configured.
cache — entries in the shared HTTP cache, when that directory exists.
TIMELINE
Requests a minute over the window, stacked by program: the busiest at
the bottom, each in its own colour, with the seven busiest named and
everyone else stacked together as other. The legend above the strip
names the colours and marks mnml's own integrations with (mnml).
On the hour, each column is one minute. On the day, minutes are grouped to fit the pane, and each column's height is the average a minute across it. The week counts in ten-minute buckets.
The dashed line is the hourly limit spread over a minute. A column above it is spending faster than the bucket refills; a run of them is what drains it. When every column is far below the limit, the line would flatten the bars, so the limit is named under the strip instead.
Point at a column — or press ← / → — and the line under the strip
reads it out: the minute, the count, and the split by program.
WHO
One row per program that drew in the window, busiest first:
| column | what it says |
|---|---|
| program | the name each line gives, with its timeline colour; (mnml) marks mnml's own |
| requests | tokens it drew in the window |
| share | its part of the window's total |
| top reason | the reason it gave most often — poll, pane_open, warm… |
| worst wait | the longest one request was held waiting on the bucket |
| last seen | how long ago it last drew |
| pids | the pid that drew last, and how many others drew under the same name |
Hovering a row lists every reason it gave with its count, and every pid.
y copies the newest pid. A right-click opens Open in REQUESTS — the
REQUESTS view filtered to that program — and Copy pid. The REQUESTS
view reads mnml's own request log, so it shows rows for mnml's
integrations; for a program outside mnml it opens empty.
Keys
| key | does |
|---|---|
Tab / Shift+Tab |
next / previous service |
w, 1 2 3 |
walk the window; jump to hour, day, week |
← → (h l, [ ]) |
pick a timeline column |
↑ ↓ (j k) |
move in the WHO table |
y |
copy the program's pid |
r |
read the files again now |
Esc / q |
close (Esc first un-picks a column) |
How often it reads
The pane follows the same cadence as the other dashboards: every two
seconds while it is on screen and something drew in the last minute,
every five while it is on screen and quiet, and not at all while it is
off screen — it reads the moment it comes back. Settings →
Integrations → Dashboard refresh (ui.dashboard_refresh) pins the
pace or turns it to manual, where only r and the refresh chip read.
Settings → Integrations → API traffic window
(integrations.api_traffic_window) is the window the pane opens on.
Throttles
Every 429 an API sends anybody on the machine is counted on the NOW
line: throttles 3 in the last hour · last 4m ago · widget.py (2), mnml-bitbucket (1). The fleet writes each one to
api-usage/<UTC day>.throttles.jsonl beside the shared buckets, and
mnml's own 429s come from its request log; the pane reads today's file
and yesterday's, so nothing written around midnight is missed.
When new ones land, mnml raises one warning toast per service — Bitbucket
throttled — 3 × 429 in the last 5 min (2 from widget.py, 1 from
mnml-bitbucket) — and then stays quiet for five minutes however many
more arrive. Its API traffic button opens the pane on that service.
The 429s already in the files when mnml starts are history and never
toast. The toasts watch the files every 30 seconds even with the pane
closed. Settings → Integrations → Toast on 429s
(integrations.throttle_toasts) turns them, and that watching, off; the
NOW line still counts.
Making your own programs show up
Anything that spends from the same budget can be counted: append one
line per request to <service>-draws.jsonl in the shared directory,
with the seven keys the SDK reference lists — ts, pid, program,
service, reason, wait_ms, tokens_after. A Python script can
write it with the standard library alone. Give the program a name you
will recognise in the WHO table, and a reason that says why it asked.