Magnemite
Features

Monitoring and automations

Rules that watch a box, act when it stops working, and say so in Discord, ntfy or a browser notification.

This feature is WIP

This feature is not finished and ships disabled by default. It wasn't properly tested. Feedback/testers appreciated.

A box can be online, heartbeating every twenty seconds, and scanning nothing: the scanner died, Pokémon GO fell out of focus, an ANR dialog is waiting for somebody to press ENTER, the loop stalled. None of that shows in the fleet table, because by every measure it has the box is fine.

Settings → Monitoring turns those into rules. A rule is a signal, how many bad readings in a row it takes to count, and a ladder of what to do, soft first, hard second.

The Monitoring page in the sidebar is it's history: what the rules saw and did across the fleet, which is where "why did that box reboot at 3am" is answered.

What it watches?

SignalAnswered byWhat it means
Service is downa probe, or the process listThe scanner's service is not running
App is not in focusthe boxSomething else owns the screen — usually the launcher
App is not respondingthe boxThe package is sitting on an ANR dialog
Health check is failinga probeThe scanner is logging faults faster than work
Loop has stalleda probeThe scanner is alive and has stopped doing anything
Box is unreachablethe hubThe socket has been gone past the offline timeout
Box is connected but silentthe hubThe socket is open and the heartbeats stopped
Rotom lost the boxthe hubRotom is not seeing it, or would not hand it work
Rotom has the box but no workersthe hubConnected to Rotom with nothing allocated on it
Scanner is allocated and doing nothingthe hubA worker is open and no requests go through it

The first five are knowable only on the box, so they need a recent agent, the tab counts how many boxes are behind, and rules skip a box that cannot answer. The rest the hub sees for itself and work on every box today; the Rotom ones need Rotom turned on.

Unreachable and silent are different faults

They look identical from the outside — nothing has been heard from the box — and the remediation is opposite. Unreachable means the socket is gone, so nothing can be sent: the ladder can only tell you. Connected but silent means the socket is open and the agent stopped reporting, so a restart, a shell command or a reboot all still land on it. It is the one fault class here whose remedy is actually available.

They never both fire on one box. Whichever one applies, the other reads unknown and leaves its ladder where it was.

A box goes silent while staying connected when its heartbeat is blocked — dumpsys waiting on a framework that is restarting is the usual cause, and on Redroid a stop && start does exactly that without the agent noticing.

What the box is costing itself

Three more, and these are the cheapest signals in the feature: no probe, no agent version, nothing new on the wire. The agent has reported all of it on every heartbeat since before monitoring existed — it is what the Load card draws — and the hub simply never compared it against anything.

SignalThe numberCompared against
Box is low on memoryavailable / total memorya percentage floor
Box is low on diskfree / total on /dataa percentage floor
Box is running hotthe SoC's thermal zonesa ceiling in °C

The disk one is the one to turn on first, and not for the reason it looks: an install already refuses on low space and fails the job. A rule that clears caches at 10% turns a failed rollout into nothing happening at all.

The temperature one only tells you, and there is no second rung to write. No action on the catalog cools a box down, and rebooting one does not — it comes back to the same cupboard. The answer is air.

A container with no memory limit can never trip the memory rule

Inside a container the agent reads the cgroup, not /proc/meminfo, so ten boxes on one machine each report their own memory rather than all reporting the machine's. That is what makes this signal safe where a /proc/loadavg probe is not. The exception is a container with no memory limit set at all: there is no cgroup ceiling to read, the reading falls back to the machine's, and a 15% floor will never be crossed. The rule stays enabled and silent, which is the one failure this feature otherwise refuses — so set a limit per machine, or use HOST_MEMORY_LOW for that fleet.

A box whose ROM exposes no thermal sensor reads unknown rather than cold, which is common on TV hardware and means the rule simply never fires there. That is the usual rule working: unknown neither counts nor resets.

The three Rotom signals are deliberately exclusive, so one fault never lights two ladders: a box Rotom has lost is only ever Rotom lost the box, a box with nothing allocated is only ever no workers, and doing nothing is read only where both of those are fine.

Two caveats worth knowing before switching them on:

  • No workers is normal on a fleet with more boxes than accounts. The spares are idle on purpose. Scope that rule to a group, or raise its threshold.
  • Doing nothing is silent unless Rotom measures request rates, which it only does in requests mode, or proxy mode with inspect. Where it does not measure, the signal reads unknown rather than idle — which is why turning it on cannot start rebooting a fleet that never reports a rate.

