HERMES Example Queries and Tutorials
Queries here run against mlab-collaboration.hermes_union.events_enriched, the stable published interface. See Access and QuickStart for access and cost, and the schema for what each field means.
Every query filters on partition_date. On a table this size that filter is not an optimization, it is the difference between scanning a day and scanning years.
Find degraded groups in one network on one day
Which of a network’s monitored groups performed worst against their own baselines?
SELECT client.metro, server.site, ip_version, COUNT(*) AS measurements, APPROX_QUANTILES(performance.ndt_rtt_ms, 100)[SAFE_ORDINAL(50)] AS rtt_ms, ANY_VALUE(performance.baseline.ndt_rtt_ms) AS baseline_rtt_ms, ANY_VALUE(performance.anomaly.rtt_difference_ms) AS rtt_difference_ms, ANY_VALUE(performance.anomaly.rtt_anomalous_sample_fraction) AS share_anomalous, LOGICAL_OR(performance.anomaly.rtt_significant) AS rtt_significantFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2025-07-04" AND DATE(measurement_time) = partition_date -- the day's own measurements AND client.asn = 174GROUP BY client.metro, server.site, ip_versionHAVING measurements >= 25ORDER BY rtt_difference_ms DESCrtt_difference_ms is the group’s median RTT on the analysis day minus its baseline median, as computed by HERMES. With the measurement_time filter, rtt_ms - baseline_rtt_ms is usually close to it, but it is not guaranteed to match: rtt_ms here is computed only over the rows in the table. rtt_significant is HERMES’s verdict for the group. share_anomalous is the fraction of the day’s measurements more than 5 ms above the baseline median. A large difference with a small anomalous share usually means a few very bad measurements; a large difference with a large share means the whole group moved.
Track one group over time
Once a group looks interesting, look at it across days rather than at one day in isolation. This is how you tell a sustained degradation from a noisy day.
SELECT partition_date, COUNT(*) AS measurements, APPROX_QUANTILES(performance.ndt_rtt_ms, 100)[SAFE_ORDINAL(50)] AS rtt_ms, ANY_VALUE(performance.baseline.ndt_rtt_ms) AS baseline_rtt_ms, APPROX_QUANTILES(performance.download_mbps, 100)[SAFE_ORDINAL(50)] AS download_mbps, ANY_VALUE(performance.baseline.download_mbps) AS baseline_download_mbps, LOGICAL_OR(performance.anomaly.rtt_significant) AS rtt_significantFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date BETWEEN "2025-07-01" AND "2025-07-08" AND DATE(measurement_time) = partition_date AND client.asn = 174 AND client.metro = "Chicago-Illinois-US" AND server.site = "ord06" AND ip_version = "v4"GROUP BY partition_dateORDER BY partition_dateThis query scans about 100 GB, because each day in the range is a separate partition. Widen the date range with that in mind, and dry-run first.
Read the AS path without unnesting hops
as_path, country_path, metro_path, and ixp_path are precomputed, so most path questions do not require touching the hop arrays.
SELECT server_to_client_path.as_path, client_to_server_path.as_path, server_to_client_path.detour_ratio, client_to_server_path.detour_ratio, COUNT(*) AS measurementsFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2025-07-04" AND DATE(measurement_time) = partition_date AND client.asn = 174 AND client.metro = "Chicago-Illinois-US" AND quality.reaches_client_asnGROUP BY 1, 2, 3, 4ORDER BY measurements DESCLIMIT 20A group whose paths were stable shows one dominant AS path per direction. A routing change shows as a new path appearing and the old one disappearing.
Compare the two directions
Internet paths are not symmetric. The path from the M-Lab server to a user and the path back are chosen independently, and a degradation may be visible in only one of them.
SELECT client.metro, AVG(server_to_client_path.distance_km) AS server_to_client_km, AVG(client_to_server_path.distance_km) AS client_to_server_km, AVG(server_to_client_path.geodesic_distance_km) AS straight_line_km, AVG(server_to_client_path.detour_ratio) AS server_to_client_detour, AVG(client_to_server_path.detour_ratio) AS client_to_server_detour, AVG(server_to_client_path.geolocation_coverage) AS s2c_geo_coverage, AVG(client_to_server_path.geolocation_coverage) AS c2s_geo_coverageFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2025-07-04" AND DATE(measurement_time) = partition_date AND client.asn = 174 AND ARRAY_LENGTH(client_to_server_path.hops) > 0 AND server_to_client_path.geodesic_distance_km >= 50 -- see belowGROUP BY client.metroORDER BY client_to_server_detour DESCThe geodesic_distance_km floor matters. When client and server are only a few kilometres apart, any detour through a distant interconnection point divides by a tiny number, and ratios in the thousands appear that say more about the denominator than the route. Filtering on the straight-line distance keeps the ratio meaningful.
Always read the detour ratios next to the geolocation coverage. A path where only a third of hops could be geolocated will report a distance, and that distance is a lower bound built from the hops that happened to be placed.
Find the countries traffic passed through
A useful shape for detour investigations: which countries appear on a group’s paths, and how often.
SELECT country, COUNT(*) AS measurementsFROM `mlab-collaboration.hermes_union.events_enriched`, UNNEST(client_to_server_path.country_path) AS countryWHERE partition_date = "2025-07-04" AND DATE(measurement_time) = partition_date AND client.asn = 174 AND client.metro = "Chicago-Illinois-US"GROUP BY countryORDER BY measurements DESCUnnest the hops
When the summaries are not enough, read the hops themselves. This is the most expensive shape on the page — filter hard first.
SELECT hop.ttl, hop.ip, hop.rdns_name, hop.asn, hop.as_name, hop.ixp, hop.city, hop.country_code, hop.geo_source, hop.geo_score, hop.cumulative_distance_km, hop.fiber_lower_bound_rtt_ms, hop.distance_rtt_checkFROM `mlab-collaboration.hermes_union.events_enriched`, UNNEST(server_to_client_path.hops) AS hopWHERE partition_date = "2025-07-04" AND client.asn = 174 AND client.metro = "Chicago-Illinois-US" AND measurement_id = "ndt-g4nnh_1748758813_00000000002BF861" -- REPLACE_WITH_A_MEASUREMENT_IDORDER BY hop.ttldistance_rtt_check is the check worth reading first: it says whether the measured RTT to this hop is consistent with where the hop was geolocated. When it is not, the geolocation is more likely wrong than the physics.
Restrict to trustworthy paths
Before any claim about where a problem sits, filter on path quality:
SELECT COUNT(*) AS trustworthy_measurementsFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2025-07-04" AND DATE(measurement_time) = partition_date AND quality.reaches_client -- the trace got to the user AND NOT server_to_client_path.loop_detected -- no AS reappears after another AS AND NOT server_to_client_path.unresponsive_within_as -- no hidden stretch inside an AS AND server_to_client_path.geolocation_coverage >= 0.5 AND NOT quality.is_virtualThese filters reduce your sample substantially. On 2025-07-04 they keep 9.4% of the day’s measurements, mostly because only 22.7% of traces reach the client itself; requiring only quality.reaches_client_asn instead keeps 28.1%. That is the point: they trade coverage for the ability to say something specific about a network segment. Loops are rare (0.7% of paths), while a hidden stretch inside an AS is common (29.5%), so decide whether your question needs the latter filter before applying it.
Get the statistical test outputs
The verdicts in performance.anomaly rest on the test results in performance.tests. Like the baseline, they are properties of the group, not of the individual measurement, so they repeat on every row of a group and ANY_VALUE reads them:
SELECT client.metro, server.site, ip_version, ANY_VALUE(performance.anomaly.rtt_difference_ms) AS rtt_difference_ms, LOGICAL_OR(performance.anomaly.rtt_significant) AS rtt_significant, ANY_VALUE(performance.tests.mann_whitney.rtt.p_value) AS mann_whitney_p, ANY_VALUE(performance.tests.welch_t.rtt.p_value) AS welch_t_p, ANY_VALUE(performance.tests.welch_t.rtt.analysis_day_mean) AS analysis_day_mean_rtt_ms, ANY_VALUE(performance.tests.welch_t.rtt.baseline_mean) AS baseline_mean_rtt_ms, ANY_VALUE(performance.tests.wasserstein.download.distance) AS download_wasserstein_distance, ANY_VALUE(performance.tests.wasserstein.download.p_value) AS download_wasserstein_pFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2025-07-04" AND client.asn = 174GROUP BY client.metro, server.site, ip_versionHAVING mann_whitney_p IS NOT NULL -- only groups large enough to testORDER BY rtt_difference_ms DESCThe tests are NULL for groups too small to test, which is why the query drops them. A p_value of exactly 1e-10 with a zero statistic means the group was too large to test (more than 20,000 measurements on one side), not that the evidence was overwhelming.
Tutorial: working through one event
This walkthrough follows one real degradation from detection to the evidence behind its localization: T-Mobile US (AS21928) IPv6 users in the Seattle metro area, testing against the M-Lab site sea03, on 18 and 19 September 2026. The figures quoted were retrieved on 2026-09-30. Step 1 scans about 14 GB, step 2 about 85 GB, and step 3 about 30 GB.
1. Find the event
Start from one network on one day, and list the groups HERMES found significantly degraded, largest first:
SELECT client.metro, server.site, ip_version, COUNT(*) AS measurements, APPROX_QUANTILES(performance.ndt_rtt_ms, 100)[SAFE_ORDINAL(50)] AS rtt_ms, ANY_VALUE(performance.baseline.ndt_rtt_ms) AS baseline_rtt_ms, ANY_VALUE(performance.anomaly.rtt_difference_ms) AS rtt_difference_msFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date = "2026-09-18" AND DATE(measurement_time) = partition_date AND client.asn = 21928 AND performance.anomaly.rtt_significantGROUP BY client.metro, server.site, ip_versionORDER BY measurements DESCLIMIT 10The top row is the Seattle group testing against sea03 over IPv6: 758 measurements that day, with a median RTT of 49.8 ms against a baseline of 22.8 ms, a difference of about 27 ms.
2. Confirm the change is sustained, and check the sample
A detection on one day can be noise. Look at the group’s daily verdicts around the event. The baseline and anomaly fields are properties of the group, so they repeat on every row and ANY_VALUE reads them:
SELECT partition_date, ANY_VALUE(performance.baseline.measurement_count) AS baseline_measurements, ANY_VALUE(performance.baseline.unique_client_ip_count) AS baseline_client_ips, ANY_VALUE(performance.baseline.ndt_rtt_ms) AS baseline_rtt_ms, ANY_VALUE(performance.anomaly.rtt_difference_ms) AS rtt_difference_ms, ANY_VALUE(performance.anomaly.rtt_anomalous_sample_fraction) AS share_anomalous, LOGICAL_OR(performance.anomaly.rtt_significant) AS rtt_significantFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date BETWEEN "2026-09-16" AND "2026-09-21" AND client.asn = 21928 AND client.metro = "Seattle-Washington-US" AND server.site = "sea03" AND ip_version = "v6"GROUP BY partition_dateORDER BY partition_date| Date | RTT difference (ms) | Share anomalous | Significant |
|---|---|---|---|
| 2026-09-16 | +0.4 | 0.30 | no |
| 2026-09-17 | +4.1 | 0.48 | no |
| 2026-09-18 | +27.0 | 0.86 | yes |
| 2026-09-19 | +25.7 | 0.87 | yes |
| 2026-09-20 | +3.8 | 0.49 | no |
| 2026-09-21 | −7.4 | 0.12 | no |
The degradation lasted two days, with a visible onset on the 17th that stayed below HERMES’s threshold. The baseline rests on about 4,700 measurements from about 3,650 distinct client IPs, far above the 25-measurement, 5-IP level recommended in the methodology, so it is a strong reference.
Note what happens afterwards. The baseline rises from 22.8 ms to 29.8 ms by the 21st, because the event days are now inside the seven-day baseline window, and the following days appear better than baseline. Read negative differences just after an event with that in mind.
3. Compare the paths before and during
Did the route change, or did the same route get slower? Compare the AS paths of the day’s own measurements on a normal day and on the event day:
SELECT partition_date, ARRAY_TO_STRING(ARRAY( SELECT CAST(asn AS STRING) FROM UNNEST(server_to_client_path.as_path) AS asn WITH OFFSET i WHERE i = 0 OR asn != server_to_client_path.as_path[OFFSET(i - 1)] ORDER BY i), " > ") AS server_to_client_as_path, COUNT(*) AS measurements, APPROX_QUANTILES(performance.ndt_rtt_ms, 100)[SAFE_ORDINAL(50)] AS rtt_ms, COUNTIF(ARRAY_LENGTH(client_to_server_path.hops) > 0) AS with_reverse_path, COUNTIF(quality.reaches_client_asn) AS reaching_client_asnFROM `mlab-collaboration.hermes_union.events_enriched`WHERE partition_date IN ("2026-09-11", "2026-09-18") AND DATE(measurement_time) = partition_date AND client.asn = 21928 AND client.metro = "Seattle-Washington-US" AND server.site = "sea03" AND ip_version = "v6"GROUP BY partition_date, server_to_client_as_pathORDER BY partition_date, measurements DESCOn 11 September, 689 of 699 measurements took GTT (3257) then Cogent (174), with a median RTT of 21.3 ms. On 18 September all 758 took the same route, with a median of 49.8 ms. The route did not change; the same route got slower. That points to congestion somewhere along it rather than a detour.
The other two columns set the limits of what the paths can tell you. with_reverse_path is zero on both days: no reverse traceroute exists for these IPv6 measurements, so only the server-to-client direction is available. reaching_client_asn is also zero: the traces end inside Cogent and never reach T-Mobile’s network.
4. See where HERMES localized it
Within this group, every measurement used the same path, so affected and unaffected measurements cannot be told apart by route. HERMES’s localization therefore compares across groups, looking for the segment that degraded groups share and healthy groups do not. For this event it attributes the degradation to the interdomain link between Cogent (AS174) and GTT (AS3257) in Seattle, with strong confidence. The localization output is not yet part of events_enriched.
5. State what the evidence supports, and what it does not
The evidence supports this: for two days, T-Mobile IPv6 users in Seattle testing against sea03 saw their median RTT roughly double, over a route that did not change, and the segment most strongly associated with the degradation is the Cogent–GTT interconnection in Seattle.
It does not support:
- that Cogent or GTT caused the degradation, or which of them could have fixed it. HERMES reports association, not responsibility;
- anything about the reverse direction, which was not measured;
- anything about T-Mobile’s own network or the last mile, which the traces never reached;
- anything about other destinations, whose paths may not cross this link.