Prometheus ve PromQL: Scrape mi Push mu, rate, histogram_quantile, Recording Rule'lar ve Native Histogram'lar
.NET metric'leri Prometheus'a iki yoldan girer: beta durumundaki exporter'ın /metrics endpoint'ini scrape etmek ya da OTLP ile Prometheus 3'ün OTLP receiver'ına push etmek. Bookstore'un metric'leriyle rate, increase, histogram_quantile ve hata oranı sorgularını, isim çevirisini, recording rule'ları ve klasik bucket'larla native histogram'ların yüzdelik doğruluğunu ölçerek karşılaştırıyoruz.
Sürüm notu: Bu yazıdaki örnekler .NET 10, OpenTelemetry .NET 1.21, OpenTelemetry.Exporter.Prometheus.AspNetCore 1.21.0-beta.1 ve Prometheus 3.16.0 (resmi prom/prometheus image'ı) ile denenmiştir.
Önce Tahmin Et
Bookstore'a (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) 111 saniye boyunca trafik gönderdik; tam 398 sipariş verildi. Prometheus'a "son 3 dakikada kaç sipariş verildi" diye sorduk: sum(increase(bookstore_orders_placed_total[3m])). Cevap 410,1 geldi. Aynı koşuda trafik aracı checkout'un p95'ini 5,09 saniye ölçtü; Prometheus'un histogram_quantile'ı 5,93 saniye dedi. Prometheus sayamıyor mu? İkisinin de cevabı PromQL'in nasıl hesap yaptığında; yazının ortasında.
Pull mu, Push mu?
Prometheus'un klasik modeli pull: hedefleri (target) bilir ve her scrape_interval'da onların HTTP endpoint'inden metric'leri çeker. Uygulama yalnızca o anki değerleri bir sayfada sunar; zaman serisini Prometheus kurar. Bu modelin pratik bir faydası var: scrape başarısız olursa Prometheus bunu kendisi bilir ve her hedef için bir up serisi tutar; "servis telemetry göndermeyi bıraktı mı, yoksa hiç trafik mi yok" ayrımı kendiliğinden yapılır. Kubernetes'te hedefler elle yazılmaz; kubernetes_sd_configs ile pod'lar ve service'ler API server'dan keşfedilir, Prometheus Operator kullanılıyorsa bir ServiceMonitor ya da PodMonitor nesnesiyle tanımlanır. Bu kısmı bu yazıda çalıştırmadık; Kubernetes serisi ve serinin production yazısı bunun yeri.
OpenTelemetry'nin modeli ise push: SDK, OTLP (OpenTelemetry Protocol; telemetry taşıma protokolü) ile veriyi periyodik olarak bir yere gönderir. Prometheus 3 bu ikisini birleştirdi: --web.enable-otlp-receiver bayrağıyla OTLP metric'lerini doğrudan kabul ediyor. Collector'ın Prometheus'a remote write ya da OTLP ile göndermesi de aynı modele giriyor. Bu yüzden .NET'ten Prometheus'a iki yol var.
.NET'ten Prometheus'a İki Yol
Birincisi scrape: uygulama bir /metrics endpoint'i açar. OpenTelemetry'nin bunun için bir paketi var, ama sürümüne dikkat:
<!-- Hâlâ prerelease: deneysel Prometheus/OpenMetrics uyumluluk spesifikasyonuna bağlı. -->
<PackageVersion Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.21.0-beta.1" />
Paket yıllardır beta ve README'si sebebini açıkça yazıyor: OpenTelemetry'nin Prometheus ve OpenMetrics uyumluluk spesifikasyonu hâlâ deneysel; production için OTLP exporter'ını öneriyor. Kurulumda iki satır yeterli:
.WithMetrics(metrics => metrics
.AddMeter("Bookstore.*", "Polly")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
// Scrape eden bir Prometheus için GET /metrics (pull); aşağıdaki OTLP aynı metric'leri push etmeye devam eder.
.AddPrometheusExporter())
// Prometheus bunu scrape eder; production'da iç bir porta ya da authorization arkasına koy.
app.MapPrometheusScrapingEndpoint();
İkincisi push: uygulamanın OTLP exporter'ı metric'leri doğrudan Prometheus'un OTLP endpoint'ine ya da bir Collector'a gönderir. Kod değişmiyor; yalnızca metric sinyali için endpoint ve protokol veriliyor. Prometheus'u iki yolu da açık olacak şekilde başlattık:
docker run -d --name bookstore-prometheus -p 9090:9090 -v ${PWD}/deploy/observability/prometheus.yaml:/etc/prometheus/prometheus.yml:ro -v ${PWD}/deploy/observability/rules.yaml:/etc/prometheus/rules.yaml:ro prom/prometheus:v3.16.0 --config.file=/etc/prometheus/prometheus.yml --web.enable-otlp-receiver
# Bookstore için Prometheus (lokal). İki giriş yolu:
# pull: Bookstore.Api'nin GET /metrics endpoint'ini 15 saniyede bir scrape eder
# push: /api/v1/otlp üzerinden OTLP (--web.enable-otlp-receiver gerekir)
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- /etc/prometheus/rules.yaml
scrape_configs:
- job_name: bookstore-api
metrics_path: /metrics
static_configs:
- targets: ["host.docker.internal:5080"]
otlp:
# Her seriye label olarak eklenecek resource attribute'ları (kalanlar target_info'da durur).
promote_resource_attributes:
- service.name
- service.version
- deployment.environment.name
Container'daki Prometheus host'taki API'ye host.docker.internal üzerinden ulaşıyor. Push için uygulamayı şu environment variable'larla başlattık; servis adını ayrı verdik ki iki yoldan gelen seriler karışmasın:
$env:OTEL_SERVICE_NAME = "bookstore-api-otlp"
$env:OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = "http://localhost:9090/api/v1/otlp/v1/metrics"
$env:OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = "http/protobuf"
$env:OTEL_METRIC_EXPORT_INTERVAL = "15000"
İlk denemede servis adını değiştirmemiştik ve bir şey öğrendik: OTLP ile gelen serilerde job label'ı service.name'den, instance ise service.instance.id'den üretiliyor. Scrape config'teki job adı da bookstore-api olduğu için aynı metric iki kere, aynı job altında yalnızca instance'ı farklı iki seri olarak geldi. sum(...) yazan her sorgu iki katını gösterirdi. Aynı metric'i iki yoldan birden toplama; birini seç.
Exposition Format ve İsim Çevirisi
Scrape edilen sayfa Prometheus'un metin formatı (Content-Type: text/plain; version=0.0.4). Siparişlerin ve checkout süresinin bir kısmı (bazı bucket satırlarını çıkardık, label'ları kısalttık):
# TYPE bookstore_orders_placed_total counter
# HELP bookstore_orders_placed_total Orders that were placed and paid.
bookstore_orders_placed_total{otel_scope_name="Bookstore.Ordering",bookstore_order_type="retail"} 447
bookstore_orders_placed_total{otel_scope_name="Bookstore.Ordering",bookstore_order_type="institutional"} 50
# TYPE bookstore_checkout_duration_seconds histogram
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="0.1"} 180
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="0.25"} 336
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="2.5"} 336
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="5"} 423
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="7.5"} 447
bookstore_checkout_duration_seconds_bucket{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail",le="+Inf"} 447
bookstore_checkout_duration_seconds_sum{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail"} 382.5907395000002
bookstore_checkout_duration_seconds_count{...,bookstore_checkout_outcome="placed",bookstore_order_type="retail"} 447
OpenTelemetry adı Prometheus'a gelirken değişti. bookstore.orders.placed noktaları alt çizgi oldu ve counter olduğu için _total eki aldı; {order} birimi süslü parantezli bir açıklama olduğu için ada eklenmedi. bookstore.checkout.duration'ın birimi s olduğu için adına _seconds eklendi ve histogram üç seri ailesine bölündü: _bucket (her le sınırı için kümülatif sayaç), _sum ve _count. Tag'ler de label oldu, meter adı otel_scope_name label'ına taşındı. bookstore.checkouts.active bir UpDownCounter'dı; Prometheus'ta gauge olarak görünüyor. Bucket'lara bakınca histogram'ın şekli de okunuyor: 336 siparişin hepsi 0,25 saniyenin altında; 0,25 ile 2,5 arasında hiç yok; sonra 87 sipariş 2,5–5 aralığına, 24'ü 5–7,5 aralığına düşmüş. Retry basamakları bucket sınırlarının arasında kalmış.
Prometheus 3, metric ve label adlarında Unicode karakterleri (UTF-8) destekliyor, yani nokta içeren adlar artık mümkün. OTLP ile gelen veride çeviriyi config'teki otlp.translation_strategy belirliyor; 3.16'nın varsayılanı UnderscoreEscapingWithSuffixes, yani yukarıdaki davranış. Noktaları ve birimleri olduğu gibi tutan stratejiler de var (NoUTF8EscapingWithSuffixes, NoTranslation), ama bunları çalıştırmadık; dashboard ve alarmlar zaten alt çizgili adlara göre yazıldıysa geçişi bir proje gibi ele al. Bir de target_info: resource attribute'larının tamamı bu ayrı seride duruyor; config'teki promote_resource_attributes listesi seçtiklerimizi her seriye label olarak ekledi (OTLP serilerinde service_name, service_version, deployment_environment_name görünüyor).
PromQL: rate, increase ve sum by
Counter ham hâliyle işe yaramıyor; uygulama yeniden başladığında sıfırlanan, sürekli büyüyen bir sayı. Anlamlı soru "saniyede kaç" ve bunun cevabı rate. Bütün sorguları trafiğin bittiği ana sabitleyerek çalıştırdık. Bu ilk koşuda aynı metric hem scrape hem OTLP ile geldiği için (yukarıda anlattığımız gibi) scrape edilen kopyayı instance label'ıyla seçtik:
sum by (bookstore_order_type) (rate(bookstore_orders_placed_total{job="bookstore-api",instance="host.docker.internal:5080"}[2m]))
{bookstore_order_type="retail"} 3.0804497507352013
{bookstore_order_type="institutional"} 0.33719619695252484
sum(increase(bookstore_orders_placed_total{job="bookstore-api",instance="host.docker.internal:5080"}[3m]))
{} 410.1175137225272
rate(x[2m]), pencerenin içindeki örneklerden saniyedeki artışı hesaplıyor; sayaç sıfırlanmışsa bunu fark edip düzeltiyor. sum by ise pod'lar, status code'lar gibi tutmak istemediğin label'ları toplayıp yalnız istediğini bırakıyor. Önce Tahmin Et'in ilk cevabı increase'de: increase, rate'in pencere süresiyle çarpılmış hâli ve PromQL, pencerenin kenarlarında örnek olmadığında artışı pencerenin tamamına ekstrapole ediyor. 15 saniyelik scrape aralığıyla 3 dakikalık pencerenin ilk ve son örneği pencerenin tam kenarlarında değil; Prometheus aradaki eğimi kenarlara uzatıyor. Sonuç tam sayı olmayan 410,1. increase bir muhasebe aracı değil, bir tahmin; kesin sayıya ihtiyacın varsa (fatura, mutabakat) metric değil, veritabanı.
histogram_quantile ve Hata Oranı
histogram_quantile(0.50, sum by (le) (rate(bookstore_checkout_duration_seconds_bucket{job="bookstore-api",instance="host.docker.internal:5080"}[3m])))
{} 0.16209084939551588
histogram_quantile(0.95, sum by (le) (rate(bookstore_checkout_duration_seconds_bucket{job="bookstore-api",instance="host.docker.internal:5080"}[3m])))
{} 5.933824811197164
histogram_quantile(0.99, sum by (le) (rate(bookstore_checkout_duration_seconds_bucket{job="bookstore-api",instance="host.docker.internal:5080"}[3m])))
{} 8.752432454185117
sum(rate(bookstore_checkout_duration_seconds_sum{job="bookstore-api",instance="host.docker.internal:5080"}[3m]))
/ sum(rate(bookstore_checkout_duration_seconds_count{job="bookstore-api",instance="host.docker.internal:5080"}[3m]))
{} 1.0270184636175672
histogram_quantile, sınırları le label'ında duran bucket'lardan yüzdeliği hesaplıyor. sum by (le) zorunlu: bucket sınırı dışındaki label'ları toplamazsan her outcome ve sipariş tipi için ayrı yüzdelik çıkar. Önce Tahmin Et'in ikinci cevabı buradan çıkıyor. Prometheus yalnızca "2,5 ile 5 arasında 87 sipariş var" biliyor; o aralığın içinde değerlerin eşit dağıldığını varsayıp doğrusal enterpolasyon yapıyor. Gerçek değerlerin hepsi 2,6 ve 5,1 saniyede kümelenmiş, yani varsayım tutmuyor: p95 5,93, p99 8,75 çıktı. Trafik aracının ölçtüğü değerler ise p95 5,09 ve p99 7,64 civarındaydı. Bucket sınırları ne kadar kaba ise yüzdelik o kadar yaklaşık. İkinci sorgu ortalamayı _sum ve _count'tan hesaplıyor: 1,03 saniye, yani ilk yazıdaki yanıltıcı ortalama.
Hata oranı iki rate'in oranı. Ödeme sağlayıcısının metric'leri de aynı yöntemle okunuyor:
sum(rate(http_server_request_duration_seconds_count{job="bookstore-api",instance="host.docker.internal:5080",http_route="/api/orders/",http_response_status_code=~"5.."}[3m]))
/ sum(rate(http_server_request_duration_seconds_count{job="bookstore-api",instance="host.docker.internal:5080",http_route="/api/orders/"}[3m]))
{} 0.005972784178019447
sum by (event_name) (increase(resilience_polly_strategy_events_total{job="bookstore-api",instance="host.docker.internal:5080",event_name=~"OnRetry|OnTimeout"}[3m]))
{event_name="OnTimeout"} 152.0865574790551
{event_name="OnRetry"} 149.7651725593902
Checkout isteklerinin binde 6'sı 502 ile bitmiş; aynı sürede ödeme sağlayıcısına 150 civarında retry yapılmış. Kullanıcıya yansıyan hata oranı düşük, ama retry'lar sağlayıcıya yaklaşık yüzde 38 fazladan yük bindiriyor (400 siparişte 150 retry). İki sayının birlikte okunması, retry politikasının değerlendirilmesi için gereken veri.
Recording Rule'lar
Yukarıdaki sorgular her açıldığında yüzlerce bucket serisini okuyup hesaplıyor. Bir dashboard'da 10 panel, her biri 30 saniyede bir yenileniyor ve aynı sorguyu bir alarm kuralı da kullanıyorsa, Prometheus aynı hesabı tekrar tekrar yapar. Recording rule, bir sorgunun sonucunu belirli aralıklarla hesaplayıp yeni bir seri olarak saklıyor:
groups:
- name: bookstore-checkout
rules:
# Sipariş tipine göre saniyedeki sipariş sayısı
- record: bookstore:orders_placed:rate5m
expr: sum by (bookstore_order_type) (rate(bookstore_orders_placed_total{job="bookstore-api"}[5m]))
# Checkout süresinin 5 dakikalık p95'i
- record: bookstore:checkout_duration_seconds:p95_5m
expr: histogram_quantile(0.95, sum by (le) (rate(bookstore_checkout_duration_seconds_bucket{job="bookstore-api"}[5m])))
# POST /api/orders isteklerinin 5xx ile biten oranı
- record: bookstore:orders_http_errors:ratio_rate5m
expr: |
sum(rate(http_server_request_duration_seconds_count{job="bookstore-api", http_route="/api/orders/", http_response_status_code=~"5.."}[5m]))
/
sum(rate(http_server_request_duration_seconds_count{job="bookstore-api", http_route="/api/orders/"}[5m]))
Ad kuralı level:metric:operations: hangi label'lara kadar toplandığı, hangi metric'ten geldiği ve ne hesaplandığı. Kurallar evaluation_interval'da (bizde 15 saniye) çalışıyor; dashboard ve alarm, hazır hesaplanmış tek bir seriyi okuyor:
bookstore:checkout_duration_seconds:p95_5m 5.120097534607394
bookstore:orders_placed:rate5m{bookstore_order_type="retail"} 1.390115888888889
bookstore:orders_placed:rate5m{bookstore_order_type="institutional"} 0.15389696296296296
bookstore:orders_http_errors:ratio_rate5m 0.005874259178432363
Kuralda job="bookstore-api" filtresi bilinçli: bunu ilk denemede yazmamıştık ve kural, scrape ve OTLP ile gelen iki kopyayı birlikte topluyordu. Alarm kuralları da aynı mekanizmayla çalışıyor; onları serinin alerting yazısında yazacağız.
Native Histogram'lar
Klasik histogram'ın iki sorununu gördük: her bucket sınırı ayrı bir seri ve yüzdelik bucket'ların kabalığı kadar doğru. Native histogram, bütün dağılımı tek bir seride, tek bir örnekte tutan bir veri tipi. Prometheus 3.8'de kararlı hâle geldi ama hâlâ opt-in: scrape_native_histograms ayarıyla açılıyor. İki türü var. Üstel (exponential) native histogram'da bucket sınırlarını kimse seçmiyor; sınırlar logaritmik ve dağılıma göre kendiliğinden ayarlanıyor, hata payı da sınırlı. Özel bucket'lı native histogram (NHCB, native histogram with custom buckets) ise klasik bucket'ları koruyup yalnızca tek seride saklıyor. .NET'in Prometheus exporter'ı yalnızca klasik histogram üretiyor; Prometheus bunu scrape sırasında NHCB'ye çevirebiliyor. OTLP ile ise OpenTelemetry SDK'sının üstel histogram'ı doğrudan native histogram olarak geliyor. İkisini aynı anda denedik. Config'e üç satır eklendi:
global:
scrape_interval: 15s
evaluation_interval: 15s
# Native histogram'lar (Prometheus 3.8'den beri kararlı, hâlâ opt-in). .NET exporter'ı yalnız klasik bucket
# üretiyor; Prometheus her seriyi özel bucket'lı tek bir native histogram (NHCB) olarak saklıyor.
scrape_native_histograms: true
convert_classic_histograms_to_nhcb: true
# Geçiş sürecinde klasik _bucket/_sum/_count serilerini de tut; mevcut sorgular çalışmaya devam etsin.
always_scrape_classic_histograms: true
Uygulamanın OTLP tarafına da tek bir environment variable: OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=base2_exponential_bucket_histogram. 500 siparişlik trafikten sonra aynı metric'in üç biçimini karşılaştırdık:
count by (job) ({__name__=~"bookstore_checkout_duration_seconds.*"})
{job="bookstore-api-otlp"} 3
{job="bookstore-api"} 42
# klasik bucket'lar
histogram_quantile(0.95, sum by (le) (rate(bookstore_checkout_duration_seconds_bucket{job="bookstore-api"}[3m])))
{} 5.377143879619664
# NHCB: aynı bucket'lar, tek seri
histogram_quantile(0.95, sum(rate(bookstore_checkout_duration_seconds{job="bookstore-api"}[3m])))
{} 5.230765495094986
# OTLP'den gelen üstel native histogram
histogram_quantile(0.95, sum(rate(bookstore_checkout_duration_seconds{job="bookstore-api-otlp"}[3m])))
{} 4.987966421682704
histogram_quantile(0.99, sum(rate(bookstore_checkout_duration_seconds{job="bookstore-api-otlp"}[3m])))
{} 5.164380590563866
histogram_fraction(0, 1, sum(rate(bookstore_checkout_duration_seconds{job="bookstore-api-otlp"}[3m])))
{} 0.743496465027412
Bu koşuda trafik aracının ölçtüğü değerler p95 için 5,09, p99 için 5,16 saniyeydi. Üstel histogram 4,99 ve 5,16 saniye ile gerçeğe en yakın sonucu verdi; üstelik her outcome ve sipariş tipi kombinasyonu için tek seri, toplam 3 seri yetti; klasik ve NHCB birlikte 42 seri tuttu (always_scrape_classic_histograms açık olduğu için ikisi birden). Sorgu sözdizimi de sadeleşiyor: _bucket ve le yok, doğrudan histogram'ın kendisi. histogram_fraction da native histogram'la gelen bir fonksiyon: checkout'ların yüzde 74'ü 1 saniyenin altında bitmiş. Eski sorguların kırılmaması için geçiş sürecinde klasik serileri tutmak mantıklı; dashboard'lar ve recording rule'lar yeni sözdizimine taşındıktan sonra always_scrape_classic_histograms kapatılabilir.
Üretimde Dikkat
rate penceresini scrape aralığının en az dört katı seç. Bizim 15 saniyelik aralıkla [1m] sınırda, [2m] rahat. Scrape aralığını değiştirdiğinde dashboard'lardaki ve kurallardaki pencereleri de gözden geçir.
Label cardinality'si Prometheus'ta doğrudan bellek. Her seri head bloğunda bellekte tutuluyor; Prometheus'un kendi metric'lerinden prometheus_tsdb_head_series, toplam seri sayısını izlemenin en kısa yolu. Klasik histogram'ın her bucket'ı bir seri olduğu için histogram'larda bu sayı hızla büyür.
/metrics endpoint'ini herkese açma. İçinde route'lar, hata sayıları, runtime bilgisi var; exporter'ın README'si de varsayılan olarak korumasız olduğunu hatırlatıyor. Ayrı bir porta al ya da yalnız iç ağdan erişilebilir yap.
Prometheus tek bir sunucu ve verisi yerel diskte. Uzun saklama, yüksek erişilebilirlik ve birden çok cluster'ı tek yerden sorgulamak için remote write ile Mimir, Thanos ya da yönetilen bir servis kullanılıyor; serinin production yazısında yönetilen seçeneklere bakacağız.
Sık Düşülen Tuzaklar
Gauge'a rate uygulamak. rate(bookstore_checkouts_active[2m]) bizde 0,022 döndürdü; bir anlamı yok, çünkü rate sayacın sıfırlandığını varsayıp düşüşleri sıfırlanma sanıyor. Gauge için avg_over_time, max_over_time ya da deriv kullan.
Pencereyi scrape aralığından kısa seçmek. rate(bookstore_orders_placed_total[10s]) bizde boş sonuç döndürdü: 10 saniyelik pencerede 15 saniyede bir alınan örneklerden ikisi bulunmuyor. Boş grafik, "hiç sipariş yok" ile karıştırılır.
histogram_quantile'dan önce sum by (le) yazmamak ya da le'yi toplamak. İlkinde her label kombinasyonu için ayrı yüzdelik çıkar, ikincisinde fonksiyon bucket'ları bulamaz.
Yüzdeliklerin ortalamasını almak. Beş pod'un p95'lerinin ortalaması, beşinin birlikte p95'i değil. Önce bucket'ları topla, sonra yüzdeliği hesapla; recording rule'lar da bu sırayla yazılmalı.
Aynı metric'i hem scrape hem OTLP ile almak. Yukarıda gördüğümüz gibi seriler aynı job altında ikiye katlanır ve sum sessizce yanlış sonuç verir.
Küçük Bir Deneme
★☆☆ bookstore.checkout.duration'ın bucket sınırlarına 2.75 ve 5.25 ekle (advice'ta). Trafiği tekrar çalıştır ve klasik histogram'ın p95'inin gerçeğe ne kadar yaklaştığını ölç.
★★☆ Ödeme sağlayıcısına yapılan çağrıların süresini http_client_request_duration_seconds'tan, server_address label'ıyla filtreleyerek oku ve checkout'un p95'i ile aynı panelde karşılaştıracak bir recording rule yaz.
★★★ Prometheus'un otlp.translation_strategy ayarını NoUTF8EscapingWithSuffixes yap, OTLP ile gelen metric'in adının nasıl göründüğüne bak ve onu UTF-8 sözdizimiyle, adı süslü parantez içinde tırnakla yazarak sorgulamayı dene.
Prometheus'ta artık doğru sorular sorabiliyoruz, ama her seferinde PromQL yazmak bir ekibin kullanacağı bir araç değil. Bir sonraki yazıda bu sorguları Grafana'da RED (istek hızı, hata, süre) ve USE (kaynak kullanımı, doygunluk, hata) dashboard'larına çeviriyoruz: variable'lar, p99 grafiğindeki bir sıçramadan exemplar ile o isteğin trace'ine geçmek ve dashboard'ları kod olarak Git'te tutmak.