On a machine

A rule declares which kind of device it is about — a box or a machine — and four signals exist for the second:

SignalAnswered byWhat it means
HOST_MEMORY_LOWthe hubFree memory plus free swap, under the rule's percentage floor
HOST_DISK_LOWthe hubFree space under that floor, on the filesystem docker keeps its images and volumes on
HOST_DOCKER_DOWNthe hubThe agent is up and the docker daemon is not
REDROID_INSTANCE_DOWNthe hubA container that should be running is not

Swap counts as headroom in the first one, because on these machines it is exactly what stands between fifty workers and the OOM killer. A machine with no swap reports zero rather than nothing, so adding it is safe and a genuinely absent reading stays unknown.

The last two are read from what the machine said on its last heartbeat, not from its row:

  • HOST_DOCKER_DOWN is the agent reporting while docker does not answer it. The machine's stored docker version deliberately keeps the last one ever seen, so a newer agent's finding is not wiped by an older one's silence — which makes the column history, and a daemon being down a question about right now. A hub that has just restarted reads unknown for one heartbeat, which the startup grace already covers.
  • REDROID_INSTANCE_DOWN counts only the instances marked present: a removed one keeps its row, and counting it would have a drained machine alarm forever. All the known ones running is healthy even while another is still unknown, so a machine recovers on the container that came back instead of waiting for one nothing has looked at.

A machine mid-converge is left alone

Its containers are being created or replaced because somebody asked for that, so instances legitimately are not running. It is skipped for the same reason a box mid-install is: the only thing a rule could report is the damage the hub itself asked for.

RESTART_INSTANCE is the narrowest remediation in the project: up -d --no-recreate, naming only the services that are down. A container that is running is not touched, nothing is removed, and the machine is never rebooted — which is the whole point of the action existing, since rebooting takes every other box on the machine down for one box's fault.

AGENT_OFFLINE needs nothing new: pointed at machines, it already means exactly the right thing, because a machine is a Device and its socket is gone the same way a box's is.

Why these are signals and not probe rules

A shell probe on the machine looks cheaper — one HEALTH_CHECK_FAILED rule per check, no enum values, no code. It does not work, and the way it fails is silent: rulesFor dedupes on signal + package name and keeps one winner per key, so three HEALTH_CHECK_FAILED rules with no package collapse into one. Two of your three checks would simply never run — no error, no probe, nothing in the feed to say why.

And a probe on a box reads the machine's load, on every box at once

Inside a container, /proc/loadavg and the thermal zones are the host's. A HEALTH_CHECK_FAILED probe that greps either is not reading one box's state; it reads the machine's, identically, on all ten boxes on it. One cause then trips ten ladders, and the per-box ceilings do not help because each box is under its own. Nothing in the code can catch this — the probe is a string you typed and the reading is perfectly valid. Use HOST_MEMORY_LOW and HOST_DISK_LOW on the machine, or a box's own cpuPercent, which is genuinely its own.

The ladder?

A rule does the gentle thing first and the expensive thing only when the gentle one did not work:

Scanner offline
  failure 1 → restart the app
  failure 2 → reboot the box

The counter resets the moment the signal goes good, so a box that recovers never reaches the reboot. A rung already spent is not spent again — except the last one, which repeats at the cooldown, because there is nothing stronger left to try.

ActionWhat it runs
Only tell menothing
Restart the appam force-stop {pkg} then relaunch
Kill the appam force-stop {pkg}
Clear the cache and restartforce-stop, delete /data/data/{pkg}/cache, relaunch
Send ENTERinput keyevent 66 — dismisses an ANR dialog
Start the servicemonkey -p … LAUNCHER — a rule knows the package, not the service inside it, and a scanner starts its own on launch
Run a commandwhatever you type
Reboot the boxthe same reboot the device page sends
Restart the scanner in RotomRotom's own restart action
Drop the scanner's connection in RotomRotom's disconnect — the box reconnects
Reboot the box through RotomRotom's own reboot action
Restart one Redroid containerfrom the machine that runs it, not from inside it

