xo xofactory
Blog
cronxo

Guessing how long a cron job has left

cronxo shows a progress bar for a shell command that has no idea it is being watched. The heuristic behind it, and where it is wrong.

A cron job is a shell command. It does not report progress. It prints whatever it prints and exits when it exits. cronxo still shows a percentage and a time-left estimate for every running job, and most of the time it is close. This is how.

Two signals

The agent knows two things about a running job: how long it has been running, and how many bytes it has written to stdout and stderr so far. It also knows the same two numbers for the previous successful run of the same job.

The estimate is a weighted mix:

progress = 0.35 * (elapsed / previous_duration)
         + 0.65 * (bytes_so_far / previous_bytes)

clamped to 99% until the process actually exits. The output share gets more weight because output tends to track work: a job that processes ten thousand rows and logs one line per hundred is a very good progress bar. Elapsed time alone would be a straight line regardless of what the job is doing.

Where it is wrong

Jobs that print everything at the end. A command that does its work silently and then dumps a summary shows a slow crawl from the time term and then jumps to 100%. The display is honest about this: if the previous run produced fewer than a kilobyte of output, the bar uses time only and is drawn as a thin line rather than a filled bar, so it reads as a guess.

The first run. There is no previous run. cronxo shows elapsed time and nothing else.

Jobs whose duration swings. A nightly export that takes four minutes on weekdays and forty on Mondays will be badly estimated on Monday. We considered a rolling median over the last several runs and did not do it, because the last run is usually the best predictor and the median lags behind changes.

Why bother

The estimate is not for scheduling decisions; cronxo never acts on it. It exists so that someone watching the live log at two in the morning knows whether to wait or go to bed. The feedback was clear enough on the first version that we kept it: people liked knowing “about six minutes” even when it turned out to be nine.

Cancelling

The other half of watching a job is being able to stop it. Cancel sends SIGTERM to the whole process group, not just the shell, so a bash -c "a | b" goes down together with a and b. After five seconds without an exit, it sends SIGKILL. If the agent itself has stopped answering, the dashboard lets you close the run after two minutes so it does not sit in “running” forever, and a note in the run log says it was force-closed rather than finished.