Monitor Analytics is a fleet-wide view that rolls up every monitor and check type in your organization into a single page — where the per-monitor metrics graph on a monitor’s detail page answers “how is this one monitor doing,” Monitor Analytics answers “how is my whole fleet doing.” Find it under Monitors → Analytics in the sidebar.

What counts as “healthy”

Every number here is based on whether the target was healthy, not just whether the job executed — a Completed job can still represent a failing check (an HTTP 500, an expired certificate, a DNSBL listing). See Job Statuses for the full explanation. Concretely, a job counts as healthy when it’s Completed and its summary reports zero failed checks.

Summary Tiles

At the top of the page: total monitor count, overall uptime over the last 7 days, monitors currently failing, and active alert count — a fleet-wide snapshot at a glance.

MQL Query Console

Below the summary tiles is the MQL Analytics Console — a Splunk-style query language for ad-hoc analysis across every job and check type in your fleet, instead of hunting for the right fixed chart.

There’s no autocomplete yet, so this page is the field reference until there is — keep it open in a tab while you write queries. The 1D / 7D / 2W / 30D / 90D buttons in the console header control the time window for whatever query is currently loaded; queries re-run automatically when you change the range. Results render as a sortable table, or for timechart queries, a line chart.

Query Anatomy

An MQL query is a search clause, optionally followed by one or more pipe commands:

<search clause> | <pipe 1> | <pipe 2> | ...

The search clause sets the data source and filters:

source=jobs status=failed type=http duration_ms>2000

Writing two filters next to each other implicitly ANDs them — status=failed type=http means both must match. You can also combine filters explicitly with AND / OR and group them with parentheses, same precedence rules as SQL: AND binds tighter than OR, so a b OR c means (a AND b) OR c. Use parentheses whenever you actually want OR to span more than the term right next to it:

status=failed AND (type=http OR type=ssl)

A bare word with no operator (e.g. just typing timeout) is shorthand for error_message LIKE '%timeout%'.

Pipes transform or shape the filtered rows, left to right, same as Splunk/SPL:

source=jobs status=failed | stats count() by error_message | sort count desc | limit 10

Data Sources

source=Backing dataGranularityUse it for
jobs (default)The jobs table directlyOne row per check executionRaw investigation — exact error messages, exact result payloads, exact timestamps
metricsThe fleet_job_metrics_15m continuous aggregatePre-aggregated in 5-minute bucketsTrends over longer windows (uptime %, avg duration) without scanning every raw job

source=metrics is much cheaper for wide time ranges (30D/90D) since it’s already bucketed, but it only has the aggregate fields listed below — it can’t reach error_message or any results.* field. type=dmarc jobs are excluded from source=metrics; DMARC has its own reporting pages and API (see the DMARC docs).

source= picks which SQL table the entire query runs against, so it can only be specified once, and only at the top level of the query — not inside parentheses or as one side of an OR. Write it plainly at the start: source=metrics type=http | ....

Standard Fields

These work for both filtering (in the search clause) and in table/stats ... by/timechart ... by/sort.

source=jobs

FieldTypeNotes
statusstringpending, running, completed, failed, cancelled (job execution state — not the same as target health, see above)
type (alias check_type)stringhttp, dns, ssl, ssh, dnsbl, tcp, udp, ehlo, email_auth, pageload, broken_links, domain_expiry, tls_audit, nmap, page_snapshot
duration_ms (alias duration)numberJob execution time in milliseconds
error_message (alias error)stringFree-text error, when the job or check failed
created_attimestampWhen the job was created/queued
completed_attimestampWhen the job finished
schedule_iduuidThe monitor’s ID
name (alias schedule_name)stringThe monitor’s display name

source=metrics

FieldTypeNotes
type (alias check_type)stringSame values as above
buckettimestampStart of the 5-minute aggregation bucket
total_jobsnumberJob count in the bucket
successful_jobsnumberJobs where the target passed (see healthy)
failed_jobsnumberJobs where the target failed, or the job itself errored
avg_duration_msnumberAverage job duration in the bucket
schedule_iduuidThe monitor’s ID
name (alias schedule_name)stringThe monitor’s display name

Reaching Into Check Results (results.*, source=jobs only)

Every check type’s raw output is stored one level below the top-level results column, nested under a type-specific key. Address a nested field with dot-separated path segments: results.<type_key>.<field>. Comparisons against numeric fields (>, >=, <, <=) are cast automatically.

