KQL · latency
Request duration p50 and p95 as a timechart
Chart the median and p95 server response time in 5-minute bins over the last 24 hours, weighted for sampling, to see when latency changed.
requests latency
let lookback = 24h;
requests
| where timestamp > ago(lookback)
| summarize
p50_ms = percentilew(duration, itemCount, 50),
p95_ms = percentilew(duration, itemCount, 95)
by bin(timestamp, 5m)
| order by timestamp asc
| render timechart Returns: One point per 5-minute interval with the p50 and p95 request duration in milliseconds, drawn as two lines over the last 24 hours.
Pitfalls
- Keep the bin size in step with the lookback. Seven days of 5-minute bins is 2,016 points per line; use 30m or 1h bins for a week.
- An interval with no requests produces no row, so the line joins straight across quiet periods instead of dropping to zero. That is the honest picture for a percentile: an empty interval has no latency.
- The percentiles are weighted by itemCount. Plain percentile() would let a change in the sampling rate move the line even when latency didn't change.
- timestamp is UTC. A spike at 02:00 on the chart is 02:00 UTC, not local time.
How it works
A table of percentiles tells you how slow things are; a timechart tells you when they got slow. This is the query to run first after a “the site feels slow” message: if the p95 line steps up at 14:10, you have a time to check against deployments, traffic and dependency charts.
Line by line
let lookback = 24h;sets the window. Change this one line to look at the last hour (1h) or the last week (7d).where timestamp > ago(lookback)keeps requests that started inside the window.ago()counts back from now, andtimestampis UTC.summarize ... by bin(timestamp, 5m)cuts the window into 5-minute buckets. Each bucket gets two numbers: the median (p50_ms) and the 95th percentile (p95_ms) ofduration, which is in milliseconds in the classicrequeststable.percentilew(duration, itemCount, N)is the weighted percentile. With sampling on, each stored row stands foritemCountrequests, so a row withitemCount10 counts ten times. That keeps the line steady when the SDK’s sampling rate moves with traffic.order by timestamp ascputs the buckets in time order; therenderoperator draws the x-axis from the first column, which must be a datetime for a timechart.render timechartdraws one line per numeric column.
Reading the chart
Watch the gap between the lines. A p95 that rises while p50 stays flat means a minority of requests got slow: often one route, one dependency or one instance. Both lines rising together points at something shared, such as CPU pressure or a slow database. Break the chart down with p50, p95 and p99 by operation for the same window, or by service with percentiles by cloud role.
To alert on this instead of watching it, drop the render line, keep p95_ms, and add | where p95_ms > 800; the alerts guide covers how a log search alert evaluates a query like that.
Variations
- One route: add
| where operation_Name == "GET /orders/{id}"after the time filter. - Per service: add
cloud_RoleNameafter the bin in thebyclause to get one line per service and percentile. - Workspace tables: use
AppRequests,TimeGenerated,DurationMsandItemCount.