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

Filters are implicitly ANDed together — there’s no explicit AND keyword, no OR, and no parentheses for grouping. 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 data Granularity Use it for
jobs (default) The jobs table directly One row per check execution Raw investigation — exact error messages, exact result payloads, exact timestamps
metrics The fleet_job_metrics_15m continuous aggregate Pre-aggregated in 5-minute buckets Trends 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).

Standard Fields

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

source=jobs

Field Type Notes
status string pending, running, completed, failed, cancelled (job execution state — not the same as target health, see above)
type (alias check_type) string http, dns, ssl, ssh, dnsbl, tcp, udp, ehlo, email_auth, pageload, broken_links, domain_expiry, tls_audit, nmap, page_snapshot
duration_ms (alias duration) number Job execution time in milliseconds
error_message (alias error) string Free-text error, when the job or check failed
created_at timestamp When the job was created/queued
completed_at timestamp When the job finished
schedule_id uuid The monitor’s ID
name (alias schedule_name) string The monitor’s display name

source=metrics

Field Type Notes
type (alias check_type) string Same values as above
bucket timestamp Start of the 5-minute aggregation bucket
total_jobs number Job count in the bucket
successful_jobs number Jobs where the target passed (see healthy)
failed_jobs number Jobs where the target failed, or the job itself errored
avg_duration_ms number Average job duration in the bucket
schedule_id uuid The monitor’s ID
name (alias schedule_name) string The 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. key Useful fields
http http_results status_code, response_time_ms, success, body, headers.<header-name>
dns dns_results.<record_type>[i] Array per record type (A, MX, TXT, …); best queried via table and read in the results view rather than filtered
ssl ssl_result is_valid, is_self_signed, expiry, issuer, cipher_suite, protocol_version
ssh ssh_result connected, banner, banner_match, version
dnsbl dnsbl_results.<server> Map keyed by DNSBL server hostname, e.g. results.dnsbl_results.zen\.spamhaus\.org.listed
tcp tcp_result connected, response_time_ms
udp udp_result reachable, response_time_ms
ehlo ehlo_results connected, ehlo_response, extensions
email_auth email_auth_results spf_valid, dmarc_valid, dkim_valid, spf_record, dmarc_record
pageload page_load_result ttfb, page_load, fcp, lcp, cls, http_status_code, resource_count
broken_links broken_links_result total_links, broken_count, target_url
domain_expiry domain_expiry_result days_until_expiry, is_expired, expiry_date, registrar
page_snapshot page_snapshot_result http_status_code, size_bytes
nmap nmap_result total_scanned, unexpected_open
tls_audit tls_audit_result has_weak_ciphers, has_weak_versions, accepted_versions, weak_ciphers
(any type) summary total_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

Operator Meaning
= Equals
!= Not equals
>, >=, <, <= Numeric/time comparison
LIKE SQL LIKE pattern match — use % as a wildcard, e.g. error_message LIKE '%timeout%'
NOT LIKE Inverse 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.

Function Meaning
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

Pipe Syntax Notes
stats stats <agg>(), <agg>(field) by field1, field2 by is optional — omit it to aggregate over the whole result set into a single row
timechart timechart span=15m <agg>() by field span accepts <N>m, <N>h, or <N>d (e.g. span=5m, span=1h, span=1d); defaults to 15m if omitted. by is optional.
table table field1, field2, field3 Explicit column selection. Omit entirely for a sensible default column set.
sort sort 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
limit limit 50 Default 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

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.