Restart one Redroid container is the host-side counterpart of restart the app: the smallest thing that fixes a wedged instance. Reboot the box is deliberately not overloaded to mean "reboot the VPS" — that takes every box on the machine down at once, which is a much larger decision than the signal that would reach for it.

It runs on the machine, over the machine's own socket, so a machine that is not answering has the rung refused rather than spent — the ladder still has it when the machine comes back. It is counted as a hard action for the same reason a reboot is: the container is the box, so restarting it cold-boots that Android and every app on it comes back from nothing.

The three Rotom actions travel Rotom's socket, not the agent's, which is what makes them the only remediation left on a box whose agent has died — every other action on the list needs the box reachable from Magnemite. The mirror of that is also true and worth planning a ladder around: Rotom cannot act on a box it has lost, so on a Rotom lost the box rule the bottom rung is the agent-side Reboot the box, and on the other two it is Reboot the box through Rotom.

Reboot the box through Rotom counts against reboots per box per day and sits under the post-reboot grace exactly like the agent-side one. A different socket, the same boot cycle.

Every shell-backed one takes an override command, {pkg} standing in for the rule's package. The catalog exists so an alert can say "cleared the cache and restarted" rather than quoting a command line; the override exists because your MITM is not the one this was written against.

Probes

Three signals need the box to look at something, and what to look at differs per MITM — so the probe is part of the rule:

  • Shell command — runs as root, passes on exit 0, optionally also having to match a pattern. dumpsys activity services | grep -e MappingService is the whole "is the scanner up" check.
  • Pattern in a log — reads the last N lines of a file and counts matches. Can also fail when nothing has been written for a while, which is what a stalled loop looks like from outside.
  • HTTP endpoint — a GET from the box itself; 2xx is healthy.

A log probe can carry a second pattern for work getting done, and fail only when faults outnumber it — ten "seconds since last ping" lines mean nothing next to a hundred completed jobs and everything next to two:

Log file          /data/local/tmp/atlas.log
Lines to read     200
A fault           \[HEALTH CHECK\] ([0-9]+) seconds since last ping\.
Faults before     10
Work looks like   I \| Worker

Probes run every heartbeat, inside half the beat. Whatever does not finish in that budget reports nothing at all — never a failure. They are skipped entirely during an install, because an install force-stops the scanner on purpose and every probe would report the damage Magnemite asked for.

The refusals

Most of this feature is things it will not do. Each is a way a watchdog turns into a fleet rebooting itself in a loop:

Unknown is never a failure

An agent too old to look, a probe out of budget, a Rotom that has never seen the box — silence resets nothing and triggers nothing.

Nothing acts during an install

The scanner is stopped on purpose and Rotom already has the box out of the pool.

Nothing acts just after a reboot

A box takes minutes to come back. Without the grace, its own reboot reads as unreachable and it is rebooted again.

Nothing acts just after the hub restarts

A restart drops every socket at once. The startup grace is what stops a deploy looking like a fleet-wide outage.

Nothing acts on a box in maintenance

Somebody is working on it and its scanner was stopped on purpose — the install case with a person holding the box. See maintenance windows.

Two ceilings per box

Actions per hour and reboots per day. Past either, one critical alert and the box is left alone until the window rolls.

Quiet hours stop the action, not the alert

Not rebooting a box at 3am is a policy. Hiding that it needs one is not.

The numbers

The master switch is on Settings → Monitoring. Everything else is the How it watches card on Settings → Tuning, because every number in it is measured against the heartbeat or the offline timeout, which sit directly above.

SettingDefaultWhat it does · what a bad value costs
Act on what it seesoffOff, no probe is sent and nothing is touched. Rules can still be written.
Ask Rotom every10sOne request for the whole fleet, whatever its size. Everything Rotom-shaped is this fresh at best. Floored at 5s, which is what Rotom's own dashboard polls at.
Rotom stale after600sHow long since Rotom saw a box before that counts as disconnected. Floored at two sync intervals, or it alerts on the sync's lag.
Grace · after a reboot600sShorter than the box takes to boot and its own reboot reads as unreachable — so it is rebooted again, forever.
Grace · after a hub restart180sA restart drops every socket at once; without this, a deploy looks like a fleet-wide outage.
Actions per box per hour4Circuit breaker. Past it, one critical alert and the box is left alone. This is what bounds a rule that turns out to be wrong.
Reboots per box per day6The same, counted separately, because a reboot costs a scanning session and the other actions do not. Reboots sent through Rotom count here too.
Collect logs on recoveryoffWhen a box comes back from a real outage, fetch its logs automatically so the window that explains it is not lost. See Logs.
One capture per box every60 minA box that flaps all night would otherwise be collected from all night.
Captures per hour, fleet10An outage at the hub end brings every box back at once; collecting from all of them together is a second outage on top of the first.
Keep history for30 daysHow long the Monitoring feed is kept. 0 keeps it forever.

