Percetils | Observabilidade | CSharp
- Percetils | Observabilidade | CSharp
DOC: Percentis (P50/P90/P95/P99) com Prometheus no .NET (Histograms)
1- O que é P50/P95/P99?
P50/P95/P99 são tempos de latência (duração).
Se você vê:
- P50 = 120ms
Significa:
✅ 50% das requisições terminaram em 120ms ou menos ✅ e 50% terminaram acima disso
Ou seja: percentil = um tempo, não “uma porcentagem de tempo”.
2- De onde saem os “tempos”?
Os tempos vêm de medições reais feitas pela sua aplicação.
Exemplo: você mede quanto tempo um trecho levou para executar:
- request levou 35ms
- request levou 140ms
- request levou 900ms
- request levou 2.4s
✅ Esses valores são coletados automaticamente quando você usa um timer.
3- O que você define vs o que é automático
✅ Automático (runtime mede)
- A latência real de cada operação (ex: request HTTP)
✅ Você define
- O histogram
- Os buckets (faixas de tempo)
- As labels (route/method/status)
📌 Prometheus calcula os percentis por aproximação com base nos buckets.
4- O jeito CERTO no Prometheus: HISTOGRAM
Para ter P50/P95/P99 no Prometheus você precisa:
✅ Histogram + histogram_quantile() (PromQL)
Prometheus trabalha assim:
- Sua aplicação grava durações em um histogram
- O Prometheus coleta do endpoint
/metrics - Você consulta P50/P95/P99 via PromQL
5- .NET: setup mínimo com /metrics
Instalar pacote
dotnet add package prometheus-net.AspNetCore
Program.cs
using Prometheus;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "OK");
// Expor endpoint para Prometheus
app.MapMetrics(); // /metrics
app.Run();
✅ Pronto: http://localhost:xxxx/metrics
6- Criar métrica de latência (Histogram)
Crie um arquivo AppMetrics.cs:
using Prometheus;
public static class AppMetrics
{
// Histogram mede em segundos por padrão (melhor prática)
public static readonly Histogram HttpDuration = Metrics.CreateHistogram(
"app_http_request_duration_seconds",
"Duração das requests HTTP (segundos)",
new HistogramConfiguration
{
LabelNames = new[] { "route", "method", "status" },
// BUCKETS = faixas de tempo (segundos)
// Você define isso (não existe “um único padrão”)
Buckets = new[] { 0.01, 0.03, 0.05, 0.1, 0.2, 0.5, 1, 2, 5 }
});
}
✅ O que esses buckets significam?
Eles são limites “até X”:
0.01= até 10ms0.1= até 100ms0.5= até 500ms1= até 1s5= até 5s+Inf= tudo
7- Medir a latência no endpoint (de onde sai o tempo medido)
Exemplo de endpoint medindo duração real:
app.MapGet("/orders/{id}", async (string id) =>
{
// Aqui começa a medir
using var timer = AppMetrics.HttpDuration
.WithLabels("/orders/{id}", "GET", "200")
.NewTimer();
// Seu código real (simula latência)
await Task.Delay(Random.Shared.Next(10, 800));
// Aqui o timer registra automaticamente o tempo medido
return Results.Ok(new { id });
});
✅ O tempo registrado vai ser o tempo real entre Start e Dispose.
8- O que aparece no /metrics (histogram gera 3 coisas)
Quando você usa histogram, aparecem essas métricas:
app_http_request_duration_seconds_bucket{le="0.1"...}app_http_request_duration_seconds_sum{...}app_http_request_duration_seconds_count{...}
Como entender “bucket”?
Exemplo:
...bucket{le="0.1"} significa:
✅ “quantas requests terminaram em 100ms ou menos”
9- PromQL: como tirar P50/P90/P95/P99
✅ Regra padrão (use sempre)
Percentis no Prometheus vêm de:
histogram_quantile(
QUANTIL,
sum(rate(SEU_HISTOGRAM_bucket[5m])) by (le)
)
📌 rate(...[5m]) = pega o comportamento nos últimos 5 minutos
📌 by (le) = mantém a estrutura dos buckets
P50 (mediana)
histogram_quantile(
0.50,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le)
)
P90
histogram_quantile(
0.90,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le)
)
P95
histogram_quantile(
0.95,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le)
)
P99
histogram_quantile(
0.99,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le)
)
✅ O resultado vem em segundos
0.12= 120ms1.3= 1.3s
10- Percentil por rota (muito usado)
Se você quer separar por rota:
P95 por rota
histogram_quantile(
0.95,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le, route)
)
P99 por rota
histogram_quantile(
0.99,
sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le, route)
)
11- “Eu defino os tempos ou tem padrão?”
✅ Os tempos medidos = vêm da aplicação (runtime) ✅ O que você “define” = buckets (faixas)
Existe padrão oficial único?
❌ Não existe 1 padrão perfeito universal.
Mas existe padrão recomendado?
✅ Sim, esse conjunto é uma base muito boa pra APIs:
10ms, 30ms, 50ms, 100ms, 200ms, 500ms, 1s, 2s, 5s
Buckets = new[] { 0.01, 0.03, 0.05, 0.1, 0.2, 0.5, 1, 2, 5 };
12- Como escolher buckets bons (sem complicar)
✅ Pegue o seu sistema e pense:
- “normal” = 30ms ~ 200ms
- “ruim” = 500ms ~ 2s
- “horrível” = > 2s
Então você precisa buckets cobrindo essas faixas.
📌 Se você escolher buckets ruins:
- P99 fica impreciso
- percentil “vai pular”
- você perde resolução
13- Boas práticas (curtas e importantes)
✅ Use labels estáveis:
route(template tipo/orders/{id})methodstatus
❌ NÃO use:
user_idorder_id- URL completa com querystring
Porque explode cardinalidade e derruba Prometheus/Grafana.
14- Extras úteis (consultas comuns)
RPS (requests por segundo)
sum(rate(app_http_request_duration_seconds_count[5m]))
Latência média (não substitui percentil)
sum(rate(app_http_request_duration_seconds_sum[5m]))
/
sum(rate(app_http_request_duration_seconds_count[5m]))
Resumo final (curtão)
✅ O “tempo” do P50/P95/P99 é a latência real medida das requisições. ✅ Você não inventa os tempos — você mede. ✅ Você define os buckets (faixas) do histogram. ✅ Prometheus calcula percentis com:
histogram_quantile(0.99, sum(rate(METRIC_bucket[5m])) by (le))
Desenhando cenário
1- De onde sai o “tempo” (latência)
Request começa Request termina
|---------------------------------------|
tempo medido (latência)
ex: 120ms = 0.12s
✅ Esse tempo é medido pelo seu código (timer), exemplo:
using var t = histogram.NewTimer();
// ... request roda ...
t.Dispose(); // registra o tempo medido
2- Histogram = “baldes de tempo” (buckets)
Você define buckets assim (em segundos):
0.01 0.03 0.05 0.1 0.2 0.5 1 2 5 +Inf
10ms 30ms 50ms 100ms 200ms 500ms 1s 2s 5s tudo
Cada request “cai” em um bucket dependendo da latência.
Exemplo de 10 requests medidas:
12ms, 18ms, 40ms, 55ms, 70ms, 110ms, 140ms, 400ms, 900ms, 1.8s
3- Como o Prometheus conta (bucket acumulado)
Bucket é acumulado (até aquele tempo):
le="0.01" (<=10ms) -> 0
le="0.03" (<=30ms) -> 2 (12ms, 18ms)
le="0.05" (<=50ms) -> 3 (+40ms)
le="0.1" (<=100ms) -> 5 (+55ms, 70ms)
le="0.2" (<=200ms) -> 7 (+110ms, 140ms)
le="0.5" (<=500ms) -> 8 (+400ms)
le="1" (<=1s) -> 9 (+900ms)
le="2" (<=2s) -> 10 (+1.8s)
le="+Inf" (todas) -> 10
4- Onde ficam P50 / P95 / P99 nesse desenho?
Se você tem 10 requests:
- P50 = posição 50% → 5ª request
- P95 = posição 95% → 10ª request (quase no fim)
- P99 = posição 99% → 10ª request (praticamente o pior caso)
Visual:
[----50%----|----------------------95%---------------------|----99%----]
^P50 ^P95/^P99
No nosso exemplo de 10 requests:
- P50 cai perto do bucket
<=100ms - P95 e P99 caem perto do bucket
<=2s
✅ O Prometheus estima isso usando:
histogram_quantile(...)
5- Fluxo completo (fim a fim)
.NET mede tempo real (timer)
|
v
Histogram junta em buckets
|
v
/metrics expõe *_bucket/_sum/_count
|
v
Prometheus coleta (scrape)
|
v
PromQL calcula:
P50/P95/P99 = histogram_quantile(...)