Latency and loss
GET /api/targets/{target_id}/data/latency
Returns a target's measurements as one time series per agent. Each data point aggregates the measurements one agent made during a fixed interval: average latency, success rate, and the lowest and highest latency.
Parameters
| Parameter | Description |
|---|---|
agent_label | Optional. Return only this agent's series. By default, every agent that measured the target in the range is included. |
start | Optional. Range start, Unix milliseconds. Default: 24 hours before end. |
end | Optional. Range end, Unix milliseconds. Default: now. |
The interval of each data point depends on the length of the range:
| Range | Interval |
|---|---|
| Up to 7 days | 1 minute |
| More than 7 days, up to 30 days | 5 minutes |
| More than 30 days | 1 hour |
Request
- curl
- Python
TARGET_ID=01M4MZ5T3A76E4RBFKGBD5MP8V
END=$(date +%s000)
START=$(( END - 3600000 )) # one hour
curl --header "Authorization: Bearer $OBSERVIANT_API_KEY" \
"$OBSERVIANT_URL/api/targets/$TARGET_ID/data/latency?start=$START&end=$END"
import time
target_id = "01M4MZ5T3A76E4RBFKGBD5MP8V"
end = int(time.time() * 1000)
start = end - 3600 * 1000 # one hour
response = session.get(
f"{base_url}/api/targets/{target_id}/data/latency",
params={"start": start, "end": end},
timeout=30,
)
response.raise_for_status()
series = response.json()["latency_series"]
Response
{
"latency_series": [
{
"target_id": "01M4MZ5T3A76E4RBFKGBD5MP8V",
"agent_label": "dc1-probe",
"display_name": "dc1-probe",
"latency_success_data": [
[1791705180000, 0.986, 100, 0.901, 1.119],
[1791705240000, 1.69, 100, 0.904, 4.315],
[1791705300000, 37.42, 92.5, 35.8, 61.07],
[1791705360000, -1, 0, -1, -1]
]
},
{
"target_id": "01M4MZ5T3A76E4RBFKGBD5MP8V",
"agent_label": "sys-amsterdam",
"display_name": "Amsterdam",
"latency_success_data": [
[1791705180000, 4.12, 100, 3.98, 4.71],
[1791705240000, 4.07, 100, 3.95, 4.33]
]
}
]
}
latency_series contains one object per agent, sorted by agent_label:
| Field | Description |
|---|---|
target_id | The target. |
agent_label | Stable agent identifier. See Agent labels and display names. |
display_name | Agent name for presentation. |
latency_success_data | Data points, oldest first. |
Each data point is an array of five numbers:
| Position | Value | Unit |
|---|---|---|
| 0 | Start of the interval | Unix milliseconds |
| 1 | Average latency of the successful measurements in the interval | milliseconds |
| 2 | Average success rate | percent, 0–100 |
| 3 | Lowest latency observed | milliseconds |
| 4 | Highest latency observed | milliseconds |
Read the values as follows:
-1means no successful measurement. When every measurement in an interval failed, latency, minimum and maximum are-1and the success rate is0. For ICMP targets this is 100% packet loss.- A missing interval means no measurements. The agent was offline, the target was not assigned to it, or the target did not exist yet. Treat a gap differently from a failure.
- Packet loss is
100 - success ratefor ICMP targets. For the other protocols, each measurement either succeeds or fails, so the success rate is the share of successful checks in the interval. - Minimum and maximum show the spread. For ICMP they are the lowest and highest round-trip times of individual echo replies. For the other protocols they are the lowest and highest measurement in the interval.
An unknown target returns 404 Not Found. An invalid start or end, or a start after end, returns 400 Bad Request. A target with no measurements in the range returns an empty latency_series.
Example: summarize the last hour
This script prints the average latency, the worst one-minute interval and the number of minutes with loss for each agent:
"""Summarize one target's latency and loss over the last hour, per agent.
Usage: python latency_summary.py TARGET_ID
"""
import os
import sys
import time
import requests
base_url = os.environ["OBSERVIANT_URL"]
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['OBSERVIANT_API_KEY']}"
target_id = sys.argv[1]
end = int(time.time() * 1000)
response = session.get(
f"{base_url}/api/targets/{target_id}/data/latency",
params={"start": end - 3600 * 1000, "end": end},
timeout=30,
)
response.raise_for_status()
for series in response.json()["latency_series"]:
points = series["latency_success_data"]
answered = [p for p in points if p[1] >= 0]
lossy = [p for p in points if p[2] < 100]
name = series["display_name"]
if not answered:
print(f"{name}: no successful measurements in {len(points)} intervals")
continue
average = sum(p[1] for p in answered) / len(answered)
worst = max(answered, key=lambda p: p[1])
worst_time = time.strftime("%H:%M", time.gmtime(worst[0] / 1000))
print(
f"{name}: average {average:.1f} ms, worst {worst[1]:.1f} ms at {worst_time} UTC, "
f"{len(lossy)} of {len(points)} minutes with loss"
)
$ python latency_summary.py 01M4MZ5T3A76E4RBFKGBD5MP8V
dc1-probe: average 1.2 ms, worst 4.4 ms at 07:58 UTC, 2 of 60 minutes with loss
Amsterdam: average 4.1 ms, worst 6.3 ms at 08:31 UTC, 0 of 60 minutes with loss
To find the traceroute that explains a spike, pass the interval's timestamp to /mtr/nearest.