type=results. keyUseful fields
httphttp_resultsstatus_code, response_time_ms, success, body, headers.<header-name>
dnsdns_results.<record_type>[i]Array per record type (A, MX, TXT, …); best queried via table and read in the results view rather than filtered
sslssl_resultis_valid, is_self_signed, expiry, issuer, cipher_suite, protocol_version
sshssh_resultconnected, banner, banner_match, version
dnsbldnsbl_results.<server>Map keyed by DNSBL server hostname, e.g. results.dnsbl_results.zen\.spamhaus\.org.listed
tcptcp_resultconnected, response_time_ms
udpudp_resultreachable, response_time_ms
ehloehlo_resultsconnected, ehlo_response, extensions
email_authemail_auth_resultsspf_valid, dmarc_valid, dkim_valid, spf_record, dmarc_record
pageloadpage_load_resultttfb, page_load, fcp, lcp, cls, http_status_code, resource_count
broken_linksbroken_links_resulttotal_links, broken_count, target_url
domain_expirydomain_expiry_resultdays_until_expiry, is_expired, expiry_date, registrar
page_snapshotpage_snapshot_resulthttp_status_code, size_bytes
nmapnmap_resulttotal_scanned, unexpected_open
tls_audittls_audit_resulthas_weak_ciphers, has_weak_versions, accepted_versions, weak_ciphers
(any type)summarytotal_checks, successful_checks, failed_checks, execution_time_ms

The dnsbl and dns keys are maps/arrays, not fixed objects — the server hostname or record type is a dynamic map key rather than something you can enumerate here. For those, table results.dnsbl_results | limit 5 and reading the raw JSON is usually faster than trying to build a precise filter.

Getting the path wrong doesn’t error — an incorrect path just extracts nothing (NULL), so a query like results.status_code>=500 (missing the http_results segment) silently matches zero rows instead of failing loudly. If a results.* filter isn’t matching what you expect, double-check the path against the table above first.

Operators

OperatorMeaning
=Equals
!=Not equals
>, >=, <, <=Numeric/time comparison
LIKESQL LIKE pattern match — use % as a wildcard, e.g. error_message LIKE '%timeout%'
NOT LIKEInverse of LIKE

String and quoted values can use single or double quotes: error_message="expected status 200" or error_message='expected status 200'.

Aggregate Functions

Used inside stats and timechart.

FunctionMeaning
count()Row count. Default aggregate if you don’t specify one.
avg(field)Average
sum(field)Sum
min(field)Minimum
max(field)Maximum
uptime()(healthy jobs / total jobs) * 100 — works with both source=jobs and source=metrics

Give an aggregate a custom output name with AS: avg(duration_ms) AS avg_latency.

Pipe Commands

PipeSyntaxNotes
statsstats <agg>(), <agg>(field) by field1, field2by is optional — omit it to aggregate over the whole result set into a single row
timecharttimechart span=15m <agg>() by fieldspan accepts <N>m, <N>h, or <N>d (e.g. span=5m, span=1h, span=1d); defaults to 15m if omitted. by is optional.
tabletable field1, field2, field3Explicit column selection. Omit entirely for a sensible default column set.
sortsort field, sort field desc, sort field asc, sort -field (desc shorthand), sort +field (asc shorthand)Defaults to created_at desc / bucket desc if you don’t add a sort pipe
limitlimit 50Default 100, capped at 1000

Worked Examples

Most common failure messages fleet-wide, worst first:

source=jobs status=failed | stats count() by error_message | sort count desc

Failed jobs on either of two check types:

source=jobs status=failed AND (type=http OR type=ssl) | table schedule_id, type, error_message

HTTP 5xx responses in the last window, with context:

source=jobs type=http results.http_results.status_code>=500 | table schedule_id, duration_ms, error_message

Certificates expiring within 14 days:

source=jobs type=ssl results.ssl_result.expiry<="2026-09-15T00:00:00Z" | table schedule_id, results.ssl_result.expiry, results.ssl_result.issuer

(SSL results store an absolute expiry timestamp, not a precomputed days-remaining, so filter against a date rather than a day count.)

Slowest monitors right now:

source=jobs duration_ms>2000 | table schedule_id, duration_ms, type | sort duration_ms desc | limit 20

Fleet-wide uptime trend, HTTP only, one point per 15 minutes:

source=metrics type=http | timechart span=15m uptime() by name

Average latency per monitor over the last window:

source=jobs | stats avg(duration_ms) AS avg_latency by name | sort avg_latency desc

DMARC/SPF/DKIM auth failures:

source=jobs type=email_auth results.email_auth_results.dmarc_valid=false | table schedule_id, error_message

Anything with “timeout” in the error, excluding a known-flaky monitor:

source=jobs error_message LIKE '%timeout%' name NOT LIKE '%staging%'

Jobs that failed to even execute (runner-level, not target-level):

source=jobs status=failed | table schedule_id, type, error_message, created_at | sort created_at desc

Weak TLS ciphers detected, across the fleet:

source=jobs type=tls_audit results.tls_audit_result.has_weak_ciphers=true | table schedule_id, results.tls_audit_result.weak_ciphers

API Access

The console is backed by GET/POST /v1/monitor-analytics/query — see the API Reference if you want to run MQL queries programmatically or build your own dashboard on top of the same data.