The monitor pass itself is not configurable: it runs once per heartbeat, because a pass reads what the boxes last said and they say it once per beat. "Box is unreachable" counts from the offline timeout — to wait longer, raise that rule's own threshold, which is per group.

The two graces and the two ceilings are the safety of this feature

Everything else is tuning. Those four stand between a useful watchdog and a fleet rebooting itself in a loop, and each exists because that has happened to somebody.

Where alerts go

The channels — Discord, ntfy and web push — are set up on Settings → Notifications, beside everything else the fleet announces, and Notifications covers each of them. Configure none of them and the rules still run: remediation never depends on a notification going out.

Which rules reach which channel is decided on the rule, in Announce it — a dropdown of the three, and picking none is a rule that acts quietly. Set up a channel there; choose who uses it here.

None of the channels has a minimum severity. There used to be one per channel, and two filters in series meant a rule set to announce could silently not, with nothing on the rule to say why. One filter, on the rule, where somebody looking for the reason is already looking.

A rule's Severity changes how an alert arrives, never whether it does. It drives ntfy's priority dial, which is what decides whether a phone in a pocket makes a sound — INFO is quiet, WARN is high, and CRITICAL is max, which on Android gets through Do Not Disturb — and only CRITICAL pings the Discord role.

What the channels do share is the repeat window, on the Monitoring alerts card of the same tab: the same signal on the same box is not announced again inside it, 30 minutes by default. The point of it is that a flapping box does not announce itself thirty times, and a window per channel would let it do exactly that by taking turns. The action still happens and is still recorded.

Individual rules can be silenced without being switched off — Announce it set to nothing. "Pogo not in focus" ships that way: a box briefly on the launcher is common enough that announcing every one teaches people to ignore the channel. The same control is how a rule goes to Discord but not to a phone at 3am, without either channel having to know about it.

A rule that announces can also announce the all-clear — the second switch next to it in the rule dialog, off by default. On, the fault is followed by one more message when the signal goes good again, which is how "Box unreachable" becomes "Box unreachable recovered" in the channel rather than only in the feed. It goes to the same channels the fault did — a recovery announced somewhere the fault was not is a message about nothing — and it ignores the repeat window, which has just been filled by the fault it ends. Only an episode that actually reached the rule's threshold gets one — an all-clear for a fault nobody was told about is noise.

Press Send test on a channel's card before trusting it — see Notifications.

A channel that stops working never stops the remediation — the box is still restarted, and the Monitoring page records that the message did not arrive. An event counts as announced when any channel took it: a Discord message that landed is not made un-sent by ntfy being down at the same moment.

Scope

A rule applies to every box, or to one device group. A group-scoped rule replaces the fleet-wide rule for the same signal and package rather than adding to it, or two ladders would race over one fault and the box would be restarted twice. That is the seam for a fleet running Atlas at one site and Aegis at another.

Which kind of device is the other half of that filter, and it is not optional: a rule is about boxes or about machines, never both. Existing rules are about boxes, which is the default and what every seeded rule is.

A rule aimed atEvaluated againstWhat the wrong side would do
boxeskind = BOX devices onlyA box rule on a VPS reads a scanner's absence as a fault on a machine that never ran one
machineskind = HOST devices onlyA host rule on a box measures a disk and a daemon that are not the box's

Both mistakes would act, not just alert, which is why the filter is a field on the rule rather than something the evaluator infers.

The group is also where that site declares which MITM it runs and what to write into its config. A group-scoped rule and its group's MITM package are two halves of the same answer, so they are worth keeping in step: a rule probing Atlas's log on a group that runs Aegis is a rule that never fires.

The seeded rules are not invented: their thresholds and ladders are what aconf's atlas_monitor.sh has done on this hardware for years. The probe strings are Atlas's, which is exactly why they are editable rows.

On this page