APM Tips

KQL · latency

Request latency p50, p95 and p99 by cloud role

Compare median, p95 and p99 request duration across the services that share one Application Insights resource, weighted for sampling, slowest first.

Updated

requests latency

let lookback = 24h;
requests
| where timestamp > ago(lookback)
| summarize
    requests = sum(itemCount),
    p50_ms = percentilew(duration, itemCount, 50),
    p95_ms = percentilew(duration, itemCount, 95),
    p99_ms = percentilew(duration, itemCount, 99)
    by cloud_RoleName
| order by p95_ms desc

Returns: One row per cloud role name with its request count and its p50, p95 and p99 duration in milliseconds, slowest p95 first.

Pitfalls

  • Services that don't set a cloud role name can share one value, or arrive with an empty one, and collapse into a single row. Set the role name in every service that sends to the resource.
  • A service with a few hundred requests a day produces a noisy p99. Read p99 only for roles with enough traffic, or add | where requests > 1000.
  • Each role's numbers mix all its routes. A fast health-check endpoint called every few seconds can pull a role's p50 down; filter it out by operation_Name.
  • Separate Application Insights resources don't appear here: this query reads one resource. Use a cross-resource query to compare services that send to different resources.

How it works

When several services send telemetry to one Application Insights resource, the first latency question is “which service?”. The cloud_RoleName column answers it: it is the name each service reports for itself, the same name Application Map uses for its nodes.

Line by line

  • let lookback = 24h; sets the window; change it in one place.
  • where timestamp > ago(lookback) keeps requests that started in the window (UTC).
  • requests = sum(itemCount) counts requests the sampling-aware way. A stored row with itemCount 4 stands for four requests.
  • percentilew(duration, itemCount, N) gives the median, p95 and p99 of duration in milliseconds, each row weighted by the requests it stands for.
  • by cloud_RoleName makes one row per service.
  • order by p95_ms desc puts the service that is slow for one user in twenty at the top.

Reading the result

Compare the roles to each other and to themselves over time. A front-end role is usually slower than the APIs it calls, because its duration includes theirs. A back-end role with a p95 close to its caller’s p95 is where the time goes. A role whose p99 sits far above its p95 has a tail problem: timeouts, cold starts, or a lock that one request in a hundred waits on.

If the role names look wrong (one row called after the process, or two services in one row), fix the names before trusting the numbers. The correlation and Application Map guide explains what the role name is for, and the initializers and processors guide shows how to set it in the classic SDK and in OpenTelemetry.

Variations

  • Drill into one role: add | where cloud_RoleName == "orders-api" and group by operation_Name instead, which is the per-operation query.
  • Per role over time: group by bin(timestamp, 1h), cloud_RoleName, keep one percentile, sort by timestamp and add | render timechart.
  • Per instance: add cloud_RoleInstance to the by clause to see whether one machine or container is slower than its siblings.
  • Workspace tables: use AppRequests, TimeGenerated, DurationMs, ItemCount and AppRoleName (the classic cloud_RoleName).

Sources, checked

  1. percentilew(), percentilesw() (Kusto)learn.microsoft.com · checked
  2. Application map in Application Insights (cloud role names)learn.microsoft.com · checked
  3. Telemetry sampling in Application Insightslearn.microsoft.com · checked
  4. Migrate an Application Insights classic resource to a workspace-based resource (table schemas)learn.microsoft.com · checked