Skip to content

Schedules and timing

Four numbers decide when a job counts as late: the schedule, the check-in margin, the max runtime, and the timezone. All four arrive from the SDK in monitor_config, and all four are re-read on every check-in, so changing them in code and redeploying is how you change them.

The schedule

Two forms, and the choice between them matters more than it looks.

crontab

php
MonitorSchedule::crontab('0 * * * *')
js
{ schedule: { type: 'crontab', value: '0 * * * *' } }

A standard five-field cron expression, evaluated with the same expression parser Laravel's own scheduler uses. After every check-in, the next expected time is the next moment the expression matches, evaluated in the monitor's timezone.

Use this when the job is driven by cron, systemd timers, Kubernetes CronJob, or Laravel's scheduler. The expectation snaps back to the grid on every check-in, so a run that starts 40 seconds late does not push the next expectation 40 seconds later.

interval

php
MonitorSchedule::interval(1, MonitorScheduleUnit::hour())
js
{ schedule: { type: 'interval', value: 1, unit: 'hour' } }

Plain arithmetic: the next expected time is the moment of the last check-in plus the interval. Supported units:

UnitSupported
minuteYes
hourYes
dayYes
weekYes
monthYes
yearNo

year silently disables detection

The Sentry SDKs accept unit: 'year' and will happily send it. Parsemend does not compute a next expected time for it, which leaves Next expected empty and the monitor permanently undetectable. Express a yearly job as crontab('0 3 1 1 *') instead.

Which to pick

Interval schedules drift. The next expectation is measured from when the last check-in actually landed, not from when it was supposed to land, so a job that consistently starts two minutes late walks its own window forward two minutes per run. Over a day of hourly runs that is 48 minutes of accumulated drift, and the monitor is now expecting the job at a time nothing scheduled it for.

So: if the job is driven by a clock, describe it with crontab. Reserve interval for things that genuinely are "every N minutes from whenever the last one finished", like a daemon loop or a worker heartbeat.

Check-in margin

How many minutes past the expected time a check-in may arrive before the run counts as missed. Defaults to 5 minutes when the SDK does not send one.

   expected              margin              missed
      │                    │                    │
──────┼────────────────────┼────────────────────▶
   11:00                11:05          sweep flips it here

Size it against how much the job's start time actually varies, not against how long it runs. A job contending for a lock with four other jobs at midnight needs a wider margin than a job that owns its slot.

A margin of 0 is legal and means the check-in must be in before the expected time elapses. Expect false alarms.

Max runtime

How many minutes a check-in may sit in in_progress before the run counts as timed out. Defaults to 30 minutes.

It is measured from when the in_progress check-in arrived at ingest, not from a timestamp the client sends. That is usually the same thing, but if the job is on a machine that queued the request behind a network outage, the clock starts when the request actually lands.

Set it to something like twice the job's worst observed run time. Too tight and a slow Tuesday pages someone; too loose and a hung job goes unnoticed for hours.

in_progress is what makes timeouts possible

Timeout detection needs a start to measure from. A job that sends a single check-in at the end, with ok or error, gives Parsemend nothing to measure, so a hang cannot be detected as a timeout.

It is still caught, but later and under a different name: the run never finishes, so the terminal check-in never arrives, so the next expected time lapses and the monitor goes missed. For an hourly job that means finding out up to an hour later, instead of at the runtime budget.

Single-check-in monitoring is a reasonable choice for a job that either works in seconds or does not run at all. For anything long enough to hang, send the in_progress check-in.

Timezone

A tz database name, Europe/Sofia or America/New_York. Defaults to UTC.

It only affects crontab schedules, where it decides what "3am" means. Interval schedules are pure arithmetic and are unaffected.

If the job is scheduled by a machine running in local time and observing daylight saving, set the timezone to match that machine. Otherwise the monitor will consider the job an hour late twice a year, on exactly the mornings nobody wants a page.

Detection latency

The sweep that detects missed and timed-out runs runs once a minute. Every detection is therefore up to 60 seconds later than the arithmetic suggests.

For a monitor expected at 11:00 with a 5 minute margin, the issue opens somewhere between 11:05 and 11:06. Nothing to design around unless your margin is 0, in which case the effective margin is "up to a minute".

Worked example

An hourly job, crontab('0 * * * *'), margin 5, max runtime 30, timezone UTC.

TimeEventMonitor state
10:00:03in_progress arrivesok, next expected 11:00:00
10:04:11ok arrives, duration 248sok, next expected 11:00:00
11:00:00Nothing arrivesStill ok, not yet late
11:05:00Margin elapsesStill ok, sweep has not run
11:05:42Sweep runsmissed, issue opens, next expected 12:00:00
12:05:38Sweep runs, still nothingmissed, second event on the same issue, next expected 13:00:00
12:41:09ok arrivesok, issue resolves, next expected 13:00:00

Two things to read out of that table. The expectation advances from the missed slot, so an outage produces one issue event per missed slot rather than one per minute. And the recovery at 12:41 resolves the issue without anyone touching it.

Things monitor_config carries that Parsemend ignores

The Sentry protocol has two more fields, and both SDKs will send them if you set them:

FieldBehaviour in Parsemend
failure_issue_thresholdIgnored. An issue opens on the first failure
recovery_thresholdIgnored. One successful check-in resolves

They are accepted on the wire and dropped. If you need "only page me after three consecutive failures", that shaping belongs in an alert rule rather than in monitor config. Uptime checks, by contrast, do have a per-check failure threshold.

Parsemend, by MAVA Design