chstore

package
v0.9.666 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

v0.9.580 — korelasyon kimliği örnekleri.

Operatör: "CoSRE örnek request_id, CHANNEL_CODE değerlerini de söylesin."

Neden değerli: bir cevabın eyleme dönüşebilmesi için operatörün ARAYABİLECEĞİ bir şey vermesi gerekir. "Ödeme servisinde hata oranı %14" bir gözlemdir; "…örnek request_id: 8f3c-…, en çok hata üreten CHANNEL_CODE: 0012" bir BAŞLANGIÇ NOKTASIDIR. Operatör onu alıp kendi log'una, kendi çağrı merkezine, kendi kaydına gider.

Neden ayrı bir okuma: request_id kasten YÜKSEK KARDİNALİTELİ bir alan ve bu yüzden kırılım/agregasyon yollarından bilerek dışlanmış (bubbleup.go highCardinalityKey). Burada kırılım İSTEMİYORUZ — birkaç ÖRNEK istiyoruz, ki bu bambaşka bir sorgu şekli: dar zaman penceresi, hata span'leri, LIMIT.

v0.9.614 — şema yerindeyken DDL boot'u BEKLETMEZ: arka plana ertelenir.

Operatör (prod, dördüncü gece): "Yeni image çıksan ve direkt deploy etsem DDL sorunu kalmasa. Şu an hâlâ prod podları ready olmuyor."

v0.9.607/608 zaten var olan nesne/kolon için DDL'i ELEDİ (prod'da 44/49 + 58/76). Ama elenemeyenler — gerçekten yeni tablolar, MODIFY COLUMN'lar, probe'ların tetiklediği onarımlar — tıkalı kuyrukta her biri bütçesini (20 sn) doldurup boot'u DAKİKALARCA bekletiyordu. Pod ölmüyordu ama ready de olmuyordu.

ASIL SORU: schema aylardır yerindeyken boot neden HERHANGİ bir DDL'i bekliyor? API'nin servise başlamak için ihtiyacı olan her şey zaten duruyor. Bekletilen ifadeler ya no-op ya da "kuyruk düzelince uygulanacak iyileştirme" — ikisi de boot'u rehin tutmayı hak etmiyor.

MEKANİZMA: migrate başında şemanın yerinde olduğu tespit edilirse (küme modu + spans tablosu mevcut) execDDL ifadeleri ÇALIŞTIRMAK yerine biriktirir; New() dönmeden önce birikenler TEK arka plan goroutine'ine devredilir. Boot saniyeler içinde biter; ClickHouse kuyruğu düzelince ertelenenler sırayla uygulanır.

NE ZAMAN ERTELENMEZ:

  • TAZE KURULUM (spans yok): API tablosuz servise başlayamaz — tamamen senkron, davranış birebir eski.
  • TEK DÜĞÜM: kuyruk hastalığı yok, DDL zaten milisaniyelik; ertelemek yalnız chsmoke'u yarıştırırdı.

BİLİNÇLİ SONUÇLAR (gizlenmiyor):

  • Probe'a bağlı özellikler (hasXCol kapıları) ertelenen kolon uygulanana kadar KAPALI kalır — bu, bugünkü prod gerçeğinin aynısı: kolon kuyrukta beklerken de kapalılar. Kuyruk düzelince bir SONRAKİ boot'ta açılırlar.
  • registerTraceAttrMaterialized SENKRON kalır (SELECT probe, DDL değil): boot-sonrası-yazım-yok sözleşmesi korunur.

v0.9.613 — dağıtık DDL kuyruğu sağlık teşhisi.

Operatör vakası (prod, 2026-08-03, üç gece): system.distributed_ddl_queue doldu, girdiler Inactive, hiçbir host görevleri almıyor. Boot buna dayanıklı hâle geldi (v0.9.604-608) ama teşhis elle SQL istiyordu. Bu dosya teşhisi ürüne koyuyor.

AYIRICI — canlı kümede kalibre edildi; üç tuzak bizzat yenildi:

  1. MaxPushedDDLEntryID BAŞLATICIYA YERELDİR (chc-1'de 0 görüldü). Kuyruk başı = küme genelindeki tepe, host'un kendi işleneni ile kıyaslanır. Metrikler restart'ta sıfırlandığı için tepe ayrıca kuyruk girdi numaralarından da türetilir (query-%010d) — üç gecelik vakada host'lar restart edildiyse metrik tek başına yalan söylerdi.

  2. system.clusters.is_local GÜVENİLİR DEĞİL (sağlıklı lokal kümede bile 0). İsim karşılaştırması verdict'e SINIRLI girer: yalnız adlandırma şemasının ÇALIŞTIĞI kanıtlıyken (en az bir eşleşme varken) bir host "ulaşılamaz" sayılır. Cluster tanımı IP taşıyorsa hiçbir ad eşleşmez — o hâlde ulaşılamazlık iddiası üretilmez, yanlış "host kapalı" teşhisi verilmez.

  3. status kolonu Nullable: bozuk Keeper girdisi NULL status üretir ve `status != 'Finished'` NULL'u da düşürürdü — en patolojik takılma biçimi görünmez olur, verdict "healthy" dönerdi.

Verdict DAVRANIŞTAN çıkar:

kuyruk boş                        → healthy
ilerleme verisi yok/kesikse       → probe_failed (uydurma teşhis YOK)
host metrikte yok + şema eşleşir  → unreachable
bir host GERİDE                   → worker_stuck  (restart çözer)
kimse geride değil                → worker_skipping (ad uyuşmazlığı)

v0.9.607 — var olan nesne için DDL GÖNDERME.

Operator-reported (prod, v0.9.606 sonrası): pod artık çökmüyor ama READY de olmuyor — migrate'te asılı kalıyor.

Zincir şöyleydi ve her adımı bir öncekinin düzeltmesiydi:

v0.9.604  kod 159 ölümcül sayılmıyor        → crashloop durdu
v0.9.605  sunucu istisna atmıyor            → CREATE DATABASE geçti
v0.9.606  sunucu bütçesi < istemci timeout  → i/o timeout bitti
          ama BOOT HÂLÂ BİTMİYOR

Kalan sebep aritmetik: boot 158 bildirimsel DDL çalıştırıyor ve tıkalı bir DDL kuyruğunda her biri bütçesini (20 sn) doluyor. 158 × 20 sn ≈ 53 dakika. Pod ölmüyor, sadece hiç hazır olmuyor.

ASIL SORU: bu DDL'lerin kaçı gerçekten gerekli? Prod'da şema aylardır yerinde — hepsi `IF NOT EXISTS`, yani hepsi ZATEN NO-OP. Ödediğimiz tek şey, sonucu baştan belli olan 158 dağıtık kuyruk turu.

Çare bu yüzden bir "atlama hilesi" değil, fazlalığın kaldırılması: nesne zaten varsa `CREATE ... IF NOT EXISTS` göndermenin HİÇBİR etkisi yok. Tek sorgu ile var olanları öğrenip o ifadeleri hiç göndermiyoruz.

NE ATLANMIYOR: ALTER'lar (kolon ekleme/tip değiştirme — yükseltme yolunun kendisi), DROP'lar, ve probe'a bağlı bölümler. Yalnız sonucu tanım gereği no-op olan CREATE'ler eleniyor.

TAZE KURULUMDA DAVRANIŞ AYNI: hiçbir nesne yok → hiçbiri elenmiyor → tüm DDL koşuyor.

v0.9.609 — altyapı-ölümcül exception'lar.

Operatör (prod trace'i, 2026-08-03): bir servis bağımlılığına ulaşamıyor ve `java.net.UnknownHostException` alıyor — hedef bir Kubernetes servis adı (`*.svc.cluster.local`). İstek: "varsa çok kritik, hemen P1 üret".

NEDEN AYRI BİR SINIF: bu, "hedef ayakta değil" demek DEĞİL. ConnectException hedefin var olduğunu ama cevap vermediğini söyler — çoğu zaman geçicidir, yeniden deneme düzeltir, pod yeniden başlarken normaldir. UnknownHostException ise İSMİN HİÇ ÇÖZÜLMEDİĞİNİ söyler: yanlış yapılandırma, silinmiş servis, yanlış namespace, bozuk DNS.

Üç özelliği onu P1 yapıyor:

  • yeniden deneme DÜZELTMEZ (isim yok, bekleyerek var olmaz)
  • kendiliğinden GEÇMEZ (biri bir şey değiştirene kadar sürer)
  • genelde TÜM çağrıları etkiler, bir kısmını değil

Bu yüzden eşik 1: patlama beklemek yok. Tek oluşum yeter — çünkü tek oluşum zaten "bu bağımlılık hiç çözülemiyor" demek.

v0.9.665 — Prometheus `job` etiketinden servis eşleştirme.

Operatör: "service overview throughput için ekrandaki metrikten job=abc/cm-put-service şeklinde Service ismi / sonrası olacak şekilde ayarlayabilir misin, metricten okusun."

Ortamdaki metrik Prometheus biçiminde geliyor ve servis kimliği `job` etiketinde, `<namespace>/<servis>` şeklinde duruyor:

http_server_request_duration_seconds_count{job="content-manager-prod/cm-put-service"}

Coremetry'nin kendi servis adı ise `cm-put-service`. Yani eşleştirme `job` değerinin SON BÖLÜMÜ üzerinden yapılmalı.

NEDEN SAF FONKSİYON: eşleştirme deseni kullanıcı girdisinden (servis adı) üretiliyor ve doğrudan bir CH `match()` ifadesine giriyor. Kaçış hatası ya sessizce yanlış servisi eşler ya da sorguyu patlatır — ikisi de tablo testiyle kapatılacak sınıf.

v0.9.621 — terfi etmiş attribute kolonları PROD'DA HİÇ ÇALIŞMADI.

Operator-reported: /traces?range=6h&filters=[{"k":"channel_code",…}] ClickHouse'ta 26 sn sürüp max_execution_time=25s'e takılıyor.

Kök neden ÜÇ ayrı katman ve her biri tek başına düzeltilirse ya işe yaramaz ya da YANLIŞ sonuç verir:

  1. Kolon ifadesi BÜYÜK harf okuyordu: MATERIALIZED attr_values[indexOf(attr_keys, 'CHANNEL_CODE')] Prod ise KÜÇÜK harf yazıyor ('channel_code' — operatör ölçümü, 10 dakikada 2.67M span). Yani kolon v0.9.198'den beri HEP BOŞTU.
  2. Yönlendirme haritası da BÜYÜK harf anahtarlıydı ve arama tam eşleşme (repo.go traceExtrasProjection / business_dims.go) — küçük harf anahtar hiç eşleşmiyordu.
  3. Filtre yolu (filterexpr.go) haritaya HİÇ bakmıyordu.

Bu dosya (1) ve (2)'yi kapatıyor; (3) ayrı bir dilim, çünkü kolon doğru dolmadan filtreyi oraya yönlendirmek BOŞ sonuç verir.

ÖLÇÜLDÜ (CH 24.8, lokal, 10M satır prod şeklinde tablo, 3 koşu medyanı):

dizi açma (bugünkü)      10.000.000 satır   3.90 GiB   362 ms
terfi etmiş kolon        10.000.000 satır   1.98 GiB   204 ms
kolon + set(0) indeks     1.310.720 satır    261 MiB    81 ms

Kolonun tek başına yalnız 2× olmasının sebebi: YENİ eklenen bir MATERIALIZED kolon eski part'larda SAKLANMAZ, okuma anında diziden hesaplanır. Asıl kazanç skip index'te (ayrı dilim).

AYNI ÖLÇÜM, DÜZELTMENİN ŞEKLİNİ BELİRLEDİ:

ALTER … MODIFY COLUMN … MATERIALIZED <yeni ifade>
  → eski part'lar ONARILMAZ, boş kalır (ölçüldü)
ALTER … DROP COLUMN + ADD COLUMN <aynı ad>
  → eski part'lar için okuma anında HESAPLANIR (ölçüldü: 200.000/
    200.000 boş → 0 boş)

Bu yüzden onarım DROP+ADD. MODIFY olsaydı tarihsel veri sessizce boş dönerdi ve filtreyi kolona yönlendirmek YANLIŞ sonuç üretirdi.

v0.9.591 — kök-neden hakem kararının kalıcı kaydı.

NEDEN: verdict istek başına üretilip yalnızca HTTP yanıtında yaşıyordu. Ne kararın kendisi ne de KALKANLARIN NE YAPTIĞI hiçbir yere düşüyordu.

Tek kalıcı iz `ai_calls.response_sample`'daki modelin HAM çıktısıydı — yani kalkanlardan ÖNCEKİ hâli. Operatörün gördüğü o değil: aradaki farkı kalkanlar üretiyor (uydurma kanıt kimliği düşürülür, geçersiz eleme iptal edilir, güven tavanlanır). Dolayısıyla "operatöre ne gösterdik" sorusunun cevabı hiçbir yerde yoktu.

İki şeyi birden mümkün kılıyor:

  1. ÖLÇÜM — kalkanlar ne sıklıkla devreye giriyor, model ne sıklıkla çözümlenemiyor, kaç karar insufficient_evidence. Bunlar bilinmeden "verdict kalitesi" bir histen ibaret.
  2. GERİ BİLDİRİM — exchange_id ai_feedback'e join olur; "👎 verilen verdict neydi" cevaplanabilir hale gelir.

(2) tasarım dokümanının [6] LEARN katmanının ÖN KOŞULU (docs/cosre-verdict-design.md §11). Öğrenmenin kendisi bu dilimde YOK ve bilinçli: hangi verdict'lerin yanlış olduğunu bilmeden neyi öğreteceğimizi de bilmiyoruz.

v0.9.572 — paylaşılan bağımlılık patlaması tespiti.

Operatör raporu (prod, gece 03:04): on beşten fazla servis AYNI saniyede aynı `java.sql.SQLRecoverableException` ORA-18730 "Socket read timed out" hatasını aldı. Coremetry bunları on beş ayrı exception grubu olarak gösterdi — her biri "şu servisin sorunu" gibi.

Oysa bu servis başına bir sorun DEĞİL: paylaşılan bir bağımlılıkta (Oracle) tek bir olay. On beş satır okuyup aradaki ortak paydayı operatörün kafasında kurması gerekiyordu.

Sinyal basit ve güçlü: AYNI exception tipi, DAR bir zaman penceresinde, ÇOK sayıda ayrı serviste BAŞLIYOR. Tek bir servisin kendi hatası yayılmaz; paylaşılan bir bağımlılığın hatası yayılır.

Tespit deterministik — LLM YOK. Bu, RCA paketinin [2] CORRELATE katmanı: model tespit etmez, tespit edileni anlatır.

v0.9.638 — /traces "Toplamı göster" listeyi MV'den DÜŞÜRÜYORDU.

`count=exact` tek başına countModeAllowsMV'yi kapatıyor ve liste ham spans yoluna iniyordu: çift ceza — hem pahalı bir DISTINCT hem 22.575:1 oranlı bir liste taraması (docs/perf/traces-plan.md "D3").

Sayım liste isteğinden TAMAMEN çıkarıldı. Liste SQL'i bayt bayt aynı kaldığı için "toplamı göster" artık listeyi MV'de BIRAKIYOR — kabul kriteri kod okumasıyla değil YAPISAL olarak sağlanıyor.

ÜÇ TASARIM ÜRETİLİP YARGILANDI; reddedilenlerin ölümcül kusuru:

  • aşama-1'in LIMIT'ini yükseltmek: bütçe TARANAN değil EŞLEŞEN satıra konuyor. %0,1 hata oranında `error_count_state > 0 … LIMIT 30000` otuz milyon satır tarar.
  • sayımı listeye gömmek: her offset değişiminde yeniden ödenir ve "kesin" toplam Pager'ı listenin ULAŞAMAYACAĞI sayfalara açar.

SAYI ASLA SAYFALAMA SINIRI DEĞİL. Pager.tsx lastPage/atEnd'i total'dan türetiyordu; UI artık total'ı Pager'a GEÇMİYOR, gezinme hasMore üzerinde kalıyor. Bu, tavanın ulaşılabilir sınırla eşleşmesi zorunluluğunu da kaldırıyor — ilk tasarımda tavanı 5.000'e çekmeyi önermiştim, kısıtı KABUL ederek; doğru hamle kısıtı KOPARMAK.

Index

Constants

View Source
const (
	OpSigReUUID = `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`
	OpSigReHex  = `/[0-9a-fA-F]{16,}`
	OpSigReNum  = `/[0-9]+`
)

opSig* are the read-time ID-collapsing regexes for the Endpoints "Group by shape" toggle, applied IN ORDER (UUID first so the numeric/hex rules don't chew its runs). v0.8.x — ALIGNED with the ingest-time normalizer templater.NormalizeOperation (the op_group column): same `:id` placeholder for every id type, and a long-hex rule mirroring LooksLikeOpaqueID's hex≥16 case — so a given path segment collapses to the SAME shape whether it's grouped read-time here (/endpoints) or by the stored op_group on the service Operations tab. (The op_sig + op_group op_sig_align_test pins this.) RE2 syntax (== Go regexp). Residual: opSigWrap covers the COMMON id types (numeric / UUID / long-hex); the rarer opaque kinds LooksLikeOpaqueID catches (base64url / consonant-only) over-match as a SQL regex, so they collapse only on the ingest path — documented, accepted. Exported so the cross-package op_sig↔op_group alignment regression test (internal/templater, which can't be imported here without a cycle) can pin that these read-time patterns collapse a path to the same shape as the ingest-time NormalizeOperation.

View Source
const (
	ExStateNew          = "new"
	ExStateAcknowledged = "acknowledged"
	ExStateResolved     = "resolved"
	ExStateRegressed    = "regressed"
	ExStateIgnored      = "ignored"
)

Possible states. The frontend filters on these.

View Source
const (
	RunExecRunning   = "running"
	RunExecWaiting   = "waiting_for_user"
	RunExecCompleted = "completed"
	RunExecFailed    = "failed"
	RunExecCancelled = "cancelled"
)

Execution statuses.

View Source
const (
	StepPending   = "pending"
	StepRunning   = "running"
	StepWaiting   = "waiting_for_user"
	StepCompleted = "completed"
	StepSkipped   = "skipped"
	StepFailed    = "failed"
)

Step statuses.

View Source
const (
	RunbookStepManual     = "manual"
	RunbookStepQuery      = "query"
	RunbookStepHTTP       = "http"
	RunbookStepJavaScript = "javascript"
	RunbookStepBash       = "bash"
)

Runbook step kinds. Exported so the API layer + agent validate against the same set without re-declaring the strings.

View Source
const (
	SLITypeAvailability = "availability"
	SLITypeLatency      = "latency"
)

SLI types — kept tiny on purpose. availability counts ok-vs-error spans against a service (and optional operation); latency counts spans whose duration is within the threshold as "good".

View Source
const DefaultExceptionStaleHorizon = 24 * time.Hour

DefaultExceptionStaleHorizon is how long a group can go with NO new occurrences before the background sweep auto-resolves it (main.go wires this into AutoResolveStaleExceptionGroups). v0.8.x — operator-reported: the old 14-day horizon made resolved transitions take far too long and contradicted the v0.6.24 "cleared by tomorrow" intent. 24h clears a genuinely-fixed exception by the next day (14× faster) while a still-active fingerprint keeps firing well inside the window so it never spuriously resolves. Lives here (not main.go) so it sits beside exResolveGrace and both windows are unit- testable. Reversible: shouldRegress re-opens a fingerprint that fires again.

View Source
const HTTPErrorTypeRe = `^[0-9]{3}$`

HTTPErrorTypeRe — "HTTP-hata grubu" tanımının TEK kaynağı: çıplak 3-haneli ex_type (gerçek bir Java/Go exception tipi asla çıplak sayı olmaz). Hem CH match() hem Go regexp bu deseni kullanır (RE2 ikisinde de aynı anlama gelir); ayrışırlarsa facet sayıları liste ile çelişir.

View Source
const JobLabelDefault = "job"

JobLabelDefault — Prometheus'un servis kimliğini taşıdığı etiket.

View Source
const ProblemScanCeiling = 2000

ProblemScanCeiling / ProblemScanLimit — öncelik daraltmasında CH taramasının genişletilmesi (v0.9.576).

ProblemFilter.Priority SQL'de UYGULANMAZ: öncelik okuma anında hesaplanır, CH satırında yoktur. Daraltma Go'da, LIMIT'ten SONRA olur — yani sayfa boyutu kadar satır taranıp içinden P1'ler süzülürse, filoda yüzlerce P1 varken SIFIR sonuç dönebilir.

Bu, make audit CHECK 8'in kovaladığı "LIMIT'ten sonra filtrele" sınıfı. Sayfa yolu (internal/api) bunu zaten doğru yapıyordu; MCP list_problems aracı v0.9.554'te aynı tuzağa düştü.

Kural chstore'a TAŞINDI çünkü iki tüketici var ve mcptools internal/api'yi import edemez (döngü). İkinci bir kopya yazmak, bu oturumda altı kez çıkan ayrışma sınıfının yenisi olurdu.

View Source
const SamplePromptCap = 4 * 1024

SamplePromptCap is the byte limit applied to prompt_sample / response_sample at insert time. 4KB is enough to see what the model was asked + how it answered without blowing up rows when an operator pastes a 30KB log into a custom Explain. CH compresses the column with ZSTD anyway so this is mostly a CH I/O guard.

View Source
const SpanMetricRowCap = 50000

SpanMetricRowCap — spans/metric_points GROUP BY sorgularının satır (bucket×seri) tavanı. v0.9.458'e dek çıplak 50000 literaliydi ve dolduğunda KİMSE söylemiyordu: ORDER BY gk alfabetik olduğundan geç-harfli seriler komple düşer, panel kalan ~ilk serileri "evren" gibi çizerdi.

View Source
const SparklineBuckets = 120

SparklineBuckets is the MAXIMUM slot count for the inline sparklines (operations table, endpoints, infra metrics). Granular-sparklines sweep (M4, 2026-07-24 — operator: "sadece çizgisel gösteriyor, daha granüle olsun"): 30 → 120, so a 24h window renders 12-min slots instead of 48-min mush. This constant is the SINGLE source for every sparkline grid; the frontend derives the axis from array length, so variable-length arrays are safe (GetEndpointsMV precedent).

View Source
const TeamAliasesKey = "team_aliases"
View Source
const TeamContactsKey = "team_contacts"
View Source
const ThroughputMetricDefault = "http_server_request_duration_seconds_count"

ThroughputMetricDefault — operatörün ekranındaki metrik. Ayarla değiştirilebilir; kuruluma göre adı farklı olabilir.

View Source
const TokenPlaintextPrefix = "cmk_"

TokenPlaintextPrefix — Bearer değerinde JWT'den ayırt etme öneki.

Variables

View Source
var ErrRollupTablesMissing = fmt.Errorf("rollup tabloları bulunamadı — migrations/0001-0002 bu ClickHouse'a uygulanmamış")

ErrRollupTablesMissing — migrations uygulanmadan endpoint çağrıldı.

View Source
var WellKnownTraceCol = map[string]string{
	"http.method": "http_method",

	"http.request.method":       "http_method",
	"http.response.status_code": "toString(http_status)",
	"db.system.name":            "db_system",
	"db.query.text":             "db_statement",
	"http.route":                "http_route",
	"http.status_code":          "toString(http_status)",
	"db.system":                 "db_system",
	"db.statement":              "db_statement",
	"rpc.system":                "rpc_system",
	"rpc.method":                "rpc_method",
	"peer.service":              "peer_service",
	"messaging.system":          "msg_system",
	"service.name":              "service_name",
	"deployment.environment":    "deploy_env",

	"deployment.environment.name": "deploy_env",
	"host.name":                   "host_name",
	"kind":                        "kind",
}

WellKnownTraceCol maps OTel semantic-convention attribute keys to their dedicated columns on the spans table. When the /traces page asks for one of these as an extra column we pull from the indexed LowCardinality column instead of scanning the attr_keys/attr_values arrays — same value, much cheaper plan.

Functions

func ApplyFilterGroup added in v0.8.151

func ApplyFilterGroup(wc *whereClause, g FilterGroup)

ApplyFilterGroup appends a FilterGroup's predicates onto a whereClause.

  • Flat-AND (no OR, no nested groups): delegated VERBATIM to ApplyFilters so the emitted conds + args are byte-identical to the legacy `[]FilterExpr` path — each leaf is a separate ` AND `-joined conjunct. This is the zero-risk back-compat path saved views ride on.
  • Anything with OR or a nested group: rendered to a single parenthesised fragment via BuildFilterGroupSQL and added as ONE conjunct, so the group's internal boolean structure is preserved against the surrounding (always-AND) time / service / error predicates.

func ApplyFilters

func ApplyFilters(wc *whereClause, filters []FilterExpr)

ApplyFilters appends each filter as a separate WHERE conjunct, skipping any that fail to compile.

v0.9.269 — the skip is now LOGGED. It used to be silent, justified by "UI validates first", and that assumption was false: the DB drill modal sent `{key, op, value}` while this struct binds `{k, op, v}` by JSON tag, so Key arrived empty, SQL() returned "missing key", and every filter was dropped here without a trace. The chart then rendered UNFILTERED under a modal that displayed the filter chip. A dropped filter does not produce an empty result an operator would question — it produces a WIDER one that looks perfectly plausible, which is the worst way for this to fail.

Skipping (rather than erroring) is kept deliberately: a hard failure here would take down every caller for one malformed clause. But it must not be invisible.

func ApplyMetricFilters added in v0.8.381

func ApplyMetricFilters(wc *whereClause, filters []FilterExpr)

ApplyMetricFilters is ApplyFilters for metric_points reads (v0.8.381): identical semantics, metric-aware column resolution.

func BuildFilterGroupSQL added in v0.8.151

func BuildFilterGroupSQL(g FilterGroup) (string, []any)

BuildFilterGroupSQL renders a FilterGroup to a single WHERE fragment + ordered args, ready to AND onto the rest of a query's predicates. Returns ("", nil) for a group with no compilable terms.

For a flat-AND group it produces the same per-leaf SQL the legacy path produces, joined by " AND " (no outer parens at the top level when there's a single combined fragment) — but callers that need byte-identical behaviour with ApplyFilters should use ApplyFilterGroup, which routes the flat-AND case straight through ApplyFilters.

func BuildFilterGroupWhere added in v0.8.151

func BuildFilterGroupWhere(g FilterGroup) (string, []any)

BuildFilterGroupWhere is the grouped-AND/OR companion to BuildFilterWhere (v0.8.x gap-2). Returns the `WHERE …` fragment + args for a FilterGroup, or ("", nil) when the group contributes no compilable terms. A flat-AND group matches BuildFilterWhere's output (each leaf a separate ` AND ` conjunct) via the same ApplyFilterGroup→ApplyFilters delegation the repo layer uses.

func BuildFilterWhere added in v0.5.261

func BuildFilterWhere(filters []FilterExpr) (string, []any)

BuildFilterWhere returns the SQL `WHERE ...` fragment + the positional args for a given FilterExpr[] (v0.5.261). Public adapter so packages outside chstore (currently internal/api's context-aware attribute-keys handler) can build filtered queries without depending on the internal whereClause type or re-implementing ApplyFilters' translation logic.

Returns ("", nil) for an empty filter set — caller can safely concat the result onto a base SQL string without checking.

func CollapseLatencyHistogram added in v0.8.360

func CollapseLatencyHistogram(hm *LatencyHeatmap) (bins []float64, counts []uint64)

CollapseLatencyHistogram sums a 2-D (time × duration) heatmap's counts across the time axis into a 1-D latency distribution over the same log-scale duration bins. Pure — table-tested (endpoints_detail_test.go, v0.8.360). Bin j's count is Σ over all time buckets of Counts[i][j]; bins keep the heatmap's upper-bound- in-ms labelling so the drawer's axis matches the heatmap's Y axis exactly. Sampling extrapolation already happened in the heatmap read (counts arrive pre-multiplied), so this is a plain sum.

func DBStmtHash added in v0.8.375

func DBStmtHash(stmt string) uint64

DBStmtHash returns the persistent statement identity: xxHash64 of the normalized statement, 0 for "no statement" — the exact value the spans.db_stmt_hash MATERIALIZED column stores for the same input (parity contract above). Read paths use it to key raw-scan rows with the same identity the MV rows carry (GetSlowQueriesGlobal), and D2 will use it to resolve a catalog row back to raw spans.

func DeriveExecStatus added in v0.6.73

func DeriveExecStatus(states []StepState) string

DeriveExecStatus computes the execution status from its step states: any failed step ⇒ failed; all steps terminal (completed|skipped) ⇒ completed; otherwise running. Cancelled is set explicitly, never derived. Pure — unit-tested. Exported: the API runner composes it.

func DisplaySpanName

func DisplaySpanName(s *SpanRow) string

DisplaySpanName mirrors the frontend's displaySpanName helper so the multi-trace aggregator on the server bucket spans the same way the trace-detail UI labels them. Generic gRPC names ("grpc", "grpc command", bare rpc.method) and bare HTTP verbs get enriched from peer / route attributes; everything else passes through unchanged.

Keep this in sync with frontend/src/lib/utils.ts:displaySpanName.

func EncodeLogsCursor added in v0.7.77

func EncodeLogsCursor(timeNs int64, rowKey uint64, ascending bool) string

EncodeLogsCursor renders a (timeNs, rowKey) position as the opaque base64 token the API layer round-trips. Kept as a pure function so the roundtrip is unit-testable (CLAUDE.md #11).

func EndpointSplitDims added in v0.8.360

func EndpointSplitDims() []string

EndpointSplitDims returns the whitelisted split-by ids, sorted — for the handler's 400 message and to keep the frontend's select in lockstep (pinned by endpoints_detail_test.go).

func ExemplarRowApproxBytes added in v0.8.355

func ExemplarRowApproxBytes(r *ExemplarRow) int

ExemplarRowApproxBytes estimates one buffered ExemplarRow's in-memory footprint; 32 bytes per FilteredAttrs entry covers map bucket overhead.

func FingerprintAnomaly

func FingerprintAnomaly(kind, pattern, service string) string

FingerprintAnomaly stitches the same (kind, pattern, service) detections into one event row across detector ticks. Stable across process restarts — sha1 is deterministic.

func FingerprintException

func FingerprintException(exType, exMessage, service, stacktrace string) string

FingerprintException computes a stable identifier for "the same exception" across many occurrences. Strategy mirrors Sentry/Honeybadger:

  1. If a stacktrace is available, hash the top 5 frame identifiers (class.method, line numbers stripped) — code path is the most stable signal even when messages contain dynamic IDs.
  2. Otherwise, normalize the message (digits / hex / UUIDs replaced with placeholders) so "order 12345 not found" and "order 67890 not found" collapse into one group.

Service is always part of the hash — same exception in two services stays in two distinct inbox rows so different teams can triage them.

func HashAPIToken added in v0.8.444

func HashAPIToken(plain string) string

HashAPIToken — düz token → saklanan hash (sha256 hex).

func IsFatalExceptionType added in v0.9.609

func IsFatalExceptionType(exType string) bool

IsFatalExceptionType — bu exception tipi altyapı-ölümcül mü? SAF.

func IsMaintenanceActive added in v0.5.26

func IsMaintenanceActive(windows []MaintenanceWindow, service, severity string, t time.Time) bool

IsMaintenanceActive reports whether any non-disabled window matches (service, severity, now). Used by the evaluator + anomaly detector to skip notification fan-out without blocking the problem-state machine (problems still open + resolve as usual; only the notify is suppressed).

The list is small (typically <100 entries even on busy stacks) so we scan in Go rather than a per-call CH query. Caller is expected to refresh the list periodically — the evaluator does this once per tick.

func JobServiceRegex added in v0.9.665

func JobServiceRegex(service string) string

JobServiceRegex — bir servis adını `job` etiketi desenine çevirir.

`^(.*/)?<servis>$` — hem "ns/servis" hem eksiz "servis" eşleşir. İkinci hâli bilinçli: her kurulum job'a namespace önekini koymuyor ve önek zorunlu tutulursa o kurulumlarda sessizce boş sonuç dönerdi.

SON BÖLÜM TAM eşleşmeli. `.*servis$` deseydik "cm-put-service" ararken "legacy-cm-put-service" de eşleşirdi — yanlış servisin trafiği doğru servisin grafiğine karışır ve bunu kimse fark etmez.

QuoteMeta ŞART: servis adları "." ve "-" taşıyor. Kaçışsız "." herhangi bir karaktere uyar, yani "cm-put-service" deseni "cmXput-service"i de eşlerdi.

func LogApproxBytes added in v0.8.355

func LogApproxBytes(l *Log) int

LogApproxBytes estimates one buffered Log's in-memory footprint. Body dominates for the fat-log fleets this budget exists for.

func MVCoveredSeconds added in v0.8.352

func MVCoveredSeconds(now time.Time, window time.Duration) float64

MVCoveredSeconds is the real span the aligned MV read covers — always ≥ the nominal window (window + up-to-299s drift).

func MVWindowStart added in v0.8.352

func MVWindowStart(now time.Time, window time.Duration) time.Time

MVWindowStart aligns the window cutoff DOWN to the 5m MV bucket grid (v0.8.315). The MV filter runs on time_bucket — the bucket START — so an unaligned `now-window` cutoff excluded the bucket containing it and a "5-minute" rule read as little as ~1 minute of data (the still-filling bucket only). Down-alignment over-covers by <1 bucket instead; count/rate metrics normalize back via ScaleToWindow/MVCoveredSeconds. time.Truncate aligns on the UTC epoch — the same grid as ClickHouse's toStartOfInterval on the UTC-typed column.

func MetricNameProbeTokens added in v0.9.665

func MetricNameProbeTokens(name string) []string

MetricNameProbeTokens — bir metrik adından arama için AYIRT EDİCİ parçalar çıkarır.

NEDEN GEREKLİ: operatörün Grafana'sı PROMETHEUS'tan okuyor, Coremetry ise OTLP alıyor. Aynı ölçüm iki tarafta farklı adlanıyor olabilir — `http_server_request_duration_seconds_count` (Prometheus) ile `http.server.request.duration` (OTel semconv) aynı şeyin iki yazımı. Ad bulunamadığında "bunu mu demek istediniz" diyebilmek için adı parçalara ayırıp katalogda aramak gerekiyor.

SAF (tablo testli). En çok iki parça döndürüyor: her parça ayrı bir katalog sorgusu demek ve ikiden fazlası öneriyi gürültüye çeviriyor.

func MetricPointApproxBytes added in v0.8.355

func MetricPointApproxBytes(p *MetricPoint) int

MetricPointApproxBytes estimates one buffered MetricPoint's in-memory footprint; histogram bucket slices count 8 bytes per element (float64 bounds + uint64 counts → 16/bucket combined).

func NewAPITokenPlaintext added in v0.8.444

func NewAPITokenPlaintext() (string, error)

NewAPITokenPlaintext — cmk_ + 32 bayt hex rastgele.

func NewSnapshotToken

func NewSnapshotToken() string

NewSnapshotToken generates a 16-byte URL-safe hex token. Long enough that random guessing is computationally infeasible — same shape we use for OIDC nonces and heartbeat tokens.

func NormalizeDBStatement added in v0.8.375

func NormalizeDBStatement(stmt string) string

NormalizeDBStatement returns the canonical literal-normalized form of a db.statement: single-quoted string literals and standalone numeric literals become '?'. It is the Go twin of the CH normalization inside dbStmtHashExpr (see the parity contract at the top of this file) — a single O(n) pass, no regex, no allocations beyond the output buffer — replicating these re2 semantics:

  • '[^']*' — a quote pairs with the NEXT quote ("'a”b'" is two adjacent literals → "??"; SQL's doubled-quote escaping is deliberately not special-cased, matching the read-time regex). An unterminated quote matches nothing: the quote stays and literals inside the tail keep normalizing ("= 'abc AND b = 5" → "= 'abc AND b = ?").
  • \b[0-9]+(\.[0-9]+){0,1}\b — numbers replace only between word boundaries ("col1", "0x1F" stay intact), one optional decimal point ("3.14" → "?", "1.2.3" → "?.?"), with re2's leftmost-first fallback when the fraction breaks the trailing boundary ("1.5abc" → "?.5abc").

Boundary checks run against the ORIGINAL bytes; that is equivalent to the CH two-pass form because a replaced literal's neighborhood chars (' and ?) are both non-word — TestNormalizeDBStatementOracle proves the equivalence against the real RE2 engine over the pinned corpus plus generated inputs.

Input is capped at dbStmtNormalizeCap bytes (byte-truncated, same as CH substring on String — both operate on bytes, so a mid-rune cut is still deterministic and hash-consistent).

func OpenProblemKey added in v0.8.520

func OpenProblemKey(ruleID, service string) string

FindOpenProblem returns the latest unresolved problem for (rule, service). "Unresolved" covers both `open` (default pageable state) and `acknowledged` (operator saw it, muted notifications, problem still in flight) — so the v0.5.83 bulk-ack flow doesn't accidentally make the evaluator open a duplicate Problem row on the next tick. OpenProblemKey — OpenProblemsSnapshot map anahtarı. Dışa açık: evaluator aynı anahtarla lookup yapar (tablo-testli).

func ProblemScanLimit added in v0.9.576

func ProblemScanLimit(pageLimit int, narrowed bool) int

ProblemScanLimit — daraltma varsa taramayı 5× açar, tavanla kırpar. Saf, tablo-testli.

func ResetSchema

func ResetSchema(ctx context.Context, cfg config.CHConfig) error

ResetSchema drops the configured ClickHouse database in its entirety so the next chstore.New() boot rebuilds it from the migration sequence. Designed for the helm pre-install / pre-upgrade hook in destructive-reset deployments — operators who want "deploy fresh, even though I'm pointing at an existing external CH" get a clean slate without manually running DROP statements.

Cluster-aware: when cfg.ClusterName is set, the DROP includes ON CLUSTER so every replica drops in lock-step. The SYNC modifier waits for the server-side detach to complete so the follow-up CREATE DATABASE in chstore.New() doesn't race a still-pending background drop.

Idempotent — `IF EXISTS` means re-running on an already- dropped namespace is a no-op, not a failure. That matters for helm hooks that may fire on every upgrade.

SAFETY: this deletes EVERY table the app has ever created, including audit logs, dashboards, anomaly history, retention overrides — basically the whole product state. The wrapper flag in helm makes this opt-in and warns loudly. Do not call this from any normal startup path.

func RunQuery added in v0.8.198

func RunQuery(ctx context.Context, cfg config.CHConfig, sql string, w io.Writer) error

RunQuery connects to the CONFIGURED ClickHouse (the same hosts / auth / database the server uses — NO schema migration, unlike New) and writes the result of `sql` as tab-separated rows (a header line + one line per row) to w.

It backs the `coremetry ch "<sql>"` debug subcommand so an operator can query the (often external) ClickHouse from inside the pod without bundling a 460 MB clickhouse-client and without re-entering the host list + credentials — the connection is read straight from the running config (config.yaml + COREMETRY_CH_* env).

func ScaleToWindow added in v0.8.352

func ScaleToWindow(n, windowSec, coveredSec float64) float64

ScaleToWindow normalizes an absolute count observed over `coveredSec` seconds to the nominal window, so thresholds keep their configured meaning ("50 errors in 5 min" stays a 5-minute quantity even though the aligned read spans up to 5m+299s).

func SeriesRowsCapped added in v0.9.458

func SeriesRowsCapped(series []SpanMetricSeries) bool

SeriesRowsCapped — sonuç kümesi satır tavanına çarptı mı? Toplam nokta sayısı == tavan, LIMIT'in ısırdığının işaretidir (tam-50000'lik meşru sonuç da işaretlenir — zararsız yön: "eksik olabilir" der, asla eksiği tam gibi göstermez; inbox len==cap sözleşmesinin aynısı). Saf — tablo-testli.

func ServiceFromJobLabel added in v0.9.665

func ServiceFromJobLabel(job string) string

ServiceFromJobLabel — `job` değerinden servis adı: son "/" sonrası.

"/" yoksa değerin kendisi servis adıdır (öneksiz kurulum). Sondaki "/" bir servis adı üretmez — boş dönerse çağıran atlamalı.

func ShapeSignatureKey added in v0.5.264

func ShapeSignatureKey(sig []string) string

shapeSignatureString joins a shape's signature into a single string for cache keys. Used by the API layer.

func SpanApproxBytes added in v0.8.355

func SpanApproxBytes(s *Span) int

SpanApproxBytes estimates one buffered Span's in-memory footprint.

func SpanLinkRowApproxBytes added in v0.8.355

func SpanLinkRowApproxBytes(r *SpanLinkRow) int

SpanLinkRowApproxBytes estimates one buffered SpanLinkRow's in-memory footprint.

func TransportFilter added in v0.8.352

func TransportFilter(metric string) (where, numerator string, ok bool)

TransportFilter returns, for a transport-scoped alert metric (http_* / db_* / rpc_* / mq_publish_* / mq_consume_*):

  • where: denominator population predicate (WHERE narrows the span set we're measuring against)
  • numerator: numerator predicate for *_rate metrics (counts the "bad" rows within the population). Unused for latency/count metrics.

All fragments are literal SQL — no user input — so they're safe to concatenate. Moved from internal/evaluator in v0.8.352; the evaluator's sub-5m per-service path delegates here so the mapping stays single-sourced.

func TransportOp added in v0.8.352

func TransportOp(metric string) string

TransportOp pulls the aggregate suffix off a transport metric:

http_5xx_rate          → error_rate (5xx-narrowed by TransportFilter)
http_p99_ms            → p99_ms
db_error_rate          → error_rate
mq_publish_error_rate  → error_rate

func UseSummaryMV added in v0.8.352

func UseSummaryMV(window time.Duration) bool

UseSummaryMV decides whether an evaluator window can ride the 5-minute MVs instead of scanning raw spans (v0.6.12). Sub-5min windows fall back to per-service raw spans because the MV's granularity can't reconstruct them faithfully. Moved from internal/evaluator in v0.8.352 so the batched reads and the evaluator share one boundary.

func ValidRunbookStepKind added in v0.6.72

func ValidRunbookStepKind(kind string) bool

ValidRunbookStepKind reports whether kind is a known step kind. Pure — unit-tested in runbooks_test.go (v0.7.0).

func ValidateHaving added in v0.8.453

func ValidateHaving(hs []HavingExpr) error

ValidateHaving — HTTP katmanının 400 kapısı; derleyip atar.

Types

type AICall added in v0.5.164

type AICall struct {
	ID             string `json:"id"`
	CreatedAt      int64  `json:"createdAt"`            // unix ns
	Surface        string `json:"surface"`              // explain-span, explain-slo, …
	ExchangeID     string `json:"exchangeId,omitempty"` // v0.8.399 — feedback correlation key (” pre-v0.8.399 / non-chat)
	Provider       string `json:"provider"`             // openai | anthropic | github
	Model          string `json:"model"`
	BaseURL        string `json:"baseUrl,omitempty"`
	DurationMs     uint32 `json:"durationMs"`
	InputTokens    uint32 `json:"inputTokens"`
	OutputTokens   uint32 `json:"outputTokens"`
	Status         string `json:"status"` // ok | error
	ErrorMsg       string `json:"errorMsg,omitempty"`
	PromptChars    uint32 `json:"promptChars"`
	ResponseChars  uint32 `json:"responseChars"`
	UserID         string `json:"userId,omitempty"`
	UserEmail      string `json:"userEmail,omitempty"`
	PromptSample   string `json:"promptSample,omitempty"`
	ResponseSample string `json:"responseSample,omitempty"`
}

AICall is one Copilot LLM round-trip. Recorded by copilot.Service on every Explain() call regardless of success — error rows show up in the /ai page so the operator can see "this Ollama endpoint is timing out half the time" instantly. Prompt/response samples are size-capped at the record path (see SamplePromptCap) so a runaway prompt can't blow up the row size.

type AICallsTimePoint added in v0.5.164

type AICallsTimePoint struct {
	Time         int64   `json:"time"` // unix ns, bucket start
	Calls        uint64  `json:"calls"`
	Errors       uint64  `json:"errors"`
	AvgMs        float64 `json:"avgMs"`
	InputTokens  uint64  `json:"inputTokens"`
	OutputTokens uint64  `json:"outputTokens"`
}

AICallsTimeseries is one bucket of the volume-by-time chart. Granularity is determined client-side based on window length; for v1 we default to 5-minute buckets and let the renderer re-bin if it wants coarser resolution.

type AIFeedback added in v0.8.399

type AIFeedback struct {
	ExchangeID string `json:"exchangeId"`
	Surface    string `json:"surface"`             // resolved server-side from the ai_calls row
	Verdict    int8   `json:"verdict"`             // 1 = thumbs up, -1 = thumbs down
	UserEmail  string `json:"userEmail,omitempty"` // who rated (full fidelity, house policy)
	CreatedAt  int64  `json:"createdAt"`           // unix ns
}

AIFeedback is one operator verdict on one AI answer.

type AIProviderStat added in v0.5.164

type AIProviderStat struct {
	Provider     string `json:"provider"`
	Model        string `json:"model"`
	Calls        uint64 `json:"calls"`
	InputTokens  uint64 `json:"inputTokens"`
	OutputTokens uint64 `json:"outputTokens"`
}

type AIStats added in v0.5.164

type AIStats struct {
	TotalCalls    uint64           `json:"totalCalls"`
	OkCalls       uint64           `json:"okCalls"`
	ErrorCalls    uint64           `json:"errorCalls"`
	ErrorRate     float64          `json:"errorRate"` // 0..1
	AvgDurationMs float64          `json:"avgDurationMs"`
	P50DurationMs float64          `json:"p50DurationMs"`
	P99DurationMs float64          `json:"p99DurationMs"`
	InputTokens   uint64           `json:"inputTokens"`
	OutputTokens  uint64           `json:"outputTokens"`
	DistinctUsers uint64           `json:"distinctUsers"`
	BySurface     []AISurfaceStat  `json:"bySurface"`
	ByProvider    []AIProviderStat `json:"byProvider"`
}

AIStats is the aggregate summary surfaced on the /ai overview cards. Computed in one ClickHouse query over the requested window so the page loads with KPIs visible before the table data streams in.

type AISurfaceStat added in v0.5.164

type AISurfaceStat struct {
	Surface   string  `json:"surface"`
	Calls     uint64  `json:"calls"`
	ErrorRate float64 `json:"errorRate"`
	AvgMs     float64 `json:"avgMs"`
	// v0.8.399 — operator thumbs up/down quality signal, merged in
	// from ai_feedback (latest verdict per exchange wins). Zero
	// FeedbackCount = no ratings in the window; ThumbsUpRate is only
	// meaningful when FeedbackCount > 0 (omitempty on both keeps the
	// old payload shape for unrated surfaces).
	FeedbackCount uint64  `json:"feedbackCount,omitempty"`
	ThumbsUpRate  float64 `json:"thumbsUpRate,omitempty"` // 0..1 over rated exchanges
}

type APIToken added in v0.8.444

type APIToken struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Role      string `json:"role"`   // admin | editor | viewer
	Prefix    string `json:"prefix"` // cmk_ab12… (ilk 10 kr) — listede tanıma için
	TokenHash string `json:"-"`
	CreatedBy string `json:"createdBy"`
	CreatedAt int64  `json:"createdAt"` // unix ns
	Revoked   bool   `json:"revoked"`
}

type AggSpanNode

type AggSpanNode struct {
	Service    string  `json:"service"`
	Operation  string  `json:"operation"`
	Kind       string  `json:"kind,omitempty"`
	Count      int     `json:"count"`
	AvgMs      float64 `json:"avgMs"`
	MaxMs      float64 `json:"maxMs"`
	ErrorCount int     `json:"errorCount"`
	// AvgStartMs is the mean offset (in ms) from the trace's
	// earliest span — drives the bar's left edge in the renderer
	// so the visual chronology survives aggregation.
	AvgStartMs float64        `json:"avgStartMs"`
	Children   []*AggSpanNode `json:"children,omitempty"`
}

AggSpanNode is one position in the multi-trace aggregated tree returned by AggregateServiceStructure. A node identifies a unique `(parent path → service → operation)` triple — every span across the sampled traces with that exact ancestry contributes to the counts. Keeps the tree shape (parent / child relationship intact) while collapsing literal repetitions into a single visible row.

JSON shape mirrors what the SPA's AggregatedStructure component expects.

type AggregateFilter

type AggregateFilter struct {
	// GroupBy picks the group dimension. Valid: "operation",
	// "service", "kind", "status", "http_method", "http_route",
	// "http_status", "host", "deploy_env", "scope". Anything else
	// (or empty) → "operation".
	GroupBy string
	// GroupAttr lets the operator group by a custom attribute key
	// (e.g. "user.id", "tenant", "order.id"). Sanitised by the
	// HTTP layer to dot/dash/underscore characters only. When set,
	// it overrides GroupBy.
	GroupAttr string
	Service   string
	Search    string
	From, To  time.Time
	HasError  bool
	MinMs     float64
	MaxMs     float64
	// Env — spans.deploy_env narrowing (v0.8.383, ?env=). First-class
	// for the same reason as TraceFilter.Env: it must survive the
	// FilterRoot-supersedes-Filters rule and the FilterGroup depth cap.
	// Non-empty disqualifies the trace_summary MV fast-path below.
	Env     string
	Filters []FilterExpr
	// FilterRoot — grouped AND/OR builder (v0.8.x gap-2). Supersedes Filters
	// when non-nil; flat-AND is byte-identical to the legacy path, OR /
	// nested disqualifies the trace_summary MV fast-path.
	FilterRoot *FilterGroup
	Sort       string // "count"|"perMin"|"errorRate"|"avg"|"p50"|"p95"|"p99"|"max"|"name"
	Order      string // "asc"|"desc"
	Limit      int
	// Having — v0.8.453 (B2-c): genel post-aggregate koşullar
	// ("errorRate > 1 AND p95 > 500"). Yalnız compileHaving'in
	// whitelist'inden geçer; MV fast-path'i diskalifiye ETMEZ (dış
	// SELECT'in kolon takma adları iki yolda da aynı) — performans
	// operatör şartı.
	Having []HavingExpr
}

GetTraceAggregate buckets traces by an attribute (operation/service) and returns RED-style stats per bucket. Each bucket = traces with the same root operation (or service). Filters mirror GetTraces, but sorting/limit applies to bucket aggregates, not individual traces.

type AggregateRow

type AggregateRow struct {
	GroupKey   string `json:"groupKey"`
	GroupExtra string `json:"groupExtra,omitempty"` // e.g. service name when grouping by operation
	TraceCount uint64 `json:"traceCount"`
	// WithRawAvailable — count of TraceCount trace_ids that still
	// have raw spans in the window. trace_summary_5m holds 90 days
	// while raw spans hold 30 (or whatever retention.spans is set
	// to), so older aggregate rows may not be drillable. The UI
	// renders a chip when this is lower than TraceCount so the
	// operator knows clicking won't always reach detail. The raw-
	// spans aggregate path (non-fast-path) sets this == TraceCount
	// since every counted trace by definition has raw data. v0.6.39.
	WithRawAvailable uint64 `json:"withRawAvailable"`
	// PerMin is traces per minute over the requested window —
	// Uptrace-style perMin(count()) so the operator can compare
	// throughput across windows of different lengths.
	PerMin     float64 `json:"perMin"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"`
	AvgMs      float64 `json:"avgMs"`
	P50Ms      float64 `json:"p50Ms"`
	P95Ms      float64 `json:"p95Ms"`
	P99Ms      float64 `json:"p99Ms"`
	MaxMs      float64 `json:"maxMs"`
	LastSeen   int64   `json:"lastSeen"` // unix nanos
}

AggregateRow is one bucket in the trace aggregate view (group-by operation or service). Counts are number of distinct traces (not spans).

type AlertRule

type AlertRule struct {
	ID         string  `json:"id"`
	Name       string  `json:"name"`
	Service    string  `json:"service"`    // empty = applies to all services
	Metric     string  `json:"metric"`     // error_rate | p99_ms | request_rate | …
	Comparator string  `json:"comparator"` // > | >= | < | <=
	Threshold  float64 `json:"threshold"`
	WindowSec  uint32  `json:"windowSec"` // sliding window size
	Severity   string  `json:"severity"`  // info | warning | critical
	Enabled    bool    `json:"enabled"`
	BuiltIn    bool    `json:"builtIn"`
	// ForSec is the sustained-breach gate (v0.5.126): the
	// threshold must stay breached for this long before a
	// problem opens. Prometheus-style `for:` — kills single-
	// sample spike noise without changing the threshold. 0 =
	// open immediately (current behaviour).
	ForSec uint32 `json:"forSec"`
	// MinSamples is the sample-count floor (v0.5.128). When > 0
	// the evaluator only fires if the window saw at least this
	// many requests — kills low-traffic flapping on rate /
	// percentile metrics (a 1-request window with 1 error = 100%
	// error_rate, which is meaningless). 0 = no floor.
	MinSamples uint32 `json:"minSamples"`
	// CooldownSec is the post-resolution silence window (v0.5.129).
	// After a problem auto-resolves the evaluator suppresses re-
	// opens on the same (rule, service) for this many seconds —
	// kills threshold-jitter flapping where the value oscillates
	// at the boundary. 0 = re-open immediately.
	CooldownSec uint32 `json:"cooldownSec"`
	// RunbookURL — optional link an oncall reaches when the
	// rule fires. Surfaces on Problem detail + alert
	// notifications. Empty = no runbook configured.
	RunbookURL string `json:"runbookUrl,omitempty"`
	// LogQuery (v0.5.242) — KQL/Lucene clause that defines a
	// "saved-search alert". When set, the evaluator counts log
	// matches via the logstore in the rule's window and compares
	// to Threshold via Comparator instead of running the
	// span-derived Metric path. Service/Metric are still set
	// (service="" + metric="log_query" by convention) so the
	// rules table renders consistently. The OTel-canonical
	// shorthand (level:error, pod:my-pod) is rewritten by the
	// ES backend's expandShorthand so the same query works
	// against any shipping pipeline.
	LogQuery string `json:"logQuery,omitempty"`
	// WatcherJSON (v0.9.x) — the verbatim ES Watcher definition
	// (PUT _watcher/watch body) this rule was imported from. When
	// set, the evaluator runs the watcher path (internal/watcher
	// parse → logstore.RawSearch count → compare) instead of the
	// span-metric or log-query paths. Stored raw so the definition
	// round-trips byte-identical regardless of what the parser
	// models. Empty for every native rule. Metric="watcher" by
	// convention so the rules table renders consistently.
	WatcherJSON string `json:"watcherJson,omitempty"`
	CreatedAt   int64  `json:"createdAt"` // unix nanoseconds
}

AlertRule defines an evaluator condition. metric is one of:

error_rate    — % of error spans  (operand: percentage)
p99_ms / p95_ms / avg_ms / p50_ms — latency in ms
request_rate  — spans per second  (typically used with `<` to detect drops)
error_count   — number of error spans (absolute)

type Announcement added in v0.8.486

type Announcement struct {
	Enabled   bool   `json:"enabled"`
	Text      string `json:"text,omitempty"`
	LinkURL   string `json:"linkUrl,omitempty"`
	LinkLabel string `json:"linkLabel,omitempty"`
	// Tone: "info" (nötr) | "warn" (amber, önemli duyuru).
	Tone        string `json:"tone,omitempty"`
	UpdatedAtNs int64  `json:"updatedAtNs,omitempty"`
}

Announcement — v0.8.486 (operatör isteği): admin'in Settings'ten girdiği, tüm kullanıcılara sayfa üstünde gösterilen duyuru şeridi ("sorularınız için: …@… · wiki: http://…"). Kaldırılan What-changed şeridinin (v0.8.481) yerine operatör-kontrollü içerik. Branding deseninin birebir kardeşi: system_settings altında "announcement" anahtarında tek JSON blob.

UpdatedAtNs revizyon damgasıdır: istemci kapatma tercihini bu damgayla saklar — aynı duyuru bir daha çıkmaz, metin güncellenince yeniden çıkar (GitLab broadcast-message davranışı).

type AnomalyEvent

type AnomalyEvent struct {
	ID           string  `json:"id"`
	Kind         string  `json:"kind"`    // "log_pattern" | "trace_op"
	Pattern      string  `json:"pattern"` // pattern name (logs) or operation name (trace ops)
	Service      string  `json:"service"`
	StartedAt    int64   `json:"startedAt"`    // unix ns — first observation
	LastSeen     int64   `json:"lastSeen"`     // unix ns — most recent observation
	PeakRatio    float64 `json:"peakRatio"`    // worst ratio seen during the event
	CurrentRatio float64 `json:"currentRatio"` // ratio at last_seen
	CurrentCount uint64  `json:"currentCount"`
	Sample       string  `json:"sample"`
	// Status is computed in the query, not stored. "active" while
	// last_seen >= now() - 10m, otherwise "cleared".
	Status string `json:"status"`
	// Clusters — k8s/openshift cluster names the anomaly's
	// service was active in around the time of detection.
	// Enriched at read time (no schema migration); empty for
	// services without cluster attrs.
	Clusters []string `json:"clusters,omitempty"`
	// RecentDeploy — v0.5.286. Most recent deploy of this
	// service observed within `lookback` (default 30m) before
	// StartedAt. Populated at READ time by
	// EnrichAnomaliesWithDeploys so the /anomalies page can
	// show a "deployed v1.2.3 · 4m before" chip — collapses
	// the "did this break because of a deploy?" question into
	// a single glance.
	RecentDeploy *RecentDeploy `json:"recentDeploy,omitempty"`
	// RootCause — compact top-suspect summary of the persisted
	// root-cause hypothesis the worker synthesized for this anomaly
	// (rc #3 of the anomaly → root-cause feature). Attached at READ
	// time by the /anomalies events handler via a single batch
	// GetHypotheses join (NO per-row fetch); nil when the worker
	// hasn't synthesized a hypothesis for this anchor yet. The
	// RootCauseRibbon renders the collapsed chip from this; the
	// expand fetches the full /anomalies/{id}/rootcause fan-out.
	RootCause *RootCauseSummary `json:"rootCause,omitempty"`
}

AnomalyEvent is one continuously-occurring anomaly tracked over time. Same fingerprint (kind, pattern, service) keeps re-using the row — last_seen advances on every detection, started_at and peak_ratio capture history. An event is "active" iff last_seen is recent; the "cleared" status is derived in the query layer from last_seen freshness so we don't need a separate sweep.

type AnomalyPromotionConfig added in v0.5.70

type AnomalyPromotionConfig struct {
	// Enabled — master switch. False means the sweep runs
	// but no promotion happens; anomalies stay on
	// /anomalies for manual triage. Operators with a very
	// noisy detector tune this off until they've calibrated
	// the detector itself.
	Enabled bool `json:"enabled"`
	// MinPeakRatio — baseline-relative ratio gate. 5× means
	// the pattern is occurring at least 5× more than its
	// rolling baseline before becoming pageable.
	MinPeakRatio float64 `json:"minPeakRatio"`
	// MinSustainedSec — how long since started_at the
	// pattern must have been observed before it gets
	// promoted. Filters out one-tick flares.
	MinSustainedSec int `json:"minSustainedSec"`
	// MinCount — absolute volume floor. A 100× ratio on 2
	// occurrences is meaningless.
	MinCount uint64 `json:"minCount"`
	// CriticalPeakRatio — the ratio at or above which a promoted
	// anomaly opens at `critical` instead of `warning`
	// (v0.9.247). This was a hard-coded 20 in the evaluator, which
	// made the severity split invisible AND created a trap: an
	// operator tightening MinPeakRatio to 20+ to get FEWER pages
	// silently made every surviving promotion a critical one, so
	// the change was louder, not quieter. Now the two are
	// independent — raise MinPeakRatio to cut volume, raise
	// CriticalPeakRatio to cut how much of it pages.
	//
	// Must be >= MinPeakRatio to mean anything (enforced at the
	// API boundary); equal means "everything promoted is
	// critical", which is a legitimate choice for a small
	// hand-curated set.
	CriticalPeakRatio float64 `json:"criticalPeakRatio"`

	// Seasonal* — consumed by the anomaly DETECTOR's seasonal
	// baseline (internal/anomaly), NOT the promotion sweep. They
	// ride this blob so the anomaly feature keeps ONE operator
	// settings surface instead of a second key/route (v0.8.250).
	// Zero/absent → the detector's compile-time defaults via
	// GetAnomalyPromotion's patch below, so an unedited install
	// keeps the shipped behaviour. Frontend binding is a separate
	// follow-up; the backend persistence is wired now.
	//
	// SeasonalDays — days of same-slot history the baseline learns
	// from (default 14).
	SeasonalDays int `json:"seasonalDays"`
	// SeasonalMinSamples — min same-slot samples before the
	// seasonal baseline is trusted over the flat 24h window
	// (default 4).
	SeasonalMinSamples int `json:"seasonalMinSamples"`
	// SeasonalNeighborBuckets — ± same-class 5-min neighbour
	// buckets folded into the baseline to beat sample scarcity on
	// thin off-peak slots (default 3 ⇒ ±15 min).
	SeasonalNeighborBuckets int `json:"seasonalNeighborBuckets"`
}

AnomalyPromotionConfig drives the evaluator's promoteStrongAnomalies sweep (v0.5.59). Was hard-coded constants in the binary until v0.5.70 — operators couldn't turn it off when the detector got chatty, or tighten the signal floor for a noisy fleet, without a redeploy. Now stored as a JSON blob under system_settings key "anomaly_promotion" and read every sweep through a tiny in-process memo.

Zero-value config is the v0.5.59 default (enabled, peak ratio 5×, 5min sustained, 10 events) — operators who never visit the settings page keep the old behaviour.

func DefaultAnomalyPromotion added in v0.5.70

func DefaultAnomalyPromotion() AnomalyPromotionConfig

Defaults — match the hard-coded constants from v0.5.59 so the upgrade keeps the same behaviour for installs that haven't visited the new settings page.

type AnomalySilence

type AnomalySilence struct {
	ID          string `json:"id"`
	Fingerprint string `json:"fingerprint"` // matches AnomalyEvent.ID
	Kind        string `json:"kind"`        // log_pattern | trace_op
	Pattern     string `json:"pattern"`
	Service     string `json:"service"`
	CreatedBy   string `json:"createdBy"` // user email
	CreatedAt   int64  `json:"createdAt"` // unix ns
	UntilAt     int64  `json:"untilAt"`   // unix ns; <=0 = no expiry
	Reason      string `json:"reason"`
	// Active is filled at query time: true while now < until_at.
	Active bool `json:"active"`
}

AnomalySilence mutes a single (kind, pattern, service) fingerprint until UntilAt. Silenced anomalies still get recorded in anomaly_events (for the history table) but are suppressed in the live sections of /anomalies and skip notification fan-out.

`Fingerprint` matches AnomalyEvent.ID — same hash recipe so no joins are needed at query time.

type AttrKeyRow

type AttrKeyRow struct {
	Key            string `json:"key"`
	DistinctValues uint64 `json:"distinctValues"`
	Occurrences    uint64 `json:"occurrences"`
	// Source labels which CH table the row was sampled from
	// (spans / logs / metric_points). Lets the admin grep the
	// emitting service for the offending label.
	Source string `json:"source"`
}

type AuditEntry

type AuditEntry struct {
	ID         string `json:"id"`
	Time       int64  `json:"time"` // unix ns
	ActorID    string `json:"actorId"`
	ActorEmail string `json:"actorEmail"`
	ActorRole  string `json:"actorRole"`
	Action     string `json:"action"`     // e.g. "alert_rule.update"
	TargetKind string `json:"targetKind"` // e.g. "alert_rule"
	TargetID   string `json:"targetId"`
	IP         string `json:"ip"`
	Details    string `json:"details"` // JSON or freeform
}

AuditEntry is one row in the audit_log table — append-only, representing a state-changing action by a user. Read by the /admin/audit page.

type AuditFilter

type AuditFilter struct {
	SinceNs    int64  // unix ns; 0 = last 24h
	Actor      string // user id OR email substring; empty = all
	Action     string // exact match; empty = all
	TargetKind string // exact match; empty = all
	TargetID   string // exact match on the target's id; empty = all
	Limit      int
}

type BlastRadius added in v0.6.29

type BlastRadius struct {
	Service           string              `json:"service"` // inspected service
	WindowSec         int                 `json:"windowSec"`
	TotalCallers      int                 `json:"totalCallers"`
	CascadingCallers  int                 `json:"cascadingCallers"` // count of callers WITH their own open problem
	TotalRPS          float64             `json:"totalRps"`
	TotalErrorsPerSec float64             `json:"totalErrorsPerSec"`
	Callers           []BlastRadiusCaller `json:"callers"` // sorted by calls desc
}

BlastRadius bundles the per-caller list with a summary so the frontend chip can render "↘ N svcs · M rps" without summing client-side.

type BlastRadiusCaller added in v0.6.29

type BlastRadiusCaller struct {
	Service        string  `json:"service"`
	Calls          uint64  `json:"calls"` // total invocations in window
	Errors         uint64  `json:"errors"`
	RPS            float64 `json:"rps"`            // calls / window seconds
	ErrorRate      float64 `json:"errorRate"`      // percent
	HasOpenProblem bool    `json:"hasOpenProblem"` // cascade indicator
}

BlastRadiusCaller is one upstream service impacted by an issue on the inspected service. RPS is computed over the window; HasOpenProblem flags the cascade case where the caller is ALREADY firing its own alert, suggesting the failure is propagating up the call graph.

type BrandingSettings added in v0.4.80

type BrandingSettings struct {
	AppName           string `json:"appName,omitempty"`
	BrowserTitle      string `json:"browserTitle,omitempty"`
	LoginTitle        string `json:"loginTitle,omitempty"`
	LoginSubtitle     string `json:"loginSubtitle,omitempty"`
	SignInButtonLabel string `json:"signInButtonLabel,omitempty"`
	UsernameLabel     string `json:"usernameLabel,omitempty"`
	FooterText        string `json:"footerText,omitempty"`
	// LogoDataURI is a "data:image/png;base64,..." string. Empty
	// → the UI renders the built-in Telescope mark.
	LogoDataURI string `json:"logoDataUri,omitempty"`
	// PrimaryColor overrides the --accent CSS var when set.
	// Optional; empty keeps the bundled theme.
	PrimaryColor string `json:"primaryColor,omitempty"`
	// Language: "en" (default) or "tr". Drives the i18n catalog
	// the SPA uses to render sidebar labels, page titles, common
	// buttons, login strings, and empty/error states.
	Language string `json:"language,omitempty"`
}

BrandingSettings — admin-customisable strings + logo rendered across the public surface (login + the browser tab title) and the chrome on logged-in pages. Stored in system_settings under the "branding" key as a JSON blob. Empty / zero-value fields fall back to the Coremetry defaults so a fresh install reads as plain "Coremetry" without an admin filling the form.

Logo is a base64 data URI (capped at ~200KB at the API layer) so we don't introduce a separate blob/object-store dependency for what's typically a 30KB PNG. The same rendering path serves the login page (where the SPA isn't authed yet) and the in-app header.

type BubbleUpAttribute

type BubbleUpAttribute struct {
	Key string `json:"key"`
	// Top values by score, descending. Capped at 6 per key.
	Values []BubbleUpValue `json:"values"`
}

type BubbleUpResult

type BubbleUpResult struct {
	// Total spans on each side of the comparison.
	SelectionTotal int64 `json:"selectionTotal"`
	BaselineTotal  int64 `json:"baselineTotal"`
	// Attributes sorted by their top-value score so the
	// most "explanatory" attribute appears first.
	Attributes []BubbleUpAttribute `json:"attributes"`
}

type BubbleUpValue

type BubbleUpValue struct {
	Value          string `json:"value"`
	SelectionCount int64  `json:"selectionCount"`
	BaselineCount  int64  `json:"baselineCount"`
	// Pct = count / total. Frontend renders as bar width.
	SelectionPct float64 `json:"selectionPct"`
	BaselinePct  float64 `json:"baselinePct"`
	// Score = SelectionPct - BaselinePct, in [-1, 1]. Sorted
	// desc; positive values mean over-represented in selection.
	Score float64 `json:"score"`
}

type BurnPoint added in v0.5.150

type BurnPoint struct {
	Time     int64   `json:"time"` // unix ns, bucket start
	Total    uint64  `json:"total"`
	Good     uint64  `json:"good"`
	BurnRate float64 `json:"burnRate"` // >1 = eating budget faster than allowed
}

BurnPoint is one bucket of the per-day burn-rate timeseries surfaced as a sparkline on the /slos overview (v0.5.150). Time is the bucket-start (UTC, day granularity for a 7d view). BurnRate is (1-SLI_bucket)/(1-target) — same definition as SLOStatus.BurnRate, just over the day instead of the SLO's rolling window.

type BusinessSlice added in v0.9.511

type BusinessSlice struct {
	Value  string  `json:"value"` // ham kod (CHANNEL_CODE değeri gibi)
	Calls  uint64  `json:"calls"`
	Errors uint64  `json:"errors"`
	ErrPct float64 `json:"errPct"`
}

BusinessSlice — bir iş boyutu değerinin pencere içindeki ağırlığı.

type CallerRow

type CallerRow struct {
	CallerService  string  `json:"callerService"`
	CallerHost     string  `json:"callerHost"`
	CallerInstance string  `json:"callerInstance"`
	ClientAddress  string  `json:"clientAddress"`
	UserAgent      string  `json:"userAgent"`
	Calls          uint64  `json:"calls"`
	Errors         uint64  `json:"errors"`
	ErrorRate      float64 `json:"errorRate"`
	AvgMs          float64 `json:"avgMs"`
	P50Ms          float64 `json:"p50Ms"`
	P95Ms          float64 `json:"p95Ms"`
	P99Ms          float64 `json:"p99Ms"`
	LastSeenNs     int64   `json:"lastSeenNs"`
}

CallerRow is one row of the inbound-callers backtrace for a service: a unique combination of (caller service × caller pod / instance × client address × user agent) that has invoked the inspected service over the requested window, plus RED stats.

Caller service / host / instance come from the PARENT span's resource attributes via a self-join on (trace_id, parent_id); client.address and user_agent.original are read directly off the receiving (server) span.

type CapacitySample added in v0.7.69

type CapacitySample struct {
	Instance string  // receiver instance identity (instance attr → service.name res key)
	Subkey   string  // e.g. Oracle tablespace name; empty for undimensioned checks
	Usage    float64 // current gauge value
	Limit    float64 // cap gauge value (0 when the check is a raw rate, e.g. evictions)
	// PostGC (v0.9.426, yalnız JVM heap yolu) — jvm.memory.used_after_last_gc
	// penceresi ortalaması; 0 = metrik akmıyor (fallback anlık used).
	// Testere-dişi heap'te GERÇEK baskı sinyali budur.
	PostGC float64
}

CapacitySample is one saturation reading for one (instance, check, subkey). Usage / Limit are the raw gauge pair; the evaluator derives the percentage so the read stays dumb. Subkey distinguishes the dimensioned checks (Oracle tablespace_name) from the undimensioned ones (sessions / processes / connections) where it is empty.

Pct is pre-computed (Usage/Limit*100) ONLY as a convenience for the rate-style checks (Redis eviction) that have no meaningful limit; for the usage/limit pairs the evaluator recomputes from Usage/Limit so a zero/absent Limit is handled in one place.

type CardinalityReport

type CardinalityReport struct {
	Services    []TopRow     `json:"services"` // top services by 24h span count
	Metrics     []TopRow     `json:"metrics"`  // top metrics by 24h point count
	AttrKeys    []AttrKeyRow `json:"attrKeys"` // top attribute keys by cardinality
	Columns     []ColumnRow  `json:"columns"`  // top columns by compressed bytes
	GeneratedAt int64        `json:"generatedAt"`
}

CardinalityReport is the meta-observability "what's eating my CH" view. Surfaces the top emitters across three axes — service, metric name, attribute key — plus per-column storage attribution from system.columns. Powers /admin/cardinality.

Why each section matters:

  • Services: when a single service starts emitting 10x its usual span rate (deploy gone wrong, infinite retry loop) it surfaces here days before the disk fills up.

  • Metrics: a misconfigured high-frequency metric can dominate the metric_points table. This panel makes the offender obvious so the admin knows which counter to drop.

  • Attribute keys: unbounded label values (raw user-id, full URL path with query string, request ID embedded as label) blow up cardinality silently. The "distinct" column flags these immediately.

  • Columns: confirms the actual disk distribution post- compression. ColumnA can be 10x more rows than ColumnB but compress 50x better — only system.columns tells the truth.

type ChangedService added in v0.4.73

type ChangedService struct {
	Service       string  `json:"service"`
	BaselineRate  float64 `json:"baselineRate"` // spans/sec, baseline window
	CurrentRate   float64 `json:"currentRate"`  // spans/sec, current window
	RateDeltaPct  float64 `json:"rateDeltaPct"`
	BaselineErr   float64 `json:"baselineErrorRate"` // 0..1
	CurrentErr    float64 `json:"currentErrorRate"`
	ErrDeltaPct   float64 `json:"errDeltaPct"`
	BaselineP99Ms float64 `json:"baselineP99Ms"`
	CurrentP99Ms  float64 `json:"currentP99Ms"`
	P99DeltaPct   float64 `json:"p99DeltaPct"`
	Score         float64 `json:"score"`
	// Reasons is the human-readable bullet form: each entry is a
	// short sentence the frontend renders verbatim. Saves the UI
	// from re-implementing the formatting logic and keeps the
	// "why did this surface?" answer co-located with the data.
	Reasons []string `json:"reasons"`
}

ChangedService is one row in the "what changed around this time" causal-correlation report. Driven by the operator clicking "Why did this fire?" on a Problem — we walk every service that emitted spans in the surrounding window and surface the ones whose RED metrics swung the most between the baseline window (before the problem started) and the current window (since).

Score is a composite z-score-ish magnitude across the three signals so a single sort surfaces "anything weird" without the operator having to flip between rate/err/latency views. Real SREs read the deltas in the columns rather than the score — the score's job is just to rank.

type ChannelMatchRules

type ChannelMatchRules struct {
	Services     []string `json:"services,omitempty"`
	SRETeams     []string `json:"sreTeams,omitempty"`
	OwnerTeams   []string `json:"ownerTeams,omitempty"`
	Clusters     []string `json:"clusters,omitempty"`
	QuietHours   string   `json:"quietHours,omitempty"`
	QuietHoursTz string   `json:"quietHoursTz,omitempty"`
}

ChannelMatchRules — small predicate set that gates delivery per channel. Each list is "OR within, AND between lists":

  • services = []string of literal service names
  • sreTeams = []string of catalog SRE team names
  • ownerTeams = []string of catalog product owner team names
  • clusters = []string of k8s/openshift cluster names — matches against the problem's enriched cluster list (typically populated by EnrichProblemsWithClusters before the channel fan-out)
  • quietHours = "HH:MM-HH:MM" window during which the channel does NOT fire. Empty = always-on. The window may cross midnight (e.g. "22:00-07:00"); evaluated in QuietHoursTz which defaults to UTC.
  • quietHoursTz = IANA timezone for quietHours (e.g. "Europe/Istanbul"). Empty = UTC.

Common operator patterns this supports:

  • "Pager rota only for prod-eu-west during business hrs": clusters=[prod-eu-west], quietHours="00:00-08:00", quietHoursTz="Europe/Istanbul"
  • "Staging channel — staging cluster only": clusters=[prod-staging]
  • "Weekend on-call inbox": ownerTeams=[payments], quietHours empty

func (ChannelMatchRules) Matches

func (m ChannelMatchRules) Matches(service string, md *ServiceMetadata) bool

Matches retains the pre-v0.5.63 signature so existing callers that only need service / catalog matching keep compiling. New code paths thread MatchInput through MatchesProblem.

func (ChannelMatchRules) MatchesProblem added in v0.5.63

func (m ChannelMatchRules) MatchesProblem(in MatchInput) bool

MatchesProblem evaluates every predicate against a Problem's runtime signals. Empty / zero-value rules mean catch-all (always true); the predicate's job is to PROVE the channel should be silenced, otherwise we fire.

type CheckedSignal added in v0.9.516

type CheckedSignal struct {
	Family  string `json:"family"`
	Found   bool   `json:"found"`
	Detail  string `json:"detail"`
	Records int    `json:"records"`
}

RootCauseHypothesis is the PERSISTED, pre-computed root-cause ranking for one anchor (an AnomalyEvent or a Problem). The worker synthesizes it on a leader-gated tick (correlator.Synthesize over the same bounded evidence the on-demand /rootcause fan-out gathers) and upserts it here, so /anomalies and /problems can render a "Root cause: <suspect> (NN%)" ribbon WITHOUT a per-row fetch (rc #2 of the anomaly → root-cause feature; rc #3 reads it).

This is COMPUTED state, not user-saved state — so a dedicated table is the right call (invariant #5's saved_views catch-all is for OPERATOR-created state like presets/views; anomaly_events is the precedent for derived, continuously-refreshed state with its own access pattern). ReplacingMergeTree keyed on the anchor so the latest synthesis per anchor wins; the worker re-upserts as the picture changes, FINAL reads collapse to the newest row.

Candidates is stored as a JSON String column (json.Marshal on write, Unmarshal on read) — deliberately NOT a nested/Array-of-Tuple schema. The shape is small, read whole, and never queried by sub-field, so a JSON blob keeps the schema flat and the ScoredCause shape (owned by the correlator) from leaking into a CH column layout that would have to track it. CheckedSignal — P1 soruşturmasının denetim izi satırı: NEYE bakıldı, bulundu mu, ne bulundu (v0.9.516'da kalıcı hale geldi).

İz süs değil: modelin ürettiği anlatıma güvenmenin tek yolu hangi sinyallerin GERÇEKTEN okunduğunun görünür olması. Kalıcı olunca sonradan da sorulabiliyor — "kaç P1'de gerçekten pod/log okundu".

type ColumnRow

type ColumnRow struct {
	Table             string  `json:"table"`
	Column            string  `json:"column"`
	CompressedBytes   uint64  `json:"compressedBytes"`
	UncompressedBytes uint64  `json:"uncompressedBytes"`
	CompressionRatio  float64 `json:"compressionRatio"`
}

type CorrelationSample added in v0.9.580

type CorrelationSample struct {
	// Key — attribute adı (request_id, correlation_id, x-request-id…).
	// Filoda hangi yazımın kullanıldığını KEŞFEDİYORUZ, dayatmıyoruz.
	Key string `json:"key"`
	// Values — birkaç gerçek örnek, tekrarsız.
	Values []string `json:"values"`
}

CorrelationSample — bir korelasyon anahtarı ve ondan birkaç örnek.

type CustomLink struct {
	Label string `json:"label"`
	URL   string `json:"url"`
}

CustomLink — one operator-bolted-on link per service. The catalog renders these as additional chips next to the built-in oncall / runbook / repo entries, so a team can surface "Grafana board" / "Kibana saved search" / "internal SRE dashboard" in one click without us baking each surface in as a column.

type DBCallerBreakdown added in v0.4.86

type DBCallerBreakdown struct {
	Service    string  `json:"service"`
	Pod        string  `json:"pod"`
	Role       string  `json:"role,omitempty"`
	SpanCount  uint64  `json:"spanCount"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"`
	AvgMs      float64 `json:"avgDurationMs"`
	// v0.9.273 — P50 completes the grid. It was missed in v0.9.263: the detail
	// AGGREGATE above gained P50/P95 but this per-caller struct only gained
	// P95, so the drawer showed three percentiles at the top and two per row.
	// The data was free the whole time — same merge, index 1.
	P50Ms float64 `json:"p50DurationMs"`
	// v0.9.263 — P95 off the same 3-wide TDigest state (index 2).
	//
	// ⚠️ This struct is filled by TWO queries — the /databases caller
	// breakdown and the /messaging one — and P95Ms is a plain float64, not
	// a pointer. A path that fails to SELECT it marshals 0 and the drawer
	// prints "0.0ms": a plausible wrong number, not a visible blank. Both
	// queries must always project it; a third producer must too.
	P95Ms float64 `json:"p95DurationMs"`
	P99Ms float64 `json:"p99DurationMs"`
}

DBCallerBreakdown is one row of the per-(service, pod) breakdown shown in the DB detail drawer. Pod is derived from resource.host.name on the calling span — k8s pod name on Kubernetes deployments, VM hostname elsewhere. Same shape works for the messaging detail drawer below.

Role is populated only by the messaging detail (span.kind promoted into the row: "producer" / "consumer" / "client" / "server" / "internal"). For DB rows it's empty since DB calls are always CLIENT-kind by OTel convention; the column would always read the same.

type DBDetail added in v0.4.86

type DBDetail struct {
	System     string  `json:"system"`
	Instance   string  `json:"instance"`
	SpanCount  uint64  `json:"spanCount"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"`
	AvgMs      float64 `json:"avgDurationMs"`
	// v0.9.263 — same db_caller_summary_5m merge as P99, indices 1 and 2.
	P50Ms   float64             `json:"p50DurationMs"`
	P95Ms   float64             `json:"p95DurationMs"`
	P99Ms   float64             `json:"p99DurationMs"`
	Callers []DBCallerBreakdown `json:"callers"`
	TopOps  []DBOpStat          `json:"topOps"`
}

DBDetail is the full payload for /api/databases/detail. The frontend renders it as a three-section drawer: time-series (call rate), per-(service, pod) breakdown, top operations.

type DBInstance added in v0.4.82

type DBInstance struct {
	System   string `json:"system"`   // db.system: postgresql / redis / oracle / mongo / mysql / cassandra / elasticsearch / …
	Instance string `json:"instance"` // peer.service when populated, else 'unknown' (host)
	// DBName — v0.5.315. Per-database split within the same host.
	// Oracle SID / service name, PostgreSQL / MongoDB / MSSQL
	// database name, Redis db index (when distinguishable). Falls
	// back to 'default' when the OTel instrumentation didn't emit
	// db.name. Row identity is now (System, Instance, DBName).
	DBName     string  `json:"dbName,omitempty"`
	SpanCount  uint64  `json:"spanCount"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"` // 0..100
	AvgMs      float64 `json:"avgDurationMs"`
	// v0.9.262 — P50/P95 read off the SAME db_summary_5m TDigest state that
	// already produced P99 (indices 1 and 2 of the 3-wide (0.5, 0.95, 0.99)
	// arg list). CH evaluates the identical quantilesTDigestMerge
	// subexpression once, so surfacing them costs no extra scan.
	//
	// Caveat: discoverReceiverInstances builds DBInstance rows from
	// metric_points, which carry no quantiles at all — those rows leave all
	// three at 0 and are tagged Source="receiver". The frontend badges them;
	// do not read a 0 here as "this database is fast".
	P50Ms float64 `json:"p50DurationMs"`
	P95Ms float64 `json:"p95DurationMs"`
	P99Ms float64 `json:"p99DurationMs"`
	// Prior* (v0.9.433) — ?compare=prior: bir-önceki eş-pencere
	// sayaçları (mergeDBPrior, api_databases.go). omitempty: yalnız
	// prior ikizi eşleşen satırlarda taşınır — sıfırlanmış prior sahte
	// NEW rozeti çizdirirdi (messaging v0.8.364 sözleşmesinin aynısı).
	PriorSpanCount  uint64   `json:"priorSpanCount,omitempty"`
	PriorErrorCount uint64   `json:"priorErrorCount,omitempty"`
	PriorAvgMs      float64  `json:"priorAvgMs,omitempty"`
	PriorP50Ms      float64  `json:"priorP50Ms,omitempty"`
	PriorP99Ms      float64  `json:"priorP99Ms,omitempty"`
	Callers         []string `json:"callers"` // top-5 calling services
	// Source telegraphs the data origin. Empty / "spans" =
	// span-derived (the historical default). "receiver" = the
	// row was discovered via the OpenTelemetry oracledb (or
	// similar) receiver and has no application traffic, so the
	// RED stats are zero and the click-through to the
	// receiver-specific panel is the actionable surface.
	Source DBSource `json:"source,omitempty"`
}

DBInstance is one row of the /databases overview — Dynatrace's "Technologies → Databases" surface. Each row is a unique (system, instance) pair observed in span traffic over the requested window. Drives the top-level Databases page so an operator can answer "which DBs is the platform calling, and which are slow / erroring" without per-service drill-down.

Caller list is bounded to top-5 by call count so a long-tail noisy caller doesn't drown the bigger consumers; UI shows the full list on click-through to the instance detail.

type DBLockMode added in v0.8.391

type DBLockMode struct {
	Mode  string  `json:"mode"`
	Value float64 `json:"value"`
	Unit  string  `json:"unit"`
}

DBLockMode is one dimensioned lock entry. Unit disambiguates the gauge-vs-rate semantics per engine ("count" | "/s").

type DBOpStat added in v0.4.86

type DBOpStat struct {
	Statement string  `json:"statement"`
	Count     uint64  `json:"count"`
	AvgMs     float64 `json:"avgDurationMs"`
}

DBOpStat is one row of the top-operations table in the DB detail drawer. Statement is truncated to 80 chars server-side so a 4 KB SQL string doesn't bloat the JSON envelope.

type DBQueryStat

type DBQueryStat struct {
	// Normalised statement — literals replaced with "?". Used
	// as the GROUP BY key in CH and as the row label in the UI.
	Statement string `json:"statement"`
	// One real (non-normalised) example of the query so the
	// operator sees actual values, not just placeholders.
	SampleStatement string `json:"sampleStatement"`
	DBSystem        string `json:"dbSystem"`
	// v0.9.272 — the actual database this statement ran against (Oracle
	// service name / SID, PostgreSQL or MongoDB database name), not the engine
	// word. The DB column read "oracle" for every row while the operator's
	// instances are named COREBANK / CARDS / DWH; db.name carried those names
	// all along and the query folded them away.
	//
	// DBNameCount is how many DISTINCT databases this (service, statement)
	// pair touched in the window. Grouping deliberately still folds db_name —
	// changing row identity would change the row COUNT of a daily triage
	// table — so the name shown is a representative one, and a count above 1
	// is surfaced rather than hidden behind an arbitrary any().
	DBName      string `json:"dbName"`
	DBNameCount uint64 `json:"dbNameCount"`
	// Span counts + latency stats for the bucket.
	Count int     `json:"count"`
	AvgMs float64 `json:"avgMs"`
	// v0.9.264 — P50 answers "is this query slow for everyone, or is it a
	// tail problem?", which avg alone can't (avg is dragged by the tail).
	//
	// ⚠️ THREE queries fill this struct: the raw slow-queries builder, the
	// MV builder, and GetTopDBQueries (raw-only, /service top statements).
	// P50Ms is a plain float64, so a producer that omits the projection
	// renders "0.00ms" — a wrong number, not a blank. All three project it.
	P50Ms      float64 `json:"p50Ms"`
	P95Ms      float64 `json:"p95Ms"`
	P99Ms      float64 `json:"p99Ms"`
	MaxMs      float64 `json:"maxMs"`
	ErrorCount int     `json:"errorCount"`
	// TotalMs = count × avgMs — the aggregate wall-clock cost
	// of this query class in the window. Sorting by total ms
	// surfaces the queries actually worth optimising (a 50ms
	// query running 10k times is a bigger problem than a 500ms
	// one running once, but the second one beats it on max).
	TotalMs float64 `json:"totalMs"`
}

DBQueryStat is one row in the database query analyzer — a single normalized statement aggregated across every span that issued it for the given service in the time window.

Normalisation collapses literal-only differences ("WHERE id = 1" vs "WHERE id = 2") so a single hot query surfaces as one row rather than thousands of near-duplicates. The sample statement keeps a real example so the operator can see what literals were involved without losing the aggregation benefit.

type DBSource added in v0.5.8

type DBSource string

GetDatabases returns one row per (db_system, peer_service) over the window. Skips spans where db_system is empty so we don't count non-DB traffic. Uses the idx_db_system skip-index for partition pruning so the scan stays bounded at billion- span scale.

Top-5 callers per row come from a paired groupArray + LIMIT in a subquery — single query trip, no per-row fan-out. DBSource is the data-origin tag on a DBInstance. We surface it to the operator so a row whose stats come from receiver-only metrics (no application spans yet) is visibly distinct from a row backed by real application traffic. Pre-v0.5.8 the /databases list was span-only; some Oracle deployments are monitored via the OpenTelemetry oracledb receiver but never touched by an instrumented service — they'd vanish entirely from the page despite having rich panel data.

const (
	DBSourceSpans    DBSource = ""         // default — derived from spans (back-compat)
	DBSourceReceiver DBSource = "receiver" // from oracledb.* metric_points only
)

type DBStmtCaller added in v0.8.378

type DBStmtCaller struct {
	Service     string  `json:"service"`
	Calls       uint64  `json:"calls"`
	Errors      uint64  `json:"errors"`
	AvgMs       float64 `json:"avgMs"`
	P95Ms       float64 `json:"p95Ms"`
	TotalMs     float64 `json:"totalMs"`
	PriorCalls  uint64  `json:"priorCalls,omitempty"`
	PriorErrors uint64  `json:"priorErrors,omitempty"`
	PriorAvgMs  float64 `json:"priorAvgMs,omitempty"`
	PriorP95Ms  float64 `json:"priorP95Ms,omitempty"`
}

DBStmtCaller is one service's slice of the statement class. Prior* additive, same contract as DBStmtSummary.

type DBStmtDetailQuery added in v0.8.378

type DBStmtDetailQuery struct {
	Hash     uint64
	DBSystem string
	DBName   string
	From, To time.Time
}

DBStmtDetailQuery bundles the statement-detail inputs. Hash is the REQUIRED identity (already parsed from the API's decimal string — 0 is the "no statement" sentinel and never reaches here); DBSystem / DBName optionally narrow a hash class that shows up under more than one engine/database (the MV keeps them as dimensions; the catalog folds across them).

type DBStmtSummary added in v0.8.378

type DBStmtSummary struct {
	// One real bucket sample — the API re-derives the normalized display
	// form via NormalizeDBStatement (hash-consistent by construction).
	SampleStatement string  `json:"sampleStatement"`
	DBSystem        string  `json:"dbSystem"`
	DBName          string  `json:"dbName"`
	Calls           uint64  `json:"calls"`
	Errors          uint64  `json:"errors"`
	TotalMs         float64 `json:"totalMs"`
	AvgMs           float64 `json:"avgMs"`
	P95Ms           float64 `json:"p95Ms"`
	P99Ms           float64 `json:"p99Ms"`
	MaxMs           float64 `json:"maxMs"`
	PriorCalls      uint64  `json:"priorCalls,omitempty"`
	PriorErrors     uint64  `json:"priorErrors,omitempty"`
	PriorAvgMs      float64 `json:"priorAvgMs,omitempty"`
	PriorP95Ms      float64 `json:"priorP95Ms,omitempty"`
}

DBStmtSummary is the drawer-header rollup for one statement class. Prior* fields are additive — filled by the API layer's second (shifted-window) read when ?compare=prior, zero otherwise (the EndpointRow Prior* convention: prior-absent renders as "NEW").

type DBStmtTrendPoint added in v0.8.378

type DBStmtTrendPoint struct {
	TsNs   int64   `json:"tsNs"`
	Calls  uint64  `json:"calls"`
	Errors uint64  `json:"errors"`
	AvgMs  float64 `json:"avgMs"`
	P95Ms  float64 `json:"p95Ms"`
}

DBStmtTrendPoint is one trend bucket: bucket-start ns + RED-shaped values for the statement class.

type DBTopSQL added in v0.7.69

type DBTopSQL = OracleSQL

DBTopSQL is one row of the engine-authoritative "heaviest statement" view for Postgres / MySQL — the receiver-side parity with Oracle's V$SQL TopSQL list (OracleSQL). The field shape deliberately mirrors OracleSQL so the frontend renders all three engines through the same TopSQLTable component:

SQL          — the statement text (normalised by the engine —
               pg_stat_statements collapses literals to $N,
               performance_schema's DIGEST_TEXT collapses to ?).
ElapsedSec   — total accumulated exec time over the window, in
               seconds (receiver values are normalised to s).
Executions   — call count over the window.
AvgElapsedMs — ElapsedSec*1000 / Executions, server-computed so
               the SRE reads "constant-but-cheap vs rare-but-heavy"
               at a glance without re-deriving the ratio.

Unlike the span-derived "Top statements" list (which only sees what the application actually traced), this is what the DB itself measured across ALL clients — the same complementary relationship Oracle's TopSQL has to our db_statement top list.

type DBTrend added in v0.7.68

type DBTrend struct {
	DbSystem string `json:"dbSystem"`
	Instance string `json:"instance"`
	DbName   string `json:"dbName"`
	Cluster  string `json:"cluster"`

	Points []DBTrendPoint `json:"points"`

	// Latest-bucket health snapshot (gauge source).
	CurRps       float64 `json:"curRps"`
	CurErrorRate float64 `json:"curErrorRate"` // 0..100
	CurP99Ms     float64 `json:"curP99Ms"`
}

DBTrend is the per-row sparkline + latest-bucket health snapshot for one database on the /databases (or /messaging) overview grid. Keyed identically to chstore.DBInstance / the frontend DepRow: (DbSystem, Instance, DbName, Cluster). Cluster is empty for db_summary_5m-sourced rows (DB rows carry no cluster dimension); it rides the struct so the same shape can serve the messaging grid join key without a second type.

Points is an ascending-time array of ~bucketsPerWindow entries (one per 5-minute bucket the window covers, capped). The Cur* fields are the latest non-empty bucket's snapshot — what the per-row health gauge renders without the frontend having to scan the array.

type DBTrendPoint added in v0.7.68

type DBTrendPoint struct {
	T         int64   `json:"t"`         // unix ns — bucket start
	Rps       float64 `json:"rps"`       // call rate: span_count / 300
	ErrorRate float64 `json:"errorRate"` // 0..100
	P99Ms     float64 `json:"p99Ms"`     // p99 duration, ms
}

DBTrendPoint is one 5-minute bucket of a database's RED trend. Aligned to the db_summary_5m time_bucket grid so the frontend can stitch sparklines without re-bucketing. t is unix ns at the bucket start; the three RED series ride alongside it.

Rps is spans/sec within the bucket (span_count / 300s, since the MV buckets on a 5-minute = 300s interval). ErrorRate is 0..100. P99Ms is the merged 0.99 quantile in milliseconds.

type DBWaitClass added in v0.8.391

type DBWaitClass struct {
	Name   string  `json:"name"`
	PerSec float64 `json:"perSec"`
}

DBWaitClass is one wait-class row. Same derivation as the Oracle panel: (max-min of the cumulative counter over window) / windowSec.

type DBWaitLock added in v0.8.391

type DBWaitLock struct {
	// System is the NORMALIZED engine key: "oracle" | "postgresql" |
	// "mysql" (mariadb folds into mysql, postgres into postgresql).
	System        string  `json:"system"`
	Instance      string  `json:"instance"`
	WindowSeconds float64 `json:"windowSeconds"`
	// Supported=false → the engine has no receiver wait/lock story
	// at all (redis, sqlserver, unknown). The strip renders nothing.
	Supported bool `json:"supported"`
	// WaitClasses — per-class wait pressure in waiting-seconds per
	// elapsed second (1.0 = one client fully blocked), descending.
	// Only Oracle's receiver emits this family; empty otherwise.
	WaitClasses []DBWaitClass   `json:"waitClasses"`
	Locks       DBWaitLockLocks `json:"locks"`
}

DBWaitLock is the payload of GET /api/databases/waitlock — the common wait/lock model for one (system, instance).

type DBWaitLockLocks added in v0.8.391

type DBWaitLockLocks struct {
	// WaitsPerSec — row-lock wait events per second (oracle, mysql).
	WaitsPerSec *float64 `json:"waitsPerSec,omitempty"`
	// TimeSec — total seconds spent in row-lock waits over the
	// window (mysql; receiver counts milliseconds).
	TimeSec *float64 `json:"timeSec,omitempty"`
	// DeadlocksPerSec — deadlocks per second (oracle sums enqueue +
	// exchange counters; postgresql has a single counter).
	DeadlocksPerSec *float64 `json:"deadlocksPerSec,omitempty"`
	// ByMode — dimensioned lock breakdown: PG current lock counts
	// per lock_type (gauge), MySQL table-lock rates per kind (/s).
	ByMode []DBLockMode `json:"byMode,omitempty"`
}

DBWaitLockLocks — nil pointer = family not seen in the window (either the receiver doesn't emit it or it isn't wired), which the frontend renders as an honest per-engine empty, never a zero.

type DDLHostProgress added in v0.9.613

type DDLHostProgress struct {
	Host      string `json:"host"`
	Processed int64  `json:"processed"`
	// Behind — kuyruk başının ne kadar gerisinde. >0 = worker takılı.
	Behind int64 `json:"behind"`
}

DDLHostProgress — bir host'un DDL worker ilerlemesi.

type DDLQueueEntry added in v0.9.613

type DDLQueueEntry struct {
	Entry      string `json:"entry"`
	Host       string `json:"host"`
	Status     string `json:"status"`
	AgeSeconds int64  `json:"ageSeconds"`
	Query      string `json:"query"` // kırpılmış
}

DDLQueueEntry — bekleyen bir kuyruk satırı (girdi × host).

type DDLQueueHealth added in v0.9.613

type DDLQueueHealth struct {
	ClusterMode bool `json:"clusterMode"`

	// Verdict — healthy | worker_stuck | worker_skipping |
	// unreachable | probe_failed | single_node.
	Verdict string `json:"verdict"`
	Detail  string `json:"detail"`

	// StuckCount — bekleyen TEKİL girdi sayısı (uniqExact(entry) —
	// satır sayısı DEĞİL: her girdi host başına satır üretir, canlıda
	// 2388 satır / 1194 girdi ölçüldü — satır sayımı 2× şişiyordu).
	StuckCount uint64 `json:"stuckCount"`
	// StuckCountApprox — sayım probe'u düştü, StuckCount alt sınır.
	// Sessiz kırpma "hepsi bu" diye okunur; yaklaşıklık İTİRAF edilir.
	StuckCountApprox bool              `json:"stuckCountApprox,omitempty"`
	OldestAgeSeconds int64             `json:"oldestAgeSeconds,omitempty"`
	QueueHead        int64             `json:"queueHead,omitempty"`
	Hosts            []DDLHostProgress `json:"hosts,omitempty"`
	UnreachableHosts []string          `json:"unreachableHosts,omitempty"`
	// Entries — kuyruğun BAŞI (≤20 satır): gerisi onu bekliyor olabilir.
	Entries []DDLQueueEntry `json:"entries,omitempty"`
	// QueueHosts / ClusterHosts — destekleyici bilgi.
	QueueHosts   []string `json:"queueHosts,omitempty"`
	ClusterHosts []string `json:"clusterHosts,omitempty"`
	ProbeErrors  []string `json:"probeErrors,omitempty"`

	Generated int64 `json:"generated"`
}

DDLQueueHealth — teşhisin tamamı.

type Dashboard

type Dashboard struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	// `omitempty` so the lightweight ListDashboards response doesn't
	// emit null fields the SPA isn't expecting (panels/variables are
	// only fetched on the single-dashboard endpoint).
	Panels    json.RawMessage `json:"panels,omitempty"`
	Variables json.RawMessage `json:"variables,omitempty"`
	CreatedAt int64           `json:"createdAt"` // unix ns
	UpdatedAt int64           `json:"updatedAt"` // unix ns
}

Dashboard is a saved set of panels + Grafana-style variables. Panels and Variables are stored as opaque JSON so the schema doesn't change every time the UI introduces a new panel or variable type.

Variables are a list of {name, type, options, defaultValue} objects; the renderer substitutes ${name} in panel DSL / service / groupBy fields before fetching. See frontend/src/lib/types.ts for the canonical shape.

type DayStat

type DayStat struct {
	Day      string `json:"day"`
	Spans    uint64 `json:"spans"`
	Errors   uint64 `json:"errors"`
	Traces   uint64 `json:"traces"`   // approximate, HLL-merged from trace_summary_1d
	Services uint64 `json:"services"` // distinct service_names that contributed that day
}

DayStat is one bucket in the 30-day history chart. Spans / errors come from service_summary_5m (5-minute rollups summed over the day), traces from trace_summary_1d (HLL-state per day), so we never re-aggregate the raw spans table for this view.

type DeepEvidence added in v0.9.516

type DeepEvidence struct {
	Checked     []CheckedSignal            `json:"checked,omitempty"`
	Exceptions  []ExceptionGroup           `json:"exceptions,omitempty"`
	Templates   []LogTemplate              `json:"templates,omitempty"`
	Heap        []CapacitySample           `json:"heap,omitempty"`
	GCPause     []CapacitySample           `json:"gcPause,omitempty"`
	Runtime     *ServiceRuntime            `json:"runtime,omitempty"`
	SlowOps     []OperationSummary         `json:"slowOps,omitempty"`
	Business    map[string][]BusinessSlice `json:"business,omitempty"`
	CodeMeaning map[string]string          `json:"codeMeaning,omitempty"`
}

DeepEvidence — P1 soruşturmasının topladığı ek kanıt + denetim izi. chstore'da yaşıyor çünkü TÜM üyeleri chstore tipleri ve hipotezle birlikte saklanıyor; anomaly paketi burayı kullanır (tersi import döngüsü olurdu).

type Deploy

type Deploy struct {
	Service string `json:"service"`
	Version string `json:"version"`
	// TimeUnixNs is the first-seen timestamp of this version
	// in the queried window — the marker position on the chart.
	TimeUnixNs int64 `json:"timeUnixNs"`
	// SpanCount = how many spans this version has produced
	// since first appearance. Helps the UI dim out noise: a
	// version that produced 3 spans is probably a stuck
	// straggler instance, not a real deploy.
	SpanCount int `json:"spanCount"`
}

Deploy is one observed (service, service.version) entry. The frontend renders one vertical dashed line per Deploy on the metric / latency / error charts so an operator can read at a glance whether a regression coincides with a deploy.

"Deploy" here is the moment a previously-unseen version of the service first emitted a span — that's what an operator reads as "the new code shipped". OTel populates resource.service.version from the SDK; if your build process doesn't set it (no SDK env var, no .ServiceVersion()), there will be nothing to show, which is the right answer.

type DeployImpact added in v0.5.189

type DeployImpact struct {
	Service      string            `json:"service"`
	Version      string            `json:"version"`
	DeployTimeNs int64             `json:"deployTimeNs"`
	WindowSec    int               `json:"windowSec"`
	Before       DeployImpactStats `json:"before"`
	After        DeployImpactStats `json:"after"`
	// Delta — friendly signed deltas the UI renders as
	// colour-coded chips. Positive = worse, negative = better.
	P99DeltaPct       float64 `json:"p99DeltaPct"`       // % change
	AvgDeltaPct       float64 `json:"avgDeltaPct"`       // % change
	ErrorRateDeltaPct float64 `json:"errorRateDeltaPct"` // absolute pct points (after - before) * 100
}

DeployImpact captures a service.version transition's before/ after RED + computed delta. Surfaced as the "last deploy impact" panel on the Service detail page so the operator gets a "did the new code regress something?" answer at a glance without opening the AI Copilot.

type DeployImpactStats added in v0.5.189

type DeployImpactStats struct {
	Count     uint64  `json:"count"`
	RPS       float64 `json:"rps"`
	ErrorRate float64 `json:"errorRate"` // 0..1
	P99Ms     float64 `json:"p99Ms"`
	AvgMs     float64 `json:"avgMs"`
}

GetServiceDeploys returns every distinct service.version observed for `service` in the time window, ordered by first appearance. Each row carries the first-seen timestamp — the position the deploy marker lands on the chart.

Why min(time): in a continuous-deployment shop, an old version may have stragglers running for a few minutes after the new one ships. Using min(time) per version finds the *earliest* moment that version became active — the actual deploy timestamp — rather than the moment some pod last saw it.

CH posture: the (service_name, time) primary key prunes by the time bound; the resource-attribute lookup is a single indexOf per row, cheap. Limit 50 is a hard cap so a chatty CD pipeline doesn't return thousands of rows. DeployImpactStats is one window's worth of RED for a deploy comparison (v0.5.189). Always reported as a pair (before / after) so the operator can read the delta directly without math-by-eye.

type DiskStat added in v0.9.289

type DiskStat struct {
	Host       string `json:"host,omitempty"`
	Name       string `json:"name"`
	Path       string `json:"path"`
	TotalBytes uint64 `json:"totalBytes"`
	FreeBytes  uint64 `json:"freeBytes"`
	// UnreservedBytes — free space minus what merges and inserts in
	// flight have already claimed. It is the honest "can I write
	// another part right now" figure and is always ≤ FreeBytes.
	UnreservedBytes uint64 `json:"unreservedBytes"`
	// KeepFreeBytes — the operator-configured reserve CH refuses to
	// dip into. Effective capacity is TotalBytes - KeepFreeBytes.
	KeepFreeBytes uint64 `json:"keepFreeBytes"`
}

DiskStat is one volume ClickHouse can write to, as reported by system.disks (v0.9.289, operator ask: "can I see the disk usage of the server ClickHouse runs on").

This is capacity, not occupancy: TotalBytes/FreeBytes come from the filesystem, so they include everything on that volume, not just Coremetry's tables. That is the point — the question behind it is "will ingest run out of room", and the answer depends on the whole disk. system.disks is metadata; the read is instant at any scale.

Host is populated only on a cluster() fan-out; on a single node it stays empty and the UI shows one unlabelled row.

func (DiskStat) UsedBytes added in v0.9.289

func (d DiskStat) UsedBytes() uint64

UsedBytes is the occupied portion of the volume.

func (DiskStat) UsedPct added in v0.9.289

func (d DiskStat) UsedPct() float64

UsedPct is how full the volume is, 0..100. Zero-capacity disks (a disk CH reports but cannot stat) answer 0 rather than dividing by zero — an unknown must not render as "100% full" and page someone.

type EdgeInstance added in v0.5.142

type EdgeInstance struct {
	Instance string  `json:"instance"`
	Calls    uint64  `json:"calls"`
	AvgMs    float64 `json:"avgMs"`
	P99Ms    float64 `json:"p99Ms"`
}

EdgeInstance is one (peer_service) bucket for an infra edge — the actual host / cluster behind a `db:postgresql` or `queue:kafka` node. Drives the EdgeDetailPanel "per-instance" expand in topology so the operator can see which postgres instance is hot without leaving the diagram.

type EndpointDetailQuery added in v0.8.360

type EndpointDetailQuery struct {
	Service     string
	Path        string
	BySignature bool
	From, To    time.Time
	// Env / Cluster (v0.9.306) — the SAME scope the table row was
	// computed under.
	//
	// Operator-reported silent-filter bug: with env=uat selected the
	// table showed uat numbers, but opening a row aggregated EVERY env
	// for that route — prod included. Two different truths on one
	// screen, and nothing said so. The drawer's reads are raw spans and
	// deploy_env is a typed LowCardinality column, so carrying the
	// scope costs a conjunct, not a schema change.
	Env     string
	Cluster string
}

EndpointDetailQuery scopes every reader in this file to one (service, path) tuple over a window. BySignature marks Path as an ID-collapsed shape (/orders/:id) — readers then match opSigWrap(http_route) instead of the raw column.

type EndpointDownstream added in v0.9.311

type EndpointDownstream struct {
	Downstream []EndpointEdge `json:"downstream"`
	Callers    []EndpointEdge `json:"callers"`
	// SampledFrom — how many traces the numbers above were derived
	// from. Zero means the window held no matching trace; the UI says
	// that rather than drawing an empty chart.
	SampledFrom int `json:"sampledFrom"`
	// TotalMs — the sampled entry spans' total duration, i.e. the
	// denominator of every share. Carried so the UI never re-derives it
	// from a rounded sum.
	TotalMs float64 `json:"totalMs"`
	// Backends (v0.9.311) — database / cache / broker time found at ANY
	// depth beneath the route, NOT just in its direct children.
	//
	// This exists because direct children alone answer the question too
	// shallowly. Verified on live data: a gateway route's 659ms had one
	// direct child (account-service, 645ms) whose OWN child was a 623ms
	// Oracle query. "645ms in account-service" is true and nearly
	// useless; "623ms of it in Oracle" is the answer the operator came
	// for — and it is the brief's own motivating example.
	//
	// Kept OUT of the Downstream share arithmetic on purpose: that
	// Oracle time is INSIDE account-service's 645ms, so listing it as a
	// sibling share would double-count the same milliseconds. Nested
	// breakdown, nested label.
	Backends []EndpointEdge `json:"backends"`
}

EndpointDownstream is the payload behind the drawer's two tabs.

type EndpointEdge added in v0.9.311

type EndpointEdge struct {
	// Name — the downstream service / db_system / peer, or the calling
	// service. "self" is reserved for the entry span's own time.
	Name string `json:"name"`
	// Kind — "service" | "db" | "messaging" | "self", so the UI can
	// pick an icon without re-deriving it from the name.
	Kind  string `json:"kind"`
	Calls uint64 `json:"calls"`
	// AvgMs / P99Ms over the SAMPLED spans, not the window.
	AvgMs  float64 `json:"avgMs"`
	P99Ms  float64 `json:"p99Ms"`
	Errors uint64  `json:"errors"`
	// ShareMs — total milliseconds this edge accounts for across the
	// sample. The UI draws the share bar from it.
	ShareMs float64 `json:"shareMs"`
}

EndpointEdge is one downstream dependency or one caller of a route.

type EndpointException added in v0.8.360

type EndpointException struct {
	Type        string `json:"type"`
	Message     string `json:"message"`
	Fingerprint string `json:"fingerprint"`
	Count       uint64 `json:"count"`
	LastSeenNs  int64  `json:"lastSeenNs"`
}

EndpointException is one exception type observed on the endpoint's spans in the window, with the Go-side inbox fingerprint so the drawer deep-links straight into /problems?exception=<fp>.

type EndpointFailingTrace added in v0.8.360

type EndpointFailingTrace struct {
	TraceID    string  `json:"traceId"`
	DurationMs float64 `json:"durationMs"`
	SpanName   string  `json:"spanName"`
	StatusMsg  string  `json:"statusMsg,omitempty"`
	HttpStatus uint16  `json:"httpStatus,omitempty"`
	ErrorSpans uint64  `json:"errorSpans"`
	TimeNs     int64   `json:"timeNs"`
}

EndpointFailingTrace is one distinct trace with at least one error span on the endpoint — the drawer's direct pivot into /trace?id=. DurationMs is the WORST endpoint-span duration inside the trace (the right ranking for an endpoint drawer — whole-trace duration would rank by unrelated downstream work).

type EndpointRow added in v0.5.365

type EndpointRow struct {
	Service   string  `json:"service"`
	Path      string  `json:"path"`
	Method    string  `json:"method,omitempty"`
	Calls     uint64  `json:"calls"`
	Errors    uint64  `json:"errors"`
	ErrorRate float64 `json:"errorRate"`
	AvgMs     float64 `json:"avgMs"`
	P99Ms     float64 `json:"p99Ms"`
	// v0.8.356 — MV-backed columns (Stage-2 slice E1). True window
	// quantiles from the spanmetrics_1m tdigest states (the old raw
	// CTE approximated window p99 as max(per-bucket p99)) plus
	// req/min throughput (calls / window minutes) so the operator
	// compares endpoints across window widths.
	P50Ms float64 `json:"p50Ms"`
	// P90Ms (v0.9.305) — the percentile between "typical" and "tail".
	// The MV already produced it; only the raw path's quantile family
	// had to be widened to expose it.
	P90Ms     float64 `json:"p90Ms"`
	P95Ms     float64 `json:"p95Ms"`
	ReqPerMin float64 `json:"reqPerMin"`
	// SlowTraceID / ErrorTraceID (v0.9.310, brief N3) — the slowest and
	// worst-error trace for THIS (service, route, window), off the MV's
	// argMax exemplar states.
	//
	// Empty means "no exemplar in this window", not "none exists": the
	// states are forward-only (written after the MV was created) and
	// the error one is empty for a healthy window. The UI renders no
	// link at all rather than a dash — same soft degrade the drawer has
	// used since v0.8.564.
	//
	// Omitted from JSON when empty so the raw path (cluster/env, which
	// has no exemplar states) simply ships rows without them.
	// Kind (v0.9.313, brief N1) — the entry span's kind on the RPC tab
	// ("server" for gRPC, "consumer" for a queue). Empty on the HTTP
	// tab, where the kind is implied by the route.
	Kind         string `json:"kind,omitempty"`
	SlowTraceID  string `json:"slowTraceId,omitempty"`
	ErrorTraceID string `json:"errorTraceId,omitempty"`
	// v0.5.370 — call-rate sparkline (≤ SparklineBuckets slots across
	// the requested window; MV grain floors the slot width so short
	// windows ship fewer, real slots). Lets the operator eye-scan
	// "is this endpoint steady / spiking / dying" from the table row
	// without a chart drill-in. Bucketing happens server-side
	// so the JSON payload size stays bounded regardless of
	// window width.
	Sparkline []float64 `json:"sparkline,omitempty"`
	// v0.5.387 — errors + p99 sparklines on the same bucket
	// grid so the row-level drill-in modal can render all three
	// RED dimensions without a second round-trip. Same payload
	// shape as Sparkline; one float per bucket. Both fields
	// share the bucket boundaries of Sparkline so the modal can
	// drive them off a single time axis.
	ErrorsSparkline []float64         `json:"errorsSparkline,omitempty"`
	P99Sparkline    []float64         `json:"p99Sparkline,omitempty"`
	StatusBreakdown map[string]uint64 `json:"statusBreakdown,omitempty"`
	// v0.5.403 — HTTP status class counts for the (service, path)
	// over the window. Source: http.status_code attr. Server-side
	// classification keeps the payload tight (4 ints per row vs
	// shipping raw codes). Operator reads "is this endpoint
	// throwing 5xx, returning 4xx, or just slow" without drilling
	// into a trace. Zero values when the spans don't carry
	// http.status_code (non-HTTP endpoints, gRPC-only services).
	Http2xx uint64 `json:"http2xx,omitempty"`
	Http3xx uint64 `json:"http3xx,omitempty"`
	Http4xx uint64 `json:"http4xx,omitempty"`
	Http5xx uint64 `json:"http5xx,omitempty"`
	// v0.5.404 — prior-window comparison values, populated only
	// when the caller asked for trend deltas (?compare=prior).
	// Frontend derives the % delta arrows + colour. Zero when
	// the (service, path) didn't exist in the prior window — UI
	// renders these as "NEW" instead of "+∞%".
	PriorCalls  uint64  `json:"priorCalls,omitempty"`
	PriorErrors uint64  `json:"priorErrors,omitempty"`
	PriorAvgMs  float64 `json:"priorAvgMs,omitempty"`
	PriorP99Ms  float64 `json:"priorP99Ms,omitempty"`
}

EndpointRow is one (service, path) tuple's RED rollup for the /endpoints page. Path resolves to http.route when the SDK emits the templated form (e.g. "/api/users/{id}") with an http.target fallback (the ingest column's chain — v0.8.356 MV path); the legacy raw path (cluster filter only) additionally falls back to url.path, matching the operator-confirmed v0.5.365 priority order.

type EndpointSplitRow added in v0.8.360

type EndpointSplitRow struct {
	Value     string  `json:"value"`
	Calls     uint64  `json:"calls"`
	Errors    uint64  `json:"errors"`
	ErrorRate float64 `json:"errorRate"`
	AvgMs     float64 `json:"avgMs"`
	P99Ms     float64 `json:"p99Ms"`
}

EndpointSplitRow is one attribute value's RED rollup inside the drawer's split-by section.

type EndpointStatus added in v0.8.360

type EndpointStatus struct {
	Http2xx uint64            `json:"http2xx"`
	Http3xx uint64            `json:"http3xx"`
	Http4xx uint64            `json:"http4xx"`
	Http5xx uint64            `json:"http5xx"`
	Codes   map[string]uint64 `json:"codes"`
}

EndpointStatus is the drawer's error-breakdown-by-status payload: the v0.8.356 sidecar's four class counts plus the per-code map the table row never had room for ("is the 5xx one 500 or a 503 storm").

type EndpointsQuery added in v0.8.356

type EndpointsQuery struct {
	From, To time.Time
	Service  string
	Search   string
	Cluster  string
	// Env narrows to spans.deploy_env — the global Topbar env picker
	// (v0.8.385, env-separation Phase 2). Like Cluster it forces the
	// raw-spans path: spanmetrics_1m carries no env dimension and the
	// approved strategy is cluster-parity raw-fallback, NO MV changes.
	// Unlike the cluster derive, deploy_env is a typed LowCardinality
	// column, so the conjunct is cheap.
	Env         string
	Limit       int
	BySignature bool
	// Sort / Dir (v0.8.356) — server-side global ordering, whitelisted
	// via endpointsOrderBy. Before this the backend always returned
	// top-N by calls and the client re-sorted that page — "top by p95"
	// was really "top-N-by-calls, reordered".
	Sort, Dir string
	// Entry (v0.9.313, brief N1) — which inbound surface to list.
	// Empty = EntryHTTP, so every existing caller keeps today's rows.
	Entry EntryKind
	// SkipStatus skips the raw-spans status/method sidecar — set by the
	// compare=prior read, which only needs calls/errors/avg/p99 for the
	// delta merge.
	SkipStatus bool
}

EndpointsQuery bundles the /endpoints read inputs (v0.8.356) — the arg list outgrew a flat signature when server-side sort + the MV/raw dispatch landed.

type EntryKind added in v0.9.313

type EntryKind string

EntryKind selects which inbound surface /endpoints lists (v0.9.313, brief N1). Before this the page showed only HTTP routes and said nothing about the rest, so gRPC server and Kafka consumer entry points were invisible rather than merely absent.

const (
	// EntryHTTP — http_route-bearing inbound spans. The default, and
	// byte-for-byte the pre-v0.9.313 table.
	EntryHTTP EntryKind = "http"
	// EntryRPC — inbound spans WITHOUT an http_route: gRPC servers and
	// message consumers, keyed on the span name.
	EntryRPC EntryKind = "rpc"
)

type Event added in v0.5.476

type Event struct {
	ID        string `json:"id"`
	Kind      string `json:"kind"` // deploy | config | incident | maintenance | custom
	Label     string `json:"label"`
	Time      int64  `json:"time"`    // unix ns; when it happened
	Service   string `json:"service"` // optional service scope ("" = global)
	Link      string `json:"link"`    // optional URL
	Owner     string `json:"owner"`   // creator email
	CreatedAt int64  `json:"createdAt"`
}

Event — operator-marked moment in time. v0.5.476.

Surfaces as a vertical marker on every time-series chart in Coremetry (frontend v0.5.477). Examples operators mark:

  • kind="deploy" label="payments v1.2.3" service="payments"
  • kind="config" label="feature flag X enabled"
  • kind="incident" label="incident-5 opened"
  • kind="maintenance" label="DB upgrade window"

Time vs CreatedAt: Time is when the event HAPPENED (operator supplies — defaults to now if unset). CreatedAt is when the row was inserted (audit trail). Most events are marked at creation, so the two match; backfill cases (operator adds a missed deploy event later) is why we keep them separate.

type EventFilter added in v0.5.476

type EventFilter struct {
	From    time.Time // inclusive; zero = unbounded
	To      time.Time // exclusive; zero = unbounded
	Service string    // exact match; "" = all
	Kind    string    // exact match; "" = all
	Limit   int       // 0 = 200
}

EventFilter — read-side cut for /api/events.

type ExceptionFilter

type ExceptionFilter struct {
	Service string
	// Services — ÇOKLU servis kapsamı (v0.9.650). Bir TAKIMIN tüm
	// servislerini tek sorguda taramak için: takım başına N ayrı
	// GetExceptions çağrısı, servis sayısıyla doğrusal büyüyen bir
	// JSON-kazıma yükü demekti.
	//
	// Service ile birlikte verilirse ikisi de uygulanır (AND) — çağıran
	// ikisini birden vermemeli; tek servis için Service, takım için
	// Services.
	Services []string
	GroupBy  string // "type" | "type-service" | "full"  (default: "type-service")
	From, To time.Time
	Limit    int
}

type ExceptionGroup

type ExceptionGroup struct {
	Fingerprint string `json:"fingerprint"`
	Type        string `json:"type"`
	Message     string `json:"message"`
	Service     string `json:"service"`
	State       string `json:"state"`
	Assignee    string `json:"assignee"`
	FirstSeen   int64  `json:"firstSeen"` // unix ns
	LastSeen    int64  `json:"lastSeen"`  // unix ns
	ResolvedAt  *int64 `json:"resolvedAt,omitempty"`
	Occurrences uint64 `json:"occurrences"`
	Notes       string `json:"notes"`
	// AISummary (v0.9.415) — ExceptionExplainer'ın proaktif kök-sebep
	// özeti (problems.ai_summary'nin ikizi). Boş = henüz üretilmedi.
	// ReplacingMergeTree tam-satır replace: HER yazma yolu bu alanı
	// taşımak ZORUNDA (Scan'e dahil → stale-sweep/state-flip korur).
	AISummary string `json:"aiSummary,omitempty"`
	// AISummaryAt (v0.9.530) — özetin ÜRETİLDİĞİ an, unix-ns.
	// problems.AISummaryAt'in ikizi. Özet tek yazımlıktır ama grubun
	// gövdesi (mesaj, occurrences) altından değişmeye devam eder; yaş
	// damgası olmadan operatör 6 saatlik bir çıkarımı canlı sayının
	// altında taze sanar. 0 = özet yok.
	AISummaryAt int64 `json:"aiSummaryAt,omitempty"`
}

type ExceptionGroupFilter

type ExceptionGroupFilter struct {
	// MinOccurrences (v0.9.315, operator-reported) — hide groups that
	// have fired fewer than this many times.
	//
	// The Problems list was filling with one-off exceptions: a single
	// Java socket timeout produced a row that looked exactly like a
	// sustained outage. Operator: "gerçek sorun 5-10 adetten fazla
	// occurrence olan problemler". A one-shot failure is a fact about
	// the window, not a problem to triage.
	//
	// Zero = no floor, which is what every non-Problems caller keeps.
	// The filtering is NEVER silent: the caller reports how many rows
	// this hid so the UI can offer them in one click — a rare exception
	// that fires once can still be the important one, and hiding it
	// without saying so is the failure mode this codebase keeps paying
	// for.
	MinOccurrences uint64
	// MaxOccurrences (v0.9.336, EXCLUSIVE) selects the rows BELOW a floor —
	// the complement of MinOccurrences. It exists so the inbox can push its
	// occurrence floor into SQL without losing the honest "N hidden" count:
	// one fetch for the rows to show, one bounded fetch for the rows the
	// floor hides, both run through the same downstream narrows. Zero = no
	// upper bound.
	MaxOccurrences uint64
	State          string // empty = all (except ignored)
	Service        string
	Assignee       string
	// Services constrains the result to this set (service IN (…)). Used
	// by the owner/SRE team filter on the Problems inbox (v0.8.310): the
	// API resolves a team pick to its member services from the catalog
	// and sets this, so the filter bites BEFORE the limit/offset — the
	// only correct way to team-filter a server-paginated list (a Go-side
	// post-filter would only trim the current page and break the count).
	// Empty = no service-set constraint.
	Services []string
	// Search (v0.8.318) — case-insensitive substring over ex_type /
	// ex_message / service, applied server-side so it covers EVERY page
	// of the paginated inbox (the old client-side filter only searched
	// the loaded 50 rows).
	Search string
	// Sort / Dir (v0.8.318) — server-side ordering (whitelisted via
	// exceptionGroupsOrderBy). The inbox is LIMIT/OFFSET paginated, so a
	// client-side sort of one page silently lied ("top by occurrences"
	// was really "most-recent 50, reordered").
	Sort   string
	Dir    string
	Limit  int
	Offset int
	// HTTPErrors (v0.9.443) — sözde-exception ayrımı: error.type
	// fallback'i (v0.8.494) HTTP durum kodunu ex_type'a yazar; "404" gibi
	// çıplak 3-haneli tipler gerçek exception değil beklenen istemci
	// hatalarıdır. "" = ikisi de; "exclude" = yalnız gerçek exception;
	// "only" = yalnız HTTP-hata grupları. Deseni HTTPErrorTypeRe taşır —
	// Go tarafı sınıflandırmayla (api.isHTTPErrorType) AYNI kaynak.
	HTTPErrors string
}

type ExceptionRow

type ExceptionRow struct {
	Type          string `json:"type"`
	Message       string `json:"message"`
	Service       string `json:"service"`
	Count         uint64 `json:"count"`
	LastSeen      int64  `json:"lastSeen"`
	SampleTraceID string `json:"sampleTraceId"`
	SampleSpanID  string `json:"sampleSpanId"`
}

type ExceptionSample

type ExceptionSample struct {
	TraceID    string `json:"traceId"`
	SpanID     string `json:"spanId"`
	Time       int64  `json:"time"`       // unix ns
	Message    string `json:"message"`    // per-sample exception message — varies within a group
	Stacktrace string `json:"stacktrace"` // raw, may be empty
	SpanName   string `json:"spanName"`   // operation that errored
	StatusMsg  string `json:"statusMsg"`  // span status message
}

ExceptionSample is one observed occurrence of a group — used to fill the "show me 10 recent examples of this exception" inline expansion.

type ExecutionFilter added in v0.6.73

type ExecutionFilter struct {
	RunbookID string
	Status    string
	ProblemID string
	SinceNs   int64 // v0.7.8 — >0 → started_at >= this (partition-prunes; see executionWhere)
	Limit     int
}

ExecutionFilter narrows the executions list.

type Exemplar

type Exemplar struct {
	TraceID    string `json:"traceId"`
	SpanID     string `json:"spanId"`
	Service    string `json:"service"`
	Name       string `json:"name"`
	DurationNs int64  `json:"durationNs"`
	StatusCode string `json:"statusCode"`
	TimeUnixNs int64  `json:"timeUnixNs"`
}

type ExemplarIngest added in v0.8.328

type ExemplarIngest struct {
	Ingested       int64 `json:"ingested"`
	DroppedNoTrace int64 `json:"droppedNoTrace"`
	// DroppedCapped — v0.8.433 (Faz C) per-series×minute ingest cap
	// drops; intentional like DroppedNoTrace, 0 unless the cap is armed.
	DroppedCapped int64 `json:"droppedCapped"`
}

ExemplarIngest — the two OTLP metric-exemplar ingest totals (cumulative since process start, v0.8.328). DroppedNoTrace counts the require-trace- context policy gate (INTENTIONAL, like the pipeline drops — never in the loss alarm); buffer/write loss for accepted exemplars is visible through the exemplars consumer like every other signal. Populated by the API getSystemStats handler from the live Ingester atomics; GetSystemStats (CH-only) leaves it zero so chstore keeps no otlp dependency.

type ExemplarKind

type ExemplarKind string
const (
	ExemplarSlow  ExemplarKind = "slow"
	ExemplarError ExemplarKind = "error"
	ExemplarAny   ExemplarKind = "any"
)

type ExemplarReq

type ExemplarReq struct {
	Service   string       // service_name (required)
	Operation string       // span name (optional — empty = any op)
	From      time.Time    // bucket start (inclusive)
	To        time.Time    // bucket end (inclusive)
	Kind      ExemplarKind // slow | error | any
}

type ExemplarRow added in v0.8.328

type ExemplarRow struct {
	Fingerprint uint64
	Metric      string
	Service     string
	Time        time.Time
	Value       float64
	// TraceID / SpanID are lowercase hex (same encoding as spans.trace_id so
	// the pivot is a same-type join); "" when the producer had no sampled
	// trace context. The require-trace-context ingest gate drops empty-trace
	// rows by default — a stored exemplar exists to be clicked through.
	TraceID string
	SpanID  string
	// FilteredAttrs are the attrs the producer's aggregation filtered OFF the
	// series (OTLP filtered_attributes) — shown as context on the pivot UI.
	FilteredAttrs map[string]string
}

ExemplarRow is one OTLP metric exemplar normalised for the `exemplars` table (v0.8.328). Extracted by otlp/convert.go from Sum / Gauge / Histogram / ExponentialHistogram datapoints (Summary carries no exemplars in the OTLP proto). Fingerprint is the SAME SeriesFingerprint stored on the datapoint's metric_points row — the join key of the metric→trace pivot.

type ExternalCaller added in v0.8.446

type ExternalCaller struct {
	Service   string   `json:"service"`
	Calls     uint64   `json:"calls"`
	Errors    uint64   `json:"errors"`
	ErrorRate float64  `json:"errorRate"`
	AvgMs     float64  `json:"avgMs"`
	P99Ms     float64  `json:"p99Ms"`
	TopLabels []string `json:"topLabels"`
}

ExternalCaller is the per-service breakdown inside one host's detail drawer — who depends on this API and how hard.

type ExternalHost added in v0.8.446

type ExternalHost struct {
	Host        string   `json:"host"`
	Display     string   `json:"display,omitempty"`
	Category    string   `json:"category,omitempty"`
	Callers     uint64   `json:"callers"`
	CallerNames []string `json:"callerNames"`
	Calls       uint64   `json:"calls"`
	Errors      uint64   `json:"errors"`
	ErrorRate   float64  `json:"errorRate"`
	AvgMs       float64  `json:"avgMs"`
	P99Ms       float64  `json:"p99Ms"`
	TopLabels   []string `json:"topLabels"`
}

ExternalHost is one row of the /external overview — a distinct third-party destination aggregated across every calling service in the window. Display/Category come from the static vendor catalogue (external_catalogue.go) and stay empty for unrecognised hosts — the frontend then shows the raw host without a badge.

type ExternalHostDetail added in v0.8.446

type ExternalHostDetail struct {
	Host     string               `json:"host"`
	Display  string               `json:"display,omitempty"`
	Category string               `json:"category,omitempty"`
	Callers  []ExternalCaller     `json:"callers"`
	Trend    []ExternalTrendPoint `json:"trend"`
}

ExternalHostDetail is the drawer payload for one host.

type ExternalTrendPoint added in v0.8.446

type ExternalTrendPoint struct {
	Bucket int64   `json:"bucket"`
	Calls  uint64  `json:"calls"`
	Errors uint64  `json:"errors"`
	AvgMs  float64 `json:"avgMs"`
	P99Ms  float64 `json:"p99Ms"`
}

ExternalTrendPoint is one 5-minute bucket of the host's RED trend. Bucket is unix seconds (the MV's native grain).

type FatalException added in v0.9.609

type FatalException struct {
	Type      string `json:"type"`
	Service   string `json:"service"`
	Message   string `json:"message"`
	FirstSeen int64  `json:"firstSeen"`
	LastSeen  int64  `json:"lastSeen"`
	Count     uint64 `json:"occurrences"`
}

FatalException — bir (tip, servis) çifti için ölümcül exception hâli.

Bu bir OLAY değil DURUM: patlama gibi başlayıp biten bir şey değil, biri düzeltene kadar süren bir koşul. Bu yüzden kimlik zaman kovası TAŞIMAZ (paylaşılan-bağımlılık dedektörünün aksine) — aynı servis aynı hostu çözemediği sürece AYNI problemdir.

type FilterExpr

type FilterExpr struct {
	Key    string   `json:"k"`
	Op     string   `json:"op"`
	Values []string `json:"v"`
}

FilterExpr is a single advanced filter clause: `key op value(s)`. `Key` may be a well-known column ("service.name", "http.method", …) or any custom attribute name — looked up in the attr_keys/attr_values arrays.

func ParseDSL

func ParseDSL(src string) ([]FilterExpr, error)

ParseDSL turns a multi-line query DSL into a slice of FilterExpr.

Syntax — one condition per line, AND-joined:

# comments allowed
duration > 500ms
service.name = "frontend"
http.status_code >= 500
resource.deployment.environment = production
span.peer.service = "payment-service"
name ~ checkout                   # LIKE substring
status_code in [error]            # IN [a, b, c]
exception.type exists

Operators: =, !=, >, >=, <, <=, ~ (LIKE), !~ (NOT LIKE), in / not in, exists / not exists.

Values may be quoted ("foo") or bare; durations (`500ms`, `1.5s`, `2m`) are normalised to milliseconds when used with the synthetic `duration` key.

func (FilterExpr) SQL

func (f FilterExpr) SQL() (string, []any, error)

SQL builds the WHERE fragment + arg list for this filter, or returns (empty, nil, error) if the expression is malformed.

For unknown attribute keys, the lookup expression `attr_values[indexOf(attr_keys, ?)]` pushes the key as a parameter. For numeric ops we cast to Float64 so that even string-stored attributes compare correctly when they parse.

func (FilterExpr) SQLAliased added in v0.8.152

func (f FilterExpr) SQLAliased(alias string) (string, []any, error)

SQLAliased is the alias-qualified twin of SQL: every column reference is prefixed with `<alias>.` so the fragment is safe to use inside a self-join over `spans AS <alias>` (relations.go, v0.8.x). With an empty alias it is byte-identical to SQL() — the existing flat-filter callers are unchanged.

Only the alias is concatenated into the SQL text; it is validated by the caller (relations.go uses the fixed literals "c" / "p"), never threaded from user input. Keys and values still flow exclusively as `?` params.

func (FilterExpr) SQLForMetricPoints added in v0.8.381

func (f FilterExpr) SQLForMetricPoints() (string, []any, error)

SQLForMetricPoints resolves against metric_points' column set (v0.8.381): only service/host typed columns exist there, so every other wellKnown key falls to the attr/res array lookups instead of referencing a spans-only column ClickHouse would reject.

type FilterGroup added in v0.8.151

type FilterGroup struct {
	Join    string        `json:"join"` // "AND" | "OR" (default AND)
	Filters []FilterExpr  `json:"filters"`
	Groups  []FilterGroup `json:"groups,omitempty"`
}

FilterGroup is a grouped boolean combination of FilterExpr leaves and (one level of) nested groups, joined by a single AND/OR operator. It is the additive, default-off upgrade over the flat conjunction-only `[]FilterExpr` path: an operator can finally express `(http.status >= 500 OR db.system = oracle) AND env = prod`.

Depth cap: v1 supports exactly ONE level of nested groups. The top-level group may carry Groups; nested groups carry only Filters (deeper Groups are ignored by BuildFilterGroupSQL). One level buys ~95% of real queries per the trace-query-explore spec; arbitrary nesting is a bigger UI + URL codec lift deferred past v1.

BACK-COMPAT CONTRACT (pinned by filtergroup_test.go): a flat-AND group — `FilterGroup{Join:"AND", Filters:<legacy []FilterExpr>}` with no Groups — emits byte-identical SQL + args to the legacy ApplyFilters path, so saved views / shared URLs / DQL / facets are untouched. The flat-AND case is routed straight through ApplyFilters precisely so it cannot drift.

func (*FilterGroup) IsFlatAnd added in v0.8.151

func (g *FilterGroup) IsFlatAnd() bool

IsFlatAnd is the exported guard the repo MV gate calls on an optional *FilterGroup: a nil root or a flat-AND root keeps the MV fast-path; any OR / nested group falls to the raw-spans GROUP-BY path (same cost class as free-text Search today).

type FlameNode

type FlameNode struct {
	Name     string       `json:"name"`
	File     string       `json:"file,omitempty"`
	Line     int64        `json:"line,omitempty"`
	Value    int64        `json:"value"`
	Self     int64        `json:"self,omitempty"`
	Children []*FlameNode `json:"children,omitempty"`
}

FlameNode is a single frame in the flame graph tree.

type FlowSig added in v0.5.156

type FlowSig struct {
	RootService string
	RootOp      string
}

FlowSig identifies a business flow by its (root_service, root_op) pair. Used as a bounded IN-list for the p99 enrichment so the query never scans more roots than the caller already listed.

type GCActivitySample added in v0.9.440

type GCActivitySample struct {
	Service    string
	Pod        string
	SharePct   float64 // GC'de geçen zaman / pencere (%)
	RatePerMin float64 // koleksiyon/dk
}

GCActivitySample — bir pod'un pencere içi GC etkinliği (v0.9.440).

type HavingExpr added in v0.8.453

type HavingExpr struct {
	Metric string  `json:"metric"` // count|perMin|errorRate|avg|p50|p95|p99|max
	Op     string  `json:"op"`     // > >= < <=
	Value  float64 `json:"value"`
}

HavingExpr — Aggregated görünümünde bir post-aggregate koşul. Metric adları sort whitelist'ini aynalar; Op yalnız karşılaştırma. SQL'e YALNIZ compileHaving'in kolon haritasından girer (değer her zaman bind parametresi) — ham interpolasyon yok.

type HistogramSeries added in v0.6.56

type HistogramSeries struct {
	Bounds  []float64  `json:"bounds"` // explicit upper bounds (len N)
	Times   []int64    `json:"times"`  // ns epoch, one per time bucket
	Counts  [][]uint64 `json:"counts"` // [timeBucket][bucket] summed (len N+1)
	P50     []float64  `json:"p50"`
	P95     []float64  `json:"p95"`
	P99     []float64  `json:"p99"`
	Skipped int        `json:"skipped"` // series dropped for mismatched bounds
	// RowCapped (v0.9.473, dürüstlük A13) — 200k satır tavanı doldu:
	// ORDER BY t ASC + LIMIT pencerenin SAĞ kenarını keser; heatmap
	// "trafik düştü" gibi okunmasın diye UI bunu söyler.
	RowCapped bool `json:"rowCapped,omitempty"`
}

HistogramSeries is the read-time shape of an explicit OTel histogram over a time window: a shared set of explicit bucket bounds, one summed bucket-count vector per time bucket, and p50/p95/p99 estimated from those vectors. Drives the /metrics histogram heatmap (v0.6.56).

type HostDetail added in v0.8.449

type HostDetail struct {
	Host     string           `json:"host"`
	Zone     string           `json:"zone,omitempty"`
	Services []HostServiceRow `json:"services"`
	Trend    []HostTrendPoint `json:"trend"`
}

HostDetail is the drawer payload.

type HostRow added in v0.8.449

type HostRow struct {
	Host     string   `json:"host"`
	Zone     string   `json:"zone,omitempty"`
	Services []string `json:"services"`
	CPUPct   float64  `json:"cpuPct"`
	MemBytes float64  `json:"memBytes"`
	MemPct   float64  `json:"memPct"` // 0 when no limit is reported
	Up       bool     `json:"up"`
	LastSeen int64    `json:"lastSeen"` // unix ns
}

HostRow is one row of the /hosts overview.

type HostServiceRow added in v0.8.449

type HostServiceRow struct {
	Service  string  `json:"service"`
	CPUPct   float64 `json:"cpuPct"`
	MemBytes float64 `json:"memBytes"`
	LastSeen int64   `json:"lastSeen"`
}

HostServiceRow is the per-service breakdown in the host drawer.

type HostTrendPoint added in v0.8.449

type HostTrendPoint struct {
	Bucket   int64   `json:"bucket"` // unix seconds
	CPUPct   float64 `json:"cpuPct"`
	MemBytes float64 `json:"memBytes"`
}

HostTrendPoint is one minute of the host's CPU/memory trend; CPU is the sum of per-service utilisation on the host (≈ pod CPU), memory the sum of per-service latest RSS in that minute.

type Incident

type Incident struct {
	ID         string `json:"id"`
	Title      string `json:"title"`
	Severity   string `json:"severity"` // info | warning | critical
	Status     string `json:"status"`   // open | acknowledged | resolved
	Service    string `json:"service,omitempty"`
	Summary    string `json:"summary,omitempty"`
	Assignee   string `json:"assignee,omitempty"`
	Postmortem string `json:"postmortem,omitempty"`
	StartedAt  int64  `json:"startedAt"`
	AckAt      *int64 `json:"ackAt,omitempty"`
	ResolvedAt *int64 `json:"resolvedAt,omitempty"`
	UpdatedAt  int64  `json:"updatedAt"`
	// Clusters — same pattern as Problem.Clusters: enriched
	// at read time from recent span activity for the
	// incident's primary service. Empty when the service
	// isn't tagged with cluster attrs. Multi-cluster
	// incidents render with multiple chips.
	Clusters []string `json:"clusters,omitempty"`
}

type IncidentEvent

type IncidentEvent struct {
	IncidentID string `json:"incidentId"`
	Time       int64  `json:"time"` // unix ns
	Kind       string `json:"kind"` // created | ack | resolved | note | problem_attached | problem_resolved
	Actor      string `json:"actor,omitempty"`
	Body       string `json:"body,omitempty"`
	RefID      string `json:"refId,omitempty"`
}

type IncidentFilter

type IncidentFilter struct {
	// ID narrows to exactly one incident (v0.9.332). Exists so GetIncident
	// can ask the database for the row it wants instead of paging the newest
	// N and scanning in Go — see the comment there.
	ID     string
	Status string
	// Services (v0.9.353) — strict service allowlist, in SQL. Same contract
	// as the anomaly filter: nil = no constraint, EMPTY = match nothing.
	// STRICT IN: under a team filter a service-less (global) incident does
	// not belong to any team — matching the Go pass it backs. (The env
	// narrow's `service=” OR …` escape is a DIFFERENT contract: env keeps
	// global rows, team does not.)
	Services []string
	// NotStatuses — statuses to EXCLUDE, applied in SQL so the LIMIT bites on
	// the rows that will be shown. See ProblemFilter.NotStatuses: on an
	// install whose incident history is 99% resolved (local: 994 of the
	// newest 1000), ORDER BY started_at DESC LIMIT 300 returned almost
	// nothing open and the inbox list disagreed with its own badge — 2 vs 29.
	NotStatuses []string
	Service     string
	Severity    string
	Limit       int
}

type InfraMetricSeries

type InfraMetricSeries struct {
	Metric string  `json:"metric"` // canonical key, e.g. "cpu" / "memory" / "rps"
	Source string  `json:"source"` // raw OTel metric name, e.g. "process.runtime.cpu.utilization"
	Unit   string  `json:"unit"`   // "%", "bytes", "/s", …
	Points []Point `json:"points"`
}

InfraMetricSeries is one named timeseries for the infra correlation panel on /service?name=…. Lightweight on purpose: a flat Points array per metric, no labels — one bucket per (service, metric, time-bucket). The frontend renders sparklines.

type IngestDrops added in v0.8.134

type IngestDrops struct {
	SpansQueueFull     int64 `json:"spansQueueFull"`
	LogsQueueFull      int64 `json:"logsQueueFull"`
	MetricsQueueFull   int64 `json:"metricsQueueFull"`
	SpansWriteFailed   int64 `json:"spansWriteFailed"`
	LogsWriteFailed    int64 `json:"logsWriteFailed"`
	MetricsWriteFailed int64 `json:"metricsWriteFailed"`
	// Pipeline — records discarded by an operator-defined ingest rule
	// (drop / sample) before the consumer buffer (v0.8.282). These are
	// INTENTIONAL, not data loss, so the UI renders them separately from
	// the queue-full / write-failed counters and never in the loss alarm.
	// Populated by the API getSystemStats handler from the live Ingester.
	SpansPipeline   int64 `json:"spansPipeline"`
	LogsPipeline    int64 `json:"logsPipeline"`
	MetricsPipeline int64 `json:"metricsPipeline"`
}

IngestDrops surfaces the in-process ingest data-loss counters (cumulative since process start) on /admin/stats — previously invisible: an operator could only see spans_dropped on /api/health and nothing about logs/metrics or write-path loss. Two loss classes per signal:

  • QueueFull: the receiver buffer was full when the item arrived (producer outran the CH writer — backpressure overflow).
  • WriteFailed: the ClickHouse insert errored and the batch was dropped, not retried (silent loss the flusher only logged before v0.8.x).

Populated by the API getSystemStats handler from the live consumers; GetSystemStats (CH-only) leaves it zero so chstore keeps no otlp dependency.

type IngestRates

type IngestRates struct {
	SpansPerSec   float64 `json:"spansPerSec"`
	LogsPerSec    float64 `json:"logsPerSec"`
	MetricsPerSec float64 `json:"metricsPerSec"`
}

IngestRates is the live "what's happening right now" view — last 5 minutes per signal kind, expressed as items / second.

type KibanaSettings added in v0.5.219

type KibanaSettings struct {
	// Enabled — disabled by default. When false the UI hides the
	// per-row "Kibana" link even if BaseURL is set.
	Enabled bool   `json:"enabled"`
	BaseURL string `json:"baseUrl,omitempty"`
	// DataView — optional Kibana data-view / index-pattern id.
	// When set, the deep-link pins the view so the operator
	// lands on the same indexes Coremetry is reading from
	// instead of Kibana's default. Empty = Kibana picks the
	// default data view, fine for single-pattern installs.
	DataView string `json:"dataView,omitempty"`
}

KibanaSettings — operator-curated link to an external Kibana install so the Logs page can render a "Open in Kibana Discover" deep-link per row. Independent of the Coremetry log backend (which can still be CH or ES); this is purely a UI shortcut for operators who prefer Kibana's deeper Discover affordances for ad-hoc log exploration.

type LatencyHeatmap

type LatencyHeatmap struct {
	// Time bucket starts (unix nanoseconds) — len = N time buckets.
	Times []int64 `json:"times"`
	// Duration bin upper bounds in ms (e.g. 1, 1.78, 3.16, 5.62, 10, …).
	// len = M latency bins. Counts[i][j] is the span count for
	// the time bucket i and the latency bin whose upper bound
	// is DurationBins[j].
	DurationBins []float64 `json:"durationBins"`
	// Counts[time_idx][dur_idx] = span count in that cell.
	Counts [][]uint32 `json:"counts"`
	// Exemplars[time_idx][dur_idx] — hücrenin temsilci trace_id'si
	// (v0.9.393: hücredeki EN YAVAŞ span'in trace'i; "" = hücre boş).
	// Tık→trace garantisi bunun üstünden: eski hücre-tık modalının trace
	// ARAMASI sampled pencerede "No traces matched" ölü ucuna düşebiliyordu.
	Exemplars [][]string `json:"exemplars,omitempty"`
	// MaxCount = peak cell value, useful for the frontend to
	// pick a colour scale without a full re-scan.
	MaxCount uint32 `json:"maxCount"`
	// SamplingRate (v0.5.238) — fraction of trace IDs read to
	// produce this heatmap. 1.0 = full pass; <1.0 = sampled. UI
	// surfaces a small "sampled at 10%" tag when this drops below
	// 1 so the operator knows the absolute counts are
	// extrapolated. Shape stays accurate; only the absolute
	// scale is multiplied by 1/SamplingRate.
	SamplingRate float64 `json:"samplingRate,omitempty"`
}

LatencyHeatmap is a 2D histogram of span counts bucketed by (time, log-scale duration). Honeycomb's signature visualisation — the eye reads "where is the population dense" and "where are the outliers" instantly because each pixel = one (time, latency) cell, not an aggregated line.

Bucket grid:

  • Time axis — N buckets across the requested window (caller picks N; default 60).
  • Latency axis — log10(ms) with sub-decade granularity (4 sub-bins per decade by default → 7 decades = 28 bins covering 0.1ms → 1Ms).

The wire format avoids a sparse map: we send Times[], DurationBins[], and Counts[][] (rows × cols) so the frontend can render a fixed grid without lookups. Empty cells = 0.

type LdapGroupRow added in v0.8.526

type LdapGroupRow struct {
	UID      string   // objectGUID as a uuid string
	DN       string   // distinguished name (display / prefix filter)
	CN       string   // common name (display)
	Users    []string // sAMAccountName members, sorted
	Deleted  bool     // tombstone
	SyncedAt int64    // unix ns of the sync that produced this row
}

LdapGroupRow is the storage projection of one directory group. The ldap sync engine maps its own ldap.Group ↔ LdapGroupRow, keeping this package free of any ldap import (feature → storage layering).

type ListAICallsParams added in v0.5.164

type ListAICallsParams struct {
	Surface  string
	Provider string
	Status   string
	From     time.Time // inclusive; zero = no lower bound
	To       time.Time // exclusive; zero = now()
	Limit    int
}

ListAICalls returns recent calls filtered by surface/provider/ status. Filters all optional — empty means "any". Caller-side pagination via since/limit; default 100 rows. Latest-first.

type ListAnomalyEventsFilter

type ListAnomalyEventsFilter struct {
	SinceNs   int64         // unix ns; 0 = last 24h default
	ActiveAge time.Duration // last_seen freshness for "active" status; 0 = 10m default
	Limit     int
	// Services (v0.9.353) constrains rows to this service set IN SQL, so the
	// LIMIT lands on rows the team filter would keep. The inbox used to fetch
	// up to srcLimit rows and drop other teams' rows in Go — the
	// filter-after-LIMIT class, and the reason an owner pick was slow: the
	// scan pulled the whole estate to find one team's rows. STRICT IN — a
	// service-less anomaly row does not match a team filter (same semantics
	// as the Go pass it backs). nil = no constraint; EMPTY = match nothing
	// (a team with no member services must yield an empty page, never an
	// unfiltered one).
	Services []string
	// FromNs/ToNs (v0.9.394, annotation şeridi Ş1) — started_at pencere
	// sorgusu: "bu pencerede BAŞLAYAN anomaliler". 0 = sınırsız (eski
	// davranış SinceNs üzerinden aynen sürer).
	FromNs int64
	ToNs   int64
	// ActiveOnly (v0.9.335) narrows to firing events IN SQL, so the LIMIT
	// lands on rows the caller will actually keep.
	//
	// The inbox's "open" pivot keeps only active events and dropped the rest
	// in Go — AFTER the limit. That is the same silent-scope shape already
	// fixed for problems and incidents (v0.9.322): a caller asking for 2000
	// rows to find 20 active ones spends its whole budget on cleared history.
	// "Active" is exactly the freshness predicate the status column is
	// derived from, so this adds no new notion — it moves an existing one to
	// where the LIMIT can respect it.
	ActiveOnly bool
}

ListAnomalyEventsFilter is the read-side cut. SinceNs filters by last_seen >= … so a 24h window returns both currently-active events and ones that cleared up to 24h ago.

type ListLogTemplatesFilter added in v0.5.244

type ListLogTemplatesFilter struct {
	SinceNs int64
	SortBy  string // "first_seen" | "last_seen" | "count" (default "count")
	Limit   int
}

ListLogTemplatesFilter narrows the read by recency and sort order. SinceNs filters last_seen ≥ X; Limit caps the response.

type Log

type Log struct {
	TraceID      string
	SpanID       string
	Time         time.Time
	SeverityNum  uint8
	SeverityText string
	Body         string
	ServiceName  string
	HostName     string
	AttrKeys     []string
	AttrValues   []string
	ResKeys      []string
	ResValues    []string
	ScopeName    string
}

Log is a normalised OTLP log record.

type LogFilter

type LogFilter struct {
	Service string
	// Env (v0.8.400 — env-separation Phase 4) — deployment-environment
	// filter, applied as the bounded res-array conjunct
	// logsEnvChainSQL. Empty = all environments.
	Env         string
	Search      string
	From, To    time.Time
	SeverityMin uint8
	TraceID     string
	SpanID      string // optional: only logs attached to this span
	// HasTrace (v0.8.406) — only rows with a trace correlation
	// (trace_id != ”). Applied before the SinceNs branch so the
	// count(), page read AND forward-tail all carry it.
	HasTrace bool
	Limit    int
	Offset   int
	// Cursor (v0.7.22, SAFE-CORE) — opaque CH keyset token from a
	// prior GetLogs NextCursor. When set, GetLogs pages AFTER the
	// encoded (time, rowKey) position with a STRICT keyset predicate
	// instead of OFFSET. Empty = first page; Offset still honoured.
	Cursor string
	// Ascending (v0.7.83) — flip the sort to oldest-first
	// (time ASC, rowKey ASC). Only honoured on a non-cursor read
	// (the keyset cursor is DESC-only). Used by the /logs Context
	// "after" window so LIMIT n returns the n rows immediately
	// AFTER the pivot, not the n newest in the forward window.
	Ascending bool
	// SinceNs (v0.8.x) — forward-tail mode for the live-tail SSE
	// stream. When > 0, GetLogs reads `time >= SinceNs` oldest-first,
	// bounded by Limit, and SKIPS the count() total + the keyset
	// cursor. The handler tracks the newest emitted timestamp and
	// passes it back; it dedups same-ns boundary rows by LogRow.ID
	// (the cityHash64 rowkey). See logstore.Filter.SinceNs.
	SinceNs int64
}

type LogRow

type LogRow struct {
	ID                 uint64            `json:"id"`
	Timestamp          int64             `json:"timestamp"`
	SeverityNumber     uint8             `json:"severity"`
	SeverityText       string            `json:"severityText"`
	Body               string            `json:"body"`
	ServiceName        string            `json:"serviceName"`
	TraceID            string            `json:"traceId"`
	SpanID             string            `json:"spanId"`
	Attributes         map[string]string `json:"attributes"`
	ResourceAttributes map[string]string `json:"resourceAttributes"`
}

type LogTemplate added in v0.5.244

type LogTemplate struct {
	ID            string   `json:"id"`
	Template      string   `json:"template"`
	FirstSeen     int64    `json:"firstSeen"` // unix ns
	LastSeen      int64    `json:"lastSeen"`
	TotalCount    uint64   `json:"totalCount"`
	Services      []string `json:"services"`
	ExceptionType string   `json:"exceptionType,omitempty"`
	Sample        string   `json:"sample"`
}

LogTemplate is one Drain-extracted log shape with its running observation stats. Persisted via ReplacingMergeTree(version) so repeated batch upserts from the templater puller fold into one row keyed by template_id.

type LogsCursor added in v0.7.77

type LogsCursor struct {
	TimeNs    int64
	RowKey    uint64
	Ascending bool
}

LogsCursor is the decoded form of a ClickHouse logs keyset token. Encoded wire format: base64("ch|"+TimeNs+"|"+RowKey). The "ch|" prefix tags the backend so an ES cursor fed to the CH path (or vice versa) fails to decode rather than silently mis-paging. RowKey is the unsigned cityHash64 of logsRowKeyExpr for the last row of the prior page.

v0.9.295 — the token also carries the DIRECTION it was minted in. The keyset is a strict inequality, so a cursor from a newest-first page paged in oldest-first order walks the wrong way and returns a plausible, wrong slice. Before, direction was simply ignored while a cursor was present; now the mismatch is DETECTED and the cursor is dropped, which costs the operator a return to page one instead of a silently wrong page.

func DecodeLogsCursor added in v0.7.77

func DecodeLogsCursor(tok string) (LogsCursor, bool)

DecodeLogsCursor parses a token produced by EncodeLogsCursor. Returns ok=false for any malformed / wrong-backend token so the caller falls back to a first-page read rather than erroring the whole request.

type MaintenanceWindow added in v0.5.26

type MaintenanceWindow struct {
	ID        string `json:"id"`
	Service   string `json:"service"`
	Severity  string `json:"severity"`
	StartAt   int64  `json:"startAt"`
	EndAt     int64  `json:"endAt"`
	Reason    string `json:"reason"`
	CreatedBy string `json:"createdBy"`
	CreatedAt int64  `json:"createdAt"`
	Disabled  bool   `json:"disabled"`
}

MaintenanceWindow is an operator-declared time range during which alert notifications are suppressed. Problems still open + auto-resolve as usual (so the timeline survives the review afterwards), but the live fan-out to Slack / email / Zoom / etc. skips while the window is active.

Match-mode on Service:

  • "*" → global silence (everything)
  • "<exact name>" → single-service silence
  • "name*" → prefix match (e.g. "payment*" covers payment-api, payment-worker, …)

Severity filter:

  • "*" → all severities
  • "info" / "warning" / "critical" → only that severity

Disabled = soft delete for audit trail. ListMaintenanceWindows hides disabled rows by default; the admin UI offers a "show disabled" toggle for a forensic review.

type MatchInput added in v0.5.63

type MatchInput struct {
	Service  string
	Metadata *ServiceMetadata
	Clusters []string  // problem.Clusters after enrichment
	Now      time.Time // override for tests; zero = time.Now()
}

MatchInput bundles the runtime signals Matches needs. We pass a struct instead of growing the arg list every time we add a predicate; existing call sites switched to use it via MatchesProblem below.

type MessagingDetail added in v0.4.86

type MessagingDetail struct {
	System      string  `json:"system"`
	Cluster     string  `json:"cluster"`
	Destination string  `json:"destination"`
	SpanCount   uint64  `json:"spanCount"`
	ErrorCount  uint64  `json:"errorCount"`
	ErrorRate   float64 `json:"errorRate"`
	AvgMs       float64 `json:"avgDurationMs"`
	// v0.9.263 — same merge as P99, indices 1 and 2. No extra scan.
	P50Ms   float64             `json:"p50DurationMs"`
	P95Ms   float64             `json:"p95DurationMs"`
	P99Ms   float64             `json:"p99DurationMs"`
	Callers []DBCallerBreakdown `json:"callers"` // same shape — service / pod / RED
	TopOps  []DBOpStat          `json:"topOps"`  // statement = span name (send / receive / process)
	// Series — v0.8.364 (Stage-2 M1). Per-5-minute produce/consume
	// counts across the window, straight off
	// messaging_caller_summary_5m (kind + time_bucket are both
	// dimensions there, so the split series is one bounded merged-
	// state GROUP BY — no raw-spans read). Drives the drawer's
	// produce/consume sparklines.
	Series []MsgKindPoint `json:"series"`
	// E2E — v0.8.372 (Stage-2 M2). span_links-correlated end-to-end
	// produce→consume latency (messaging_e2e.go). Nil when the read
	// fails (drawer omits the section); non-nil with Linkless=true
	// when no links correlated in the window (honest empty state).
	E2E *MsgE2E `json:"e2e,omitempty"`
}

MessagingDetail mirrors DBDetail for queues / topics. Op stats here are per-(operation name) since messaging spans don't carry a SQL-equivalent; the operation (send / receive / process) plus the destination already discriminates work.

type MessagingInstance added in v0.4.82

type MessagingInstance struct {
	System      string  `json:"system"`      // kafka / rabbitmq / ibmmq / nats / sqs / kinesis
	Cluster     string  `json:"cluster"`     // bootstrap host / cluster name / "(default)"
	Destination string  `json:"destination"` // queue / topic name (resolved from messaging.destination.name or peer.service)
	SpanCount   uint64  `json:"spanCount"`
	ErrorCount  uint64  `json:"errorCount"`
	ErrorRate   float64 `json:"errorRate"`
	AvgMs       float64 `json:"avgDurationMs"`
	// v0.8.364 (Stage-2 M1) — full quantile grid. The MV state is
	// quantilesTDigestState(0.5, 0.95, 0.99); pre-M1 only element
	// 3 (p99) was projected out of the merge.
	P50Ms float64 `json:"p50DurationMs"`
	P95Ms float64 `json:"p95DurationMs"`
	P99Ms float64 `json:"p99DurationMs"`
	// v0.8.364 (Stage-2 M1) — producer/consumer split. Sourced from
	// messaging_caller_summary_5m (the kind dimension lives on that
	// MV; messaging_summary_5m collapses it). Raw counts, not
	// rates: the caller knows the window length, and equal-length
	// prior windows make count deltas identical to rate deltas.
	// Spans of other kinds (client/server/internal brokers chatter)
	// count toward SpanCount but neither split bucket.
	ProduceCount  uint64 `json:"produceCount"`
	ConsumeCount  uint64 `json:"consumeCount"`
	ProduceErrors uint64 `json:"produceErrors"`
	ConsumeErrors uint64 `json:"consumeErrors"`
	// Prior* — same rollup over the immediately-preceding
	// equal-length window. Populated only when /api/messaging runs
	// with compare=prior (v0.8.364; the endpoints v0.5.404
	// pattern). omitempty keeps the default payload unchanged.
	PriorSpanCount    uint64   `json:"priorSpanCount,omitempty"`
	PriorErrorCount   uint64   `json:"priorErrorCount,omitempty"`
	PriorProduceCount uint64   `json:"priorProduceCount,omitempty"`
	PriorConsumeCount uint64   `json:"priorConsumeCount,omitempty"`
	PriorAvgMs        float64  `json:"priorAvgMs,omitempty"`
	PriorP50Ms        float64  `json:"priorP50Ms,omitempty"`
	PriorP99Ms        float64  `json:"priorP99Ms,omitempty"`
	Callers           []string `json:"callers"`
}

MessagingInstance is the parallel structure for /messaging — Kafka / RabbitMQ / IBM MQ / NATS / etc. Same shape as DBInstance plus a Cluster dimension so multi-cluster deployments (e.g. "Kafka Konsolide" + "Kafka Mobile" both running under the same OTel msg_system tag) show as separate rows instead of one bucket.

Destination tries to be the queue / topic name. messaging SDKs in OTel populate `messaging.destination.name` as an attribute; we resolve it via the attr_keys/attr_values arrays. peer.service is the fallback (Kafka brokers register themselves there).

Cluster resolves in priority order:

  1. `server.address` — bootstrap host (most reliable)
  2. `messaging.kafka.bootstrap.servers` — kafka-specific
  3. `messaging.kafka.cluster.name` — newer semconv
  4. `peer.service` — coarse fallback
  5. `(default)` — single-cluster install

type MetricBaseline added in v0.5.50

type MetricBaseline struct {
	Metric      string  `json:"metric"`
	Service     string  `json:"service,omitempty"` // empty = all services
	P50         float64 `json:"p50"`
	P95         float64 `json:"p95"`
	P99         float64 `json:"p99"`
	Max         float64 `json:"max"`
	Mean        float64 `json:"mean"`
	SampleCount int64   `json:"sampleCount"` // # of spans / minutes scanned
	WindowSec   int64   `json:"windowSec"`   // lookback the percentiles were computed over
}

MetricBaseline summarises the recent distribution of one alertable metric for one service (or globally when service is empty). Drives the "✨ suggest threshold" panel on the alert-rule editor — operators see what NORMAL looks like before they pick a threshold, instead of guessing 5 / 500ms and getting paged at 4am because the actual P99 baseline was 800ms.

All fields are in the SAME unit the alert evaluator compares against, so the operator can paste a value directly into the threshold input:

error_rate    → percentage (0..100)
p50_ms / p95_ms / p99_ms / avg_ms → milliseconds
request_rate  → requests per second
error_count   → absolute count per window (5 min default)

type MetricExemplar added in v0.8.51

type MetricExemplar struct {
	Time         int64    `json:"time"`     // bucket start, unix nanos
	GroupKey     []string `json:"groupKey"` // matches the series it annotates
	SlowTraceID  string   `json:"slowTraceId,omitempty"`
	ErrorTraceID string   `json:"errorTraceId,omitempty"`
}

MetricExemplar is "click a bucket → open the trace" — the representative slow and/or errored trace_id for one (bucket, groupKey) cell, served from the rollup's argMax(If)State exemplar columns.

type MetricInfo

type MetricInfo struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Unit        string `json:"unit"`
	Type        string `json:"type"`
}

type MetricPoint

type MetricPoint struct {
	Metric      string
	Instrument  string // gauge, sum, histogram, summary
	Description string
	Unit        string
	ServiceName string
	HostName    string
	Time        time.Time
	StartTime   time.Time
	Value       float64
	Count       uint64
	SumValue    float64
	MinValue    float64
	MaxValue    float64
	AttrKeys    []string
	AttrValues  []string
	ResKeys     []string
	ResValues   []string
	// v0.5.358 — histogram bucket layout. Populated for
	// Histogram data points by otlp/convert.go; nil/empty for
	// all other instruments. BucketBounds carries the explicit
	// upper bounds (length N); BucketCounts carries the
	// per-bucket counts (length N+1 in canonical OTel,
	// element[i] = count of observations ≤ BucketBounds[i],
	// element[N] = +Inf bucket). At read time we sum these
	// element-wise across data points to compute quantiles.
	BucketBounds []float64
	BucketCounts []uint64
	// v0.6.56 — OTLP aggregation temporality ("delta" | "cumulative"
	// | ""). Set for Histogram + Sum points by otlp/convert.go. The
	// histogram read path deltas cumulative series before bucketing
	// so a cumulative heatmap doesn't grow monotonically to the right.
	Temporality string
	// v0.8.328 — persisted series identity (cross-signal pivot).
	// xxhash64 over metric name + sorted dp attrs + service.name/
	// service.instance.id, computed per datapoint by
	// otlp.SeriesFingerprint. Joins metric_points ↔ exemplars so the
	// metric→trace pivot is a primary-key scan. 0 = legacy row
	// (pre-v0.8.328) — readers treat 0 as "no identity", never match it.
	SeriesFingerprint uint64
	// v0.9.106 (F2) — OTLP Sum monotonicity (1 = monotonic counter, rate/
	// increase-valid; 0 = UpDownCounter, rate meaningless). Default 1 for
	// non-Sum (gauge/histogram; irrelevant — rate filters instrument='sum').
	IsMonotonic uint8
}

MetricPoint is a single metric data point.

type MetricPointRow

type MetricPointRow struct {
	Time  int64   `json:"time"`
	Value float64 `json:"value"`
	Count uint64  `json:"count"`
	Sum   float64 `json:"sum"`
	Attrs string  `json:"attrs"`
}

type MetricQueryFilter

type MetricQueryFilter struct {
	Name    string // metric name (required)
	Service string // shortcut filter on service_name
	// Instance + Engine — v0.9.279, DB receiver drill scoping. service_name
	// cannot do this job: it names the RECEIVER, so every instance of an
	// engine shares one value. See dbInstanceScopeClause for why it takes an
	// engine and why the predicate is an OR.
	Instance    string
	Engine      string
	Filters     []FilterExpr // arbitrary attribute filters (resource.X / span.X also supported)
	GroupBy     []string     // 0..N attribute keys → multi-line series
	Aggregation string       // avg | sum | min | max | last | p50 | p95 | p99 (default: avg)
	From, To    time.Time
	StepSeconds int
	// MaxDataPoints (F1, v0.9.105) — panel pixel width ≈ target bucket count.
	// When > 0 and StepSeconds is auto (≤0), the step is pixel-adaptive
	// (rangeSec/maxDataPoints, snapped) instead of the fixed span ladder, so
	// wide windows expose the sub-bucket resolution OTLP metrics carry. 0 =
	// px unknown → fixed ladder. clampStepToExport still caps the LOWER bound.
	MaxDataPoints int
}

MetricQueryFilter is a Grafana-style query against metric_points. Same shape as SpanMetricFilter but targets the metrics table.

type MetricResolveQuery added in v0.8.51

type MetricResolveQuery struct {
	Source           string            // "spanmetrics" (D2) | "tracemetrics" (D3+)
	Metric           string            // calls_total | duration_milliseconds_bucket | errors_total …
	Agg              string            // rate|count|sum|avg|errors|error_rate|p50|p90|p95|p99
	Filters          map[string]string // key→value, equality matchers
	GroupBy          []string          // 0..N dotted keys; one series per unique tuple
	From, To         time.Time
	StepSeconds      int  // 0 = auto-pick from the window
	IncludeExemplars bool // fold per-bucket slow/error trace_ids into the result
}

MetricResolveQuery is the backend mirror of the frontend MetricQuery descriptor. Filters/GroupBy use the dotted attribute syntax (service.name, span.kind, http.route, status, …) — the same keys the frontend emits and filterexpr.go's wellKnown map already translates.

type MetricResolveResult added in v0.8.51

type MetricResolveResult struct {
	Series      []SpanMetricSeries `json:"series"`
	Tier        string             `json:"tier"` // 1s|10s|1m|operation_summary_5m|spans
	StepSeconds int                `json:"stepSeconds"`
	Exemplars   []MetricExemplar   `json:"exemplars,omitempty"`
}

MetricResolveResult carries the series plus which tier served them (so the caller / operator can see whether the fine rollups or the fallback answered) and the resolved step.

type Monitor

type Monitor struct {
	ID             string `json:"id"`
	Name           string `json:"name"`
	Type           string `json:"type"`          // http | tcp | ssl-cert | keyword | heartbeat
	URL            string `json:"url,omitempty"` // http + keyword
	Method         string `json:"method,omitempty"`
	ExpectedStatus uint16 `json:"expectedStatus,omitempty"`
	TimeoutSec     uint16 `json:"timeoutSec,omitempty"`
	IntervalSec    uint32 `json:"intervalSec"` // active probe interval OR heartbeat grace window
	Enabled        bool   `json:"enabled"`
	HeartbeatToken string `json:"heartbeatToken,omitempty"`
	Target         string `json:"target,omitempty"`        // tcp + ssl-cert (host:port)
	CertWarnDays   uint16 `json:"certWarnDays,omitempty"`  // ssl-cert warn threshold (days)
	Keyword        string `json:"keyword,omitempty"`       // keyword type
	KeywordInvert  bool   `json:"keywordInvert,omitempty"` // keyword type: must-NOT-contain
	CreatedAt      int64  `json:"createdAt"`
}

type MonitorResult

type MonitorResult struct {
	MonitorID string `json:"monitorId"`
	Time      int64  `json:"time"`   // unix ns
	Status    string `json:"status"` // up | down | degraded
	LatencyMs int64  `json:"latencyMs"`
	HTTPCode  uint16 `json:"httpCode,omitempty"`
	Message   string `json:"message,omitempty"`
	Detail    int64  `json:"detail"` // type-specific number (ssl-cert: days remaining; 0/negative are meaningful)
}

type MonitorStats

type MonitorStats struct {
	Uptime1h        float64 `json:"uptime1h"`  // 0..100
	Uptime24h       float64 `json:"uptime24h"` // 0..100
	AvgLatencyMs1h  int64   `json:"avgLatencyMs1h"`
	AvgLatencyMs24h int64   `json:"avgLatencyMs24h"`
	Probes24h       int64   `json:"probes24h"` // sample size for 24h numbers
}

MonitorStats is the rollup the /monitors page renders next to each card — uptime percentage over the last 1h and 24h, plus average latency over the same windows. Computed in a single CH query so a big fleet of monitors doesn't fan out into N round-trips.

type MsgE2E added in v0.8.372

type MsgE2E struct {
	Count    uint64        `json:"count"` // correlated produce→consume pairs in window
	P50Ms    float64       `json:"p50Ms"`
	P95Ms    float64       `json:"p95Ms"`
	P99Ms    float64       `json:"p99Ms"`
	Linkless bool          `json:"linkless,omitempty"` // no span links correlated — SDKs not emitting links
	Series   []MsgE2EPoint `json:"series"`
	// Slowest pair — the drawer's one exemplar pivot (→ /trace?id=consumer).
	SlowestLagMs           float64 `json:"slowestLagMs,omitempty"`
	SlowestConsumerTraceID string  `json:"slowestConsumerTraceId,omitempty"`
	SlowestProducerTraceID string  `json:"slowestProducerTraceId,omitempty"`
}

MsgE2E is the end-to-end produce→consume latency block of the messaging destination detail (v0.8.372). Lag is clamped at 0 server-side — cross- host clock skew can put a consumer start before the producer end, and a negative "latency" reads as a bug, not as skew.

type MsgE2EPoint added in v0.8.372

type MsgE2EPoint struct {
	TimeS int64   `json:"timeS"`
	Count uint64  `json:"count"`
	AvgMs float64 `json:"avgMs"`
}

MsgE2EPoint is one 5-minute bucket of the e2e series: pair count + the bucket's average lag (avg, not a quantile — a 26px sparkline can't show a distribution, and per-bucket plain quantiles wouldn't merge into the overall p50/p95/p99 anyway; those come from the rollup total row).

type MsgKindPoint added in v0.8.364

type MsgKindPoint struct {
	TimeS        int64  `json:"timeS"`
	ProduceCount uint64 `json:"produceCount"`
	ConsumeCount uint64 `json:"consumeCount"`
}

MsgKindPoint is one 5-minute bucket of the messaging detail's produce/consume series (v0.8.364). TimeS is the bucket start in unix seconds; counts are spans in that bucket by span kind.

type MySQLBufferPool added in v0.5.12

type MySQLBufferPool struct {
	PagesData  float64 `json:"pagesData"`
	PagesDirty float64 `json:"pagesDirty"`
	PagesFree  float64 `json:"pagesFree"`
	PagesTotal float64 `json:"pagesTotal"`
	UsagePct   float64 `json:"usagePct"`
	DirtyPct   float64 `json:"dirtyPct"`
}

MySQLBufferPool — InnoDB buffer pool snapshot. Dirty/total is the "how dirty is the cache" signal; usage_pct shows whether the operator should grow innodb_buffer_pool_size.

type MySQLHandlers added in v0.5.12

type MySQLHandlers struct {
	ReadFirstPS   float64 `json:"readFirstPerSec"`
	ReadKeyPS     float64 `json:"readKeyPerSec"`
	ReadNextPS    float64 `json:"readNextPerSec"`
	ReadRndNextPS float64 `json:"readRndNextPerSec"`
	WritePS       float64 `json:"writePerSec"`
}

MySQLHandlers — read_first / read_key are index-driven; read_rnd_next is full-table-scan-driven. A spike in read_rnd_next over read_key means new query plans turned sequential.

type MySQLMetrics added in v0.5.12

type MySQLMetrics struct {
	Instance       string          `json:"instance"`
	Status         string          `json:"status"`
	WindowSeconds  float64         `json:"windowSeconds"`
	Threads        MySQLThreads    `json:"threads"`
	Connections    PGGaugeWithCap  `json:"connections"`
	QuestionsPS    float64         `json:"questionsPerSec"`
	SlowQueriesPS  float64         `json:"slowQueriesPerSec"`
	RowLockWaitsPS float64         `json:"rowLockWaitsPerSec"`
	RowLockTimeSec float64         `json:"rowLockTimeSec"`
	TmpDiskPS      float64         `json:"tmpDiskTablesPerSec"`
	OpenedTblPS    float64         `json:"openedTablesPerSec"`
	BufferPool     MySQLBufferPool `json:"bufferPool"`
	HandlersPS     MySQLHandlers   `json:"handlers"`
	RowOpsPS       MySQLRowOps     `json:"rowOps"`
	ReplicaDelay   float64         `json:"replicaDelaySec"`
	// TopSQL — engine-authoritative heaviest statements from
	// performance_schema (events_statements_summary_by_digest),
	// receiver-side parity with Oracle's V$SQL TopSQL. Empty when
	// the operator hasn't enabled the performance_schema statement
	// scrape — panel renders an empty state.
	TopSQL []DBTopSQL `json:"topSQL"`
}

MySQLMetrics is the receiver-flavoured drill-down for one MySQL instance — analogous to OracleMetrics / PostgresMetrics. Reads from OpenTelemetry mysql receiver metric_points (`mysql.*`).

Operator-relevant signals:

  • Threads (connected / running) + connections cap → load ceiling proximity
  • Buffer-pool usage % + pages_dirty → InnoDB memory health
  • Slow-query rate → easy "is the DB hot?" signal
  • Row-lock waits → contention indicator (MySQL's parallel to Oracle row-lock waits)
  • Handler ratios (read_first vs read_next vs read_rnd_next) → index efficiency proxy
  • Replica delay (seconds behind master) → replication health

type MySQLRowOps added in v0.5.12

type MySQLRowOps struct {
	InsertPS float64 `json:"insertPerSec"`
	UpdatePS float64 `json:"updatePerSec"`
	DeletePS float64 `json:"deletePerSec"`
	SelectPS float64 `json:"selectPerSec"`
}

MySQLRowOps — insert/update/delete/select per sec. Same shape every other engine surfaces; lets the operator pivot between write-heavy and read-heavy DBs.

type MySQLThreads added in v0.5.12

type MySQLThreads struct {
	Connected float64 `json:"connected"`
	Running   float64 `json:"running"`
	Created   float64 `json:"createdPerSec"` // thread cache hit rate proxy
}

MySQLThreads — running vs connected says how many of the connected client threads are actually working right now.

type NegativeFeedbackCall added in v0.9.423

type NegativeFeedbackCall struct {
	Surface   string `json:"surface"`
	CreatedAt int64  `json:"createdAt"` // unix ns
	UserEmail string `json:"userEmail,omitempty"`
	Prompt    string `json:"prompt"`
	Response  string `json:"response,omitempty"`
}

NegativeFeedbackCall — 👎 alan bir cevabın madencilik satırı (v0.9.423).

type NeighborProvider

type NeighborProvider interface {
	Neighbors(service string) []string
}

NeighborProvider is a 1-hop adjacency lookup the correlator passes in. Optional — when nil, only same-service grouping applies (legacy behaviour).

type NeighborStat

type NeighborStat struct {
	Service    string `json:"service"`
	TraceCount int    `json:"traceCount"`
	SpanCount  int    `json:"spanCount"`
}

NeighborStat is one neighbouring service in the service-level topology — the count of distinct sampled traces in which the edge (this neighbour ↔ inspected service) was observed, plus the raw number of crossing call edges.

type NoisyRule added in v0.5.131

type NoisyRule struct {
	RuleID       string  `json:"ruleId"`
	RuleName     string  `json:"ruleName"`
	Severity     string  `json:"severity"`
	OpenCount    uint64  `json:"openCount"`
	MedianDurSec float64 `json:"medianDurSec"`
	LastFiredNs  int64   `json:"lastFiredNs"`
	TotalDurSec  float64 `json:"totalDurSec"`
}

NoisyRule summarises how frequently and how long a rule's problems have been open over a window. Drives the /admin/alert- tuning report — operators looking to silence prod alert spam scan this list to find the loudest rules and tighten them.

type NotificationChannel

type NotificationChannel struct {
	ID          string          `json:"id"`
	Name        string          `json:"name"`
	Type        string          `json:"type"`   // email | slack | webhook
	Config      json.RawMessage `json:"config"` // type-specific
	Enabled     bool            `json:"enabled"`
	MinSeverity string          `json:"minSeverity"` // info | warning | critical
	// MatchRules — routing predicates. Empty / zero-value
	// fields mean "match anything" so the default channel
	// stays a catch-all. Populated arrays AND together: a
	// channel only fires when its services / sreTeams /
	// ownerTeams ALL match the problem's service catalog.
	MatchRules ChannelMatchRules `json:"matchRules,omitempty"`
	CreatedAt  int64             `json:"createdAt"` // unix ns
}

type NotificationLog added in v0.8.247

type NotificationLog struct {
	ID          string `json:"id"`
	SentAt      int64  `json:"sentAt"`      // unix ns
	ChannelKind string `json:"channelKind"` // email|slack|mattermost|teams|zoomchat|webhook|whatsapp
	ChannelName string `json:"channelName"`
	Target      string `json:"target"` // full-fidelity recipient (operator policy); webhook URLs host-only (URL embeds a live credential)
	Subject     string `json:"subject"`
	BodyPreview string `json:"bodyPreview"` // first ~200 chars of the notification body
	RelatedKind string `json:"relatedKind"` // problem|test|runbook|incident|alert|monitor|…
	RelatedID   string `json:"relatedId"`
	OK          bool   `json:"ok"`
	Error       string `json:"error"`
}

NotificationLog is one dispatched notification — the append-only audit trail of every channel send (success AND failure) fanned out by internal/notify. v0.8.241.

The row is IMMUTABLE once written (a send happened at a point in time), so the engine is a plain MergeTree — NOT ReplacingMergeTree. Reads never use FINAL. Retained 90 days via a partition-aligned day TTL (see the CREATE TABLE in store.go migrate()).

type OTLPExemplar added in v0.8.328

type OTLPExemplar struct {
	Fingerprint uint64            `json:"fingerprint"`
	TimeUnixNs  int64             `json:"timeUnixNs"`
	Value       float64           `json:"value"`
	TraceID     string            `json:"traceId"`
	SpanID      string            `json:"spanId"`
	Attrs       map[string]string `json:"attrs,omitempty"`
}

OTLPExemplar is one exemplar row as served to the pivot read path.

type OccurrencePoint added in v0.8.309

type OccurrencePoint struct {
	Time  int64  `json:"time"` // unix ns, bucket start
	Count uint64 `json:"count"`
}

OccurrencePoint is one time-bucket of the "occurrences over time" histogram on the problem detail page — a real server-side COUNT, not a sample. Time is the bucket START in unix ns; Count is how many occurrences of the group landed in [Time, Time+step).

type OpenIncidentRollup added in v0.7.33

type OpenIncidentRollup struct {
	ID            string
	ProblemCount  int
	Unresolved    int
	MaxResolvedAt int64 // unix ns of the latest attached-problem resolution; 0 if none
	// StartedAt (v0.9.332) lets the cascade tell a freshly-created incident
	// whose problem has not attached YET from one that has been sitting
	// unattached for days. Without it, resolving on problemCount == 0 would
	// race every creation.
	StartedAt int64
}

OpenIncidentRollup summarises one open incident's attached-problem state for the cascade-resolution sweep (v0.7.33).

type OpenProblemCounts added in v0.5.274

type OpenProblemCounts struct {
	Critical int `json:"critical"`
	Warning  int `json:"warning"`
	Info     int `json:"info"`
}

OpenProblemCounts is the per-service open-problem tally (v0.5.274). Powers the /services health badge. Counts the max severity per service so the badge can flip yellow on a warning AND red on a critical without a second query.

type OpenProblems added in v0.9.575

type OpenProblems struct {
	// contains filtered or unexported fields
}

OpenProblems — tek FINAL taramanın İKİ indeksi.

v0.9.575 — bu tip, düz map'in ürettiği SESSİZ bir hatayı kapatıyor.

Snapshot her zaman (rule_id|service) ile anahtarlanıyordu, ama deterministik-ID üreten dedektörler (runtime pod denetimleri, DB kapasitesi, paylaşılan exception patlaması) problem ID'siyle arıyordu:

snap["runtime:jvm-gc:odeme-api:pod-abc"]   ← ID, "|" YOK
harita anahtarı: "runtime:jvm-gc|odeme-api" ← rule|service

İki uzay kesişmiyor, dolayısıyla hasOpen DAİMA false kalıyordu. Sonuçları ağır ve hepsi sessiz:

  • "aç" dalı HER TİK yeniden koşuyor → dakikada bir PROBLEM OPENED, incident-attach ve BİLDİRİM (sendOne'da dedup yok)
  • StartedAt her tik sıfırlanıyor → yaş-bazlı eskalasyon ve "4 saattir açık → P1" triyajı hiç ateşlemiyor
  • histerezis kolu (wasOpen) ölü kod
  • "kapat" dalı hiç çalışmıyor; satır ancak stale-sweep ile ve YANLIŞ etiketle ("source silent") kapanıyor
  • acknowledged her tik open'a geri yazılıyor

Kök sebep v0.9.522: FindOpenProblemByID(ctx, xProblemID(...)) çağrıları snap[xProblemID(...)] yapıldı ama ANAHTAR ÇEVRİLMEDİ.

Neden iki AYRI map, tek map'e iki anahtar değil: snapshot'ı DOLAŞAN kod var (anomali resolve geçişi, emekli heap tahliyesi) ve tek map'e çift anahtar koymak her problemi iki kez gösterirdi — bir hatayı düzeltirken başka bir hata.

Neden ID indeksi ByKey'in yerine geçmiyor: reduceLatestProblem bir servisin TÜM pod'larını tek girdiye çöktürüyor (en yeni kazanır), yani per-pod granülerlik orada yok. İki indeks farklı sorulara cevap veriyor ve ikisi de gerekli.

func (*OpenProblems) All added in v0.9.575

func (o *OpenProblems) All() []*Problem

All — her açık problem TAM BİR KEZ. Dolaşan kod bunu kullanmalı; indeks map'lerini dolaşmak çift sayım üretir.

func (*OpenProblems) ByID added in v0.9.575

func (o *OpenProblems) ByID(id string) *Problem

ByID — deterministik problem ID araması (per-pod granülerlik).

func (*OpenProblems) ByKey added in v0.9.575

func (o *OpenProblems) ByKey(ruleID, service string) *Problem

ByKey — (kural, servis) araması. nil alıcı güvenli: snapshot hatasında çağıranlar nil geçiyor ve "açık problem yok" davranışı korunuyor.

func (*OpenProblems) Len added in v0.9.575

func (o *OpenProblems) Len() int

Len — açık problem sayısı (tekrarsız).

type OperationSummary

type OperationSummary struct {
	Name       string   `json:"name"`
	SpanCount  uint64   `json:"spanCount"`
	ErrorCount uint64   `json:"errorCount"`
	ErrorRate  float64  `json:"errorRate"`
	AvgMs      float64  `json:"avgDurationMs"`
	P50Ms      float64  `json:"p50DurationMs"`
	P95Ms      float64  `json:"p95DurationMs"`
	P99Ms      float64  `json:"p99DurationMs"`
	Apdex      float64  `json:"apdex"`
	Sparkline  []uint64 `json:"sparkline,omitempty"`
	// v0.5.392 — companion error + p99 sparklines aligned to the
	// same SparklineBuckets grid as Sparkline. Drives the per-row
	// metric drill-in modal on the service detail page so the
	// operator reads RED dimensions side-by-side without leaving
	// the table. Both fields are optional (the raw-spans fallback
	// path doesn't always compute them); UI tolerates absence.
	ErrorsSparkline []uint64  `json:"errorsSparkline,omitempty"`
	P99Sparkline    []float64 `json:"p99Sparkline,omitempty"`
	// v0.9.60 (Elastic-parity Operations) — latency hücresinin
	// percentile-seçicili sparkline'ı için avg/p50/p95 serileri (p99
	// yukarıda zaten var); aynı SparklineBuckets ızgarası.
	AvgSparkline []float64 `json:"avgSparkline,omitempty"`
	P50Sparkline []float64 `json:"p50Sparkline,omitempty"`
	P95Sparkline []float64 `json:"p95Sparkline,omitempty"`
	// compare=prior alanları (Endpoints deseninin operations karşılığı):
	// bir-önceki-eş-pencerenin skalerleri + calls/errors gölge serileri.
	// HasPrior 0-değer belirsizliğini çözer (yeni operasyon ≠ sıfırlı
	// eski operasyon) — TrendDelta'nın NEW rozeti buna bakar.
	HasPrior             bool     `json:"hasPrior,omitempty"`
	PriorSpanCount       uint64   `json:"priorSpanCount,omitempty"`
	PriorErrorCount      uint64   `json:"priorErrorCount,omitempty"`
	PriorErrorRate       float64  `json:"priorErrorRate,omitempty"`
	PriorAvgMs           float64  `json:"priorAvgDurationMs,omitempty"`
	PriorP50Ms           float64  `json:"priorP50DurationMs,omitempty"`
	PriorP95Ms           float64  `json:"priorP95DurationMs,omitempty"`
	PriorP99Ms           float64  `json:"priorP99DurationMs,omitempty"`
	PriorSparkline       []uint64 `json:"priorSparkline,omitempty"`
	PriorErrorsSparkline []uint64 `json:"priorErrorsSparkline,omitempty"`
}

OperationSummary is one row of the per-operation aggregate shown on the service detail page. Same shape as ServiceSummary but keyed by span name within a single service. Apdex is computed against the same 200ms threshold used by GetServices so the numbers are comparable.

Sparkline carries a call-rate histogram (uint64 per bucket) over the same window as the aggregate row. Length is ≤ SparklineBuckets — the sparklineGrid helper (repo.go) floors the slot width at the source's native grain, so short windows ship fewer, REAL slots. The frontend renders it as an inline SVG (axis derived from array length) so the operator can spot a slow-burn vs. spike pattern at a glance without leaving the table.

type OracleGaugeWithCap added in v0.5.3

type OracleGaugeWithCap struct {
	Usage float64 `json:"usage"`
	Limit float64 `json:"limit"`
}

OracleGaugeWithCap is a (usage, limit) pair — Oracle exposes both as separate metrics (oracledb.sessions.usage, oracledb.sessions.limit). The frontend renders these as a progress bar so the operator sees "67/200 sessions" at a glance.

type OracleMetrics added in v0.5.3

type OracleMetrics struct {
	Instance      string  `json:"instance"`
	Synthetic     bool    `json:"synthetic"`
	WindowSeconds float64 `json:"windowSeconds"`
	// Status — "up" when any oracledb.* metric_points exist in
	// the window; "down" otherwise. Mirrors the Oracle Grafana
	// dashboard's database-alive indicator (its `oracledb_up`
	// stat panel) — the first thing an SRE looks at when paged.
	Status          string             `json:"status"`
	Sessions        OracleSessions     `json:"sessions"`
	Processes       OracleGaugeWithCap `json:"processes"`
	CPUTimeSec      float64            `json:"cpuTimeSec"`
	PGAMemoryBytes  float64            `json:"pgaMemoryBytes"`
	SGAMemoryBytes  float64            `json:"sgaMemoryBytes"` // shared global area
	LogicalReadsPS  float64            `json:"logicalReadsPerSec"`
	PhysicalReadsPS float64            `json:"physicalReadsPerSec"`
	CacheHitPct     float64            `json:"cacheHitPct"`
	HardParsesPS    float64            `json:"hardParsesPerSec"`
	ParseCallsPS    float64            `json:"parseCallsPerSec"`
	ExecutionsPS    float64            `json:"executionsPerSec"`
	UserCommitsPS   float64            `json:"userCommitsPerSec"`
	RollbacksPS     float64            `json:"userRollbacksPerSec"`
	TransactionsPS  float64            `json:"transactionsPerSec"`
	// Row-lock waits per second. Concurrency wait class subset —
	// the canonical Oracle "is something blocked behind a long
	// transaction" indicator. Surfaced as its own KPI because
	// SREs page off this independently of the broader wait-class
	// distribution.
	RowLockWaitsPS float64 `json:"rowLockWaitsPerSec"`
	// Top wait classes over the window, descending by accumulated
	// time. Mirrors the Grafana "System Wait Classes" panel which
	// breaks down where the DB is actually spending its time.
	// SREs read this as the answer to "the DB is slow — slow at
	// what?" (network? user_io? commit?).
	WaitClasses []OracleWaitClass `json:"waitClasses"`
	// Top SQL by total elapsed seconds in the window. Mirrors
	// Grafana's `oracledb_top_sql_elapsed` panel — the heaviest
	// statements in the DB's own measurement, complementary to
	// our span-derived db_statement top list (which only sees
	// what the application traced).
	TopSQL      []OracleSQL        `json:"topSQL"`
	Tablespaces []OracleTablespace `json:"tablespaces"`
}

OracleMetrics is the OracleDB-receiver-flavoured drill-down payload — what the operator sees when expanding a row whose db.system = "oracle" on /databases. The numbers come from the OpenTelemetry oracledb receiver, which scrapes V$ views on the database itself and publishes them as oracledb.* instrument-shaped metric_points.

When the receiver isn't wired up (or the operator is still proving the integration on a staging cluster), we fall back to deterministic synthetic values so the UI doesn't look empty — Synthetic=true tells the frontend to render a "demo data" badge over the panel.

The two cumulative gauges (logical/physical reads) get converted to per-second rates server-side; the operator reads "37k logical reads/sec" rather than the raw monotonic counter that Oracle exposes.

type OracleSQL added in v0.5.4

type OracleSQL struct {
	SQL          string  `json:"sql"`
	ElapsedSec   float64 `json:"elapsedSec"`
	Executions   uint64  `json:"executions"`
	AvgElapsedMs float64 `json:"avgElapsedMs"`
}

OracleSQL captures one row of the Top SQL view. ElapsedSec is total cumulative elapsed time in the window; Executions is the run count. The SRE reads "executions × avg_elapsed" to decide whether a slow statement is slow because it runs constantly or because each run is heavy.

type OracleSessions added in v0.5.4

type OracleSessions struct {
	Usage    float64 `json:"usage"`
	Limit    float64 `json:"limit"`
	Active   float64 `json:"active"`
	Inactive float64 `json:"inactive"`
}

OracleSessions extends the basic gauge with an active/inactive split — the SRE's first triage question after "how many sessions" is "of those, how many are doing work right now?".

type OracleTablespace added in v0.5.3

type OracleTablespace struct {
	Name      string  `json:"name"`
	UsedBytes float64 `json:"usedBytes"`
	MaxBytes  float64 `json:"maxBytes"`
	UsedPct   float64 `json:"usedPct"`
}

OracleTablespace is one row of the per-tablespace size table. oracledb.tablespace_size.usage / .limit are dimensioned by the "tablespace_name" attribute, so the operator can spot a specific tablespace running out of room (the #1 reason an Oracle DBA gets paged at 3am).

type OracleWaitClass added in v0.5.4

type OracleWaitClass struct {
	Name   string  `json:"name"`
	PerSec float64 `json:"perSec"`
}

OracleWaitClass is one row of the wait-class distribution. PerSec is computed as (sum of cumulative wait time over window) / window seconds — i.e. average waiting-seconds per real-time second. A value of 1.0 means one full second of wait per second elapsed: a single concurrent client fully blocked on this class.

type PGBgwriter added in v0.5.12

type PGBgwriter struct {
	BuffersAllocatedPS  float64 `json:"buffersAllocatedPerSec"`
	BuffersCheckpointPS float64 `json:"buffersCheckpointPerSec"`
	BuffersBgwriterPS   float64 `json:"buffersBgwriterPerSec"`
	BuffersBackendPS    float64 `json:"buffersBackendPerSec"`
}

PGBgwriter is the background-writer slice that drives buffer allocation efficiency. Surfaces the three rates an operator cares about: allocated (new buffers), via_checkpoint (clean shutdown evictions), and via_bgwriter (proactive). A workload where via_backend climbs vs via_bgwriter says the bgwriter isn't keeping up — actionable signal.

type PGDatabase added in v0.5.12

type PGDatabase struct {
	Name       string  `json:"name"`
	SizeBytes  float64 `json:"sizeBytes"`
	CommitsPS  float64 `json:"commitsPerSec"`
	RollbackPS float64 `json:"rollbacksPerSec"`
	BackendCt  float64 `json:"backendCount"`
}

PGDatabase is one row of the per-database breakdown — pg's equivalent of Oracle's tablespace table. Size + commit/ rollback activity + live-row count let the operator spot the one DB that's both biggest and busiest.

type PGGaugeWithCap added in v0.5.12

type PGGaugeWithCap struct {
	Usage float64 `json:"usage"`
	Limit float64 `json:"limit"`
}

PGGaugeWithCap is the (usage, limit) pair pattern shared with Oracle's panel. Frontend renders as a progress bar so the operator sees "47/100 connections" at a glance.

type PGLockEntry added in v0.5.12

type PGLockEntry struct {
	Mode  string  `json:"mode"`
	Count float64 `json:"count"`
}

PGLockEntry is per-lock-mode count: AccessShareLock / RowExclusiveLock / ShareLock / etc. The Oracle parallel is the wait-class breakdown — answers "where is contention concentrated".

type Point

type Point struct {
	TimeNs int64   `json:"t"`
	Value  float64 `json:"v"`
}

type PostgresMetrics added in v0.5.12

type PostgresMetrics struct {
	Instance      string         `json:"instance"`
	Status        string         `json:"status"` // up / down
	WindowSeconds float64        `json:"windowSeconds"`
	Backends      PGGaugeWithCap `json:"backends"` // current connections + max_connections
	CommitsPS     float64        `json:"commitsPerSec"`
	RollbacksPS   float64        `json:"rollbacksPerSec"`
	DeadlocksPS   float64        `json:"deadlocksPerSec"`
	BlocksReadPS  float64        `json:"blocksReadPerSec"`
	BlocksHitPS   float64        `json:"blocksHitPerSec"`
	CacheHitPct   float64        `json:"cacheHitPct"` // derived: hit/(hit+read)
	TempFilesPS   float64        `json:"tempFilesPerSec"`
	TempBytesPS   float64        `json:"tempBytesPerSec"`
	WALAgeSec     float64        `json:"walAgeSec"`
	WALLagBytes   float64        `json:"walLagBytes"`
	ReplDelaySec  float64        `json:"replicationDelaySec"`
	BgwriterPS    PGBgwriter     `json:"bgwriter"`
	Databases     []PGDatabase   `json:"databases"`
	Locks         []PGLockEntry  `json:"locks"`
	// TopSQL — engine-authoritative heaviest statements from
	// pg_stat_statements (receiver-side parity with Oracle's
	// V$SQL TopSQL). Empty when the operator hasn't enabled the
	// pg_stat_statements scrape — the panel renders an empty
	// state, same no-fake-data policy as the rest of the panel.
	TopSQL []DBTopSQL `json:"topSQL"`
}

PostgresMetrics is the receiver-flavoured drill-down for one Postgres instance — what an operator sees when expanding a row whose db.system="postgresql" on /databases.

Numbers come from the OpenTelemetry postgresql receiver, which scrapes pg_stat_database / pg_stat_replication / pg_locks and publishes them as postgresql.* metric_points. When no data is in flight the panel renders zeros + a DOWN status badge (same pattern as Oracle in v0.5.8 — no demo synthetic).

The cumulative counters (commits / rollbacks / blks_read / blks_hit / deadlocks) get window-derived rates server-side; the operator reads "847 commits/sec" not the raw monotonic counter pg exposes.

type Problem

type Problem struct {
	ID        string  `json:"id"`
	RuleID    string  `json:"ruleId"`
	RuleName  string  `json:"ruleName"`
	Severity  string  `json:"severity"`
	Service   string  `json:"service"`
	Metric    string  `json:"metric"`
	Value     float64 `json:"value"`
	Threshold float64 `json:"threshold"`
	Status    string  `json:"status"` // open | resolved
	// Pod (v0.9.403) — runtime pod denetimlerinde alarmın pod kimliği;
	// diğer üreticilerde boş. Service GERÇEK servis kalır (v0.9.401).
	Pod         string `json:"pod,omitempty"`
	Description string `json:"description"`
	// Assignee (v0.5.209) — free-form string, two flavours:
	//   • team name auto-set from service_metadata.owner_team
	//     when the problem opens (so "payments" surfaces without
	//     an operator action)
	//   • email of a specific operator after manual claim/assign
	// Empty = unassigned. Operator-editable via PATCH
	// /api/problems/{id}/assignee.
	Assignee   string `json:"assignee,omitempty"`
	StartedAt  int64  `json:"startedAt"` // unix ns
	ResolvedAt *int64 `json:"resolvedAt,omitempty"`
	// RunbookURL — composed at read time from the firing
	// alert rule (preferred) or the service catalog metadata
	// (fallback). Not stored on the problems table; the URL
	// is operator-curated and likely to change between when
	// the problem opened and when an oncall reads it. NEVER
	// scanned from CH — populated by EnrichProblems.
	RunbookURL string `json:"runbookUrl,omitempty"`
	// Clusters — k8s/openshift cluster names this problem's
	// service was active in around the time of the alert.
	// Populated at READ time from recent span activity (NOT
	// stored on the problems table). Empty when the service
	// hasn't carried a cluster attribute. Multi-cluster
	// services typically list 2-3 names; the UI renders
	// chips so the oncall sees "this fires on eu-west AND
	// eu-central" at a glance.
	Clusters []string `json:"clusters,omitempty"`
	// OwnerTeam / SRETeam (v0.8.290) — the owning + reliability
	// team for the firing service, pulled from the operator-
	// curated service catalog at READ time (NOT stored on the
	// problems row — a catalog edit reflects on the next refresh
	// without rewriting history). Empty when the service has no
	// catalog entry. Populated by EnrichProblemsWithTeams; powers
	// the owner/SRE team filters on /problems, mirroring the inbox.
	OwnerTeam string `json:"ownerTeam,omitempty"`
	SRETeam   string `json:"sreTeam,omitempty"`
	// RecentDeploy — most recent observed service.version
	// transition for this service in the window leading up
	// to the problem firing, or nil. The AI explain /
	// runbook prompts use this signal to ask "did a deploy
	// just happen?", and the UI surfaces it as a small
	// "deployed v1.2.3 · 6 min before" tag next to the
	// problem row. Populated at READ time from spans (NOT
	// stored on the problems table) — the deploy might be
	// confirmed retroactively after the row was written.
	RecentDeploy *RecentDeploy `json:"recentDeploy,omitempty"`
	// Priority (v0.5.210) — computed at read time from severity +
	// breach magnitude + deploy proximity. Three buckets:
	//   • P1 — handle now (critical + significant overshoot
	//     OR critical + just deployed). Top of the triage queue.
	//   • P2 — handle today (criticals at minor overshoot, OR
	//     warnings hit by recent deploy / 2x threshold breach).
	//   • P3 — handle when convenient (steady warnings, info-
	//     level rules). Filterable out of the default view.
	// Not stored on the problems table — recomputed every read
	// so a fresh deploy or a worsening value flips the bucket
	// without requiring a re-write of the row.
	Priority string `json:"priority,omitempty"`
	// PriorityReason — short human string explaining the bucket
	// pick ("critical + deploy 4m before", "2.5x threshold").
	// Driven by the same logic that sets Priority; surfaces in
	// the UI tooltip so the rule is auditable, not magic.
	PriorityReason string `json:"priorityReason,omitempty"`
	// AISummary (v0.5.254) — short LLM-generated context blurb
	// answering "why did this fire + what to look at first". Filled
	// asynchronously by the problemExplainer goroutine within ~30s
	// of problem open (critical severity only by default). Empty
	// when the explainer hasn't run yet OR the AI Copilot isn't
	// configured. AISummaryAt is the unix-ns timestamp of the last
	// generation; lets the UI show "AI insight · 12s ago".
	AISummary   string `json:"aiSummary,omitempty"`
	AISummaryAt int64  `json:"aiSummaryAt,omitempty"`
	// RootCause — compact top-suspect summary of the persisted
	// root-cause hypothesis the worker synthesized for this problem
	// (rc #3 of the anomaly → root-cause feature). Attached at READ
	// time by the /problems list handler via a single batch
	// GetHypotheses join (NO per-row fetch); nil when the worker
	// hasn't synthesized a hypothesis for this anchor yet. The
	// RootCauseRibbon renders the collapsed chip from this; the
	// expand fetches the full /rootcause fan-out on demand.
	RootCause *RootCauseSummary `json:"rootCause,omitempty"`
}

func EnrichProblemsWithPriority added in v0.5.210

func EnrichProblemsWithPriority(problems []Problem) []Problem

EnrichProblemsWithPriority computes the P1/P2/P3 triage bucket for every problem in the slice. Pure function over already- loaded fields — no CH round-trip — so this is the last step in the enrichment chain and runs against the post-runbook / cluster / deploy values. Doesn't mutate stored rows; the bucket lives only on the wire so a worsening metric or a fresh deploy flips the rank on the next read.

Blend formula (transparent on purpose — operator sees it in PriorityReason and can argue with it):

P1 (drop-everything) when ANY of:
  • severity = critical AND value ≥ 2x threshold     (significant breach)
  • severity = critical AND deploy ≤ 5min ago        (post-deploy critical)
  • severity = critical AND open ≥ 4h                (stale critical)

P2 (today) when ANY of:
  • severity = critical                              (criticals default to P2)
  • severity = warning  AND value ≥ 2x threshold     (significant warning)
  • severity = warning  AND deploy ≤ 5min ago        (post-deploy warning)

P3 otherwise — steady warnings, info-level rules.

"Value above threshold" only makes sense when comparator is >= / >; for < comparators we use the inverse ratio. info severity always pins to P3.

Reason string is the FIRST trigger that fired — so the tooltip surfaces the most relevant signal (e.g. "deploy 4m ago" beats "1.8x threshold" when both apply, because the former is the more actionable correlate).

func FilterProblemsByPriority added in v0.9.554

func FilterProblemsByPriority(probs []Problem, want []string) []Problem

FilterProblemsByPriority — P1/P2/P3 daraltması (v0.9.554).

ProblemFilter.Priority SQL'de UYGULANMAZ: öncelik okuma anında hesaplanır, CH satırında yoktur (problem.go:594-605). Filtre alanını set edip bu daraltmayı ÇAĞIRMAMAK, argümanın sessizce yok sayılması demek — MCP list_problems aracının v0.9.554 öncesi hatası buydu.

Boş bucket "P3" sayılır: frontend'in bucket'sız satırlar için kullandığı geri düşüş değeriyle aynı, böylece chip davranışı okuma ile render arasında tutarlı kalır.

func SortProblemsByPriority added in v0.9.554

func SortProblemsByPriority(probs []Problem) []Problem

SortProblemsByPriority — P1 → P2 → P3, eşitlikte en yeni önce (v0.9.554). Yerinde sıralar ve aynı dilimi döner.

MCP "Open problems" kaynağının açıklaması "Sorted by priority then recency" DİYORDU ama öncelik hiç hesaplanmadığı için ona göre sıralanması imkânsızdı — liste yalnız started_at DESC geliyordu. Açıklamanın doğru olabilmesi için hem zenginleştirme hem bu sıralama gerekiyor.

Bilinmeyen/boş bucket P3 sayılır: FilterProblemsByPriority ile aynı geri düşüş, iki yerin ayrışmaması için.

type ProblemEscalationConfig added in v0.9.248

type ProblemEscalationConfig struct {
	// Enabled — master switch. False means severity stays wherever
	// the rule that opened the Problem put it. Useful for fleets
	// where "open for 30 minutes" is normal (batch windows,
	// long-running incidents already being worked) and the
	// automatic climb is pure pager noise.
	Enabled bool `json:"enabled"`
	// InfoToWarningSec — age at which an `info` Problem becomes
	// `warning`. Default 900 (15 min).
	InfoToWarningSec int `json:"infoToWarningSec"`
	// WarningToCriticalSec — age at which a `warning` Problem
	// becomes `critical` (and pages anyone subscribed to critical
	// only). Default 1800 (30 min). Must be >= InfoToWarningSec,
	// otherwise an info Problem would reach critical before it ever
	// reached warning; enforced at the API boundary and clamped on
	// read.
	WarningToCriticalSec int `json:"warningToCriticalSec"`
}

ProblemEscalationConfig drives the evaluator's age-based escalation sweep (escalateStaleProblems). An open Problem that nobody acks climbs the severity ladder on its own: info → warning → critical.

Hard-coded at 15 min / 30 min until v0.9.248. Operator-reported: tightening anomaly promotion to get FEWER pages barely helped, because anything that did get through still escalated itself to critical half an hour later and re-fired the notify channel on the way (escalateStaleProblems calls SendProblemAlert on every bump). The knob that was supposed to make things quieter was fighting a constant nobody could see or change.

Zero-value config is the pre-v0.9.248 behaviour, so an install that never opens the settings page keeps escalating exactly as before.

func DefaultProblemEscalation added in v0.9.248

func DefaultProblemEscalation() ProblemEscalationConfig

DefaultProblemEscalation mirrors the constants this replaced (escalateInfoToWarningAfter / escalateWarningToCriticalAfter) so upgrading changes nothing until the operator says so.

func NormalizeProblemEscalation added in v0.9.248

func NormalizeProblemEscalation(c ProblemEscalationConfig) ProblemEscalationConfig

NormalizeProblemEscalation patches absent / nonsensical numeric values back into a usable shape. Split out so the API and the tests exercise the same rules the read path uses.

type ProblemFilter

type ProblemFilter struct {
	Status string // "open" | "resolved" | ""
	// NotStatuses — statuses to EXCLUDE, applied in SQL so the narrow bites
	// BEFORE the LIMIT. Status (singular) stays for callers that want exactly
	// one bucket; the two AND together if both are set.
	//
	// v0.9.322 — deliberately an EXCLUSION, not an allow-list. The inbox's Go
	// keepers are written as "anything that isn't resolved still needs a
	// human", so a row with an unrecognised (or empty) status survives on
	// purpose. An allow-list in SQL would contradict that and silently drop
	// exactly those rows — and, worse, drop them from the LIST while the
	// badge still counted them.
	NotStatuses []string
	Service     string
	Severity    string
	// RuleIDPrefix narrows to rules whose id starts with the given
	// string — used by the Anomalies page to surface only the
	// anomaly-detector entries (rule_id = "anomaly:…") and skip
	// the rule-driven Problems.
	RuleIDPrefix string
	// RuleID narrows to the problems of ONE rule, exactly (v0.9.196 —
	// the /watchers history drawer: fire/resolve timeline of a single
	// imported watcher rule). Exact match, unlike RuleIDPrefix.
	RuleID string
	// Priority alanı v0.9.583'te KALDIRILDI.
	//
	// Öncelik okuma anında hesaplanır (v0.5.210), CH kolonu değildir —
	// yani SQL'e İNEMEZ. Alan yıllarca burada durdu ama ListProblems
	// gövdesi ona HİÇ bakmadı: sadece "Go'da daraltmayı unutma" notuydu.
	//
	// Bir filtre struct'ında uygulanmayan bir alan tutmak, bir tuzaktır.
	// İki kez ısırdı:
	//   v0.9.342 — buradaki yorum "ListProblems Limit'i bu filtreden
	//              SONRA uygular" diyordu; hiç uygulamadı.
	//   v0.9.576 — MCP list_problems alanı doldurdu, daraltmayı unuttu;
	//              priority=P1 istemek filoda yüzlerce P1 varken SIFIR
	//              sonuç döndürebiliyordu.
	//
	// Artık çağıran AÇIKÇA iki adımı yazıyor ve ikisi de görünür:
	//   f.Limit = chstore.ProblemScanLimit(page, true)   // taramayı aç
	//   rows = chstore.FilterProblemsByPriority(rows, prios)  // daralt
	//
	// Alan olmayınca yanlışlıkla güvenilemez.
	// IDs constrains the result to these problem ids (id IN (…)), applied in
	// SQL. v0.9.343 — the incident explain path resolved attached problems by
	// paging the newest 2000 and keeping the subset it wanted, so an older
	// attached problem silently dropped out of the prompt. Nil = no
	// constraint; empty behaves like Services (constrain to nothing).
	IDs []string
	// Services constrains the result to this set (service IN (…)), applied in
	// SQL so it bites BEFORE the LIMIT. v0.9.342 — the owner/SRE team and
	// cluster filters used to run in Go on the already-capped page; both
	// resolve to a service set up front, so they belong here. Same shape as
	// ExceptionGroupFilter.Services. Nil = no constraint; EMPTY = constrain to
	// nothing (a team with no members must return an empty page, never an
	// unfiltered one).
	Services []string
	// Env (v0.8.387 — env-separation Phase 3) narrows to problems
	// whose SERVICE ran in the given deployment environment within
	// the last hour, per the 60s-cached service→env map. Problems
	// carry no env dimension (state table keyed rule+service, values
	// computed over all-env metrics), so this is the only honest env
	// semantics — see env_members.go. Applied in SQL (service IN …)
	// by ListProblems AND CountProblems so list / count / buckets /
	// sidebar badge agree, and Limit bites AFTER the env narrowing.
	// service=” (global log-query) rows always survive. Empty = off.
	Env   string
	Limit int
}

type Profile

type Profile struct {
	ProfileID    string
	ServiceName  string
	HostName     string
	ProfileType  string // cpu, heap, goroutine, alloc
	StartTime    time.Time
	DurationNs   int64
	PprofData    []byte
	SampleCount  uint32
	LabelsKeys   []string
	LabelsValues []string
}

Profile is a stored pprof profile (CPU, heap, etc.).

type ProfileFilter

type ProfileFilter struct {
	Service     string
	ProfileType string
	From, To    time.Time
	Limit       int
}

type ProfilePayload added in v0.5.332

type ProfilePayload struct {
	ProfileID   string
	ProfileType string
	StartTime   time.Time
	DurationNs  int64
	HostName    string
	Bytes       []byte
}

ProfilePayload is a profile's raw bytes alongside the metadata needed to attribute hotspots back to a host / window.

type ProfileRow

type ProfileRow struct {
	ProfileID   string `json:"profileId"`
	ServiceName string `json:"serviceName"`
	HostName    string `json:"hostName"`
	ProfileType string `json:"profileType"`
	StartTime   int64  `json:"startTime"` // unix nanos
	DurationMs  int64  `json:"durationMs"`
	SampleCount uint32 `json:"sampleCount"`
}

type PublishedIncident

type PublishedIncident struct {
	IncidentID  string `json:"incidentId"`
	Published   bool   `json:"published"`
	PublicTitle string `json:"publicTitle,omitempty"`
	PublicBody  string `json:"publicBody,omitempty"`
	UpdatedAt   int64  `json:"updatedAt"`
}

type PurgeResult added in v0.8.196

type PurgeResult struct {
	TablesPurged []string `json:"tablesPurged"`      // truncated successfully
	Skipped      []string `json:"skipped,omitempty"` // absent on this install (e.g. op_group MV)
	Errors       []string `json:"errors,omitempty"`  // per-table failures (best-effort: purge continues)
}

PurgeResult reports the outcome of a telemetry purge.

type RCASignature added in v0.9.595

type RCASignature struct {
	Service     string `json:"service"`
	Entity      string `json:"entity"`
	FailureMode string `json:"failureMode"`
	// Confirmations — kaç AYRI vakada 👍 aldı. Ayrı vaka şartı
	// önemli: aynı vakanın tekrar tekrar oylanması bir örüntü değil.
	// uint64 — uniqExact() UInt64 döner (yukarıdaki tip notunun aynısı).
	Confirmations uint64 `json:"confirmations"`
	// LastSeen — unix ns. Bayat bir imza, taze olandan az şey söyler.
	LastSeen int64 `json:"lastSeen"`
}

RCASignature — geçmişte DOĞRULANMIŞ bir kök neden.

type RCAVerdictQuality added in v0.9.591

type RCAVerdictQuality struct {
	Total         uint64  `json:"total"`
	RootCause     uint64  `json:"rootCauseIdentified"`
	Probable      uint64  `json:"probableCause"`
	Insufficient  uint64  `json:"insufficientEvidence"`
	Unparsed      uint64  `json:"unparsed"`
	Repaired      uint64  `json:"repaired"`
	Shielded      uint64  `json:"shielded"`
	AvgConfidence float64 `json:"avgConfidence"`
	ThumbsUp      uint64  `json:"thumbsUp"`
	ThumbsDown    uint64  `json:"thumbsDown"`
}

RCAVerdictQuality — bir pencerede verdict kalitesinin özeti.

Alan seçimi "operatör neye bakıp "bu motor işe yarıyor mu" der" sorusundan türedi: kaç karar verildi, kaçı gerçekten kök neden gösterdi, model ne sıklıkla çözümlenemedi, kalkanlar ne sıklıkla devreye girdi, ve operatör ne dedi. Sayaç alanları uint64 ve bu ZORUNLU: count()/countIf() ClickHouse'ta UInt64 döner. int'e taramak DERLENİR, tüm testlerden GEÇER ve yalnız gerçek CH'de patlar — bu oturumun baskın hata sınıfı (v0.9.543 struct alanı ↔ kolon tipi). ComputeAIStats emsali toUInt64() + uint64 ile açıkça eşliyor; aynısı burada.

type RCAVerdictRecord added in v0.9.591

type RCAVerdictRecord struct {
	ExchangeID string `json:"exchangeId"`
	AnchorKind string `json:"anchorKind"` // problem | anomaly
	AnchorID   string `json:"anchorId"`
	Service    string `json:"service,omitempty"`

	// Verdict — üç enum'dan biri (root_cause_identified |
	// probable_cause | insufficient_evidence). Kalkanlar sonrası.
	Verdict string `json:"verdict"`

	// RCEntity / RCFailMode (v0.9.595) — İMZA. Bir vakayı "kök neden
	// şu varlıkta, şu arıza kipiyle" diye özetleyen ikili; LEARN
	// katmanı geçmiş vakaları bununla eşleştiriyor.
	RCEntity   string `json:"rootCauseEntity,omitempty"`
	RCFailMode string `json:"rootCauseFailureMode,omitempty"`

	// Üç ayrı güven, üçü FARKLI şey ve aynı ekranda buluşuyorlar:
	// Confidence nihai (tavanlanmış), ModelConf modelin beyanı,
	// HypoConf deterministik korelasyon motorunun güveni. Tavanın ne
	// kadar indirdiği ancak üçü birlikte saklanınca görünür.
	Confidence float64 `json:"confidence"`
	ModelConf  float64 `json:"modelConfidence"`
	HypoConf   float64 `json:"hypothesisConfidence"`

	// HypoVersion — verdict'in dayandığı hipotezin sürümü. Yeniden
	// sentez sürümü değiştirir; hangi girdiye bakıldığını bilmeden
	// "bu verdict yanlıştı" geri bildirimi yorumlanamaz.
	HypoVersion uint64 `json:"hypothesisVersion"`

	// Parsed / Repaired — modelin şemaya uyup uymadığı. parsed=false
	// ⇒ deterministik düşüş; o karar MODELİN değil bizim.
	Parsed   bool `json:"parsed"`
	Repaired bool `json:"repaired,omitempty"`

	// ShieldNotes — kalkanların operatöre gösterilen kısa notları.
	ShieldNotes []string `json:"shieldNotes,omitempty"`

	CreatedAt int64 `json:"createdAt"`
}

RCAVerdictRecord — operatöre GÖSTERİLEN verdict'in kaydı.

Modelin ham çıktısı DEĞİL: kalkanlardan geçmiş hâli. Ham çıktı zaten ai_calls.response_sample'da; buradaki kayıt onunla kıyaslanabilsin diye aynı exchange_id'yi taşıyor.

type RagChunk added in v0.8.441

type RagChunk struct {
	// SourceHash (v0.8.442) — url kaynağında sayfa içeriğinin sha256'sı;
	// senkron diff'i "hash değişmediyse yeniden embed etme" bununla yapar.
	SourceHash string    `json:"-"`
	DocID      string    `json:"docId"`
	DocName    string    `json:"docName"`
	Source     string    `json:"source"`              // upload | url
	SourceRef  string    `json:"sourceRef,omitempty"` // url kaynağında sayfa adresi
	UploadedBy string    `json:"uploadedBy,omitempty"`
	ChunkIdx   uint32    `json:"chunkIdx"`
	Content    string    `json:"content"`
	Embedding  []float32 `json:"-"`
}

rag.go — v0.8.438 RAG (doküman soru-cevap) depo katmanı.

Tek tablo: rag_chunks. saved_views'a BİLEREK gitmiyor (invariant #5 istisnası, savunma): embedding Array(Float32) + cosineDistance sorgusu kendi şeklini ister ve bu İÇERİK verisidir, kullanıcı view state'i değil. ReplacingMergeTree(version) ORDER BY (doc_id, chunk_idx): bir dokümanın yeniden yüklenmesi / wiki sayfasının yeniden senkronu aynı (doc_id, chunk_idx) satırlarını yeni version'la değiştirir — diff mantığı bedava. Hacim düşük (yüzlerce doküman × onlarca chunk), FINAL okumaları bütçe içinde.

type RagDocument added in v0.8.441

type RagDocument struct {
	DocID      string `json:"docId"`
	DocName    string `json:"docName"`
	Source     string `json:"source"`
	SourceRef  string `json:"sourceRef,omitempty"`
	UploadedBy string `json:"uploadedBy,omitempty"`
	Chunks     uint64 `json:"chunks"`
	Bytes      uint64 `json:"bytes"`
	UpdatedAt  int64  `json:"updatedAt"` // unix ns
	SourceHash string `json:"-"`
}

RagDocument — liste görünümü (GROUP BY projeksiyonu).

type RagHit added in v0.8.441

type RagHit struct {
	RagChunk
	Score float64 `json:"score"` // 1 - cosineDistance; 1.0 = özdeş
}

RagHit — retrieval sonucu: chunk + benzerlik skoru.

type RecentDeploy added in v0.5.48

type RecentDeploy struct {
	Version    string `json:"version"`
	TimeUnixNs int64  `json:"timeUnixNs"`
	AgeSeconds int64  `json:"ageSeconds"`
}

RecentDeploy is the compact deploy signal attached to a firing Problem. AgeSeconds = problem.StartedAt - deploy.time, rounded — positive means deploy was BEFORE the problem (the typical correlate-with-incident case).

type RecentDeployEntry added in v0.5.277

type RecentDeployEntry struct {
	Service     string `json:"service"`
	Version     string `json:"version"`
	FirstSeenNs int64  `json:"firstSeenNs"`
	SpanCount   uint64 `json:"spanCount"`
}

RecentDeployEntry is one row from GetRecentDeploys — powers the "what changed" page-top banner (v0.5.277).

type RedisClients added in v0.5.12

type RedisClients struct {
	Connected    float64 `json:"connected"`
	Blocked      float64 `json:"blocked"`
	MaxInputBuf  float64 `json:"maxInputBufferBytes"`
	MaxOutputBuf float64 `json:"maxOutputBufferBytes"`
}

RedisClients — total + blocked. Blocked clients are stuck on BLPOP / BRPOP / XREAD etc. — operationally useful to spot long queues vs healthy waiting consumers.

type RedisDB added in v0.5.12

type RedisDB struct {
	Name    string  `json:"name"` // "db0" etc.
	Keys    float64 `json:"keys"`
	Expires float64 `json:"expires"`
}

RedisDB — per-keyspace (db0 / db1 / …) key counts and expire counts. Redis stores up to 16 logical databases per instance; this lets the operator see which one is doing the work.

type RedisMemory added in v0.5.12

type RedisMemory struct {
	UsedBytes          float64 `json:"usedBytes"`
	RSSBytes           float64 `json:"rssBytes"`
	PeakBytes          float64 `json:"peakBytes"`
	MaxBytes           float64 `json:"maxBytes"`
	FragmentationRatio float64 `json:"fragmentationRatio"`
	LuaBytes           float64 `json:"luaBytes"`
	UsagePct           float64 `json:"usagePct"`
}

RedisMemory — used vs max gives saturation %. Fragmentation ratio over 1.5 hints at memory waste; over 5 means restart- to-recover territory. RSS is what Linux sees; used is what Redis allocated.

type RedisMetrics added in v0.5.12

type RedisMetrics struct {
	Instance       string       `json:"instance"`
	Status         string       `json:"status"`
	Role           string       `json:"role"` // master / replica / unknown
	WindowSeconds  float64      `json:"windowSeconds"`
	UptimeSec      float64      `json:"uptimeSec"`
	Clients        RedisClients `json:"clients"`
	Memory         RedisMemory  `json:"memory"`
	CommandsPS     float64      `json:"commandsPerSec"`
	NetInputBPS    float64      `json:"netInputBytesPerSec"`
	NetOutputBPS   float64      `json:"netOutputBytesPerSec"`
	KeyspaceHitsPS float64      `json:"keyspaceHitsPerSec"`
	KeyspaceMissPS float64      `json:"keyspaceMissesPerSec"`
	HitRatePct     float64      `json:"hitRatePct"`
	EvictedPS      float64      `json:"keysEvictedPerSec"`
	ExpiredPS      float64      `json:"keysExpiredPerSec"`
	ReplLagBytes   float64      `json:"replicationLagBytes"`
	ChangesSince   float64      `json:"changesSinceLastSave"`
	SlowlogEntries float64      `json:"slowlogEntries"`
	ConnRefusedPS  float64      `json:"connectionsRejectedPerSec"`
	Keyspaces      []RedisDB    `json:"keyspaces"`
}

RedisMetrics is the receiver-flavoured drill-down for one Redis instance — reads from OpenTelemetry redis receiver (`redis.*`).

Operator-relevant signals:

  • Clients (connected / blocked) → connection saturation
  • Memory used / max + fragmentation → eviction risk
  • Keyspace hits/misses → cache effectiveness
  • Ops/sec by category (commands, instantaneous_ops_per_sec)
  • Evicted/expired key rates → cache turnover signal
  • Replication lag (replica_offset distance from master)
  • Persistence: changes_since_last_save → durability risk
  • Role (master/replica) → topology awareness

type RepeatedSpanFilter added in v0.4.96

type RepeatedSpanFilter struct {
	Filters    []FilterExpr
	GroupBy    []string
	MinRepeats int
	From, To   time.Time
	Limit      int
}

RepeatedSpanFilter is the input shape for the repeated-spans finder. MinRepeats defaults to 5 (operators rarely care about 2-3× duplicates); GroupBy is what defines "same span shape" — typical picks: ["db.statement"] for SQL N+1, ["name", "peer.service"] for chatty RPC, ["http.route"] for endpoint fan-out.

type RepeatedSpanRow added in v0.4.96

type RepeatedSpanRow struct {
	TraceID     string   `json:"traceId"`
	Service     string   `json:"service"`
	RootName    string   `json:"rootName"`
	GroupValues []string `json:"groupValues"` // parallel to filter's GroupBy
	Count       uint64   `json:"count"`
	TotalMs     float64  `json:"totalDurationMs"`
	StartedAt   int64    `json:"startedAt"` // unix ns of the trace's earliest span
}

RepeatedSpanRow is one row of the "find repeated work inside a single trace" view — the N+1 detector. Each row is a (trace_id, group-by tuple) pair where the same span shape appeared `Count` times within the same trace.

Classic use cases:

  • Same SQL run 50× in one request → N+1 query smell
  • Same `<peer.service, name>` chained 30× → chatty downstream call from one upstream request
  • Same HTTP route hit 20× → fan-out loop calling itself

`Service` + `RootName` carry the originating service and the root operation name so the UI doesn't need a second lookup to label the row.

type RetentionSpec

type RetentionSpec struct {
	Spans    string `json:"spans,omitempty"` // e.g. "48h", "30d"
	Logs     string `json:"logs,omitempty"`
	Metrics  string `json:"metrics,omitempty"`
	Profiles string `json:"profiles,omitempty"`
}

RetentionSpec is the on-the-wire shape sent by /api/settings/retention. Empty / zero fields preserve the existing value for that signal.

type Rollout added in v0.8.25

type Rollout struct {
	TimeUnixNs    int64    `json:"timeUnixNs"`
	PodsAdded     int      `json:"podsAdded"`
	PodsRemoved   int      `json:"podsRemoved"`
	ActivePods    int      `json:"activePods"`            // active set size after the rollout
	AddedPods     []string `json:"addedPods,omitempty"`   // up to 5 sample ids
	RemovedPods   []string `json:"removedPods,omitempty"` // up to 5 sample ids
	VersionBefore string   `json:"versionBefore,omitempty"`
	// Kind (v0.8.405): "deploy" when the effective version changed
	// across the churn, "restart" when pods were replaced at the SAME
	// version (reschedule / node drain / crash-restart / HPA wave) —
	// the operator-reported false-deploy class. Deploy chips, markers
	// and impact analysis key on "deploy"; restarts render as their
	// own muted event type.
	Kind         string        `json:"kind"`
	VersionAfter string        `json:"versionAfter,omitempty"`
	Impact       *DeployImpact `json:"impact,omitempty"` // before/after RED, filled by the API layer
}

Rollout is one detected pod-churn event — a 5-minute bucket where the service's active instance set materially turned over (old pods gone + new pods in), i.e. a rollout / restart. Replaces the version-bump deploy marker in environments where service.version is constant (the common case when the build pipeline doesn't set it). v0.8.x — operator-reported: constant service.version made the version-based markers pure noise.

type RolloutsResult added in v0.8.25

type RolloutsResult struct {
	Service  string    `json:"service"`
	Rollouts []Rollout `json:"rollouts"`
	// VersionConstant is true when the effective service.version never
	// changes across the window — the UI uses it to HIDE the version
	// chip/column so "1.0.0" isn't rendered on every surface.
	VersionConstant bool `json:"versionConstant"`
	// InstancesTracked is false when no pod identity (k8s.pod.name /
	// service.instance.id / host_name) is present, so churn can't be
	// computed — the UI shows nothing rather than a misleading empty.
	InstancesTracked bool `json:"instancesTracked"`
}

RolloutsResult is the GetServiceRollouts payload.

type RollupPlan added in v0.9.385

type RollupPlan struct {
	Table        string `json:"table"`
	StepSeconds  int64  `json:"stepSeconds"`
	QuantileMode string `json:"quantileMode"` // "tdigest" | "buckets"
	Reason       string `json:"reason"`
	// PartialWindow: From, seçilen kademenin TTL penceresinden eski —
	// seri baş tarafı boş dönebilir (backfill sınırı, tasarım §6).
	PartialWindow bool `json:"partialWindow,omitempty"`
}

RollupPlan — seçicinin kararı. StepSeconds YANITTA DA döner (grafik-audit Faz B {series, stepSeconds} kontratı, types.ts:1892 emsali).

func PickRollup added in v0.9.385

func PickRollup(q RollupQuery, now time.Time) (RollupPlan, error)

PickRollup — karar sırası (tasarım §5): aile → kademe → step yuvarlama.

type RollupPoint added in v0.9.385

type RollupPoint struct {
	TS     int64   `json:"ts"`
	Calls  uint64  `json:"calls"`
	Errors uint64  `json:"errors"`
	AvgMs  float64 `json:"avgMs"`
	P50Ms  float64 `json:"p50Ms"`
	P95Ms  float64 `json:"p95Ms"`
	P99Ms  float64 `json:"p99Ms"`
}

RollupPoint — tek zaman kovası (bucket başı, unix saniye).

type RollupQuery added in v0.9.385

type RollupQuery struct {
	From, To time.Time
	// MaxDataPoints: panel genişliğinden gelen nokta bütçesi; 0 → 1500.
	MaxDataPoints int
	// Dims: istenen breakdown boyutları (filtre VEYA groupBy'da geçen her
	// boyut). Boş = yalnız zaman serisi (service filtresi dahil değildir —
	// service iki ailede de var).
	Dims []string
	// NeedExactQuant: SLO-hassas percentile (tdigest) şartı. Geniş boyut
	// istenmişse karşılanamaz — Pick hata döner, sessizce bucket'a düşmez.
	NeedExactQuant bool
}

RollupQuery — bir rollup okumasının seçici girdisi.

type RollupSeries added in v0.9.385

type RollupSeries struct {
	Group  string        `json:"group,omitempty"`
	Points []RollupPoint `json:"points"`
}

type RollupSeriesFilter added in v0.9.385

type RollupSeriesFilter struct {
	Service  string
	Kind     string
	Status   string
	Endpoint string
	Channel  string
	Function string
	GroupBy  string
	// MaxGroups: GroupBy'lı sorguda tutulacak en kalabalık grup sayısı;
	// 0 → 20. Kesme yanıtta truncated=true olarak İFŞA edilir.
	MaxGroups int
}

RollupSeriesFilter — okuma filtreleri. Tüm alanlar opsiyonel eşitlik; GroupBy tek boyut ("" = toplam seri). Değerler DAİMA bind-arg; kolon adları rollupCols whitelist'inden çözülür (enterpolasyon yok).

type RootCauseHypothesis added in v0.8.168

type RootCauseHypothesis struct {
	AnchorKind   string        `json:"anchorKind"`             // "anomaly" | "problem"
	AnchorID     string        `json:"anchorId"`               // AnomalyEvent.ID or Problem.ID
	Service      string        `json:"service"`                // the anchor's service
	ComputedAt   int64         `json:"computedAt"`             // unix ns — when the worker synthesized this
	TopSuspect   string        `json:"topSuspect"`             // the #1 candidate's Service (empty = no clear cause)
	TopScore     float64       `json:"topScore"`               // the #1 candidate's blended score
	Confidence   float64       `json:"confidence"`             // 0..1 — honest low/zero when evidence is thin
	Candidates   []ScoredCause `json:"candidates"`             // full ranked list, best first (reused correlator shape)
	RecentDeploy *RecentDeploy `json:"recentDeploy,omitempty"` // the deploy that the fuser weighted, if any
	Version      uint64        `json:"version"`                // set by the table DEFAULT on insert; read back on FINAL
	// Deep (v0.9.516) — P1 soruşturmasının kanıtı + denetim izi. nil =
	// derin soruşturma koşmadı (P2/P3 ya da plan boş). candidates ile
	// AYNI teknik: JSON String kolonu — şekil küçük, bütün okunuyor,
	// alt-alanla sorgulanmıyor.
	Deep *DeepEvidence `json:"deep,omitempty"`
}

type RootCauseSummary added in v0.8.169

type RootCauseSummary struct {
	TopSuspect string  `json:"topSuspect"` // the #1 candidate's service ("" = no clear cause)
	TopScore   float64 `json:"topScore"`   // the #1 candidate's blended score
	Confidence float64 `json:"confidence"` // 0..1 — honest low/zero when evidence is thin
}

RootCauseSummary is the COMPACT slice of a hypothesis the /anomalies and /problems list rows carry so the in-page ribbon (rc #3) renders the "Root cause: <suspect> (NN%)" chip WITHOUT a per-row fetch of the full hypothesis. Only the three fields the collapsed ribbon needs — the expand fetches the full /rootcause fan-out on demand. Attached at read time by the list handlers (same posture as Problem.RecentDeploy / .Priority): never stored on the problems / anomaly_events rows, joined from root_cause_hypotheses on each read.

type RootFlow added in v0.5.103

type RootFlow struct {
	RootService string   `json:"rootService"`
	RootOp      string   `json:"rootOp"`
	TraceCount  uint64   `json:"traceCount"`
	Services    []string `json:"services"`
	P99Ns       uint64   `json:"p99Ns,omitempty"`
}

RootFlow describes one business-level entry point: the root span (kind=server, parent_id=”) groups under (service, op) and counts how many traces start there. Services carries the set of unique services those traces touch, in arbitrary order (use GetFlowTopology to recover the call-graph shape for one flow). P99Ns is the 99th-percentile root-span duration in the window — computed lazily by ComputeFlowsLatencyP99 and merged in at the API layer (v0.5.156). 0 = not yet computed / no samples.

type RouterGap added in v0.9.549

type RouterGap struct {
	// Question — operatörün yazdığı soru (prompt_sample, kırpılmış).
	Question string `json:"question"`
	Count    uint64 `json:"count"`
	LastAt   int64  `json:"lastAt"` // unix ns
	// Users — kaç FARKLI kullanıcı sordu. Tek kişinin ısrarla denediği
	// bir soru ile ekibin tamamının sorduğu soru aynı öncelikte
	// değildir; sayı tek başına bunu ayırt edemez.
	Users uint64 `json:"users"`
}

type Runbook added in v0.6.72

type Runbook struct {
	ID          string        `json:"id"`
	Title       string        `json:"title"`
	Description string        `json:"description,omitempty"` // markdown — the "knowledge"
	Steps       []RunbookStep `json:"steps"`
	Enabled     bool          `json:"enabled"`
	Labels      []string      `json:"labels,omitempty"`
	CreatedBy   string        `json:"createdBy,omitempty"` // creator email
	CreatedAt   int64         `json:"createdAt"`           // unix ns
	UpdatedAt   int64         `json:"updatedAt"`           // unix ns
	// NotifyOnComplete — when set, an execution of this runbook reaching a
	// terminal state (completed/failed) fans out to the configured
	// notification channels + a runbook.complete SSE event. (v0.7.7)
	NotifyOnComplete bool `json:"notifyOnComplete"`
	// NotifyChannels — which notification channel TYPES the completion
	// notification fires to (email / slack / teams / zoomchat / webhook /
	// whatsapp). Empty = email only (the default + back-compat for runbooks
	// created before the selector). Only consulted when NotifyOnComplete is
	// set. (v0.7.22)
	NotifyChannels []string `json:"notifyChannels,omitempty"`
}

Runbook is an operator-authored, executable operational procedure (v0.7.0 — see docs/runbooks-agent-design.md). Modelled on OneUptime's Runbook{steps[]}: an ordered list of steps stepped through during an incident, each run tracked as a RunbookExecution (separate table).

Storage note: runbooks live in a DEDICATED ReplacingMergeTree(version) table — NOT saved_views. saved_views (invariant #5) is the catch-all for per-user VIEW/preset state (dashboards, topology views, alert presets). A Runbook is a first-class SHARED operational entity with its own lifecycle, executions that reference it, and audit coverage — the same class as alert_rules / problems (invariant #4), which each own a table.

type RunbookExecution added in v0.6.73

type RunbookExecution struct {
	ID            string      `json:"id"`
	RunbookID     string      `json:"runbookId"`
	TitleSnapshot string      `json:"titleSnapshot"`
	Status        string      `json:"status"`
	StartedBy     string      `json:"startedBy,omitempty"`
	StartedAt     int64       `json:"startedAt"`             // unix ns
	CompletedAt   int64       `json:"completedAt,omitempty"` // unix ns; 0 = running
	ProblemID     string      `json:"problemId,omitempty"`
	StepStates    []StepState `json:"stepStates"`
	UpdatedAt     int64       `json:"updatedAt"` // unix ns (version source)
}

RunbookExecution is one tracked RUN of a runbook (v0.7.0). It is the durable audit record — "who ran what when, which steps executed". Steps are SNAPSHOTTED onto StepStates at start so template edits never rewrite a historical or in-flight run.

type RunbookStep added in v0.6.72

type RunbookStep struct {
	ID           string            `json:"id"`
	Order        int               `json:"order"`
	Kind         string            `json:"kind"`
	Title        string            `json:"title"`
	Instructions string            `json:"instructions,omitempty"` // markdown
	Expected     string            `json:"expected,omitempty"`     // expected outcome
	Query        string            `json:"query,omitempty"`        // kind=query
	URL          string            `json:"url,omitempty"`          // kind=http
	Method       string            `json:"method,omitempty"`       // kind=http
	Headers      map[string]string `json:"headers,omitempty"`      // kind=http
	Body         string            `json:"body,omitempty"`         // kind=http
	TimeoutMs    int               `json:"timeoutMs,omitempty"`    // kind=http|bash
	Script       string            `json:"script,omitempty"`       // kind=javascript
	Command      string            `json:"command,omitempty"`      // kind=bash
}

RunbookStep is one step in a runbook. kind decides where it runs:

manual     — pauses the run until a responder ticks it off (no agent)
query      — runs a Coremetry CH/Explore query inline (server-side)
http       — outbound HTTP call (PagerDuty/Slack/webhook) — coremetry-agent
javascript — sandboxed JS (goja) — coremetry-agent
bash       — shell command — coremetry-agent

Only the payload group matching kind is populated.

type RuntimeAlertConfig added in v0.9.485

type RuntimeAlertConfig struct {
	// GC pause (pencere ortalaması, ms). Varsayılan 2000/3000 —
	// v0.9.485 öncesi 500/1000'di ve false-pozitif seliydi.
	GCPauseWarnMs float64 `json:"gcPauseWarnMs"`
	GCPauseCritMs float64 `json:"gcPauseCritMs"`
	// GC zaman payı (%, pencere). v0.9.440 varsayılanları korunur —
	// sürdürülmüş pay, pause ortalamasından daha az flap'li bir sinyal.
	GCShareWarnPct float64 `json:"gcShareWarnPct"`
	GCShareCritPct float64 `json:"gcShareCritPct"`
	// Heap doluluk (%, v0.9.426 iki-sinyalli varsayılanlar).
	HeapPostGCWarnPct float64 `json:"heapPostGCWarnPct"`
	HeapPostGCCritPct float64 `json:"heapPostGCCritPct"`
	HeapRawWarnPct    float64 `json:"heapRawWarnPct"`
	HeapRawCritPct    float64 `json:"heapRawCritPct"`
}

RuntimeAlertConfig (v0.9.485, operator-reported: "JVM alertleri daha sıkı olmalı, false pozitif çok") — runtime pod detektörünün eşikleri system_settings'e taşındı. Prod'da 500ms/1000ms GC pause eşikleri filonun yarısını alarma boğuyordu; operatör ölçütü net: "saniye mertebesinde, 2-3 saniye pause olursa sorun GERÇEKTEN vardır". Varsayılanlar bu ölçütle sıkılaştı; alan operatörce ayarlanabilir (AnomalyPromotionConfig şablonu — key başına JSON blob, boot'ta zero-patch).

func DefaultRuntimeAlerts added in v0.9.485

func DefaultRuntimeAlerts() RuntimeAlertConfig

type SLO

type SLO struct {
	ID          string  `json:"id"`
	Name        string  `json:"name"`
	Service     string  `json:"service"`
	SLIType     string  `json:"sliType"`
	Target      float64 `json:"target"`      // 0..1, e.g. 0.99
	WindowDays  uint16  `json:"windowDays"`  // rolling window
	ThresholdMs float64 `json:"thresholdMs"` // latency only
	Operation   string  `json:"operation"`   // optional span-name filter
	CreatedAt   int64   `json:"createdAt"`   // unix ns
}

type SLOForecast added in v0.6.30

type SLOForecast struct {
	BurnRate            float64 `json:"burnRate"`            // short-window burn rate
	BurnWindowSec       int     `json:"burnWindowSec"`       // window the rate was measured over
	BudgetRemaining     float64 `json:"budgetRemaining"`     // 0..1 — copied from status
	HoursToExhaust      float64 `json:"hoursToExhaust"`      // projected; 0 when SafeBurn
	WillBreachWithin24h bool    `json:"willBreachWithin24h"` // operator-attention flag
	SafeBurn            bool    `json:"safeBurn"`            // burnRate ≤ 1, no forecast needed
}

SLOForecast — v0.6.30. Given an SLO + its current short-window burn rate + the remaining error budget, projects when the budget will be fully consumed at the current pace. Operator- facing answer to "is this going to breach before the weekend?"

At BurnRate ≤ 1 the budget grows back faster than it's consumed; HoursToExhaust = +Inf (we represent it as 0 with SafeBurn=true so the UI can render an "OK" pill).

At BurnRate > 1 the math is:

hoursToExhaust = budgetRemaining × (windowDays × 24) / burnRate

rounded down. When that value ≤ 24h, WillBreachWithin24h is flagged so the /slos page can promote the row to the operator's attention without an actual alert wired up yet.

type SLOStatus

type SLOStatus struct {
	Total           uint64  `json:"total"`           // events in window
	Good            uint64  `json:"good"`            // satisfying events
	Bad             uint64  `json:"bad"`             // total - good
	SLI             float64 `json:"sli"`             // good/total, 0..1
	BudgetRemaining float64 `json:"budgetRemaining"` // 0..1, share of error budget left
	BurnRate        float64 `json:"burnRate"`        // current_error_rate / (1 - target)
	Healthy         bool    `json:"healthy"`         // SLI >= target
}

SLOStatus is the computed runtime state of an SLO. Burn rate > 1 means the budget is being consumed faster than its replenishment rate.

type SavedView

type SavedView struct {
	ID          string `json:"id"`
	OwnerID     string `json:"ownerId"` // user.id; "" = team-shared
	Name        string `json:"name"`
	Page        string `json:"page"`        // "traces" | "logs" | "anomalies" | "metrics" | …
	QueryString string `json:"queryString"` // ?key=val&… (no leading ?)
	Pinned      bool   `json:"pinned"`
	CreatedAt   int64  `json:"createdAt"` // unix ns
}

SavedView is a per-user (or team-shared, OwnerID="") named query for one of the SPA's filterable pages (`traces`, `logs`, `anomalies`, `metrics`, …). The query state is the raw URL search string the SPA already encodes — applying a view = restoring that URL. No coupling between server and SPA schemas, no breakage when filters evolve.

type ScoredCause added in v0.8.168

type ScoredCause struct {
	Service string   `json:"service"`
	Score   float64  `json:"score"`
	Hops    int      `json:"hops"`
	Path    []string `json:"path,omitempty"`
	Reason  string   `json:"reason,omitempty"`
}

ScoredCause mirrors correlator.ScoredCause so chstore (the lowest layer) does not import correlator. The correlator's Synthesize fills these and the worker copies the fields across — same names, same JSON tags, so the wire shape is identical whichever side constructs it. Service/Score/Hops/Path match correlator.ScoredCause exactly; Reason is the human-readable "why this rank" line the fuser attaches (e.g. "fresh deploy 4m before onset").

type ServerStat added in v0.9.290

type ServerStat struct {
	Host string `json:"host,omitempty"`

	// ── Memory ──────────────────────────────────────────────────
	OSMemoryTotal     uint64 `json:"osMemoryTotal"`
	OSMemoryAvailable uint64 `json:"osMemoryAvailable"`
	// MemoryResident — the ClickHouse process's RSS, i.e. its share of
	// the node. Compare against OSMemoryTotal to see whether CH is the
	// pressure or merely living next to it.
	MemoryResident uint64 `json:"memoryResident"`
	// MemoryTracking — what CH's own allocator accounting believes is
	// in use. Diverges from RSS by cached/freed-but-unreturned pages.
	MemoryTracking uint64 `json:"memoryTracking"`
	// MaxServerMemory / MaxQueryMemory — the two ceilings that produce
	// a code-241 "Query memory limit exceeded". Surfaced because that
	// error names a number the operator otherwise has to go find on the
	// node; 0 = unlimited.
	MaxServerMemory uint64 `json:"maxServerMemory"`
	MaxQueryMemory  uint64 `json:"maxQueryMemory"`

	// ── CPU ─────────────────────────────────────────────────────
	// Normalised per core, so 1.0 = every core saturated regardless of
	// core count. Rendered as a percentage.
	CPUUser   float64 `json:"cpuUser"`
	CPUSystem float64 `json:"cpuSystem"`
	CPUIOWait float64 `json:"cpuIoWait"`
	LoadAvg1  float64 `json:"loadAvg1"`

	// ── Activity ────────────────────────────────────────────────
	RunningQueries uint64  `json:"runningQueries"`
	RunningMerges  uint64  `json:"runningMerges"`
	UptimeSec      float64 `json:"uptimeSec"`
}

ServerStat is one ClickHouse node's live resource utilisation (v0.9.290, operator ask: "can I see the ClickHouse server's memory and CPU utilisation too"). Companion to DiskStat — same panel, same question one layer up: not "is the data too big" but "is the node under pressure right now".

Everything here comes from system.asynchronous_metrics / system.metrics / system.server_settings, which are in-memory counters. The read is instant and independent of data volume.

func (ServerStat) CPUBusyPct added in v0.9.290

func (s ServerStat) CPUBusyPct() float64

CPUBusyPct sums the non-idle normalised CPU time. Clamped to 100: the three counters are sampled independently and can momentarily sum past 1.0, which must not overflow a gauge.

func (ServerStat) MemoryUsedPct added in v0.9.290

func (s ServerStat) MemoryUsedPct() float64

MemoryUsedPct is how much of the NODE's memory is in use (not just ClickHouse's share). Unknown capacity answers 0 rather than dividing by zero — same rule as DiskStat.UsedPct.

type ServiceAttrRow added in v0.5.381

type ServiceAttrRow struct {
	Key          string   `json:"key"`
	Scope        string   `json:"scope"` // "span" | "resource"
	Occurrences  uint64   `json:"occurrences"`
	SampleValues []string `json:"sampleValues"`
}

ServiceAttrRow surfaces the per-key + sample-value distribution of attrs the operator's spans actually emit for a given service. Powers the new /services/X/attrs view (v0.5.380) — answers "what attrs is my SDK actually putting on these spans" without opening a single trace and squinting.

Scope distinguishes span attrs (per-span data: http.route, db.statement, rpc.method) from resource attrs (per-process data: service.namespace, host.name, k8s.pod.name). The distinction matters when picking attrs to use in queries — resource attrs are stable, span attrs vary per request.

type ServiceClusterStat added in v0.5.14

type ServiceClusterStat struct {
	Cluster    string  `json:"cluster"`
	SpanCount  uint64  `json:"spanCount"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"`
	AvgMs      float64 `json:"avgDurationMs"`
	P99Ms      float64 `json:"p99DurationMs"`
}

ServiceClusterStat is one row of the per-cluster breakdown rendered on the Service detail page when traffic comes from more than one k8s/openshift cluster. Same numeric set the services-list row carries (span count, error rate, p99) plus the cluster identifier so an SRE can spot "same service is slow only on cluster-eu-prod" at a glance.

type ServiceDisplayFilters added in v0.9.345

type ServiceDisplayFilters struct {
	ErrorsOnly bool
	MinSpans   uint64
	MinP99Ms   float64
}

ServiceDisplayFilters are the three /services controls that ran in the BROWSER until v0.9.345, over the 50 rows of the current page. At 1000s of services "Errors only" could empty page 1 while erroring services sat on page 7, and nothing on screen said so.

They are plain aggregate predicates, so they belong in the HAVING where the LIMIT/OFFSET can respect them — the same correction the triage queue's filters got in v0.9.322/330/335/336 and /api/problems in v0.9.342.

func (ServiceDisplayFilters) Active added in v0.9.345

func (f ServiceDisplayFilters) Active() bool

Active reports whether any constraint is set — the caller uses it to decide whether the page's own "filtered on this page only" warning still applies.

type ServiceEdge

type ServiceEdge struct {
	Source    string  `json:"source"`
	Target    string  `json:"target"`
	CallCount uint64  `json:"callCount"`
	ErrorRate float64 `json:"errorRate"`
	AvgMs     float64 `json:"avgMs"`
}

type ServiceEdgePair added in v0.5.304

type ServiceEdgePair struct {
	Caller string
	Callee string

	// Weighted-edge fields — populated by GetServiceAdjacencyWeighted,
	// left zero by GetServiceAdjacency.
	Calls         uint64 // total calls on this edge in the window
	Errors        uint64 // error-status calls on this edge in the window
	SumDurationNs uint64 // summed span duration — window avg = SumDurationNs/Calls
}

ServiceEdgePair is one directed service-to-service edge. The two endpoints (Caller → Callee) are always populated; the weight fields are filled only by GetServiceAdjacencyWeighted.

v0.8.67 (correlator Faz 5) — added the weight fields so the correlator can build a DIRECTED, WEIGHTED adjacency graph (rank a service's downstream deps by error-carrying volume) instead of the symmetric unweighted set it used through Faz 4. The plain GetServiceAdjacency still returns endpoint-only pairs (weights zero) — its only caller, the fusion evidence bundle, needs just the "who calls who" topology, so its lean query is left untouched.

type ServiceEdgeStats

type ServiceEdgeStats struct {
	Service   string  `json:"service"`
	Calls     uint64  `json:"calls"`
	ErrorRate float64 `json:"errorRate"`
	AvgMs     float64 `json:"avgMs"`
	P99Ms     float64 `json:"p99Ms"`
}

type ServiceInstance added in v0.7.111

type ServiceInstance struct {
	ID       string  `json:"id"`       // host_name (pod identity)
	Zone     string  `json:"zone"`     // cloud.availability_zone / k8s zone res-attr, "" if absent
	CPUPct   float64 `json:"cpuPct"`   // 0-100 (utilization fraction × 100)
	MemBytes float64 `json:"memBytes"` // latest RSS / used bytes
	MemPct   float64 `json:"memPct"`   // 0-100 when a memory limit is reported, else 0
	Up       bool    `json:"up"`       // saw a sample within the freshness window
	LastSeen int64   `json:"lastSeen"` // unix ns of the most recent sample
}

ServiceInstance is one pod/host emitting telemetry for a service — the per-pod row in the Service Overview "Instances" card. Latest CPU / memory (and memory % when a limit is reported) plus a liveness flag, derived entirely from metric_points grouped by host_name. No raw-spans scan — invariant #3 stays intact (metric_points is small vs spans).

type ServiceMap

type ServiceMap struct {
	Nodes        []ServiceMapNode `json:"nodes"`
	Edges        []ServiceMapEdge `json:"edges"`
	RemovedNodes []ServiceMapNode `json:"removedNodes,omitempty"`
	RemovedEdges []ServiceMapEdge `json:"removedEdges,omitempty"`
	SampledFrom  int              `json:"sampledFrom"`           // traces actually inspected
	TotalSpans   int              `json:"totalSpans"`            // span count across them
	BaselineAgo  string           `json:"baselineAgo,omitempty"` // e.g. "24h" — echoed for UI labelling
	// TotalNodes / ShownNodes (v0.8.215) — set by pruneServiceMapTopN. When the
	// overview top-N cap trims a large graph, ShownNodes < TotalNodes and the UI
	// shows "showing X of Y services" so the operator knows the map is pruned,
	// not the whole truth.
	TotalNodes int `json:"totalNodes"`
	ShownNodes int `json:"shownNodes"`
}

ServiceMap is the wire format returned to the frontend.

RemovedNodes / RemovedEdges are populated by GetServiceMapWithDiff when a baseline window is supplied: they list services and dependencies that were active in the baseline but have stopped appearing in the current window. Useful for catching "we silently dropped a downstream call" regressions.

type ServiceMapEdge

type ServiceMapEdge struct {
	Caller     string `json:"caller"`
	Callee     string `json:"callee"`
	TraceCount int    `json:"traceCount"`
	SpanCount  int    `json:"spanCount"`
	ErrorCount int    `json:"errorCount"`
	// IsNew is set when the (caller, callee) pair didn't appear
	// in the baseline window. A new edge typically signals either
	// a feature deploy that wired up a previously-decoupled service
	// or a regression where a code path started talking to an
	// unintended dependency.
	IsNew bool `json:"isNew,omitempty"`
}

ServiceMapEdge is a directed call: caller → callee. Weight = number of distinct sampled traces in which the edge appeared (so a one-off edge from a single trace doesn't visually equal a hot path that runs every request). ErrorCount is the count of CALLEE spans on this edge that returned an error status.

type ServiceMapNode

type ServiceMapNode struct {
	Service   string `json:"service"`
	SpanCount int    `json:"spanCount"`
	// ErrorRate is computed across all spans of this service in the
	// sampled traces. Used to colour the node — green=healthy,
	// red=error-heavy — without re-querying per-node.
	ErrorRate float64 `json:"errorRate"`
	// Kind: "" = real service emitting OTel data; "db" =
	// db.system synthesised dependency (redis / oracle / mysql /
	// …); "queue" = messaging.system synthesised dependency
	// (kafka / rabbitmq / …); "external" = peer.service'd HTTP
	// endpoint that isn't an OTel service.
	Kind string `json:"kind,omitempty"`
	// DBSystem / Subkind carries the underlying type so the UI
	// can show "redis" or "postgresql" rather than just "db".
	Subkind string `json:"subkind,omitempty"`
	// DbName (v0.8.297) — dominant db.name for this node's db.system
	// (db_summary_5m via DbNamesBySystem), best-effort read-time
	// enrichment: the pill shows WHICH database ("COREBANK"), not just
	// the engine ("oracle"). db nodes only; empty when the system
	// never reports db.name.
	DbName string `json:"dbName,omitempty"`
	// IsNew is set by GetServiceMapWithDiff when this node didn't
	// appear in the baseline window (e.g. yesterday's same slot).
	// Frontend pulses these green so a freshly-deployed service or
	// newly-discovered dependency stands out at a glance.
	IsNew bool `json:"isNew,omitempty"`
	// Cluster — the k8s/openshift cluster this service ran in
	// during the sampled window. Populated server-side via
	// GetServiceClusterMap as a read-time enrichment so the
	// frontend can group / colour / filter the map by cluster
	// without an N+1 lookup. Empty when the SDK didn't ship a
	// cluster resource attribute. "multi" when the service
	// spans more than one cluster in the window — frontend
	// renders these with a distinct chip so an operator
	// scanning a topology hairball still spots the boundary
	// crossings.
	Cluster string `json:"cluster,omitempty"`
}

ServiceMapNode is one node in the global topology graph. The frontend renders these as nodes in a force-directed layout; node size scales with span count so visually-heavy nodes sit at the top of the operator's attention.

Kind discriminates real OTel services (Kind="") from synthetic "infrastructure" nodes (Kind="db" / "queue" / "external") that represent the things services talk to but that don't emit OTel data themselves. The frontend renders the two kinds with distinct shapes so an operator can tell at a glance whether a node is "your code" or "your dependency".

type ServiceMetadata

type ServiceMetadata struct {
	Service   string `json:"service"`
	OwnerTeam string `json:"ownerTeam,omitempty"`
	// SRETeam is the platform / reliability team that owns
	// the operational health of the service — typically
	// distinct from the product owner team. Surfaces as a
	// second chip on the catalog pill so the oncall who
	// inherits the service knows who to escalate to for
	// infra issues vs feature regressions.
	SRETeam     string `json:"sreTeam,omitempty"`
	Description string `json:"description,omitempty"`
	Repository  string `json:"repository,omitempty"`
	RunbookURL  string `json:"runbookUrl,omitempty"`
	OncallURL   string `json:"oncallUrl,omitempty"`
	// ChatChannel — Zoom Chat / Mattermost / Slack channel
	// for the team. Renamed from slack_channel because the
	// catalog target cluster runs on Zoom Chat; the legacy
	// slack_channel column still backfills here on read so
	// pre-rename rows keep showing.
	ChatChannel string `json:"chatChannel,omitempty"`
	// CustomLinks — operator-bolted-on links per service
	// (Grafana / Kibana / Sensei / status page / etc.).
	// Stored as a JSON-encoded array in custom_links column.
	CustomLinks []CustomLink `json:"customLinks,omitempty"`
	UpdatedAt   int64        `json:"updatedAt"` // unix nanoseconds
	// OwnerTeamAuto / SRETeamAuto (v0.8.100) — the last value the span-attr
	// team-deriver auto-wrote for each field. Deriver-managed, NOT human-edited
	// (excluded from JSON). The deriver owns owner_team/sre_team while they
	// equal these (or are empty); a human edit (value != auto) pins the field.
	OwnerTeamAuto string `json:"-"`
	SRETeamAuto   string `json:"-"`
	// Namespace (v0.8.436) — the service's logical namespace, derived
	// from service.namespace / k8s.namespace.name span resource attrs
	// (deriver tick shared with teams; NamespaceAuto is the provenance
	// pin — a manual edit where value != auto stops the deriver).
	// Powers the flow-graph namespace grouping.
	Namespace     string `json:"namespace,omitempty"`
	NamespaceAuto string `json:"-"`
	// Deployment (v0.9.25) — k8s.deployment.name'den türetilen iş
	// yükü adı; Servis→Cluster pivotunun &deployment= hassasiyeti.
	Deployment     string `json:"deployment,omitempty"`
	DeploymentAuto string `json:"-"`
}

ServiceMetadata is operator-curated context for a single service: owner team, oncall channel, runbook URL, repo, and a free-text description. Joins to the spans table by service_name. Lives in a tiny ReplacingMergeTree separate from the spans hot path because the data doesn't fit a span resource attribute (it's per-team-decided, not per- span-emitted) and the row count is bounded by service count not span count.

All fields except `service` are optional; an empty row surfaces as "no metadata yet" on the frontend with an edit CTA so the catalog grows organically.

type ServiceRuntime

type ServiceRuntime struct {
	Language       string `json:"language,omitempty"`       // telemetry.sdk.language: "go", "java", "dotnet", "nodejs", "python"
	SDKVersion     string `json:"sdkVersion,omitempty"`     // telemetry.sdk.version
	RuntimeName    string `json:"runtimeName,omitempty"`    // process.runtime.name: "OpenJDK Runtime Environment", "go", ".NET"
	RuntimeVersion string `json:"runtimeVersion,omitempty"` // process.runtime.version: "21.0.1+12", "go1.22.5", "8.0.4"
	RuntimeDesc    string `json:"runtimeDesc,omitempty"`    // process.runtime.description: full free-text
	Host           string `json:"host,omitempty"`           // host.name (last seen)
	OS             string `json:"os,omitempty"`             // os.type
	Service        string `json:"service"`                  // pass-through
}

ServiceRuntime is the technology fingerprint of a service — what SDK / language / runtime version is emitting telemetry. Powers the small "Java OpenJDK 21" / "Go 1.22" / ".NET 8.0" badge above the infra panel on /service?name=… so the operator instantly knows what stack they're investigating before they pick a runtime-specific debugger.

All fields are optional — many SDKs only set a subset. The frontend renders whatever is non-empty in priority order: language + runtime version > runtime name > SDK version.

type ServiceSummary

type ServiceSummary struct {
	Name       string  `json:"name"`
	SpanCount  uint64  `json:"spanCount"`
	ErrorCount uint64  `json:"errorCount"`
	ErrorRate  float64 `json:"errorRate"`
	AvgMs      float64 `json:"avgDurationMs"`
	P99Ms      float64 `json:"p99DurationMs"`
	// Apdex score in [0, 1]. 1 = all satisfying; 0 = all frustrated.
	//   satisfied  : duration ≤ T
	//   tolerating : T < duration ≤ 4T
	//   frustrated : duration > 4T
	//   apdex = (satisfied + tolerating/2) / total
	Apdex            float64 `json:"apdex"`
	ApdexThresholdMs float64 `json:"apdexThresholdMs"`
	// Health (v0.5.274) — auto-scored red/yellow/green badge.
	// Computed at READ time in the api layer from errorRate +
	// open problem counts. NOT stored in the MV; recomputed on
	// every /api/services response so a freshly-opened
	// critical flips the badge immediately. The HealthReason
	// string explains the rule that fired so the operator
	// can argue with the verdict.
	Health       string `json:"health,omitempty"`       // "" | "green" | "yellow" | "red"
	HealthReason string `json:"healthReason,omitempty"` // short string e.g. "1 open critical"
	OpenProblems int    `json:"openProblems,omitempty"` // count of all open problems on this service
}

type ServiceSummaryRow

type ServiceSummaryRow struct {
	Service     string  `json:"service"`
	BucketStart int64   `json:"bucketStart"` // unix ns
	SpanCount   uint64  `json:"spanCount"`
	ErrorCount  uint64  `json:"errorCount"`
	AvgMs       float64 `json:"avgMs"`
	P50Ms       float64 `json:"p50Ms"`
	P95Ms       float64 `json:"p95Ms"`
	P99Ms       float64 `json:"p99Ms"`
}

ServiceSummaryRow is one 5-minute bucket of pre-aggregated stats for a single service, sourced from the service_summary_5m materialized view. Use for time-bucketed reads that span hours/days — the MV merges AggregateFunction states cheaply at query time, no raw spans scan.

type ServiceTeams added in v0.8.95

type ServiceTeams struct {
	OwnerTeam string
	SRETeam   string
}

ServiceTeams is the dominant owner/sre team pair derived for one service from its span attributes.

type ServiceTopologyEdge added in v0.5.102

type ServiceTopologyEdge struct {
	ParentService  string   `json:"parentService"`
	ChildNode      string   `json:"childNode"`
	NodeKind       string   `json:"nodeKind"` // "service" | "db" | "queue" | "cache" | "external"
	Protocol       string   `json:"protocol"` // "http" | "rpc" | "kafka" | "db" | "internal"
	TopLabels      []string `json:"topLabels"`
	DistinctLabels uint64   `json:"distinctLabels"`
	Calls          uint64   `json:"calls"`
	// v0.5.393 — errors + error-rate per edge so the topology
	// page can tint hot edges red and surface (errors / calls)
	// in the tooltip. The errors column landed on
	// topology_edges_5m in v0.5.367; we now pipe it through to
	// the read path so the operator reads "is this edge
	// breaking?" directly off the graph rather than having to
	// click into the dependent service.
	Errors    uint64  `json:"errors"`
	ErrorRate float64 `json:"errorRate"` // (errors / calls) * 100
	AvgMs     float64 `json:"avgMs"`     // window-wide avg ms (sum/calls)
	P99Ms     float64 `json:"p99Ms"`     // conservative window p99
	// v0.5.409 — known external SaaS / cloud annotation. When
	// NodeKind == "external" and the peer host matches the
	// external_catalogue, these carry the human-friendly display
	// name + category (payments / messaging / cdn / etc.) so the
	// frontend can render a colored badge. Empty when the peer
	// isn't in the catalogue — UI falls back to the raw
	// `ext:<peer>` label.
	ExtDisplay string `json:"extDisplay,omitempty"`
	ExtKind    string `json:"extKind,omitempty"`
	// v0.5.410 — environment annotation per side. Resolved at
	// aggregation time from deployment.environment /
	// service.namespace / k8s.namespace.name resource attrs.
	// Display-only — same-name service in different envs still
	// merges in the MV's ReplacingMergeTree dedup (env not in
	// ORDER BY); a strict per-env split needs a table rebuild
	// and is deferred. Empty when no env attr was present on
	// the underlying spans.
	ParentEnv string `json:"parentEnv,omitempty"`
	ChildEnv  string `json:"childEnv,omitempty"`
	// v0.5.414 — prior-window comparison values for the
	// what-changed banner. Populated only when the API caller
	// asks for the compare=prior variant. Frontend derives the
	// delta + surfaces edges whose errorRate or p99 jumped ≥2×.
	PriorCalls  uint64  `json:"priorCalls,omitempty"`
	PriorErrors uint64  `json:"priorErrors,omitempty"`
	PriorAvgMs  float64 `json:"priorAvgMs,omitempty"`
	PriorP99Ms  float64 `json:"priorP99Ms,omitempty"`
}

ServiceTopologyEdge collapses the per-operation join into a service-level interaction with a protocol family. One edge per (parent_service, child_node, protocol) so the UI can draw "service A → service B via HTTP" and "service A → postgres via db" as two separate strands even when they share endpoints.

TopLabels carries up to 5 distinct method+endpoint strings by frequency — the renderer shows TopLabels[0] inline on the edge and surfaces the rest on click-to-expand without a second round-trip. DistinctLabels is the global count, which lets the UI render "(N endpoints)" hints even when TopLabels truncates.

type ServicesQuery added in v0.9.345

type ServicesQuery struct {
	Since         time.Duration
	From, To      time.Time
	NameMatch     string
	ServiceIn     []string
	Sort, Dir     string
	Limit, Offset int
	Cluster, Env  string

	Display ServiceDisplayFilters
}

cluster (when non-empty) narrows results to spans whose derived k8s/openshift cluster name matches exactly. The match is on the resolved string returned by clusterDeriveExpr — operators pass the cluster name they see in the /api/clusters dropdown. env (when non-empty) narrows to spans.deploy_env — the global Topbar env picker (v0.8.385, env-separation Phase 2). Same raw-fallback semantics as cluster, but CHEAPER: deploy_env is a typed LowCardinality column, no indexOf derive needed. ServicesQuery is the /services read, as a struct.

v0.9.345 — GetServicesFilteredIn had grown to twelve positional arguments and the three filters this release pushes into SQL would have made fifteen. The struct exists so the next filter is a named field rather than another unlabelled bool three commas deep. GetServicesFilteredIn stays as a thin wrapper: its six callers are unchanged.

type SharedExceptionBurst added in v0.9.572

type SharedExceptionBurst struct {
	// Type — exception sınıfı (java.sql.SQLRecoverableException gibi).
	Type string `json:"type"`
	// Message — temsilci mesaj (ilk grubunki). Gruplar arasında
	// değişebilir; tanı için tipin kendisi taşıyıcı olan.
	Message string `json:"message"`
	// Services — etkilenen servisler, alfabetik (deterministik kimlik).
	Services []string `json:"services"`
	// FirstSeen / LastSeen — patlamanın ucu (unix ns).
	FirstSeen int64 `json:"firstSeen"`
	LastSeen  int64 `json:"lastSeen"`
	// Occurrences — tüm servislerdeki toplam.
	Occurrences uint64 `json:"occurrences"`
	// BucketStart — patlamanın ait olduğu zaman kovası (unix ns).
	// Kimliğin parçası: aynı tip, farklı gecelerde ayrı olaydır.
	BucketStart int64 `json:"bucketStart"`
}

SharedExceptionBurst — birden çok serviste eşzamanlı başlayan aynı exception tipi.

type SlowQueryRow added in v0.5.165

type SlowQueryRow struct {
	DBQueryStat
	Service string `json:"service"`
	// StmtHash — persistent statement identity (v0.8.375, Stage-2 D1):
	// spans.db_stmt_hash / chstore.DBStmtHash as a DECIMAL STRING, because
	// a uint64 in JSON silently loses precision past 2^53 in JS and D2
	// keys the statement detail view on this value (same reason pivot
	// fingerprints ride URLs as decimal strings). Additive: MV-path rows
	// carry the stored column, raw-path rows compute it Go-side from the
	// sample (hash-consistent by the dbstmt.go parity contract); empty
	// only on pre-D1 cached responses.
	StmtHash string `json:"stmtHash,omitempty"`
}

SlowQueryRow extends DBQueryStat with the originating service so the global slow-query catalog (v0.5.165) can show which service is responsible. The same query text issued from two different services is intentionally kept as two rows — same SQL, different teams to ping.

type Span

type Span struct {
	TraceID     string
	SpanID      string
	ParentID    string
	Name        string
	OpGroup     string // normalized operation shape (group_id rel A); ” = no group
	Kind        string
	ServiceName string
	HostName    string
	DeployEnv   string
	StatusCode  string // "ok" | "error" | "unset"
	StatusMsg   string
	Time        time.Time
	Duration    int64 // nanoseconds
	DBSystem    string
	DBStatement string
	HTTPMethod  string
	HTTPRoute   string
	HTTPStatus  uint16
	RPCSystem   string
	RPCMethod   string
	PeerService string
	MsgSystem   string
	AttrKeys    []string
	AttrValues  []string
	ResKeys     []string
	ResValues   []string
	Events      string // JSON array
	ScopeName   string
}

Span is an OTLP span normalised for ClickHouse storage.

type SpanBreakdownPoint added in v0.4.77

type SpanBreakdownPoint struct {
	TimeNs int64              `json:"time"`
	Kinds  map[string]float64 `json:"kinds"`
}

SpanBreakdownPoint is one time bucket of the "where does this service spend its time?" stacked-area chart. Elastic APM's signature service-overview surface — gives the operator a quick "is the slowness coming from DB, HTTP, internal compute, or queue waits" answer without flipping between panels.

Kinds is a map of bucket → cumulative ms of duration grouped by span.kind (server / client / internal / producer / consumer) plus a synthetic "db" / "queue" / "http" bucket derived from db.system / messaging.system / http.method when set. The frontend renders each as a stacked band.

type SpanLink struct {
	TraceID       string            `json:"traceId"`
	SpanID        string            `json:"spanId"`
	LinkedTraceID string            `json:"linkedTraceId"`
	LinkedSpanID  string            `json:"linkedSpanId"`
	TimeUnixNs    int64             `json:"timeUnixNs"`
	ServiceName   string            `json:"serviceName"`
	Attrs         map[string]string `json:"attrs,omitempty"`
}

SpanLink is one span-link row as served to the pivot read path (both directions project the same columns).

type SpanLinkIngest added in v0.8.329

type SpanLinkIngest struct {
	Ingested       int64 `json:"ingested"`
	DroppedInvalid int64 `json:"droppedInvalid"`
}

SpanLinkIngest — the two OTel span-link ingest totals (cumulative since process start, v0.8.329). DroppedInvalid counts links whose linked trace id arrived empty/all-zero — MALFORMED per the OTel spec (link trace_id is required), so like the exemplar policy gate it's an intentional drop, never in the loss alarm. Populated by the API getSystemStats handler from the live Ingester atomics; GetSystemStats (CH-only) leaves it zero so chstore keeps no otlp dependency.

type SpanLinkRow added in v0.8.329

type SpanLinkRow struct {
	// TraceID / SpanID identify the OWNING span — the span that DECLARED the
	// link. Lowercase hex, same encoding as spans.trace_id so both pivot
	// directions are same-type lookups against the trace view.
	TraceID string
	SpanID  string
	// LinkedTraceID / LinkedSpanID are the link's TARGET. "" when the wire
	// carried nil or all-zero bytes (the SDK "no context" disagreement
	// parentID collapses) — the Ingester's invalid gate drops those rows: a
	// link pointing nowhere can't be traversed in either direction.
	LinkedTraceID string
	LinkedSpanID  string
	// Time is the OWNING span's start time — links have no timestamp of
	// their own in OTLP, and anchoring to the owner keeps the row inside the
	// same partition/TTL horizon as the span it belongs to.
	Time        time.Time
	ServiceName string
	// Link attributes as parallel arrays (attrsToArrays), the same layout
	// spans uses — tiny sets ("follows_from", messaging batch ids), read
	// whole by the trace view.
	AttrKeys []string
	AttrVals []string
}

SpanLinkRow is one OTel span link normalised for the `span_links` table (v0.8.329, cross-signal pivot Phase 1b — previously convertSpan dropped sp.Links entirely, pivot-audit §2). One row per link; the reverse-direction copy in span_links_reverse is populated by span_links_reverse_mv, never written directly.

type SpanMetricAggSpec added in v0.5.54

type SpanMetricAggSpec struct {
	Name        string // result key, e.g. "rate" / "error_rate" / "p99"
	Aggregation string // count | error_rate | rate | avg | sum | p50 | p95 | p99 | max | min
	Field       string // attribute / column when aggregation needs one (default duration_ms)
}

type SpanMetricBatchFilter added in v0.5.54

type SpanMetricBatchFilter struct {
	Filters     []FilterExpr
	GroupBy     []string
	From, To    time.Time
	StepSeconds int
	// MaxDataPoints (v0.9.391, grafik-audit Faz B) — panel nokta bütçesi.
	// 0 = eski sabit ladder + 2000'lik emniyet tavanı; >0 = px-adaptif
	// step (metricAutoStepPx) + bütçe tavanı. Cache key'e GİRER.
	MaxDataPoints int
	// Search (v0.9.601) — serbest metin yüklemi. Tek-agg
	// SpanMetricFilter'da baştan beri vardı; batch şeklinde YOKTU ve
	// bu, /traces hacim şeridinin bu yüzeye geçmesini engelliyordu:
	// geçseydi arama sessizce düşer, grafik filtrelenmemiş seriyi
	// çizerken tablo filtreli sonucu gösterirdi.
	//
	// Aynı searchPredicate paylaşılıyor (GetTraces ile de) — iki yüzey
	// AYNI şekilde daraltmak zorunda, yoksa histogram toplamı tablonun
	// gösterdiği kümeyle uyuşmaz.
	Search string
	Aggs   []SpanMetricAggSpec
}

SpanMetricBatchFilter computes N aggregations over the same span selection in a single CH query. Drives the Service detail page's "rate + error_rate + p99" chart row (and the compare-period twin) — three independent QuerySpanMetric calls fanned out into one CH pass over the spans table. Cold-cache time drops from ~3 × singleN to ~1 × singleN.

All aggregations share the SAME GroupBy + StepSeconds + filters; they only differ in (Name, Aggregation, Field). Name is the operator's label for the result key in the response map — callers pick something stable ("rate", "error_rate", "p99") so the frontend can address each series without inspecting types.

type SpanMetricFilter

type SpanMetricFilter struct {
	Filters []FilterExpr // span filter chips
	// FilterRoot is the optional grouped AND/OR builder (v0.8.x gap-2,
	// extended into Explore). When non-nil, QuerySpanMetric routes the
	// predicate through ApplyFilterGroup INSTEAD of ApplyFilters(f.Filters),
	// so the operator can express `(http.status >= 500 OR db.system = oracle)
	// AND env = prod` in an Explore panel. Mirrors how repo.go's TraceFilter
	// gained FilterRoot. A flat-AND FilterRoot is byte-identical to the legacy
	// Filters path (ApplyFilterGroup delegates flat-AND to ApplyFilters); an
	// OR / nested group disqualifies the MV fast-paths (it can't ride the
	// service_summary_5m / operation_summary_5m rollups, same cost class as a
	// free-text Search), so it falls to the bounded raw-spans GROUP BY.
	FilterRoot  *FilterGroup
	Aggregation string   // count | error_rate | rate | avg | sum | p50 | p95 | p99 | max | min
	Field       string   // attribute / column to aggregate (default: duration_ms)
	GroupBy     []string // 0..N attribute names; same syntax as FilterExpr.Key
	From, To    time.Time
	StepSeconds int // bucket size; if 0, auto-pick from time range
	// v0.6.32 — free-text search predicate. Same shape as
	// GetTraces' search HAVING (positionCaseInsensitive across
	// name / http_route / http_method+route concat / attr
	// values). Operator-reported: /traces span-volume histogram
	// counted 929 spans for a service while the trace list with
	// `search=SELECT * FROM FND_USER` showed only 3 traces — the
	// histogram wasn't honouring the search filter. Pushing it
	// down at the WHERE level makes the histogram's total
	// agree with the spans the search actually selects.
	Search string
}

SpanMetricFilter selects a slice of spans and turns them into a time-series metric (Tempo's span-metrics generator pattern). Optional groupBy keys produce one series per unique combination — Dynatrace-style MDA.

type SpanMetricPoint

type SpanMetricPoint struct {
	Time  int64   `json:"time"` // unix nanos (bucket start)
	Value float64 `json:"value"`
}

type SpanMetricSeries

type SpanMetricSeries struct {
	GroupKey []string          `json:"groupKey"` // raw tuple, joined in UI
	Points   []SpanMetricPoint `json:"points"`
}

SpanMetricSeries is one line on the chart — typically one per groupKey.

type SpanRow

type SpanRow struct {
	TraceID            string            `json:"traceId"`
	SpanID             string            `json:"spanId"`
	ParentSpanID       string            `json:"parentSpanId"`
	Name               string            `json:"name"`
	Kind               string            `json:"kind"`
	ServiceName        string            `json:"serviceName"`
	HostName           string            `json:"hostName"`
	StartTime          int64             `json:"startTime"`
	EndTime            int64             `json:"endTime"`
	DurationMs         float64           `json:"durationMs"`
	StatusCode         string            `json:"statusCode"`
	StatusMessage      string            `json:"statusMessage"`
	Attributes         map[string]string `json:"attributes"`
	ResourceAttributes map[string]string `json:"resourceAttributes"`
	Events             interface{}       `json:"events"`
	ScopeName          string            `json:"scopeName"`
	DBSystem           string            `json:"dbSystem,omitempty"`
	DBStatement        string            `json:"dbStatement,omitempty"`
	HTTPMethod         string            `json:"httpMethod,omitempty"`
	HTTPRoute          string            `json:"httpRoute,omitempty"`
	HTTPStatus         uint16            `json:"httpStatus,omitempty"`
	PeerService        string            `json:"peerService,omitempty"`
}

type StatusComponent

type StatusComponent struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	Description  string `json:"description"`
	MonitorID    string `json:"monitorId,omitempty"`
	ServiceName  string `json:"serviceName,omitempty"`
	DisplayOrder int32  `json:"displayOrder"`
	CreatedAt    int64  `json:"createdAt"`
}

type StatusPageConfig

type StatusPageConfig struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	SupportURL  string `json:"supportUrl"`
}

type StatusSubscriber

type StatusSubscriber struct {
	ID       string `json:"id"`
	Email    string `json:"email"`
	Verified bool   `json:"verified"`
	// Token is only populated on the read path internally —
	// never round-trips to the admin UI. The public confirm
	// link is the only consumer.
	Token         string `json:"-"`
	ConfirmSentAt int64  `json:"confirmSentAt,omitempty"`
	CreatedAt     int64  `json:"createdAt"`
}

type StepState added in v0.6.73

type StepState struct {
	StepID       string `json:"stepId"`
	Order        int    `json:"order"`
	Kind         string `json:"kind"`
	Title        string `json:"title"`
	Instructions string `json:"instructions,omitempty"`
	Status       string `json:"status"`
	By           string `json:"by,omitempty"`     // user (manual) or agent id (automated)
	Note         string `json:"note,omitempty"`   // operator note on tick
	Output       string `json:"output,omitempty"` // stdout / returnValue / HTTP body
	Error        string `json:"error,omitempty"`
	StartedAt    int64  `json:"startedAt,omitempty"`
	EndedAt      int64  `json:"endedAt,omitempty"`
	// Executable payload — snapshotted from the step at execution start so the
	// coremetry-agent runs exactly what the runbook said at run time (template
	// edits never change an in-flight run). Only the fields for Kind are set.
	URL       string            `json:"url,omitempty"`
	Method    string            `json:"method,omitempty"`
	Headers   map[string]string `json:"headers,omitempty"`
	Body      string            `json:"body,omitempty"`
	Script    string            `json:"script,omitempty"`
	Command   string            `json:"command,omitempty"`
	TimeoutMs int               `json:"timeoutMs,omitempty"`
}

StepState is a step's snapshot + live status within an execution.

func ApplyStepResult added in v0.6.73

func ApplyStepResult(states []StepState, stepID, status, by, note, output, errStr string, nowNs int64) ([]StepState, bool)

ApplyStepResult sets the status (+ optional by/note/output/error) on the matching step and stamps EndedAt. Returns the updated slice and whether the step was found. Pure — unit-tested. Exported: the API runner + the agent-result path both compose it.

type Store

type Store struct {
	// contains filtered or unexported fields
}

func New

func New(cfg config.CHConfig, ret config.RetentionConfig) (*Store, error)

func (*Store) AICallSurfaceByExchange added in v0.8.399

func (s *Store) AICallSurfaceByExchange(ctx context.Context, exchangeID string) (string, error)

AICallSurfaceByExchange resolves the surface label of the ai_calls row a feedback POST refers to — server-side, so the client can't mislabel a verdict, and so an unknown exchangeId is detectable (returns ""). ai_calls is a small 90d-TTL table; the unindexed exchange_id filter follows the GetAICall `WHERE id = ?` precedent, bounded the same way.

func (*Store) AICallsTimeseries added in v0.5.164

func (s *Store) AICallsTimeseries(ctx context.Context, from, to time.Time, bucketSec int) ([]AICallsTimePoint, error)

func (*Store) AcknowledgeProblems added in v0.5.83

func (s *Store) AcknowledgeProblems(ctx context.Context, ids []string, actor string) (int, error)

AcknowledgeProblems flips a batch of problems to status= "acknowledged". Idempotent — already-resolved problems are silently skipped (you can't ack a resolved row, nothing to mute). The evaluator's auto-resolve path still flips them to "resolved" once the threshold stops firing, which is the right answer for an ack'd problem too.

func (*Store) ActiveAPITokenHashes added in v0.8.444

func (s *Store) ActiveAPITokenHashes(ctx context.Context) (map[string]APIToken, error)

ActiveAPITokenHashes — hash → rol haritası (auth cache'inin beslemesi).

func (*Store) ActiveSilencedFingerprints

func (s *Store) ActiveSilencedFingerprints(ctx context.Context) (map[string]bool, error)

ActiveSilencedFingerprints returns a set of fingerprints currently muted. Hot path on /anomalies; tiny result set (rarely more than a few dozen). Caller can compare in O(1).

func (*Store) AddStatusSubscriber

func (s *Store) AddStatusSubscriber(ctx context.Context, email string) (string, error)

AddStatusSubscriber inserts an unverified subscriber row + a freshly-minted confirm token. The token is returned so the caller (api layer) can deliver it via SMTP without us having to know the public URL here. If the row already exists and is verified, we treat the call as a no-op and return empty token — re-confirming a verified email is just noise. If the row exists but is unverified, we mint a new token (covers "operator lost the confirmation mail" without leaking signal to a third party).

func (*Store) AddVerifiedSubscriber added in v0.5.158

func (s *Store) AddVerifiedSubscriber(ctx context.Context, email string) error

AddVerifiedSubscriber is the admin-curated path. An operator invites a teammate from the admin UI; that subscriber is trusted to already exist and skips the email confirmation dance entirely.

func (*Store) AggregateServiceStructure

func (s *Store) AggregateServiceStructure(
	ctx context.Context, service string, since time.Duration, sampleCount int,
	internalOnly bool,
) (roots []*AggSpanNode, totalSpans, sampledFrom int, err error)

AggregateServiceStructure builds a Grafana-Drilldown-style composite span tree across the most recent N traces involving `service`. Nodes are bucketed by `(parent_path, service.name, displayName)`; counts + average / max duration + error count are accumulated per bucket.

`internalOnly` controls how the DFS walks descendants of each focused-service entry point:

  • false (default) — follow every child regardless of service. This produces a CROSS-SERVICE flame showing "what the focused service does, including the downstream RPC / queue / DB hops it calls into". Downstream service frames are coloured distinctly so the operator can tell where the time leaves the local process.
  • true — clip the walk at any non-focused service. Produces a SERVICE-ONLY flame: "where does this service spend its own time, ignoring how long the things it calls took". Useful when investigating a perf regression that's internal to the service rather than downstream.

Returns:

roots        — top-level nodes (multiple if sampled traces
               have different root spans; chronological order
               by avg start time).
totalSpans   — span count across the sampled traces (for the
               "X spans used" header).
sampledFrom  — trace count actually inspected.

func (*Store) AnnotateDbNames added in v0.8.297

func (s *Store) AnnotateDbNames(nodes []ServiceMapNode, dbNames map[string]string)

AnnotateDbNames is the exported wrapper the api package calls.

func (*Store) AppendAudit

func (s *Store) AppendAudit(ctx context.Context, e AuditEntry) error

AppendAudit writes one entry. Best-effort — callers fire without checking the error since auth-success paths shouldn't be blocked by audit-write failure (we log internally).

func (*Store) AppendAuditBatch added in v0.5.339

func (s *Store) AppendAuditBatch(ctx context.Context, entries []AuditEntry) error

AppendAuditBatch writes N entries in a single CH INSERT. ID + Time defaults are applied per-row. Used by the API server's background drainer (v0.5.339) so a burst of admin actions doesn't fan out into N goroutines + N one-row INSERTs — both wasteful at scale.

func (*Store) AppendIncidentEvent

func (s *Store) AppendIncidentEvent(ctx context.Context, e IncidentEvent) error

func (*Store) ApplyPersistedRetention

func (s *Store) ApplyPersistedRetention(ctx context.Context) error

ApplyPersistedRetention re-runs the live ALTERs from whatever's currently in system_settings. Called on boot so a restart picks up previously-persisted overrides without the operator having to click "Apply" again.

func (*Store) AssignExceptionGroup

func (s *Store) AssignExceptionGroup(ctx context.Context, fingerprint, userID string) error

AssignExceptionGroup sets or clears the assignee (empty → unassigned).

func (*Store) AttachProblemToIncident

func (s *Store) AttachProblemToIncident(ctx context.Context, p Problem) (*Incident, error)

AttachProblemToIncident either finds an existing OPEN incident matching this problem's service or a topological neighbour within `groupingWindow`, OR creates a new incident headed by this problem. Idempotent.

Grouping rules (in priority order):

  1. Already attached? Return that incident.
  2. Open incident with matching service in the window — even if the severities differ. Critical p99 and warning error_rate on the same service are one incident, not two.
  3. Open incident on a 1-hop topological neighbour of this problem's service (caller or callee in sampled traces) in the window. Catches cascading failures: an upstream saturation alert and a downstream timeout alert end up under one incident the oncall drives end-to-end.
  4. Else: create a new incident.

Topology (3) requires a NeighborProvider — pass nil to fall back to the rule-2 same-service-only behaviour.

func (*Store) AttachProblemToIncidentWith

func (s *Store) AttachProblemToIncidentWith(ctx context.Context, p Problem, np NeighborProvider) (*Incident, error)

AttachProblemToIncidentWith is the topology-aware variant — the correlator is wired here so non-correlator call sites stay untouched. The base AttachProblemToIncident calls this with nil for the provider.

func (*Store) AutoResolveStaleExceptionGroups added in v0.6.24

func (s *Store) AutoResolveStaleExceptionGroups(ctx context.Context, staleAfter time.Duration) (int, error)

AutoResolveStaleExceptionGroups transitions any open/acknowledged group whose last occurrence is older than staleAfter into the `resolved` state. Operator-reported (v0.6.24): without this, the /problems "Resolved" tab stays empty forever on installs where operators forget to click Resolve manually. Sentry / Honeycomb / Datadog all default to this behaviour.

Sets resolved_at to the row's existing last_seen so the audit trail reflects "last touched at" rather than "swept at" — keeps the timeline honest. UpsertExceptionGroup's regression detector will flip the row back to `regressed` if the exception starts firing again later.

Returns the number of rows transitioned. Lock-gated at the caller (main.runExceptionRefresher) so multi-replica installs don't double-sweep.

func (*Store) BubbleUp

func (s *Store) BubbleUp(
	ctx context.Context,
	baseline []FilterExpr,
	selection []FilterExpr,
	from, to time.Time,
) (*BubbleUpResult, error)

BubbleUp computes the attribute-divergence report for a (selection, baseline) pair. `baseline` is the WHERE clause for the wider population; `selection` is an additional predicate that narrows it. We pass them as parallel FilterExpr lists so callers compose the same way they compose other span queries.

func (*Store) BusinessBreakdown added in v0.9.511

func (s *Store) BusinessBreakdown(ctx context.Context, service, attrKey string, from, to time.Time) ([]BusinessSlice, error)

BusinessBreakdown — servis + pencere için verilen attribute anahtarının hata-ağırlıklı kırılımı. En çok HATA üreten değer başta: bir SRE'nin sorduğu "hangi kanal patlıyor" sorusunun sırası bu, hacim sırası değil.

Boş değerler elenir — attribute'u taşımayan span'ler kırılımı kirletmemeli (o span'ler "bilinmeyen kanal" değil, "bu boyutla ilgisiz").

func (*Store) CalleesOf

func (s *Store) CalleesOf(ctx context.Context, service string, since time.Duration) ([]ServiceEdgeStats, error)

CalleesOf returns services that `service` calls (outgoing dependency view).

func (*Store) Close

func (s *Store) Close() error

func (*Store) ClusterName added in v0.5.388

func (s *Store) ClusterName() string

ClusterName returns the configured CH cluster identifier (e.g. the value that lands inside `ON CLUSTER`) when the operator set COREMETRY_CH_CLUSTER, or "" for a single-shard standalone install. Used by /admin/clickhouse to render the topology banner so the operator can confirm at a glance whether the running pod is talking to a cluster vs a single CH node.

func (*Store) ComponentUptime

func (s *Store) ComponentUptime(ctx context.Context, monitorID string, days int) ([]float64, error)

ComponentUptime computes uptime% for a monitor over the last N days from monitor_results. Returns one ratio per day (0.0..1.0) where the most recent day is last. Days with no probe data return -1 to signal "no data" so the UI can render those bars in grey rather than green.

func (*Store) ComputeAIStats added in v0.5.164

func (s *Store) ComputeAIStats(ctx context.Context, from, to time.Time) (*AIStats, error)

ComputeAIStats does the aggregate query for the overview cards. Window-bounded (from..to) so we never scan beyond the TTL.

func (*Store) ComputeDeployImpact added in v0.5.189

func (s *Store) ComputeDeployImpact(
	ctx context.Context, service, version string, deployTimeNs int64, windowSec int,
) (*DeployImpact, error)

ComputeDeployImpact runs the side-by-side window comparison for one (service, deployTime). Single CH pass via quantileIf / countIf gates so before + after come back together without two scans. Cost is bounded by the window size (default 10 min) — at 1B-span/day this is sub-second on the partition- pruned spans table.

func (*Store) ComputeFlowsLatencyP99 added in v0.5.156

func (s *Store) ComputeFlowsLatencyP99(ctx context.Context, from, to time.Time, sigs []FlowSig) (map[string]uint64, error)

ComputeFlowsLatencyP99 returns the p99 root-span duration (ns) for each requested flow signature over the window. Keyed on "service\x00op" so the caller can look up without a struct equality dance. Empty input → empty map, no query.

The IN list is bounded by the caller's flow limit (cap 200 on the API surface), so even at billion-span scale this is a thin GROUP BY over (parent_id=”) roots filtered to a handful of signatures — far cheaper than ranking flows from raw spans, which is why we let the agg path own ranking and use this only for latency enrichment.

func (*Store) ComputeSLOBurnRate

func (s *Store) ComputeSLOBurnRate(ctx context.Context, o SLO, window time.Duration) (float64, uint64, error)

ComputeSLOBurnRate calculates the burn rate over a SHORT look-back window — used by the 2-window burn-rate alarm pattern (Google SRE Workbook). The status method above runs over the SLO's full rolling window (e.g. 30 days), which smooths out short bursts; for alerting we want to detect "currently burning fast" within the last 1h or 6h.

Returned rate units: same as BurnRate on SLOStatus — (1 − SLI_window) / (1 − target). > 1 means the budget would be exhausted before the SLO window completes.

func (*Store) ComputeSLOBurnSeries added in v0.5.150

func (s *Store) ComputeSLOBurnSeries(ctx context.Context, o SLO, days int) ([]BurnPoint, error)

ComputeSLOBurnSeries returns a per-day burn-rate timeseries over the past `days` days. Used by the SLO list page to render a sparkline next to each row so the operator can spot "this SLO has been eroding for the last 3 days" without opening the detail view. Day granularity is enough for a 7-30 day picture; tighter granularity would just add noise to the thumb-sized chart.

func (*Store) ComputeSLOForecast added in v0.6.30

func (s *Store) ComputeSLOForecast(ctx context.Context, o SLO, burnWindow time.Duration) (*SLOForecast, error)

ComputeSLOForecast runs ComputeSLOStatus + ComputeSLOBurnRate (over `burnWindow`) and combines them into a forecast. Two CH reads — both bounded by service+time WHEREs so total cost is tiny.

func (*Store) ComputeSLOStatus

func (s *Store) ComputeSLOStatus(ctx context.Context, o SLO) (*SLOStatus, error)

ComputeSLOStatus derives total/good counts within the SLO's rolling window. Availability reads the summary MV (count + error per 5m bucket — no raw-spans scan); latency needs a per-span threshold compare so it reads `spans`, bounded by max_execution_time. Called once per SLO by the (cached) /api/slos list.

func (*Store) ConfirmStatusSubscriber added in v0.5.158

func (s *Store) ConfirmStatusSubscriber(ctx context.Context, token string) (string, error)

ConfirmStatusSubscriber flips verified=1 and clears the confirm token when the operator clicks the link in the confirmation email. Returns the email so the caller can render a thank-you page. Empty email + nil error = token not found / already consumed.

func (*Store) ConfirmedRCASignatures added in v0.9.595

func (s *Store) ConfirmedRCASignatures(ctx context.Context, service string, now time.Time) ([]RCASignature, error)

ConfirmedRCASignatures — bir servis için DOĞRULANMIŞ kök nedenler.

Yalnız 👍 almış verdict'ler; 👎 almış ya da hiç oylanmamışlar dışarıda. INNER JOIN bilinçli (kalite okumasındaki LEFT JOIN'in tersi): orada amaç her verdict'i saymaktı, burada amaç yalnız onaylananları almak.

Bounded: küçük TTL'li state tablosu, servis + zaman WHERE, LIMIT, max_execution_time. FINAL şart — hem rca_verdicts hem ai_feedback ReplacingMergeTree ve operatör oyunu değiştirebilir.

func (*Store) Conn

func (s *Store) Conn() driver.Conn

func (*Store) ConnectedHosts added in v0.5.388

func (s *Store) ConnectedHosts() []string

ConnectedHosts returns the configured comma-separated CH host list parsed into individual entries. With cluster mode this is the driver-side fan-out (the connection pool round-robins across them); with standalone it's usually a single host.

func (*Store) CountActiveAnomalyEvents added in v0.8.288

func (s *Store) CountActiveAnomalyEvents(ctx context.Context, activeAge time.Duration, envServices []string) (uint64, error)

CountActiveAnomalyEvents returns the number of anomaly events currently "active" — i.e. last_seen fresher than activeAge (the same derivation the list/detail reads use). For the /inbox badge (v0.8.288): a cheap COUNT(*) FINAL on the small state table, no row scan. activeAge 0 → 10m default. envServices follows the same nil/empty contract as CountProblemsInStatuses: nil = unscoped, empty = env resolved to no services (only service-less rows count), otherwise membership.

func (*Store) CountAdmins

func (s *Store) CountAdmins(ctx context.Context) (int64, error)

CountAdmins is used to reject the "disable / demote the last admin" case before it can lock everyone out.

func (*Store) CountAnomalyEventsByStatus added in v0.9.465

func (s *Store) CountAnomalyEventsByStatus(ctx context.Context, sinceNs int64, activeAge time.Duration) (active, cleared uint64, err error)

CountAnomalyEventsByStatus (v0.9.465, dürüstlük A9) — pencere içi GERÇEK aktif/cleared toplamları: /anomalies sayfası sayıları yüklü 200'lük sayfadan türetiyordu; gürültülü günde ikisi de yalan söylüyordu. Status hesabı ListAnomalyEvents ile AYNI ifade (last_seen tazeliği, 10dk varsayılan).

func (*Store) CountExceptionGroups added in v0.5.95

func (s *Store) CountExceptionGroups(ctx context.Context, f ExceptionGroupFilter) (int64, error)

CountExceptionGroups returns the total number of rows that match f (Limit + Offset are ignored). Drives the paginator's "X of N" indicator on the inbox so the UI can offer a "last page" jump without having to fetch every group.

func (*Store) CountIncidentsByStatus added in v0.9.456

func (s *Store) CountIncidentsByStatus(ctx context.Context, service string) (map[string]uint64, error)

CountIncidentsByStatus (v0.9.456, dürüstlük A4) — /incidents sayfa başlığındaki open/ack/resolved sayıları SQL'den: eskiden en-yeni-200 penceresinden sayılıyordu; 200'ün dışında kalan eski açık incident hem listeden hem sayımdan kayboluyor, /inbox rozetiyle çelişiyordu (inbox'ın v0.9.321'de kapattığı "2 vs 29" sınıfının sayfa kalıntısı). Service daraltması listeyle aynı conjunct; status daraltması BİLEREK yok — sayılar sekme chip'leridir, tüm durumları kapsar.

func (*Store) CountIncidentsNotInStatuses added in v0.9.322

func (s *Store) CountIncidentsNotInStatuses(ctx context.Context, exclude []string, envServices []string) (uint64, error)

CountIncidentsNotInStatuses returns how many incidents are NOT in the given statuses — the /inbox badge's fourth term (v0.9.321).

v0.9.322: exclusion rather than an allow-list, so it classifies every status exactly as the list's Go keeper does. See ProblemFilter.NotStatuses.

The badge has to count the same rows the list shows or the two disagree, which is the bug this codebase keeps re-fixing (v0.9.219 did it for env). COUNT-only on a small state table with FINAL, run in parallel with the other three terms.

envServices follows the shared contract: nil = unscoped, empty = the env resolved to no services (only service-less rows count), otherwise membership. Incidents CAN be service-less — an incident declared across several services has no single owner — so unlike exception groups the empty case is a real query, not a short-circuit to zero.

func (*Store) CountProblems added in v0.5.398

func (s *Store) CountProblems(ctx context.Context, f ProblemFilter) (uint64, error)

func (*Store) CountProblemsNotInStatuses added in v0.9.322

func (s *Store) CountProblemsNotInStatuses(ctx context.Context, exclude []string, envServices []string) (uint64, error)

CountProblems returns the row count matching the same filter shape ListProblems uses, without materialising the actual rows. Drives the sidebar badge so the count stays truthful when there are >200 open problems — the badge previously fetched 200 rows and counted the array, capping the displayed value at 200. FINAL on the spans is the same as the list path so the merged dedup result is what counts; using a plain count() would double-count rows mid-merge. CountProblemsNotInStatuses — inbox rozetinin "hâlâ insan bekleyen" toplamı. v0.9.322: izin listesi yerine DIŞLAMA — liste tarafındaki Go süzgeciyle birebir aynı sınıflandırma, yoksa rozet ve liste ayrışıyor. TEK FINAL taramasında (v0.8.472 perf dalga-1 #2; önceden iki ayrı CountProblems çağrısıydı). Statüler sabit enum, IN bind'li. envServices scopes the count to an environment, mirroring the inbox list's envKeepsRow exactly: a service-LESS (global) row always counts, otherwise the service must be an env member. nil = no env constraint; a non-nil EMPTY slice means the env resolved to no services, so only global rows count — the nil-vs-empty distinction is load-bearing here the same way it is for the team filter (v0.9.219).

func (*Store) CountRootFlows added in v0.7.40

func (s *Store) CountRootFlows(ctx context.Context, from, to time.Time) (int, error)

CountRootFlows returns the number of DISTINCT business flows (root_service, root_op) in the window — the denominator for the "showing N of M flows" honesty banner (v0.7.39). Operator-reported: Business Flows is capped at ?top and gave no signal that more flows existed beyond the cut. Cheap: one uniqExact over the small pre-aggregated MV.

func (*Store) CountServicesAgg added in v0.7.44

func (s *Store) CountServicesAgg(ctx context.Context, from, to time.Time, nameMatch string, serviceIn []string) (int, error)

GetServicesAggFiltered narrows the row set by a substring match on service_name *before* the GROUP BY — used by the Services page dropdown so a service that's outside the limited top-N still surfaces when the user types its name. `nameMatch` empty disables the filter. GetServicesAggFilteredIn — same as GetServicesAggFiltered plus a service-name allowlist (the API uses this to pre-narrow the universe by ownerTeam / sreTeam without joining at query time). nil / empty = no constraint. CountServicesAgg returns the number of DISTINCT services matching the same MV-path filters as GetServicesAggFilteredIn — the denominator the Services page needs for First/Last paging. uniqExact over service_summary_5m is cheap (no per-service aggregation). Kept OPT-IN at the handler (?withTotal=1) so the default hot path stays count-free per the /api/services p99<50ms budget (v0.7.44).

func (*Store) CountTracesCapped added in v0.9.638

func (s *Store) CountTracesCapped(ctx context.Context, f TraceFilter) (TraceCount, error)

CountTracesCapped — listeyle AYNI evreni tavana kadar sayar.

func (*Store) CountUsers

func (s *Store) CountUsers(ctx context.Context) (int64, error)

func (*Store) CreateAPIToken added in v0.8.444

func (s *Store) CreateAPIToken(ctx context.Context, name, role, createdBy string) (plain string, tok APIToken, err error)

CreateAPIToken — üretir, hash'ler, yazar; düz değeri döner (tek sefer).

func (*Store) CreateTraceSnapshot

func (s *Store) CreateTraceSnapshot(ctx context.Context, snap TraceSnapshot) error

CreateTraceSnapshot persists a new (or rotates an existing) share token. We don't dedupe — calling twice for the same trace mints two unrelated tokens, which is fine: revoking one doesn't break the other.

func (*Store) DBStmtExemplars added in v0.8.378

func (s *Store) DBStmtExemplars(ctx context.Context, q DBStmtDetailQuery) (slowTraceID, errorTraceID string, err error)

DBStmtExemplars resolves the statement class's slow + error exemplar trace_ids — the TRUE pivot D2 replaces the lossy `db.statement LIKE prefix%` deep-link with (v0.8.378). Two bounded raw-spans point reads keyed on the stored db_stmt_hash column; empty ids mean "no exemplar" (no traffic / all-healthy window) and surface as a soft-missing section, never an error. Guarded on the D1 boot probe: installs where the column couldn't land (external Distributed, cluster_name unset) skip the raw read entirely.

func (*Store) DatabaseName added in v0.5.388

func (s *Store) DatabaseName() string

DatabaseName returns the configured CH database name (used in the ON CLUSTER + ON DATABASE clauses). Exposed for the same admin surfaces — the operator needs to see which DB the running build is bound to without ssh'ing into the container.

func (*Store) DbNamesBySystem added in v0.8.37

func (s *Store) DbNamesBySystem(ctx context.Context, from, to time.Time) (map[string]string, error)

DbNamesBySystem returns the dominant db.name (schema / instance) per db.system over [from,to], ranked by call volume, read from db_summary_5m (the db.name dimension added in v0.5.327). The service-graph endpoint uses it to enrich database nodes — which are keyed on db.system — with the OTel db.name on their card. MV-only (never raw spans); the MV coalesces a missing db.name to 'default', which we skip so only a real schema/instance shows.

func (*Store) DeleteAlertRule

func (s *Store) DeleteAlertRule(ctx context.Context, id string) error

DeleteAlertRule removes the row entirely (ALTER … DELETE) rather than the previous soft-disable pattern — operators hitting Delete expect the rule to go AWAY from the list, not stay around as a disabled tombstone. Matches the precedent already established by monitors / status-page components / notification channels. Built-in rules are still resurrected on next boot from the seed list, so deleting a preset gives the operator a clean slate without a permanent "ghost" row. Disable-without-delete remains available through SetAlertRuleEnabled (toggled by the noisy-rules bulk path).

func (*Store) DeleteAnomalySilence

func (s *Store) DeleteAnomalySilence(ctx context.Context, id string) error

DeleteAnomalySilence soft-deletes by setting until_at to now. ReplacingMergeTree keeps the latest version; queries filtering by `until_at > now()` exclude it cleanly without an actual DELETE (which mutations are slow on partitioned tables).

func (*Store) DeleteAnomalySilences added in v0.5.94

func (s *Store) DeleteAnomalySilences(ctx context.Context, ids []string) (int, error)

DeleteAnomalySilences soft-deletes a batch in one call. Already- expired or non-existent ids are silently skipped (idempotent so the UI can blast every visible id without checking which are still live). Returns the number that were actually flipped.

func (*Store) DeleteChannel

func (s *Store) DeleteChannel(ctx context.Context, id string) error

func (*Store) DeleteDashboard

func (s *Store) DeleteDashboard(ctx context.Context, id string) error

DeleteDashboard issues a tombstone via OPTIMIZE-friendly ALTER DELETE. We don't have a "deleted" flag here (unlike users) because dashboards are owner-managed and full removal is the expected behaviour.

func (*Store) DeleteEvent added in v0.5.476

func (s *Store) DeleteEvent(ctx context.Context, id string) error

func (*Store) DeleteMaintenanceWindow added in v0.5.26

func (s *Store) DeleteMaintenanceWindow(ctx context.Context, id string) error

DeleteMaintenanceWindow soft-deletes by upserting with disabled=1. Preserves the row for audit trail.

func (*Store) DeleteMonitor

func (s *Store) DeleteMonitor(ctx context.Context, id string) error

func (*Store) DeleteRagDocument added in v0.8.441

func (s *Store) DeleteRagDocument(ctx context.Context, docID string) error

DeleteRagDocument — dokümanı tamamen kaldırır (admin, audit'li).

func (*Store) DeleteRunbook added in v0.6.72

func (s *Store) DeleteRunbook(ctx context.Context, id string) error

DeleteRunbook hard-removes the row (ALTER…DELETE), matching alert_rules: operators hitting Delete expect the runbook to leave the list, not linger as a disabled tombstone.

func (*Store) DeleteSLO

func (s *Store) DeleteSLO(ctx context.Context, id string) error

func (*Store) DeleteSavedView

func (s *Store) DeleteSavedView(ctx context.Context, id string) error

DeleteSavedView soft-removes by inserting a tombstone row at a new version. Read paths skip rows whose name is empty after FINAL — operator's eye-grep can also see these are gone.

func (*Store) DeleteStatusComponent

func (s *Store) DeleteStatusComponent(ctx context.Context, id string) error

func (*Store) DeriveServiceDeployments added in v0.9.25

func (s *Store) DeriveServiceDeployments(ctx context.Context, since time.Duration) (map[string]string, error)

DeriveServiceDeployments — servis → baskın deployment adı.

İki kaynak, öncelik sırasıyla:

  1. deployment.name attr'ları (yetkili kaynak — v0.9.25/53/54)
  2. v0.9.531 — gözlemlenen pod adlarından türetim, YALNIZ 1'in boş bıraktığı servisler için. Çapraz-env güvenli: aday servisin KENDİ telemetrisindeki pod'lardan gelir; mobile-loans-bff-prod kendi pod'undan "mobile-loans-bff" türetir, -int servisi kendi pod'undan kendininkini.

func (*Store) DeriveServiceNamespaces added in v0.8.436

func (s *Store) DeriveServiceNamespaces(ctx context.Context, since time.Duration) (map[string]string, error)

DeriveServiceNamespaces returns service → dominant namespace over the window; services emitting neither attribute are omitted.

func (*Store) DeriveServiceTeams added in v0.8.95

func (s *Store) DeriveServiceTeams(ctx context.Context, since time.Duration) (map[string]ServiceTeams, error)

DeriveServiceTeams returns service → dominant {owner, sre} team derived from span/resource attributes over the window. Services emitting none of the four keys are omitted.

func (*Store) DimensionedUsageLimit added in v0.7.69

func (s *Store) DimensionedUsageLimit(
	ctx context.Context, usageMetric, limitMetric, attrKey string,
) ([]CapacitySample, error)

DimensionedUsageLimit is UsageLimit for a check that is dimensioned by a single attribute (Oracle tablespace_size.* is keyed by `tablespace_name`). Groups by (instance, attr-value) so each tablespace is its own pageable check; Subkey carries the dimension value.

func (*Store) DisableUser

func (s *Store) DisableUser(ctx context.Context, userID string) error

DisableUser soft-deletes by inserting a new row with disabled=1.

func (*Store) DistinctTraceIDsForFilters added in v0.5.271

func (s *Store) DistinctTraceIDsForFilters(ctx context.Context, filters []FilterExpr, from, to time.Time, limit int) ([]string, error)

DistinctTraceIDsForFilters returns up to `limit` distinct trace IDs from the spans table that match the supplied filters within [from, to]. Powers the DQL cross-signal join (v0.5.271) where the source-side trace_id set narrows the target-side aggregation.

Cap is a hard ceiling — operators writing `spans | filter X | join logs ...` get a join scoped to at most `limit` traces. At billion-row scale this prevents the generated `trace_id IN (...)` clause from ballooning past what the target backend (ES / CH) can plan in one query.

func (*Store) EnabledChannelsForSeverity

func (s *Store) EnabledChannelsForSeverity(ctx context.Context, severity string) ([]NotificationChannel, error)

EnabledChannelsForSeverity is what the notifier calls when a Problem opens. Returns only enabled channels whose min_severity ≤ the problem's severity (so a "critical" problem fires every channel; "info" fires only the ones explicitly subscribed at info level).

func (*Store) EndpointExemplars added in v0.8.360

func (s *Store) EndpointExemplars(ctx context.Context, q EndpointDetailQuery) (slowTraceID, errorTraceID string, err error)

EndpointExemplars resolves the slow + error exemplar trace_ids for one endpoint off the spanmetrics_1m argMax states — MV-first and endpoint-precise: the MV carries http_route as a dimension, which FindExemplarRollup (service+window keyed) cannot scope to. One MV read yields both (argMaxMerge / argMaxIfMerge — the combinator contract from the v0.8.51 catch). Reads via spanmetricsSourceFor() so chstore-owned clusters fan out across shards (the v0.8.356 per- shard-MV posture). Empty strings mean "no exemplar in window" (pre-cutover / TTL'd / all-healthy for the error state) — soft, the caller renders the section without links.

func (*Store) EndpointFailingTraces added in v0.8.360

func (s *Store) EndpointFailingTraces(ctx context.Context, q EndpointDetailQuery, limit int) ([]EndpointFailingTrace, error)

EndpointFailingTraces returns the top `limit` failing traces on the endpoint, worst span duration first. Deliberately a bounded raw GROUP BY trace_id rather than GetTraces: the trace-list machinery (root resolution, count modes, MV gate) buys nothing here, and this scan rides the (service_name, time) PK + route + status filters, so the grouped set is tiny by construction.

func (*Store) EndpointLatencyHistogram added in v0.8.360

func (s *Store) EndpointLatencyHistogram(ctx context.Context, q EndpointDetailQuery) (*LatencyHeatmap, error)

EndpointLatencyHistogram runs the shared latency-heatmap core (heatmap.go — same log10 bin grid, same >1h trace-ID sampling with Go-side scale-back) restricted to one endpoint. The caller collapses the 2-D result to a 1-D distribution via CollapseLatencyHistogram — the drawer wants "what is THIS endpoint's latency shape", not "when"; the time dimension is already covered by the row sparkline.

func (*Store) EndpointSplit added in v0.8.360

func (s *Store) EndpointSplit(ctx context.Context, q EndpointDetailQuery, by string, limit int) ([]EndpointSplitRow, error)

EndpointSplit groups the endpoint's spans by one whitelisted attribute and returns the top `limit` values by call volume with RED each. Bounded raw GROUP BY: the scope predicate rides the (service_name, time) PK + route filter, quantileTDigest keeps the per-group quantile memory flat, LIMIT + max_execution_time cap the rest. `by` outside the whitelist is a caller bug → error (handler 400s).

func (*Store) EndpointStatusBreakdown added in v0.8.360

func (s *Store) EndpointStatusBreakdown(ctx context.Context, q EndpointDetailQuery) (*EndpointStatus, error)

EndpointStatusBreakdown groups one endpoint's spans by the dedicated http_status UInt16 column (minmax-indexed — same source the v0.8.356 sidecar reads) and rolls the classes up in Go. Zero statuses (non- HTTP spans / SDKs that never set http.status_code) are excluded so the map doesn't lead with a meaningless "0".

func (*Store) EndpointTopExceptions added in v0.8.360

func (s *Store) EndpointTopExceptions(ctx context.Context, q EndpointDetailQuery, limit int) ([]EndpointException, error)

EndpointTopExceptions returns the top exception types on this endpoint, by occurrence count in the window.

SCOPING (the honest part, documented per the E2 audit note): the exception_groups inbox has NO route dimension, so we count raw exception events instead, on spans of the service that either carry the route (http_route match, signature-collapsed when the drawer is in shape mode) OR whose span NAME equals the path — the common SDK conventions ("GET /orders/{id}" spans carry http_route; some SDKs name the server span exactly the route). LIMITATION: an exception recorded on a child internal span with neither the route attr nor a route-shaped name is attributed to the service, not this endpoint, and won't appear here — the /problems inbox remains the service-complete view. Fingerprint per row is the SAME FingerprintException(type, msg, service, stack) the inbox refresher computes (over the argMax-latest sample), so the deep link lands on the matching group whenever that sample is representative.

func (*Store) EndpointWhereTheTimeGoes added in v0.9.311

func (s *Store) EndpointWhereTheTimeGoes(
	ctx context.Context, q EndpointDetailQuery,
) (*EndpointDownstream, error)

EndpointWhereTheTimeGoes samples this route's traces and splits their time across downstream edges (plus the route's own self time), and collects who calls it.

func (*Store) EnforceRetention added in v0.5.320

func (s *Store) EnforceRetention(ctx context.Context) error

EnforceRetention drops every active partition older than the configured retention horizon on each retention-managed table. Idempotent: re-running on a clean state is a no-op. Logs every drop with size so the operator can audit storage reclaim from the server log.

func (*Store) EnrichAnomaliesWithClusters added in v0.5.22

func (s *Store) EnrichAnomaliesWithClusters(ctx context.Context, events []AnomalyEvent, since time.Duration) []AnomalyEvent

EnrichAnomaliesWithClusters mirrors the pair above for AnomalyEvent. Same one-shot map lookup keyed by service.

func (*Store) EnrichAnomaliesWithDeploys added in v0.5.286

func (s *Store) EnrichAnomaliesWithDeploys(ctx context.Context, events []AnomalyEvent, lookback time.Duration) []AnomalyEvent

EnrichAnomaliesWithDeploys is the AnomalyEvent twin of EnrichProblemsWithDeploys. v0.5.286 — same one-shot bulk-query pattern (one round-trip regardless of how many services / events are in the slice). Each event's RecentDeploy points at the most recent deploy of that service whose first_seen falls in [event.startedAt-lookback, event.startedAt]. Uses the effectiveVersionExpr chain (v0.5.283) so Helm-only installs (app.kubernetes.io/version label) and image-tag fallbacks correlate too, not just bare service.version.

func (*Store) EnrichAnomaliesWithRootCause added in v0.8.169

func (s *Store) EnrichAnomaliesWithRootCause(ctx context.Context, events []AnomalyEvent) []AnomalyEvent

EnrichAnomaliesWithRootCause is the anomaly-anchored sibling — same single batch GetHypotheses("anomaly", ids) join for the /anomalies events list ribbon. Soft-fails to the unenriched slice on error.

func (*Store) EnrichIncidentsWithClusters added in v0.5.22

func (s *Store) EnrichIncidentsWithClusters(ctx context.Context, incidents []Incident, since time.Duration) []Incident

EnrichIncidentsWithClusters mirrors EnrichProblemsWithClusters for the Incidents list. Same single-batch lookup, same soft- fail behaviour.

func (*Store) EnrichProblemsForRead added in v0.9.554

func (s *Store) EnrichProblemsForRead(ctx context.Context, probs []Problem, lookback time.Duration) []Problem

EnrichProblemsForRead — bir problem listesini OKUMA için hazırlar: önce deploy, sonra öncelik (v0.9.554).

Sıra ZORUNLU. computePriority'nin kritik kolu RecentDeploy'a bakar:

postDeploy := p.RecentDeploy != nil && AgeSeconds <= 5*60
case postDeploy: return "P1", "critical + deploy Ns before"
default:         return "P2", "critical"

Deploy adımı koşmazsa RecentDeploy nil kalır ve AYNI SATIR P2 olur. v0.9.553'te sohbet yüzeyleri tam bu yüzden Problems sayfasıyla çelişiyordu.

Zincir chstore'a TAŞINDI çünkü ikinci bir tüketici çıktı: MCP list_problems aracı (internal/mcptools) — o da api.Server'a erişemiyor. İki paketin ayrı ayrı "deploy sonra öncelik" yazması, düzeltilen ayrışmanın yeni bir kopyası olurdu.

func (*Store) EnrichProblemsWithClusters added in v0.5.22

func (s *Store) EnrichProblemsWithClusters(ctx context.Context, problems []Problem, since time.Duration) []Problem

EnrichProblemsWithClusters fills each problem's Clusters field from the recent service-to-cluster map. One batch query covers every problem in the slice. Soft-fails: if the lookup errors we return the slice unchanged rather than blocking the page on a transient CH blip.

func (*Store) EnrichProblemsWithDeploys added in v0.5.48

func (s *Store) EnrichProblemsWithDeploys(ctx context.Context, problems []Problem, lookback time.Duration) []Problem

EnrichProblemsWithDeploys attaches the most recent observed service.version deploy that happened up to `lookback` before each problem's started_at. Single bulk CH query covers every service across every problem in the slice — N+1 free regardless of problem count. Soft-fails: CH error returns the slice unchanged rather than blocking the page render.

Mechanism: one GROUP BY over spans for the union of involved services in [min(started)-lookback, max(started)] (cached ~15s, v0.8.359), then per-problem in-memory match against the highest first_seen time ≤ that problem's started_at.

func (*Store) EnrichProblemsWithRootCause added in v0.8.169

func (s *Store) EnrichProblemsWithRootCause(ctx context.Context, problems []Problem) []Problem

EnrichProblemsWithRootCause attaches the persisted root-cause summary to each problem in ONE batch read — the N+1-free join the /problems list handler uses for the in-page ribbon. Collects the ids, fires a single GetHypotheses, and sets p.RootCause only for problems that have a hypothesis (the rest keep nil → honest "no clear cause yet" ribbon). Soft-fails to the unenriched slice on error so a transient blip on this advisory join never blanks the page (same posture as EnrichProblemsWithClusters).

func (*Store) EnrichProblemsWithRunbooks

func (s *Store) EnrichProblemsWithRunbooks(ctx context.Context, problems []Problem) []Problem

EnrichProblemsWithRunbooks resolves each problem's RunbookURL from (a) the alert rule that fired or (b) the service catalog metadata as a fallback. Two single-shot queries (alert_rules + service_metadata) joined in-memory against the problems slice — N+1 free regardless of the problem count. Safe to call on resolved problems too; the URL is contextual to the rule / service, not the status.

func (*Store) EnrichProblemsWithTeams added in v0.8.290

func (s *Store) EnrichProblemsWithTeams(ctx context.Context, problems []Problem) []Problem

EnrichProblemsWithTeams attaches each problem's owning team (OwnerTeam) + reliability team (SRETeam) from the service catalog. One batch ListServiceMetadata lookup covers every problem in the slice — N+1 free, the same shape as the Clusters / Runbooks enrichers. Read-time only: team ownership lives on the operator-curated catalog, NOT the problems row, so a catalog edit reflects on the next refresh without rewriting history. Soft-fails: a catalog read error (or empty catalog) returns the slice unchanged rather than blanking the team chips on a transient CH blip. (v0.8.290 — powers the owner/SRE team filters on /problems, mirroring the inbox enrichment so the two pages agree on which team owns a firing service.)

func (*Store) EnvMemberServices added in v0.8.387

func (s *Store) EnvMemberServices(ctx context.Context, env string) ([]string, error)

EnvMemberServices resolves an environment name to the sorted set of services that ran in it, from the 60s-cached 1h-clamped service→env map (v0.8.387 — the env twin of clusterMemberServices, v0.8.386). Exported because the /inbox handler applies the same service-scoped env semantics to its merged item list.

Return contract (load-bearing, unlike clusterMemberServices' nil):

  • (members, nil) — authoritative; an EMPTY slice means the env genuinely has no services in the last hour, and callers filter to zero service-scoped rows (honest empty, not "show all").
  • (nil, err) — the map could not be resolved (cold conn-less store, CH blip); callers MUST soft-fail to unfiltered so a transient error never hides a firing P1.

func (*Store) ExemplarsForMetric added in v0.8.328

func (s *Store) ExemplarsForMetric(ctx context.Context, metric, service string, from, to time.Time, limit int) ([]OTLPExemplar, error)

ExemplarsForMetric is the fingerprint-less fallback (legacy rows / service-level rollups): every exemplar of one metric on one service in the window, regardless of series. Granule scan — bounded by the window, the LIMIT and the execution cap.

func (*Store) ExemplarsForSeries added in v0.8.328

func (s *Store) ExemplarsForSeries(ctx context.Context, fingerprints []uint64, from, to time.Time, limit int) ([]OTLPExemplar, error)

ExemplarsForSeries is the canonical metric→trace pivot read: exemplars for a set of series fingerprints (one chart = the fingerprints of its plotted series) inside a time window. Primary-key scan by construction — see the file header. fingerprint 0 is the legacy-row sentinel and never a real identity; callers shouldn't pass it, and rows can't match it anyway (SeriesFingerprint of real inputs is never stored as 0 by the write path).

func (*Store) FindExemplar

func (s *Store) FindExemplar(ctx context.Context, req ExemplarReq) (*Exemplar, error)

func (*Store) FindExemplarRollup added in v0.8.157

func (s *Store) FindExemplarRollup(ctx context.Context, req ExemplarReq) (*Exemplar, error)

FindExemplarRollup resolves the representative trace_id for (service, window, kind) straight off the spanmetrics rollup's argMax(If) exemplar states — the EXACT same per-bucket exemplar trace_id Explore's ◆ glyphs use, and the most precise + cheapest exemplar we have: one MV read over spanmetrics_1m (the always-present 30d tier), no raw-spans scan.

slow_exemplar_state is argMaxState(trace_id, duration) and error_exemplar_state is argMaxIfState(trace_id, duration, status_code='error') — argMaxMerge over the whole window collapses every bucket's per-bucket winner into THE single max-duration (or max-duration-among-errors) trace for the span, so one row gives the representative trace directly. Combinator contract: argMaxMerge for the slow state, argMaxIfMerge for the If-state (mixing them up fails at runtime — the v0.8.51 catch).

Returns (nil, nil) — the soft "not found" posture — when the window predates the rollup cutover / has TTL'd away / has no exemplar for that span, so the caller can fall through to FindExemplar (raw spans, still real). The result carries only TraceID + Service (the rollup exemplar state is a trace_id, not a span row); the caller pivots on the trace_id, which is the precise part.

Bound: spanmetrics_1m is an AggregatingMergeTree MV — the WHERE is service_name equality + a time_bucket range on the ORDER BY prefix, so CH prunes to the relevant granules. LIMIT 1 + max_execution_time cap it.

func (*Store) FindFatalExceptions added in v0.9.609

func (s *Store) FindFatalExceptions(ctx context.Context, lookback time.Duration) ([]FatalException, error)

FindFatalExceptions — penceredeki altyapı-ölümcül exception'lar.

Tazelik kapısı LAST_SEEN'de (v0.9.576 dersi): first_seen kullansaydık (a) uzun süren bir arıza pencereden düşüp hiç görünmez, (b) daha kötüsü, KAPANIŞ görünürlüğe bağlı kalırdı — dedektör "artık aktif değil" kararını ancak koşulu GÖREREK verebilir.

Bounded: state tablosu, zaman pencereli WHERE, LIMIT, max_execution_time. FINAL şart (ReplacingMergeTree).

func (*Store) FindOpenProblem

func (s *Store) FindOpenProblem(ctx context.Context, ruleID, service string) (*Problem, error)

func (*Store) FindOpenProblemByID added in v0.9.401

func (s *Store) FindOpenProblemByID(ctx context.Context, id string) (*Problem, error)

FindOpenProblemByID — deterministik ID'li üreticiler (runtime pod denetimleri, v0.9.401) için açık/ack problem araması. FindOpenProblem (ruleID, service) anahtarı per-pod granülerliği taşıyamaz — pod artık service alanında DEĞİL.

func (*Store) FindProfilesForSpan

func (s *Store) FindProfilesForSpan(ctx context.Context, service string, spanStart, spanEnd time.Time) ([]ProfileRow, error)

func (*Store) FindSharedExceptionBursts added in v0.9.572

func (s *Store) FindSharedExceptionBursts(ctx context.Context, since time.Duration, minServices int) ([]SharedExceptionBurst, error)

FindSharedExceptionBursts — son `since` içinde başlamış, en az minServices ayrı serviste görülen aynı-tip exception patlamaları.

Kova mantığı ŞART: kova olmadan, aylardır her serviste ara sıra görülen yaygın bir exception tipi (NullPointerException gibi) her taramada "paylaşılan patlama" sayılırdı. Kova, EŞZAMANLI BAŞLAMA koşulunu SQL'e taşıyor.

func (*Store) FindSimilarResolvedProblems added in v0.5.35

func (s *Store) FindSimilarResolvedProblems(ctx context.Context, service, ruleID string, limit int) ([]Problem, error)

FindSimilarResolvedProblems returns up to `limit` resolved problems matching the given service + rule_id, ordered most recent first. Used by the runbook AI to anchor LLM suggestions in actually-resolved-before incidents rather than generic SRE wisdom. We require status='resolved' so the model sees only outcomes it can learn from — open problems have no time-to-resolve signal yet.

func (*Store) FindTraceIDBySpan added in v0.9.548

func (s *Store) FindTraceIDBySpan(ctx context.Context, spanID string, from, to time.Time) (string, error)

FindTraceIDBySpan — v0.9.548. Çıplak bir SPAN id'sinin ait olduğu trace'i bulur. "" = bulunamadı (hata değil).

Operatör CoSRE'ye 16-hex yapıştırdığında gereken tek şey bu: trace bulununca mevcut kanıt paketi (buildTraceExplainInput, v0.9.537) olduğu gibi devreye giriyor.

MALİYET UYARISI — spans ORDER BY (service_name, time) ve span_id'de İNDEKS YOK (trace_id'de bloom var, span_id'de yok). Yani bu arama pencere içinde bir kolon taraması. Kabul edilebilir çünkü:

  • açık bir operatör eylemi (ID yapıştırdı), arka plan yoklaması değil
  • LIMIT 1 → eşleşme bulununca CH erken çıkar
  • pencere ÇAĞIRAN tarafından sınırlanır + max_execution_time tavanı, yani eşleşme YOKKEN bile maliyet üstten kapalı

Bunu bir hot path'e ya da bir poll'a bağlamak YANLIŞ olur.

func (*Store) GetAICall added in v0.5.164

func (s *Store) GetAICall(ctx context.Context, id string) (*AICall, error)

GetAICall fetches one row by id — drives the drill-in panel where the operator inspects prompt/response in full.

func (*Store) GetAlertRule

func (s *Store) GetAlertRule(ctx context.Context, id string) (*AlertRule, error)

func (*Store) GetAllServiceRuntimes

func (s *Store) GetAllServiceRuntimes(ctx context.Context) (map[string]ServiceRuntime, error)

GetAllServiceRuntimes returns the technology fingerprint for every service that emitted spans in the last hour. Single CH query using argMaxIf(field, time, <has runtime attrs>) to extract the latest ATTRIBUTE-BEARING res_keys / res_values per service in one pass — avoids the N-services × N-requests fan-out the /services listing page would otherwise hit.

v0.9.45 (operator-reported: "çoğu serviste rozet yok"):

  1. argMax → argMaxIf: bir servis birden fazla resource'tan span basıyorsa (app + sidecar, karışık pipeline) düz argMax en-son span'i seçiyordu — attribute'suz kaynak son basmışsa rozet piyango gibi kayboluyordu. Koşul, badge'in render şartıyla birebir aynı üçlü.
  2. LIMIT 500 → ORDER BY count() DESC LIMIT 2000: sırasız LIMIT, 500+ aktif servisli filoda RASTGELE bir alt kümeye rozet verip gerisini kalıcı rozetsiz bırakıyordu (hangi 500'ün geldiği cache yenilendikçe değişiyordu). Artık en aktif 2000 servis deterministik olarak kapsanır.

Output is a map keyed by service name. Missing services (no span in the lookback, or beyond the top-2000) are absent from the map; callers render the badge only for the names that exist. A service whose spans carry NO runtime attrs gets empty fields → the badge stays hidden (honest, not lottery).

func (*Store) GetAnnouncement added in v0.8.486

func (s *Store) GetAnnouncement(ctx context.Context) (Announcement, error)

GetAnnouncement returns the saved banner (zero-value when unset).

func (*Store) GetAnomalyEvent added in v0.8.167

func (s *Store) GetAnomalyEvent(ctx context.Context, id string, activeAge time.Duration) (*AnomalyEvent, error)

GetAnomalyEvent reads one event by id (the FingerprintAnomaly hash). FINAL collapses the ReplacingMergeTree versions to the latest row. Returns (nil, nil) on no-match so the API layer can answer a clean 404 instead of treating "not found" as an error. The status is derived the same way ListAnomalyEvents derives it — active iff last_seen is fresh within ActiveAge (default 10m) — so an event-anchored read agrees with the list view. Bounded by the id equality on the PK; anomaly_events is a small state table, not spans/metric_points, so no time-bound is needed.

func (*Store) GetAnomalyPromotion added in v0.5.70

func (s *Store) GetAnomalyPromotion(ctx context.Context) AnomalyPromotionConfig

GetAnomalyPromotion returns the persisted config, or the defaults when nothing's saved. Soft-fails to defaults on CH error so a transient blip doesn't accidentally disable promotion in a long-running evaluator.

func (*Store) GetAnomalySilence

func (s *Store) GetAnomalySilence(ctx context.Context, id string) (*AnomalySilence, error)

func (*Store) GetBranding added in v0.4.80

func (s *Store) GetBranding(ctx context.Context) (BrandingSettings, error)

GetBranding returns the saved branding overlay (or an empty struct if unset — caller applies defaults). The endpoint that serves this is public, since the login page renders the result before the operator has a session.

func (*Store) GetCardinality

func (s *Store) GetCardinality(ctx context.Context) (*CardinalityReport, error)

GetCardinality runs four bounded queries serially. The HTTP handler caches the result for 5 minutes so the operator can refresh the page without hammering CH; sub-second cold cost on a typical CH at 100M spans/day, ~5s at 1B+ (system.columns is metadata-fast either way; the sampled scans dominate).

Sampling caps:

  • Service / metric counts: full 24h scan, partition-pruned by toDate — cheap enough not to bother sampling.
  • Attribute keys: LIMIT 100k rows of recent data, then arrayJoin. 100k × ~20 attrs/row = 2M rows post-explode, uniqExact handles that sub-second.

func (*Store) GetChannel

func (s *Store) GetChannel(ctx context.Context, id string) (*NotificationChannel, error)

func (*Store) GetCorrelatedChanges added in v0.4.73

func (s *Store) GetCorrelatedChanges(
	ctx context.Context, at time.Time, windowSec, baselineSec int,
) ([]ChangedService, error)

GetCorrelatedChanges runs one ClickHouse pass that pivots span stats by service across two adjacent time windows: the baseline (before `at`) and the current (since `at`). Returns the top 20 services whose composite anomaly score is highest, plus a per-row "reasons" list explaining what triggered the rank.

Caller passes `at` (typically Problem.StartedAt), `windowSec` (how long since the problem fired — typically the SLO eval window, 5-15 min) and `baselineSec` (how far back to look for the comparison — typically 4× windowSec to give the baseline statistical weight).

One query, partition-pruned + bounded by HAVING clauses so a long-tail service emitting one span every 10 min doesn't pollute the rank with a 100% rate change off a baseline of 1.

func (*Store) GetCustomRolesRaw added in v0.5.251

func (s *Store) GetCustomRolesRaw(ctx context.Context) ([]byte, error)

GetCustomRolesRaw returns the saved JSON blob for operator-defined custom roles, or nil if none have been persisted yet.

func (*Store) GetDBStmtCallers added in v0.8.378

func (s *Store) GetDBStmtCallers(ctx context.Context, q DBStmtDetailQuery, limit int) ([]DBStmtCaller, error)

GetDBStmtCallers returns the per-service breakdown of the statement class, top `limit` by total time.

func (*Store) GetDBStmtSummary added in v0.8.378

func (s *Store) GetDBStmtSummary(ctx context.Context, q DBStmtDetailQuery) (*DBStmtSummary, error)

GetDBStmtSummary returns the statement's window rollup, or (nil, nil) when the class has no rows in the window (a GROUP-BY-less aggregate always yields one row — cnt==0 IS the "not found" signal). safeF on every merged float: TDigest merges over edge-case states can yield NaN and encoding/json rejects NaN (the v0.5.301 500-class).

func (*Store) GetDBStmtTrend added in v0.8.378

func (s *Store) GetDBStmtTrend(ctx context.Context, q DBStmtDetailQuery) (points []DBStmtTrendPoint, bucketSec int64, err error)

GetDBStmtTrend returns the statement's trend series — one MV scan, GROUP BY coarsened bucket. Buckets with no data are absent (sparse); the returned bucketSec lets the frontend densify against the window exactly (gaps render as zeros) without re-deriving the coarsening.

func (*Store) GetDBTrends added in v0.7.68

func (s *Store) GetDBTrends(ctx context.Context, from, to time.Time) ([]DBTrend, error)

GetDBTrends reads db_summary_5m bucketed by (db_system, instance, db_name, time_bucket) over [from,to] and returns one DBTrend per (db_system, instance, db_name) — a small call-rate / p99 / error-rate sparkline plus the latest-bucket health snapshot. Drives the per-row RED sparklines (#1) + health gauges (#6) on the /databases overview grid.

MV-only (NOT raw spans) per the aggregate-read invariant — same AggregatingMergeTree the overview's GetDatabases reads, so the row identities line up exactly. The query is bounded three ways even though the MV is already small: time-bounded WHERE on the ORDER-BY-leading time_bucket, a LIMIT on the result, and a max_execution_time guard.

The from is truncated to the 5-minute grid so a rolling window snaps to bucket boundaries (the same trick GetDatabases uses) — keeps the cache key + bucket alignment stable across adjacent polls.

func (*Store) GetDBWaitLock added in v0.8.391

func (s *Store) GetDBWaitLock(ctx context.Context, system, instance string, from, to time.Time) (*DBWaitLock, error)

GetDBWaitLock reads the per-engine wait/lock families for one (system, instance) and normalizes them into the common strip model. At most two bounded metric_points trips (counters + the optional dimensioned lock-mode read). Unsupported engines return Supported=false with zero CH trips.

func (*Store) GetDDLQueueHealth added in v0.9.613

func (s *Store) GetDDLQueueHealth(ctx context.Context) (DDLQueueHealth, error)

GetDDLQueueHealth — üç bağımsız probe + saf verdict.

Her probe soft-fail eder: teşhis aracı, teşhis ettiği arızada (Keeper sıkıntısı, host down) tamamen kör kalmamalı — ama kör kaldığı yerde UYDURMAZ, probe_failed der.

func (*Store) GetDashboard

func (s *Store) GetDashboard(ctx context.Context, id string) (*Dashboard, error)

func (*Store) GetDatabaseDetail added in v0.4.86

func (s *Store) GetDatabaseDetail(
	ctx context.Context, system, instance string, from, to time.Time,
) (*DBDetail, error)

func (*Store) GetDatabases added in v0.4.82

func (s *Store) GetDatabases(ctx context.Context, from, to time.Time) ([]DBInstance, error)

func (*Store) GetDeploysInWindow added in v0.9.435

func (s *Store) GetDeploysInWindow(ctx context.Context, from, to time.Time, limit int) ([]RecentDeployEntry, error)

func (*Store) GetEdgeInstances added in v0.5.142

func (s *Store) GetEdgeInstances(ctx context.Context, parentService, system, kind string, from, to time.Time, limit int) ([]EdgeInstance, error)

GetEdgeInstances returns the peer_service breakdown for one (parentService, system, kind) edge over [from, to]. Bounded by the spans (service_name, time) primary key + filtered by db_system / msg_system so the scan stays tight even at billions of spans/day. Limit caps the buckets — 50 hosts is more than enough for any realistic per-service db/queue fan-out.

kind: "db" → filter by db_system; "queue" → filter by msg_system. Returns empty slice when nothing matches (empty window).

func (*Store) GetEndpoints added in v0.5.365

func (s *Store) GetEndpoints(ctx context.Context, q EndpointsQuery) ([]EndpointRow, error)

GetEndpoints dispatches the /endpoints read (v0.8.356, Stage-2 slice E1). Default path reads the spanmetrics_1m MV (MV-first invariant — the old raw CTE was a bounded full-scan of spans at billion-span scale; on the reference install it ran 16-19s cold and sometimes tripped its own 15s cap). The raw path survives ONLY for the cluster + env filters: cluster is derived from res/attr arrays (clusterExpr) and env is spans.deploy_env — dimensions the MV doesn't carry (v0.8.385 kept it that way: cluster-parity raw-fallback, NO MV changes).

Known trade-off, documented: spans.http_route is populated at ingest from http.route with an http.target fallback (internal/otlp/convert.go) — the MV path therefore does NOT see url.path-only spans the raw CTE's coalesce chain caught. Those are the untemplated, cardinality-bomb paths; losing them from the DEFAULT view is acceptable (they still appear under the cluster-filtered raw path).

func (*Store) GetEndpointsMV added in v0.8.356

func (s *Store) GetEndpointsMV(ctx context.Context, q EndpointsQuery) ([]EndpointRow, error)

GetEndpointsMV is the spanmetrics_1m-backed /endpoints read (v0.8.356). One MV scan produces the whole table: RED counts via countMerge/countIfMerge/sumMerge, TRUE window p50/p95/p99 via a two-level tdigest merge (per-bucket -MergeState in the CTE, final -Merge across buckets — the raw CTE could only max() per-bucket p99s), and the three ≤SparklineBuckets-slot sparklines rebuilt from the MV's time_bucket series. Tier + slot width come from endpointsSparkGrid (v0.9.207): slots are exact multiples of the chosen tier's grain and short windows ship fewer, real slots — the frontend bucketsToSeries derives the axis from array length, so variable length is safe.

HTTP status-class pills + the method chip need http_status / http_method, which the MV does NOT carry — those come from ONE bounded raw-spans sidecar over the returned top-N keys only (endpointStatusSidecar), skipped for compare=prior reads.

func (*Store) GetExceptionGroup

func (s *Store) GetExceptionGroup(ctx context.Context, fingerprint string) (*ExceptionGroup, error)

func (*Store) GetExceptionGroupSamples

func (s *Store) GetExceptionGroupSamples(ctx context.Context, fingerprint string, limit int) ([]ExceptionSample, int, bool, error)

GetExceptionGroupSamples returns up to `limit` recent occurrences of the group (by fingerprint), most-recent first. Because v2 fingerprints merge messages that differ only in dynamic IDs, we can't filter the candidate set by exact message — instead we scan recent spans matching (service, type), recompute the fingerprint per row in Go, and return the first `limit` that match. v0.9.463 (dürüstlük A11) — dönüş zarfı scanned/scanCapped taşır: aday taraması (service, type) üzerinden en-yeni-500'dür; sıcak serviste kardeş fingerprint 500'ü doldurunca 10K occurrence'lı grup "No samples" okuyordu — yanlış-boş. Şerit artık "en yeni 500 aday tarandı, bu grubun örneği pencerede yok" diyebiliyor.

func (*Store) GetExceptionGroupsByFingerprints added in v0.9.523

func (s *Store) GetExceptionGroupsByFingerprints(ctx context.Context, fps []string) (map[string]ExceptionGroup, error)

GetExceptionGroupsByFingerprints — TOPLU okuma (GetHypotheses emsali).

func (*Store) GetExceptionOccurrences added in v0.8.309

func (s *Store) GetExceptionOccurrences(ctx context.Context, fingerprint string) ([]OccurrencePoint, error)

GetExceptionOccurrences returns a real, gap-filled occurrences-over- time series for the group (by fingerprint), spanning its whole [first_seen, last_seen] window. It replaces the old client-side bucketing of the 100 most-recent samples, which mis-rendered any busy group: the newest 100 samples cluster near last_seen, so all-but-one bucket read zero even for a steadily-firing problem (v0.8.309).

The count is coarse-scoped to (service, exception.type) — the same candidate population GetExceptionGroupSamples draws from — so a group whose (service, type) hosts a single fingerprint (the common case) reads exactly; a rare (service, type) shared by sibling fingerprints reads slightly high. That's the honest, bounded trade for a temporal distribution SQL can compute without recomputing the Go-side fingerprint per row.

func (*Store) GetExceptions

func (s *Store) GetExceptions(ctx context.Context, f ExceptionFilter) ([]ExceptionRow, error)

GetExceptions returns OTel `exception` events grouped by (type, message, service) with totals and a sample trace/span pointer for drill-down.

We dig the events JSON column with JSON_VALUE — slower than dedicated columns, but the volume of error spans is small relative to the total.

func (*Store) GetExecution added in v0.6.73

func (s *Store) GetExecution(ctx context.Context, id string) (*RunbookExecution, error)

func (*Store) GetExternalHostDetail added in v0.8.446

func (s *Store) GetExternalHostDetail(ctx context.Context, host string, from, to time.Time) (*ExternalHostDetail, error)

GetExternalHostDetail returns the drawer payload for one host: per-caller breakdown plus the 5-minute RED trend. Two bounded MV reads; either could legitimately be empty (host aged out of the window) — the caller renders the empty state, not an error.

func (*Store) GetExternalHosts added in v0.8.446

func (s *Store) GetExternalHosts(ctx context.Context, from, to time.Time) ([]ExternalHost, error)

GetExternalHosts returns every external destination seen in the window, busiest first. Capped at 500 rows — beyond that the page is a search problem, not a list problem.

func (*Store) GetFlowTopology added in v0.5.103

func (s *Store) GetFlowTopology(ctx context.Context, from, to time.Time, rootService, rootOp string, limit int) ([]ServiceTopologyEdge, error)

GetFlowTopology returns the service-level subgraph restricted to traces whose root span matches (rootService, rootOp). Same shape as GetServiceTopologyEdges so the renderer reuses one code path. Used by the flow-detail view.

func (*Store) GetHostDetail added in v0.8.449

func (s *Store) GetHostDetail(ctx context.Context, host string, from, to time.Time) (*HostDetail, error)

GetHostDetail returns the drawer payload for one host: per-service breakdown + per-minute CPU/mem trend. Both queries carry the host_name filter, so they touch a sliver of the window.

func (*Store) GetHosts added in v0.8.449

func (s *Store) GetHosts(ctx context.Context, from, to time.Time) ([]HostRow, error)

GetHosts returns every host/pod seen in the (clamped) window, busiest CPU first.

v0.8.449 review-fix: the aggregation is two-pass — the inner query picks each SERVICE's latest cpu/mem/limit on the host, the outer sums across services. A flat argMaxIf per host (the ServiceInstances idiom) is only correct when scoped to one service; host-wide it returns whichever service exported last, so multi-service hosts flip-flopped between services' values and MemPct could divide service A's usage by service B's limit. The pct now pairs each service's usage with its OWN limit: only limit-reporting services count in the numerator.

func (*Store) GetHypotheses added in v0.8.169

func (s *Store) GetHypotheses(ctx context.Context, anchorKind string, ids []string) (map[string]RootCauseHypothesis, error)

GetHypotheses batch-reads the latest hypothesis for many anchors of ONE kind in a SINGLE FINAL query — `WHERE anchor_kind = ? AND anchor_id IN (?, ?, …)`. This is the N+1-free join the /anomalies + /problems list handlers use to attach a RootCauseSummary per row: one round-trip for the whole page instead of GetHypothesis per row. Returns a map keyed by anchor_id holding only the anchors that HAVE a synthesized hypothesis — callers omit the summary for the rest (the ribbon shows an honest "no clear cause yet" state).

Plain `IN (?, …)` — NOT `GLOBAL IN`: root_cause_hypotheses is a local ReplacingMergeTree state table (like anomaly_events / problems), not a Distributed table, and the values are bound literals, not a subquery. GLOBAL IN only matters when the right-hand side is a subquery executed over a Distributed table. The id slice is de-duplicated + capped at hypothesesIDCap so the IN-list can't fan out unbounded. The (anchor_kind, anchor_id) ORDER BY key bounds the scan; the table is small + low-volume so no time-bound is needed (same rationale as GetHypothesis).

func (*Store) GetHypothesis added in v0.8.168

func (s *Store) GetHypothesis(ctx context.Context, anchorKind, anchorID string) (*RootCauseHypothesis, error)

GetHypothesis reads the latest hypothesis for one anchor. FINAL collapses the ReplacingMergeTree versions to the newest row. Returns (nil, nil) on no-match so the API layer answers a clean empty-state instead of treating "not yet synthesized" as an error (same soft-not-found idiom as GetAnomalyEvent). Bounded by the (anchor_kind, anchor_id) equality on the ORDER BY key; root_cause_hypotheses is a small low-volume state table, not spans / metric_points, so no time-bound is needed.

func (*Store) GetIncident

func (s *Store) GetIncident(ctx context.Context, id string) (*Incident, error)

GetIncident returns one incident by id, or (nil, nil) when it genuinely does not exist.

v0.9.332 — this used to fetch the newest 1000 incidents and linear-scan them in Go. Any incident outside that window was INVISIBLE and the function returned (nil, nil) — indistinguishable from "deleted". Locally there are 7,539 incidents, so ~87% of them could not be fetched at all; prod is deeper still.

Everything that reads one incident was affected, and all of it failed quietly: the auto-resolve cascade skipped old incidents (`inc == nil → continue`), which is why the orphan fix in this same release closed them one at a time instead of all at once; Acknowledge / Resolve / Update answered 404 on anything older; the detail page opened empty.

It now asks the database for the row it wants. Same scan code as the list (one filter field) rather than a second hand-rolled query that could drift.

func (*Store) GetInfraMetrics

func (s *Store) GetInfraMetrics(ctx context.Context, service string, since, bucket time.Duration) ([]InfraMetricSeries, error)

GetInfraMetrics returns the curated set of timeseries for one service over the requested window. One CH query that:

  • Filters by service_name + the union of all candidate metric names (LowCardinality + primary key prefix → granule prune).
  • Buckets time by `bucket` so the frontend gets a fixed-size sparkline regardless of point density.
  • Picks the single most-specific source per slot via a priority-ordered IN list — the per-row `metric` is then stitched back to its slot in Go.

func (*Store) GetKibana added in v0.5.219

func (s *Store) GetKibana(ctx context.Context) (KibanaSettings, error)

GetKibana returns the saved settings (or empty struct when unconfigured — Enabled stays false so the UI hides the link).

func (*Store) GetLatencyHeatmap

func (s *Store) GetLatencyHeatmap(
	ctx context.Context,
	filters []FilterExpr,
	from, to time.Time,
	timeBuckets int,
) (*LatencyHeatmap, error)

GetLatencyHeatmap runs a single CH GROUP BY against the spans table, bucketing by (time, log-scale duration). The log-bin formula floor(log10(ms+ε)*subBins) is fast (no quantile state) and matches Honeycomb's heatmap binning. Filters mirror the rest of the span-metric API so a chart drawn from /explore reads the same dataset as the metric trend chart on the same page.

Posture: at billion-span scale a 60×28 = 1680-bucket result set is trivial; the cost is in the GROUP BY which is bound by the (service_name, time) primary key + any filter on service.name. Sub-second on a 24h window for a single service.

func (*Store) GetLogs

func (s *Store) GetLogs(ctx context.Context, f LogFilter) ([]LogRow, uint64, string, error)

GetLogs reads a page of the logs table newest-first. v0.7.22 (SAFE-CORE) hardened it for billion-row scale:

  • Bounded LIMIT (capped at logsMaxLimit) + SETTINGS max_execution_time = 25 — CLAUDE.md hard constraint that was missing before (the count() + main SELECT could full-scan unbounded).
  • STABLE sort: ORDER BY time DESC, <rowKey> DESC, where rowKey is a deterministic cityHash64 over the line's identifying columns (logsRowKeyExpr). v0.7.23 (SAFE-CORE) replaced the span_id tiebreak: span_id is String DEFAULT ” and most log lines are emitted outside a span, so (time, span_id) was not a total order — a page boundary inside a run of (t0,”) rows dropped every remaining (t0,”) row on the next page. The hash makes (time, rowKey) a provable total order, so no boundary drop/dup.
  • Keyset cursor paging: when f.Cursor decodes, page strictly AFTER the encoded (time, rowKey) instead of OFFSET. Empty cursor → first page; Offset still honoured for back-compat.

Returns the rows, the (capped-cost) total match count for the UI, and a NextCursor — empty when fewer than the requested limit came back (last page).

func (*Store) GetLogstoreESSettingsRaw added in v0.8.232

func (s *Store) GetLogstoreESSettingsRaw(ctx context.Context) ([]byte, error)

GetLogstoreESSettingsRaw returns the saved JSON blob for the UI-managed logstore config, or nil if none has been persisted yet (env/YAML config stays authoritative until the first admin save).

func (*Store) GetMessaging added in v0.4.82

func (s *Store) GetMessaging(ctx context.Context, from, to time.Time) ([]MessagingInstance, error)

GetMessaging is the structural parallel for messaging systems. Resolves the destination name from messaging.destination.name when present (OTel semconv), falling back to peer.service. arrayElement / indexOf is cheap because attr_keys is bounded per row + the WHERE prunes by msg_system on the indexed column first.

func (*Store) GetMessagingDetail added in v0.4.86

func (s *Store) GetMessagingDetail(
	ctx context.Context, system, cluster, destination string, from, to time.Time,
) (*MessagingDetail, error)

func (*Store) GetMessagingRollup added in v0.8.364

func (s *Store) GetMessagingRollup(ctx context.Context, from, to time.Time) ([]MessagingInstance, error)

GetMessagingRollup — v0.8.364 (Stage-2 M1). Prior-window read for the /api/messaging compare=prior merge. Identical rollup to GetMessaging minus the top-callers pass: the delta badges only consume counts + quantiles, so the prior scan skips the extra MV trip (the endpoints SkipStatus pattern, v0.5.404).

func (*Store) GetMessagingTrends added in v0.9.434

func (s *Store) GetMessagingTrends(ctx context.Context, from, to time.Time) ([]DBTrend, error)

GetMessagingTrends (v0.9.434, kuyruk #3b — desen paritesi) — GetDBTrends'in messaging ikizi: messaging_summary_5m'i (msg_system, cluster, destination, time_bucket) kırılımında okur, satır başına sparkline + son-bucket sağlık anlık görüntüsü döner. DBTrend şekli yeniden kullanılır (başlık yorumu bunu zaten vadediyordu): DbSystem=msg_system, Instance=destination, Cluster=cluster, DbName boş — frontend join anahtarı messaging'de (system|cluster|destination). MV-only; üç sınır (time-bounded WHERE + LIMIT + max_execution_time). error_count_state bu MV'de countState — countMerge (db_summary_5m'in countIfMerge'ünden farklı, getMessaging ile birebir aynı okuma).

func (*Store) GetMetricBaseline added in v0.5.50

func (s *Store) GetMetricBaseline(
	ctx context.Context, service, metric string, lookback time.Duration,
) (*MetricBaseline, error)

GetMetricBaseline runs the right percentile query for the requested metric over the given lookback. Service filter is optional — global baselines help when the operator is adding a "warn on any service exceeding X" cross-service rule. Hard cap of 7 days; longer lookbacks were measured to add ~3s without changing the percentile values meaningfully (recent distribution dominates).

func (*Store) GetMetricNames

func (s *Store) GetMetricNames(ctx context.Context, service string) ([]MetricInfo, error)

func (*Store) GetMetricPoints

func (s *Store) GetMetricPoints(ctx context.Context, metric, service string, from, to time.Time, limit int) ([]MetricPointRow, bool, error)

func (*Store) GetMonitor

func (s *Store) GetMonitor(ctx context.Context, id string) (*Monitor, error)

func (*Store) GetMonitorByToken

func (s *Store) GetMonitorByToken(ctx context.Context, token string) (*Monitor, error)

func (*Store) GetMySQLMetrics added in v0.5.12

func (s *Store) GetMySQLMetrics(
	ctx context.Context, instance string, from, to time.Time,
) (*MySQLMetrics, error)

func (*Store) GetMySQLTopSQL added in v0.7.69

func (s *Store) GetMySQLTopSQL(
	ctx context.Context, instance string, from, to time.Time,
) ([]DBTopSQL, error)

GetMySQLTopSQL returns the heaviest statements for one MySQL instance as measured by the database itself, sourced from performance_schema (events_statements_summary_by_digest)-shaped metric_points the OpenTelemetry mysql receiver (or a sqlquery receiver scraping performance_schema) publishes.

Same empty-is-expected contract as Postgres: the stock mysql receiver does NOT emit per-digest statement metrics unless the operator enables performance_schema statement instrumentation and the corresponding scrape. Absent → empty slice → panel EMPTY state. The bundled demo emits only Oracle, so MySQL shows empty there — expected.

performance_schema reports SUM_TIMER_WAIT in picoseconds; the receiver is expected to normalise to milliseconds before export (the mysql receiver normalises its other timer metrics the same way). We treat the value as milliseconds total and convert to seconds for the panel — matching the Postgres path so a single frontend renderer covers both.

func (*Store) GetOpenProblemCountsByService added in v0.5.274

func (s *Store) GetOpenProblemCountsByService(ctx context.Context) (map[string]OpenProblemCounts, error)

GetOpenProblemCountsByService returns per-service tallies of currently-open problems grouped by severity. Single FINAL scan over the problems table — bounded by `status = 'open'` so the row count is tiny even on a busy install (open problems are a triage state, not a historical archive).

func (*Store) GetOperationSummary

func (s *Store) GetOperationSummary(ctx context.Context, service string, since time.Duration, from, to time.Time, normalized bool) ([]OperationSummary, error)

GetOperationSummary returns per-operation aggregates for a single service: count, error rate, p50/p95/p99 latency, apdex, plus a fixed-length call-rate sparkline over the same window. Drives the "Operations" table on the service detail page. Rows ordered by span count desc so the heaviest operations surface first; the front-end applies its own sort if the user clicks a column header.

Pass `since` for a relative window OR a non-zero from/to for an absolute one (matches GetServices semantics). Service name is required; passing "" returns all operations across all services, which is rarely useful but mirrors the existing GetOperations behaviour.

Sparkline data comes from a second query that GROUPs BY (name, bucket_idx) so the worst case is `numNames × SparklineBuckets` rows rather than one-row-per-span — safe at billion-span scale. The two queries run sequentially (not parallel) because the cache key is shared and the second one is small/fast enough that the round-trip cost dominates over its execution time. normalized=true groups the operations by op_group (the normalized operation-shape column; group_id rel B) instead of the raw operation name — both the MV path (operation_group_summary_5m) and the raw-spans fallback group by op_group and exclude the ungrouped ” bucket. The OperationSummary.Name field carries the op_group value in that mode, so the scanner, sparkline, and frontend are unchanged. normalized=false is byte-for-byte the pre-rel-B behaviour.

func (*Store) GetOperationSummaryCompared added in v0.9.60

func (s *Store) GetOperationSummaryCompared(ctx context.Context, service string, since time.Duration, from, to time.Time, normalized bool) ([]OperationSummary, error)

GetOperationSummaryCompared — GetOperationSummary + bir-önceki eş-uzunluklu pencerenin skalerleri ve calls/errors gölge serileri, isimle merge edilmiş (v0.9.60, Endpoints ?compare=prior deseninin operations karşılığı). Prior pencere okuma hatası soft-düşer: current sonuç Prior'suz döner (karşılaştırma görünmez-düşer).

func (*Store) GetOperations

func (s *Store) GetOperations(ctx context.Context, service string, since time.Duration, from, to time.Time) ([]string, error)

GetOperations returns the distinct span names ("operations") seen in the given window, optionally filtered by service. Ordered by call count desc, so the most common operations appear first in the autocomplete list.

func (*Store) GetOracleMetrics added in v0.5.3

func (s *Store) GetOracleMetrics(
	ctx context.Context, instance string, from, to time.Time,
) (*OracleMetrics, error)

GetOracleMetrics returns the OracleDB-receiver-style drill-down for one instance. When no oracledb.* points exist in the window, returns a deterministic synthetic payload with Synthetic=true so the UI can still render and the operator can visualise what the panel will look like once their receiver is online.

The instance argument matches peer_service on the spans the row was derived from — we use it both as a deterministic seed for synthetic generation (same instance → same fake numbers across reloads) and as a `instance` attribute filter on metric_points if the receiver tags points with it (newer oracledb receiver versions do).

func (*Store) GetPipelineRulesRaw added in v0.5.263

func (s *Store) GetPipelineRulesRaw(ctx context.Context) ([]byte, error)

func (*Store) GetPostgresMetrics added in v0.5.12

func (s *Store) GetPostgresMetrics(
	ctx context.Context, instance string, from, to time.Time,
) (*PostgresMetrics, error)

GetPostgresMetrics queries metric_points for postgresql.* instruments scoped to one instance. Falls back to a zero- filled response (status=down) when no data is in flight — matches the no-demo-data policy from v0.5.8.

func (*Store) GetPostgresTopSQL added in v0.7.69

func (s *Store) GetPostgresTopSQL(
	ctx context.Context, instance string, from, to time.Time,
) ([]DBTopSQL, error)

GetPostgresTopSQL returns the heaviest statements for one Postgres instance as measured by the database itself, sourced from pg_stat_statements-shaped metric_points the OpenTelemetry postgresql receiver (or a sqlquery receiver scraping pg_stat_statements) publishes.

IMPORTANT — empty is the expected default. The stock postgresql receiver does NOT emit pg_stat_statements metrics; the operator must enable the pg_stat_statements extension + the receiver's statement scrape (or run a sqlquery receiver). When no such points exist in the window we return an empty slice and the panel renders its EMPTY state — exactly the path the bundled demo takes (demo only emits Oracle TopSQL).

Metric-name + attribute coverage is intentionally broad because the receiver naming for statement-level stats is not yet stabilised across versions / community variants. We match every plausible shape and let the engine-authoritative ones that exist win; absent ones contribute nothing.

func (*Store) GetProblem added in v0.5.209

func (s *Store) GetProblem(ctx context.Context, id string) (*Problem, error)

GetProblem fetches a single problem by id, or nil when no row matches. Lighter than ListProblems for the patch-one path.

func (*Store) GetProblemEscalation added in v0.9.248

func (s *Store) GetProblemEscalation(ctx context.Context) ProblemEscalationConfig

GetProblemEscalation returns the persisted config, or the defaults when nothing is saved. Soft-fails to defaults on CH error so a transient blip can't silently change escalation behaviour in a long-running evaluator — same posture as GetAnomalyPromotion.

NOTE on the Enabled field: unlike the numeric knobs, `false` is a meaningful saved value, so it is never patched back to the default. The whole struct is replaced only when the row is absent or unparseable; Normalize below touches the numbers only.

func (*Store) GetProfileBytes

func (s *Store) GetProfileBytes(ctx context.Context, id string) ([]byte, *ProfileRow, error)

GetProfileBytes returns the raw pprof payload for a profile id.

func (*Store) GetRecentDeploys added in v0.5.277

func (s *Store) GetRecentDeploys(ctx context.Context, since time.Duration, limit int) ([]RecentDeployEntry, error)

GetRecentDeploys returns service.version transitions first-seen in the requested window, ordered most-recent first. Cross-service "what changed" signal for the global banner — operator sees "frontend just shipped v1.2.3 14m ago" the moment they open ANY page.

CH posture: scans the (service_name, time) primary key inside the time bound, then min()s per (service, version) pair so a service that's been emitting the same version for hours doesn't dominate the result. Limit 20 caps the banner footprint; SETTINGS max_execution_time = 5 keeps it snappy enough to fire from a global 30s poll.

func (*Store) GetRedisMetrics added in v0.5.12

func (s *Store) GetRedisMetrics(
	ctx context.Context, instance string, from, to time.Time,
) (*RedisMetrics, error)

func (*Store) GetRetention

func (s *Store) GetRetention(ctx context.Context) (RetentionSpec, error)

GetRetention reads the current overrides. Falls back to the config-file defaults via the caller (we don't peek at config from this layer; just return what's persisted in system_settings).

func (*Store) GetRootFlows added in v0.5.103

func (s *Store) GetRootFlows(ctx context.Context, from, to time.Time, limit int) ([]RootFlow, error)

GetRootFlows returns the top business flows by trace count over [from, to]. A flow is identified by (root_service, root_op); the typical examples are HTTP entry points (POST /login, POST /payment), Kafka consumer roots, and scheduled jobs. limit caps the number of flows returned so the UI list stays scannable. The companion Services slice is materialised via groupUniqArray so the operator can see "login flow involves: api-gateway, user-service, postgresql, redis" without opening each one.

func (*Store) GetRunbook added in v0.6.72

func (s *Store) GetRunbook(ctx context.Context, id string) (*Runbook, error)

func (*Store) GetRuntimeAlerts added in v0.9.485

func (s *Store) GetRuntimeAlerts(ctx context.Context) RuntimeAlertConfig

GetRuntimeAlerts — kalıcı config ya da varsayılanlar. CH hatasında varsayılana düşer (uzun ömürlü evaluator'da geçici blip detektörü kapatmasın). Sıfır/eksik alanlar varsayılana yamalanır (kısmi kayıt eşiği 0'a sabitleyemesin).

func (*Store) GetSLO

func (s *Store) GetSLO(ctx context.Context, id string) (*SLO, error)

func (*Store) GetSavedView

func (s *Store) GetSavedView(ctx context.Context, id string) (*SavedView, error)

func (*Store) GetServiceAdjacency added in v0.5.304

func (s *Store) GetServiceAdjacency(
	ctx context.Context, since time.Duration,
) ([]ServiceEdgePair, error)

GetServiceAdjacency returns the distinct service→service edges observed in the last `since` window, read from the pre- aggregated topology_edges_5m MV.

v0.5.304 — operator-reported boot timeout: the previous correlator path called GetServiceMap which runs

SELECT trace_id FROM spans WHERE time >= ? GROUP BY trace_id
ORDER BY count() DESC LIMIT 200

over a 1h window. At billion-span scale that GROUP BY hits the 30s max_execution_time ceiling and the boot-time adjacency refresh fails (initial map stays empty until the next 5-min tick — also fails). This helper bypasses the trace walk entirely: the edges are already pre-aggregated per 5-min bucket, so we read directly and filter to node_kind = 'service' (db / queue / external nodes aren't separately addressable services to correlate against).

time_bucket is aligned to the bucket boundary (5-min) per v0.5.299's predicate-overlap fix so the most-recent partial bucket isn't silently excluded.

func (*Store) GetServiceAdjacencyWeighted added in v0.8.67

func (s *Store) GetServiceAdjacencyWeighted(
	ctx context.Context, since time.Duration,
) ([]ServiceEdgePair, error)

GetServiceAdjacencyWeighted is GetServiceAdjacency plus per-edge weights — total calls, error calls and summed duration over the window — summed across the 5-min buckets (and protocols) of each (parent_service, child_node) pair. The correlator uses these to build a directed weighted graph (v0.8.67, Faz 5): Caller's downstream deps ranked by error-carrying volume, Callee's upstream callers likewise.

Same MV, same bounds and partition pruning as GetServiceAdjacency (MV-bypass invariant satisfied — this never touches raw spans). The only addition is the three sum() aggregates, which FINAL collapses per ORDER-BY key before summing across buckets, so duplicate ReplacingMergeTree versions of a bucket are not double-counted — the exact pattern GetServiceGraph (repo.go) and ReadServiceTopologyAgg (topology.go) already use.

ORDER BY errors DESC, calls DESC before the LIMIT so that, at a 1000s-services mesh where the distinct directed-edge count can exceed the cap, truncation is DETERMINISTIC and keeps the highest-error / highest-volume edges — the ones the correlator's Downstream/Upstream ranking (errors-first) actually consumes. Without it, LIMIT returns an arbitrary subset and could silently drop the single edge carrying the incident's error traffic. Cap is 20000 (matching ReadServiceTopologyAgg) — ~20k EdgeStat structs is a few MB, trivially bounded memory.

func (*Store) GetServiceAttrs added in v0.5.381

func (s *Store) GetServiceAttrs(
	ctx context.Context, service string, from, to time.Time,
	topPerScope, sampleLimit int,
) ([]ServiceAttrRow, error)

GetServiceAttrs samples spans for the given service in the window and surfaces the top attr keys + a few sample values each. Bounded by an inner LIMIT on the sample size so the scan stays fast regardless of fleet volume (the operator cares about "what shapes of attrs does this service emit", not "every attr value across 30 days" — 5k sampled spans produce a thoroughly representative picture).

Returns up to topPerScope keys per scope (span + resource) ranked by occurrence count. sampleLimit controls how many distinct values per key the result carries — typical operator use surfaces 3-5 examples to confirm the format.

func (*Store) GetServiceBlastRadius added in v0.6.29

func (s *Store) GetServiceBlastRadius(
	ctx context.Context, service string, since time.Duration,
) (BlastRadius, error)

GetServiceBlastRadius returns the upstream-caller impact summary for `service` over [now - since, now]. Reads service_callers_5m (FINAL) for the per-bucket aggregates and joins open-problem status in one extra query.

Top-N cap at 25 callers. At billion-span scale a single service can have hundreds of callers (sidecars, mesh daemons); the UI surfaces the worst-impacted by calls desc + provides a chip-level summary for the long tail.

func (*Store) GetServiceClusterBreakdown added in v0.5.14

func (s *Store) GetServiceClusterBreakdown(
	ctx context.Context, service string, from, to time.Time,
) ([]ServiceClusterStat, error)

GetServiceClusterBreakdown returns RED stats per cluster for one service in the window. The aggregation is over raw spans because the service MV doesn't carry the cluster dim; the filter on service_name is selective enough that this stays fast (one service's slice of spans, not the whole table).

Returns an empty slice when the service has zero traffic in the window — the SPA renders "no cluster breakdown" in that case rather than blanking the panel.

func (*Store) GetServiceClusterMap added in v0.5.22

func (s *Store) GetServiceClusterMap(ctx context.Context, since time.Duration) (map[string][]string, error)

GetServiceClusterMap returns one entry per service with the distinct cluster names it ran in during the last `since` window. Used to enrich Problems / Anomalies / Incidents at read time so the operator sees which cluster(s) the firing service spans — same service can run across 3+ clusters simultaneously (eu-west / eu-central / us-east) and a problem on one might not affect the others.

Single batched query — N+1-free regardless of problem count. Capped at 1000 services × 50 clusters as a defensive bound; well above any realistic bank-scale deployment.

Cached 60s per `since` (v0.8.359, perf P2-C): this raw-spans GROUP BY measured 120-220ms and re-ran on every problems / inbox / incidents / anomalies recompute. Cluster membership is infrastructure-stable, so a minute of staleness is invisible. Single-entry cache keyed by since — the enrichment callers all pass time.Hour, so a variable-window caller (service map) simply misses without thrashing them. The cached map is returned SHARED: callers must treat it as read-only (all current callers only index into it).

func (*Store) GetServiceDeploys

func (s *Store) GetServiceDeploys(
	ctx context.Context, service string, from, to time.Time,
) ([]Deploy, error)

func (*Store) GetServiceEnvMap added in v0.8.387

func (s *Store) GetServiceEnvMap(ctx context.Context, since time.Duration) (map[string][]string, error)

GetServiceEnvMap returns one entry per service with the distinct deployment environments (spans.deploy_env) it emitted from during the last `since` window. Backs the /problems + /inbox env filter (service-scoped semantics, see EnvMemberServices).

Single batched query — N+1-free regardless of problem count. deploy_env is a typed LowCardinality column, so unlike the cluster map's res/attr derive this GROUP BY is a cheap dict pass even at billion-span scale. Capped at 50000 rows (1000 services × 50 envs class of bound — far above any realistic install).

Cached 60s per `since` (the v0.8.359 P2-C discipline, mirrored from GetServiceClusterMap): env membership is deploy-stable, so a minute of staleness is invisible, and the /problems + sidebar 30s polls never pay more than one map refresh per minute. The cached map is returned SHARED: callers must treat it as read-only.

func (*Store) GetServiceEnvironments added in v0.8.383

func (s *Store) GetServiceEnvironments(ctx context.Context, service string, from, to time.Time) ([]string, error)

GetServiceEnvironments returns the distinct environments ONE service emitted spans from in the window — drives the Envs chip group on the Service detail header (v0.8.383, env-separation Phase 0c; the operator's "same mobile-bff in int/uat/prep" case). service_name leads the WHERE so the (service_name, time) primary key prunes the scan; deploy_env is LowCardinality so the GROUP BY is a dict pass. Empty env excluded — single-env installs simply render no chip group.

func (*Store) GetServiceGraph

func (s *Store) GetServiceGraph(ctx context.Context, service string, since time.Duration, from, to time.Time) ([]ServiceEdge, error)

GetServiceGraph returns the directed call graph between services. If `service` is non-empty, only edges where it appears as source OR target are returned (the neighborhood of that service).

Two-source derivation, UNION'd then re-aggregated:

  1. parent→child self-join across different service_names. This is the strong signal — both sides emit OTel spans, so the edge reflects a real cross-service call.

  2. Outbound (client / producer) spans where the downstream identity is inferred from the first non-empty among: a. peer.service (OTel SDK hint) b. rpc.service (gRPC contract — the same string Grafana Tempo's traces-drilldown uses to bucket child gRPC calls) c. server.address / http.host (HTTP downstream) d. db.system (DB engine) e. messaging.system (queue / topic broker) This catches edges to non-instrumented downstreams (managed DBs, third-party APIs, brokers) AND covers environments where the OTel SDK isn't populating peer.service — common in older Java auto-instrumentation and hand-rolled gRPC clients.

We deliberately DO NOT derive edges from net.peer.ip / pod names — they're network-layer identifiers that change on every restart and would create spurious nodes for sidecars / proxies / load balancers. service_name + the application-layer attributes above are stable. GetServiceGraph signature kept narrow for callers; the topN cap is passed via GetServiceGraphTopN below. The original entry point keeps the legacy "no cap" behaviour for non-UI consumers (tests, SLO eval, etc.) — at scale the HTTP handler should always go through the capped variant.

func (*Store) GetServiceGraphTopN

func (s *Store) GetServiceGraphTopN(ctx context.Context, service string, since time.Duration, from, to time.Time, topN int) ([]ServiceEdge, error)

GetServiceGraphTopN returns at most `topN` highest-traffic edges (by call count). topN <= 0 disables the cap. Without a cap, a large fleet (>500 services) regularly produces 5k+ edges, which the SPA can't lay out in real time.

func (*Store) GetServiceMap

func (s *Store) GetServiceMap(
	ctx context.Context, since time.Duration, sampleCount int,
) (*ServiceMap, error)

GetServiceMap derives the global service-level topology from a bounded sample of recent traces. Mirrors the ServiceNeighbors approach but globally — no anchor service. Two queries:

  1. Pick the heaviest N traces by span-count over the last `since` window, ORDER BY count() DESC. This biases the map toward the request paths that actually drive load, not edge-case 1-span traces.
  2. Pull only the four columns the edge walk needs (trace_id, span_id, parent_id, service_name, status_code) for those traces. Skips event blobs / attributes — the edge walk doesn't read them.

In-memory walk: for every span S whose parent's service ≠ S's service, emit an edge (parent.service → S.service). Errors are counted on the callee side. The status_code column stores the lowercase token the ingest path writes (otlp/convert.go maps STATUS_CODE_ERROR → "error"), so the predicate compares against "error" — NOT the OTLP enum name. Result is bounded by the sample size so a billion-span/day deployment still answers in <2s.

The IN (?,...) construct holds N=200-ish trace IDs; ClickHouse happily plans this against the partition key + bloom-filter on trace_id, granule pruning keeps the second query cheap.

func (*Store) GetServiceMapWithDiff

func (s *Store) GetServiceMapWithDiff(
	ctx context.Context, since time.Duration, sampleCount int,
	baselineAgo time.Duration, baselineLabel string,
) (*ServiceMap, error)

GetServiceMapWithDiff returns the current map annotated against a baseline window taken `baselineAgo` earlier. New nodes/edges (in the current window but not the baseline) carry IsNew=true; nodes/edges in the baseline that have disappeared land in RemovedNodes / RemovedEdges so the operator can spot silent regressions ("the payment service stopped calling fraud-check this morning").

Baseline failure is non-fatal: the current map is returned without diff annotations. The two queries are sequential rather than parallel because the cache key is shared and the second query is served from the cache 99% of the time anyway.

func (*Store) GetServiceMetadata

func (s *Store) GetServiceMetadata(ctx context.Context, service string) (*ServiceMetadata, error)

GetServiceMetadata returns the catalog row for one service. Missing rows return nil, nil — the page handles the empty state inline (no special "404" UI needed).

Read-time fallback: chat_channel is the new column; if a pre-rename row only populated slack_channel we surface that value so legacy curation doesn't disappear from the UI.

func (*Store) GetServiceNamespaces added in v0.5.312

func (s *Store) GetServiceNamespaces(ctx context.Context, since time.Duration) (map[string]string, error)

GetServiceNamespaces returns the most-frequent k8s.namespace.name (or service.namespace fallback) for every service that has emitted a span in the last `since` window. Used by the Service Topology redux to soft-cluster nodes visually by namespace.

v0.5.312 — operator runs 3000+ services in a multi-tenant OpenShift estate; the topology was unscannable without grouping. This is a read-time enrichment (not stored on service_metadata) so the namespace stays fresh as workloads move between namespaces during migrations.

Performance posture: one CH query, partition-pruned to the 1h default window, GROUP BY service_name. service_name is LowCardinality; the indexOf() expressions over res_keys are per-row but cheap. Caps at 5000 services.

func (*Store) GetServiceRollouts added in v0.8.25

func (s *Store) GetServiceRollouts(
	ctx context.Context, service string, from, to time.Time,
) (*RolloutsResult, error)

GetServiceRollouts detects pod-churn rollouts for a service by reading the DISTINCT active instance set per 5-minute bucket from raw spans and diffing consecutive buckets: a rollout is a bucket where ≥50% of the previous active pods disappeared AND ≥1 new pod appeared (full turnover, not autoscaling jitter). Adjacent churn buckets coalesce into one event (a staggered rollout spans a few buckets). Also reports whether the effective service.version is constant across the window.

CH posture: single-service + time-bound WHERE prunes by the (service_name, time) primary key; groupUniqArray over a bucket is bounded by pod count (tens), not span count. LIMIT on buckets + max_execution_time cap it. At billions-of-spans/day a per-bucket distinct-instance scan over a WIDE window could get hot — add a service_instances_5m MV if system.query_log flags it; bounded raw is fine for the service-detail windows this serves.

func (*Store) GetServiceRuntime

func (s *Store) GetServiceRuntime(ctx context.Context, service string) (*ServiceRuntime, error)

GetServiceRuntime returns the technology fingerprint for one service. Reads the latest span's resource attributes — the OTel SDK stamps these on every span so we just need one recent row to know the runtime. Falls back gracefully when any individual key is missing.

Implementation: one row from the spans table over the last hour, scanning all res_keys / res_values arrays in-process for the keys we want. The query is partition-pruned (time filter), service_name is the primary key prefix, so this is a microsecond CH lookup even at 1B spans/day.

func (*Store) GetServiceSummary5m

func (s *Store) GetServiceSummary5m(ctx context.Context, service string, from, to time.Time) ([]ServiceSummaryRow, error)

GetServiceSummary5m reads pre-aggregated 5-minute buckets from the MV. Suitable for "show last N hours per-service trend" without paying the cost of scanning raw span rows. Buckets that haven't materialised yet (under 5 minutes old) will be missing — callers should overlay raw spans for the most recent window if they need second-fresh numbers.

func (*Store) GetServiceSummary5mFor

func (s *Store) GetServiceSummary5mFor(ctx context.Context, services []string, from, to time.Time) ([]ServiceSummaryRow, error)

GetServiceSummary5mFor reads MV buckets for a set of named services. Same shape as GetServiceSummary5m but accepts a list — used by the sparklines endpoint to scope the result to the visible top-N rows on the services page (otherwise the response is one array per service across all of them, which is multi-MB at high cardinality).

Empty list returns ALL services (so an internal caller that genuinely wants the full set still has a path).

func (*Store) GetServiceTopologyEdges added in v0.5.102

func (s *Store) GetServiceTopologyEdges(ctx context.Context, from, to time.Time, limit int) ([]ServiceTopologyEdge, error)

GetServiceTopologyEdges returns service-pair interactions with protocol classification + a top label set per strand.

  1. Cross-service pass (parent_service != child_service) joins spans on (trace_id, parent_id). Grouped by (parent, child, protocol) so HTTP-only and gRPC-only edges between the same pair render separately.

  2. Infra pass synthesises destination nodes from db_system / msg_system / peer_service for leaf-ish client spans, so databases / queues / external APIs render as nodes the same way real services do.

Both passes use topK(5)(label) for the per-edge top labels and uniqExact(label) for the global distinct count. argMax with a constant weight (the original v0.5.100 query) was buggy — it returned any label, not the most common one.

func (*Store) GetServices

func (s *Store) GetServices(ctx context.Context, since time.Duration, from, to time.Time) ([]ServiceSummary, error)

GetServices returns aggregate stats per service for the requested window. Pass `since` for a relative window (now-since … now), or non-zero `from`/`to` for an absolute window (overrides since).

func (*Store) GetServicesAgg

func (s *Store) GetServicesAgg(ctx context.Context, from, to time.Time, limit int) ([]ServiceSummary, error)

GetServicesAgg returns one aggregate row per service for the requested window, reading entirely from service_summary_5m. Replaces the raw-spans scan in GetServices for any window where the MV has data — orders of magnitude faster at scale (sub-second across 10s of thousands of services / billions of source spans).

`limit` caps the result to the top-N services by span count; pass 0 to disable. Apdex is computed from the new countIfState columns; if the MV pre-dates the schema upgrade those columns are NULL → apdex = 0.

30-second hard execution timeout via SETTINGS — this endpoint must never hang the UI thread, even when the MV itself has a backlog.

func (*Store) GetServicesAggFiltered

func (s *Store) GetServicesAggFiltered(ctx context.Context, from, to time.Time, nameMatch, sort, dir string, limit, offset int) ([]ServiceSummary, error)

GetServicesAggFiltered — preserves the prior surface (no service-name allowlist). New callers should use GetServicesAggFilteredIn directly.

func (*Store) GetServicesAggFiltered2 added in v0.9.345

func (s *Store) GetServicesAggFiltered2(ctx context.Context, from, to time.Time, nameMatch string, serviceIn []string, sort, dir string, limit, offset int, display ServiceDisplayFilters) ([]ServiceSummary, error)

GetServicesAggFiltered2 is the MV fast path with the v0.9.345 display filters. Kept as a second entry point so the eight-argument original stays valid for its existing callers.

The HAVING is built by the SAME method the raw path uses, with this query's aliases passed in — the two paths cannot drift into disagreeing about what "Errors only" means, which matters because the operator flips between them just by picking a cluster or an env.

func (*Store) GetServicesAggFilteredIn

func (s *Store) GetServicesAggFilteredIn(ctx context.Context, from, to time.Time, nameMatch string, serviceIn []string, sort, dir string, limit, offset int) ([]ServiceSummary, error)

func (*Store) GetServicesFiltered

func (s *Store) GetServicesFiltered(ctx context.Context, since time.Duration, from, to time.Time, nameMatch, sort, dir string, limit, offset int) ([]ServiceSummary, error)

GetServicesFiltered keeps the prior surface intact (no service-name allowlist). The newer GetServicesFilteredIn is the variant the API uses when the operator filtered by owner / SRE team.

func (*Store) GetServicesFilteredIn

func (s *Store) GetServicesFilteredIn(ctx context.Context, since time.Duration, from, to time.Time, nameMatch string, serviceIn []string, sort, dir string, limit, offset int, cluster, env string) ([]ServiceSummary, error)

func (*Store) GetServicesQuery added in v0.9.345

func (s *Store) GetServicesQuery(ctx context.Context, q ServicesQuery) ([]ServiceSummary, error)

func (*Store) GetSetting

func (s *Store) GetSetting(ctx context.Context, key string) ([]byte, error)

GetSetting returns the JSON-encoded value for key, or nil if missing.

func (*Store) GetSlowQueriesGlobal added in v0.5.165

func (s *Store) GetSlowQueriesGlobal(
	ctx context.Context, from, to time.Time, dbSystem, dbName string, limit int,
) ([]SlowQueryRow, error)

func (*Store) GetSpanBreakdown added in v0.4.77

func (s *Store) GetSpanBreakdown(
	ctx context.Context, service string, from, to time.Time,
) ([]SpanBreakdownPoint, error)

GetSpanBreakdown returns time-bucketed cumulative duration per span "category" for a single service. Category is derived from db.system / messaging.system / http_method / span.kind in priority order so DB time doesn't double-count as "client" time. Bucket size auto-picks from the window so the resulting series fits a chart cleanly (~60-200 buckets).

Cached for 30s on the calling handler — the surrounding service detail page is rendered every time an operator switches range, so the cache amortises the GROUP BY across N visitors during active triage.

func (*Store) GetSpansForTraces

func (s *Store) GetSpansForTraces(ctx context.Context, traceIDs []string, from, to time.Time) ([]SpanRow, error)

GetSpansForTraces fetches every span belonging to the supplied trace IDs in a single round-trip. Used by the structure aggregator to avoid N round-trips per sample. from/to bound the scan to the window the trace ids were picked from (padded by traceFetchPad). A zero `from` means "unbounded" and is kept only for callers that genuinely have no window; every in-tree caller passes one.

func (*Store) GetStatusPageConfig

func (s *Store) GetStatusPageConfig(ctx context.Context) (StatusPageConfig, error)

func (*Store) GetSystemStats

func (s *Store) GetSystemStats(ctx context.Context) (*SystemStats, error)

GetSystemStats returns the full meta-observability payload. All queries are independent so we run them serially with bounded SQL — no fan-out goroutines: the calling HTTP handler caches the result for 60s, so the full one-shot cost is amortised cheaply.

func (*Store) GetTeamAliases added in v0.9.427

func (s *Store) GetTeamAliases(ctx context.Context) (TeamAliases, error)

func (*Store) GetTeamContacts added in v0.8.429

func (s *Store) GetTeamContacts(ctx context.Context) (TeamContacts, error)

GetTeamContacts loads the blob; a missing key returns the zero value (disabled, empty map) — never an error the caller must special-case.

func (*Store) GetTempoSettingsRaw added in v0.5.208

func (s *Store) GetTempoSettingsRaw(ctx context.Context) ([]byte, error)

GetTempoSettingsRaw returns the saved JSON blob for the tempo backend config, or nil if no settings have been persisted yet. The caller (tempo.Service.LoadPersisted) does the decode.

func (*Store) GetThanosSettingsRaw added in v0.8.575

func (s *Store) GetThanosSettingsRaw(ctx context.Context) ([]byte, error)

GetThanosSettingsRaw returns the saved JSON blob for the remote cluster list, or nil if none persisted yet.

func (*Store) GetTopDBQueries

func (s *Store) GetTopDBQueries(
	ctx context.Context, service string, from, to time.Time, limit int,
) ([]DBQueryStat, error)

GetTopDBQueries returns the top-N normalized DB statements for the given service in the time window, ordered by total wall-clock time spent in them (count × avgMs).

Performance posture: the query reads only spans where db_statement != ” (a small slice of total span volume), applies regex normalisation in CH (no Go-side post-pass), groups in-store, and the result is bounded by `limit`. At billion-span scale it lands in <2s with the (service_name, time) primary key handling the partition pruning.

The two replaceRegexpAll passes:

  1. Replace single-quoted string literals with "?". A bracketed character class with negation handles embedded apostrophes badly, but the simple form covers the vast majority of ORM-emitted SQL — and pathological cases just produce an extra normalisation cluster rather than an incorrect result.
  2. Replace integer / decimal numeric literals with "?". Boundary anchors (\\b) prevent munging column names that happen to end in digits ("col1" stays intact).

IN-list collapse and parameter-binding placeholders ($1 / ?N) are left as-is — they're not literals, they're already normalised forms.

func (*Store) GetTopologyEdges added in v0.5.100

func (s *Store) GetTopologyEdges(ctx context.Context, from, to time.Time, limit int) ([]TopologyEdge, error)

GetTopologyEdges aggregates parent→child operation pairs from the spans table over [from,to]. Self-join on (trace_id, span_id) = (trace_id, parent_id). Capped at `limit` heaviest edges so an install with very high operation cardinality (each HTTP route a distinct op) still serves an answer.

func (*Store) GetTopologyHiddenRaw added in v0.8.241

func (s *Store) GetTopologyHiddenRaw(ctx context.Context) ([]byte, error)

GetTopologyHiddenRaw returns the saved JSON blob for the hidden pattern list, or nil when none has been persisted yet (the API layer falls back to its seeded defaults).

func (*Store) GetTrace

func (s *Store) GetTrace(ctx context.Context, traceID string) ([]SpanRow, error)

func (*Store) GetTraceAggregate

func (s *Store) GetTraceAggregate(ctx context.Context, f AggregateFilter) ([]AggregateRow, error)

func (*Store) GetTraceAggregateStub added in v0.6.34

func (s *Store) GetTraceAggregateStub(ctx context.Context, traceID string) (TraceAggregateStub, bool)

GetTraceAggregateStub returns (stub, true) when the trace_id exists in any trace_summary_5m bucket; (zero, false) otherwise. One FINAL read keyed by trace_id — sub-ms even at billion-trace scale because the MV's ORDER BY (time_bucket, trace_id) keeps the lookup index-friendly when combined with the trace_id equality.

func (*Store) GetTraceShapes added in v0.5.264

func (s *Store) GetTraceShapes(ctx context.Context, f TraceShapesFilter) ([]TraceShape, error)

GetTraceShapes runs a two-level GROUP BY: first per-trace_id to compute each trace's shape fingerprint + duration, then per-shape to count traces + roll up p99 / error rate. Both stages run inside one CH query so the network round trip stays at one.

trace_id hash-sampling at 1/10 keeps the inner aggregate from touching the full spans table on long windows. The 30s execution-time ceiling guards against pathological windows where even a 10% sample stalls.

func (*Store) GetTraceSnapshot

func (s *Store) GetTraceSnapshot(ctx context.Context, token string) (*TraceSnapshot, error)

GetTraceSnapshot returns (snapshot, nil) for a valid + unexpired token, (nil, nil) for "not found / already expired" — caller translates both to a 404 so we don't leak which case it was.

func (*Store) GetTraces

func (s *Store) GetTraces(ctx context.Context, f TraceFilter) ([]TraceRow, uint64, bool, error)

func (*Store) GetUserByEmail

func (s *Store) GetUserByEmail(ctx context.Context, email string) (*User, error)

GetUserByEmail returns the latest version of a user (ReplacingMergeTree FINAL). Returns (nil, nil) when no row matches — callers treat that as "unknown user".

func (*Store) GetUserByID

func (s *Store) GetUserByID(ctx context.Context, id string) (*User, error)

func (*Store) HasNotification added in v0.8.429

func (s *Store) HasNotification(ctx context.Context, relatedKind, relatedID, channelName string) (bool, error)

HasNotification — the "ilk defa" gate for the team-routing mail (v0.8.429): has a SUCCESSFUL send for (relatedKind, relatedID, channelName) already been logged? Bounded to the table's 90-day retention window; the row volume is notification-scale (not span- scale) so the related_id predicate without a prefix key is fine under the execution cap.

func (*Store) HydrateLdapGroups added in v0.8.526

func (s *Store) HydrateLdapGroups(ctx context.Context) ([]LdapGroupRow, error)

HydrateLdapGroups loads the live (non-tombstoned) group set for the boot / periodic snapshot rebuild. Ordered by cn for deterministic UI.

func (*Store) IncidentProblems

func (s *Store) IncidentProblems(ctx context.Context, incidentID string) ([]string, error)

IncidentProblems lists all problem ids attached to an incident.

func (*Store) IncidentTimeline

func (s *Store) IncidentTimeline(ctx context.Context, incidentID string) ([]IncidentEvent, error)

func (*Store) InsertAICall added in v0.5.164

func (s *Store) InsertAICall(ctx context.Context, c AICall) error

InsertAICall writes one row. Sync (single-row INSERT) — the recording happens on a goroutine in copilot.Service so the user-facing latency isn't impacted by CH ingest time.

func (*Store) InsertExemplars added in v0.8.328

func (s *Store) InsertExemplars(ctx context.Context, rows []*ExemplarRow) error

InsertExemplars is the batched exemplar write — the flush function of the `exemplars` consumer (main.go), riding the same asyncInsertCtx coalescing as every other ingest INSERT (v0.5.346 settings, untouched).

func (*Store) InsertLogs

func (s *Store) InsertLogs(ctx context.Context, logs []*Log) error

func (*Store) InsertMetrics

func (s *Store) InsertMetrics(ctx context.Context, pts []*MetricPoint) error

func (*Store) InsertMonitorResult

func (s *Store) InsertMonitorResult(ctx context.Context, r MonitorResult) error

func (*Store) InsertNotificationLog added in v0.8.247

func (s *Store) InsertNotificationLog(ctx context.Context, e NotificationLog) error

InsertNotificationLog appends one send record. Fire-and-forget from the notify funnel — the caller logs-and-continues on error so a record failure never blocks (or re-fires) the notification itself. Uses the async_insert context like every other write path.

func (*Store) InsertProfile

func (s *Store) InsertProfile(ctx context.Context, p *Profile) error

InsertProfile stores a single pprof profile.

func (s *Store) InsertSpanLinks(ctx context.Context, rows []*SpanLinkRow) error

InsertSpanLinks is the batched span-link write — the flush function of the `span_links` consumer (main.go), riding the same asyncInsertCtx coalescing as every other ingest INSERT (v0.5.346 settings, untouched).

func (*Store) InsertSpans

func (s *Store) InsertSpans(ctx context.Context, spans []*Span) error

func (*Store) InvalidateAlertRulesCache added in v0.9.196

func (s *Store) InvalidateAlertRulesCache()

InvalidateAlertRulesCache — exported wrapper (v0.9.196): the watcher history endpoint's 404 miss-path drops this pod's 30s rule cache once before answering "not found", so a fresh import served by ANOTHER pod doesn't 404 here for up to a cache TTL.

func (*Store) IsIncidentPublished

func (s *Store) IsIncidentPublished(ctx context.Context, incidentID string) (PublishedIncident, error)

func (*Store) IterateProfilePayloads added in v0.5.340

func (s *Store) IterateProfilePayloads(ctx context.Context, f ProfileFilter, fn func(ProfilePayload) error) error

IterateProfilePayloads scans matching profiles row-by-row, handing each to fn for in-place parsing. The CH driver is already streaming (rows.Next() pulls one block at a time); this just inverts the call so the caller never holds more than one pprof in RAM. Used by the service-level hotspot aggregator — a 1h window can match hundreds of MB of raw pprof, and the prior ListProfilePayloads variant collected them all into a slice before parsing. Returning fn's error halts the scan; nil from fn continues.

func (*Store) IterateProfilesForSpan added in v0.5.340

func (s *Store) IterateProfilesForSpan(ctx context.Context, service string, spanStart, spanEnd time.Time, fn func(ProfilePayload) error) error

IterateProfilesForSpan streams every profile whose sample window overlaps a span's window (same overlap rules as FindProfilesForSpan), handing the pprof bytes inline so the caller doesn't need a second GetProfileBytes per row. Removes the N+1 round-trip pattern in profileHotspotsForSpan (v0.5.340).

func (*Store) JVMGCActivity added in v0.9.440

func (s *Store) JVMGCActivity(ctx context.Context) ([]GCActivitySample, error)

JVMGCActivity (v0.9.440, operatör istegi: "çok uzun GC + GC sayısı yüksek podlar") — jvm.gc.duration histogramından pencere içi GC ZAMAN PAYI ve koleksiyon hızı. Tek ölçüde iki şikâyet: uzun pause'lar da sık kısa pause'lar da zaman payını şişirir.

Delta hesabı SERİ-başına (svc, pod, attr seti — G1 Young/Old ayrı seriler): cumulative temporality'de max-min per seri, delta'da düz toplam; karışık min/max havuzlar-arası YANLIŞ olurdu. Restart sıfırlaması greatest(0,·) ile yutulur (pencere payı eksik sayılır — alarm tarafında güvenli yön). max_value BİLEREK kullanılmıyor: cumulative'de ömür-boyu max'tır, tek kötü pause sonsuza dek alarm çaldırırdı.

func (*Store) JVMGCPodPause added in v0.9.90

func (s *Store) JVMGCPodPause(ctx context.Context) ([]CapacitySample, error)

JVMGCPodPause returns per-(service, pod) average GC pause over the window, in MILLISECONDS (Usage; Limit=0 — eşik evaluator'da).

jvm.gc.duration histogram'dır; ingest value kolonu per-export ORTALAMA pause'dur (Sum/Count, convert.go) — avg(value) pencere-ortalama pause verir. HAVING n >= 3: MinSamples tabanı (tek örnekli pencere flapping'i).

func (*Store) JVMHeapPodUsage added in v0.9.90

func (s *Store) JVMHeapPodUsage(ctx context.Context) ([]CapacitySample, error)

JVMHeapPodUsage returns per-(service, pod) heap saturation samples: Usage = 10-dk ortalaması toplam heap kullanımı (byte), Limit = -Xmx.

İki seviyeli toplama ŞART: jvm.memory.used HAVUZ BAŞINA datapoint'tir (jvm.memory.pool.name attr'lı — G1 Eden/Old/Survivor…). Düz avg havuz sayısına böler (heap/N gibi görünür); doğrusu her timestamp'te havuzlar ÜZERİNDEN SUM, sonra pencere üzerinden AVG. jvm.memory.limit'i yalnız cap'i tanımlı havuzlar emit eder (G1'de Old Gen = -Xmx); sum ≈ -Xmx.

func (*Store) LastMonitorStatus

func (s *Store) LastMonitorStatus(ctx context.Context) (map[string]MonitorResult, error)

LastMonitorStatus returns the most recent result for each monitor — drives the dashboard cells.

func (*Store) LdapIdentityOverlap added in v0.8.526

func (s *Store) LdapIdentityOverlap(ctx context.Context, aliases []string) (matched, total int, err error)

LdapIdentityOverlap answers the §10 early-warning question: of the distinct alias keys produced by the sync, how many resolve to a real user in the `users` table (lowercase email OR ldap_username)? A ratio of 0 means "sync succeeded but nothing matched" — the classic sAMAccountName↔email mismatch. `aliases` are already normalized (lowercased) by the caller. Reads the bounded `users` state table (< 10k rows) fully rather than shipping a giant IN list.

func (*Store) LinksFromTrace added in v0.8.329

func (s *Store) LinksFromTrace(ctx context.Context, traceID string, limit int) ([]SpanLink, error)

LinksFromTrace is the FORWARD pivot read: every link declared by the spans of one trace ("this trace links TO …"). WHERE trace_id = ? is a pure primary-key scan on span_links' ORDER BY (trace_id, time) — no time-window argument needed, the PK equality already prunes to the trace's granules.

func (*Store) LinksToTrace added in v0.8.329

func (s *Store) LinksToTrace(ctx context.Context, traceID string, limit int) ([]SpanLink, error)

LinksToTrace is the REVERSE pivot read: every link that points AT one trace ("… links TO this trace" — backlinks). Served by span_links_reverse, whose ORDER BY (linked_trace_id, time) makes THIS direction the primary-key scan; the forward table would need a table scan (bloom-assisted) for it.

func (*Store) ListAICalls added in v0.5.164

func (s *Store) ListAICalls(ctx context.Context, p ListAICallsParams) ([]AICall, error)

func (*Store) ListAPITokens added in v0.8.444

func (s *Store) ListAPITokens(ctx context.Context) ([]APIToken, error)

ListAPITokens — hepsi (revoked dahil; UI rozetle gösterir).

func (*Store) ListActiveServiceNames added in v0.8.506

func (s *Store) ListActiveServiceNames(ctx context.Context, window time.Duration) ([]string, error)

ListActiveServiceNames returns the distinct service names seen in the last `window`, from the MV. v0.8.506 (perf raporu #3): evaluator (1dk tick) + anomaly detector (2dk tick) yalnız İSİM listesi için GetServices(24h)'ü — yani apdex+quantile'lı ham spans GROUP BY'ını (~3.7M satır/koşu lokalde) — çağırıyordu. MV'den DISTINCT, ORDER BY (service_name, ...) sayesinde read_in_order ile neredeyse bedava.

func (*Store) ListActiveSilences

func (s *Store) ListActiveSilences(ctx context.Context) ([]AnomalySilence, error)

ListActiveSilences returns silences whose until_at is still in the future. Used by the anomaly read path + recorder notification fan-out to suppress muted entries.

func (*Store) ListAlertRules

func (s *Store) ListAlertRules(ctx context.Context) ([]AlertRule, error)

func (*Store) ListAnomalyEvents

func (s *Store) ListAnomalyEvents(ctx context.Context, f ListAnomalyEventsFilter) ([]AnomalyEvent, error)

func (*Store) ListAuditLog

func (s *Store) ListAuditLog(ctx context.Context, f AuditFilter) ([]AuditEntry, error)

func (*Store) ListChannels

func (s *Store) ListChannels(ctx context.Context) ([]NotificationChannel, error)

func (*Store) ListClusters added in v0.5.13

func (s *Store) ListClusters(ctx context.Context, from, to time.Time) ([]string, error)

ListClusters returns the distinct cluster names observed in the window, sourced from the same resource/attr coalesce chain the filter uses. Drives the cluster-filter dropdown on /services and /service?name=. Capped at 200 — beyond that the dropdown is unusable anyway, and the operator can type the cluster directly into the filter URL.

func (*Store) ListDashboards

func (s *Store) ListDashboards(ctx context.Context) ([]Dashboard, error)

func (*Store) ListEnvironments added in v0.8.383

func (s *Store) ListEnvironments(ctx context.Context, from, to time.Time, q string, limit int) (envs []string, total uint64, err error)

ListEnvironments enumerates deploy_env values for the picker (v0.8.383; reworked v0.8.389 — operator-reported: LIMIT 50 + ALPHABETICAL order starved later names once feature-branch envs (int-feature-*) exploded the set: "release" sorted past the cap and never appeared. Now count-ordered (busiest first, ties by name), optional case-insensitive substring q, and a total so the picker can say "+N more — type to refine" instead of implying completeness).

func (*Store) ListEvents added in v0.5.476

func (s *Store) ListEvents(ctx context.Context, f EventFilter) ([]Event, error)

func (*Store) ListExceptionGroups

func (s *Store) ListExceptionGroups(ctx context.Context, f ExceptionGroupFilter) ([]ExceptionGroup, error)

func (*Store) ListExecutions added in v0.6.73

func (s *Store) ListExecutions(ctx context.Context, f ExecutionFilter) ([]RunbookExecution, error)

func (*Store) ListIncidents

func (s *Store) ListIncidents(ctx context.Context, f IncidentFilter) ([]Incident, error)

func (*Store) ListLogTemplates added in v0.5.244

func (s *Store) ListLogTemplates(ctx context.Context, f ListLogTemplatesFilter) ([]LogTemplate, error)

ListLogTemplates returns the persisted templates ordered by the requested signal. The "spike" sort is computed in the API layer because it needs both the 1h and 24h counts which don't live on the row.

func (*Store) ListMaintenanceWindows added in v0.5.26

func (s *Store) ListMaintenanceWindows(ctx context.Context, includeDisabled bool) ([]MaintenanceWindow, error)

ListMaintenanceWindows returns every active or future window, sorted earliest-end first so the about-to-expire row is on top (typical operator question: "how much longer is this silenced?"). Past + disabled rows are hidden.

func (*Store) ListMetricNames added in v0.5.181

func (s *Store) ListMetricNames(ctx context.Context, service, pattern string, limit, offset int) ([]MetricInfo, int, error)

func (*Store) ListMonitors

func (s *Store) ListMonitors(ctx context.Context) ([]Monitor, error)

func (*Store) ListNegativeFeedbackCalls added in v0.9.423

func (s *Store) ListNegativeFeedbackCalls(ctx context.Context, from, to time.Time, limit int) ([]NegativeFeedbackCall, error)

ListNegativeFeedbackCalls (v0.9.423, CoSRE fikir #6) — pencere içindeki verdict=-1 feedback'leri ai_calls örnekleriyle birleştirir: hangi soru şekilleri kötü cevap alıyor → yeni guided-intent adayları VERİDEN çıkar. İki tablo da küçük state tablosu (ai_feedback ReplacingMergeTree 90g TTL, ai_calls örnekleri 4KB cap'li) — JOIN hot-path değil, admin paneli okuması.

func (*Store) ListNotificationLog added in v0.8.247

func (s *Store) ListNotificationLog(ctx context.Context, from, to time.Time, kind string, limit, offset int) ([]NotificationLog, error)

ListNotificationLog reads the dispatch history newest-first. Always time-bounded (a zero from/to defaults to the full 90-day retention window) so the read carries a prefix predicate on the sent_at ORDER BY key — never a full-table scan. kind filters on channel_kind exactly; "" = all channels.

func (*Store) ListNotificationLogByRelated added in v0.9.196

func (s *Store) ListNotificationLogByRelated(ctx context.Context, relatedIDs []string, limit int) ([]NotificationLog, error)

ListNotificationLogByRelated reads the sends recorded for a SET of related ids (v0.9.196 — the /watchers history drawer joins one rule's problem ids to their notification rows). Newest-first, bounded to the table's 90-day retention window so the read keeps its sent_at ORDER BY prefix predicate. An empty id set returns nil without touching CH.

func (*Store) ListOperationNames added in v0.5.180

func (s *Store) ListOperationNames(ctx context.Context, service, pattern string, limit, offset int) ([]string, int, error)

ListServiceNames is the lookup behind UI service-name pickers (traces, logs, services filter, alerts, SLOs, exceptions, ...).

Reads DISTINCT service_name from the 5-minute MV. The MV stores one row per (service, 5min bucket) so DISTINCT is essentially "what services have we seen in the last 90 days" (= MV TTL) — exactly the set the pickers care about, and the read is cheap because the MV's ORDER BY (service_name, time_bucket) makes the distinct streamable.

`pattern` accepts simple Lucene-style wildcards:

  • bare text → case-insensitive substring (LIKE '%text%')
  • "*" → multi-char wildcard
  • "?" → single-char wildcard

SQL LIKE special chars in user input ('%', '_') are escaped first so they're matched literally rather than acting as inadvertent wildcards. ListOperationNames — operations-picker counterpart to ListServiceNames (v0.5.180). Reads operation_summary_5m so the GROUP BY is cheap even at billions of spans / tens of thousands of operations per service. Service filter is optional but recommended at scale — a global op listing on an install with 10k services × 100 ops/service is approaching the limits of "useful in a dropdown".

Wildcard semantics match ListServiceNames: `*` and `?` map to CH `%` / `_`; bare strings are wrapped in `%…%` for substring match. Returns (names, total, err) so the UI can surface "showing 200 of 12,345 — refine" hints.

func (*Store) ListOpsForService added in v0.5.109

func (s *Store) ListOpsForService(ctx context.Context, service string, from, to time.Time) ([]string, error)

ListOpsForService returns the operation names that appear as outbound callers for a given service in the window. Drives the op-picker dropdown on the operation deep-dive view. Reads directly from the agg table so the response is fast.

func (*Store) ListProblemWindowEvents added in v0.9.394

func (s *Store) ListProblemWindowEvents(ctx context.Context, service string, from, to time.Time) ([]Problem, error)

ListProblemWindowEvents — annotation şeridinin alarm olayları (v0.9.394, Ş1): pencere içinde TETİKLENEN (started_at ∈ [from,to)) ya da ÇÖZÜLEN (resolved_at ∈ [from,to)) problemler, ÇÖZÜLMÜŞLER DAHİL. Hot triage yolundaki ProblemFilter'a dokunmamak için ayrı odaklı okuma — ReplacingMergeTree FINAL + bounded LIMIT. service "" = tüm servisler (global kurallar dahil).

func (*Store) ListProblems

func (s *Store) ListProblems(ctx context.Context, f ProblemFilter) ([]Problem, error)

func (*Store) ListProfilePayloads added in v0.5.332

func (s *Store) ListProfilePayloads(ctx context.Context, f ProfileFilter) ([]ProfilePayload, error)

ListProfilePayloads returns raw pprof bytes for every profile matching the filter. Used by the service-level hotspot aggregator — one CH round-trip pulls the full window so the API handler can merge in-process. The pprof column is large (per-snapshot kilobytes to ~MB), so the caller MUST pass a sensible Limit; the query also caps execution at 5s to bound blast radius on a wide window.

Deprecated for hotspot aggregation: prefer IterateProfilePayloads which keeps RAM proportional to one payload rather than the full result set. Retained for any caller that needs the whole window materialised.

func (*Store) ListProfiles

func (s *Store) ListProfiles(ctx context.Context, f ProfileFilter) ([]ProfileRow, error)

ListProfiles returns recent profiles matching the filter (without payload).

func (*Store) ListPublishedIncidents

func (s *Store) ListPublishedIncidents(ctx context.Context, limit int) ([]Incident, map[string]PublishedIncident, error)

ListPublishedIncidents returns incidents marked public with an optional status filter. Used by the public page to render recent + active incidents.

func (*Store) ListRagDocuments added in v0.8.441

func (s *Store) ListRagDocuments(ctx context.Context) ([]RagDocument, error)

ListRagDocuments — katalog: doküman başına chunk sayısı + boyut.

func (*Store) ListRunbooks added in v0.6.72

func (s *Store) ListRunbooks(ctx context.Context) ([]Runbook, error)

func (*Store) ListSLOs

func (s *Store) ListSLOs(ctx context.Context) ([]SLO, error)

func (*Store) ListSavedViews

func (s *Store) ListSavedViews(ctx context.Context, ownerID, page string) ([]SavedView, error)

ListSavedViews returns the union of (a) the requesting user's own views and (b) team-shared views (OwnerID=""). Both buckets are filtered to the requested page so the topbar dropdown only shows relevant entries.

func (*Store) ListServiceMetadata

func (s *Store) ListServiceMetadata(ctx context.Context) (map[string]ServiceMetadata, error)

ListServiceMetadata returns every catalog row in one shot — used by the /services list to render the owner-team chip on every row without N round-trips. Cheap because the table is at most a few thousand rows.

Cached 30s with write-side invalidation (v0.8.359, perf P2-C): the problems enrich chain reads it twice per recompute (runbooks + teams), the inbox and /services once more — a FINAL scan each time. A local Upsert invalidates immediately, so "a catalog edit reflects on the next refresh" still holds; a peer pod's edit lands within the TTL (same tolerance as the alertRules cache). Each call returns a fresh top-level COPY so a caller mutating its map cannot poison the shared snapshot.

func (*Store) ListServiceNames

func (s *Store) ListServiceNames(ctx context.Context, pattern string, limit, offset int) ([]string, int, error)

func (*Store) ListStaleOpenProblems added in v0.5.352

func (s *Store) ListStaleOpenProblems(ctx context.Context, staleCutoff time.Time) ([]Problem, error)

ListStaleOpenProblems returns open/acknowledged problems whose updated_at is older than `staleCutoff`. v0.5.352 — operator-reported: when a service stops emitting, the evaluator's measure() returns no data, the resolve path is never taken, and the problem stays open forever. This list feeds the periodic stale-sweep that auto-closes them.

FINAL on the read so a recently-resolved-but-not-yet-merged row doesn't leak into the sweep.

func (*Store) ListStatusComponents

func (s *Store) ListStatusComponents(ctx context.Context) ([]StatusComponent, error)

func (*Store) ListStatusSubscribers

func (s *Store) ListStatusSubscribers(ctx context.Context) ([]StatusSubscriber, error)

func (*Store) ListTraceSnapshots added in v0.5.85

func (s *Store) ListTraceSnapshots(ctx context.Context, traceID string) ([]TraceSnapshot, error)

ListTraceSnapshots returns every active (unexpired) snapshot for one trace_id, ordered by most recent first. Drives the admin "manage shares" panel — operator sees what's out there AND who minted each one. Capped at 50 entries per trace to keep the response small (operationally there should never be more than 1-2 active per trace anyway).

func (*Store) ListUsers

func (s *Store) ListUsers(ctx context.Context) ([]User, error)

ListUsers returns every active user, newest first. Disabled users are hidden — they're effectively deleted from the UI's perspective.

func (*Store) ListUsersByTeam added in v0.5.25

func (s *Store) ListUsersByTeam(ctx context.Context, team string) ([]User, error)

ListUsersByTeam returns every active user whose team field matches the requested label (case-insensitive). Drives the /service?name=… page's "owner team members" popover so an operator viewing a service can see who to ping. Bounded list (users < 10k in any realistic install) so no LIMIT; ordering by email keeps the popover deterministic across refreshes.

func (*Store) LiveAuthz added in v0.9.352

func (s *Store) LiveAuthz(ctx context.Context, userID string) (string, bool, error)

LiveAuthz resolves a user's CURRENT authorization state for the request path (v0.9.352). It backs auth.AuthzLookup.

ok=false covers both "deleted" and "disabled" on purpose: GetUserByID already filters `disabled = 0` and returns nil when absent, and the caller must not be able to tell the two apart — both are a 401, and distinguishing them would leak whether an account exists.

Called at most once per user per 10s (the auth package caches), so this stays off the hot path even though it is a FINAL read.

func (*Store) LocalTableName

func (s *Store) LocalTableName(name string) string

LocalTableName returns the `<name>_local` flavour when in cluster mode, or the bare name otherwise. Used inside Replicated*MergeTree CREATE statements; the `<name>` itself is reserved for the Distributed wrapper. Also exported so external callers (chmigrate, ad-hoc tools) can resolve the per-shard table name without re-implementing the suffix logic.

func (*Store) MeasureAllServices added in v0.8.352

func (s *Store) MeasureAllServices(ctx context.Context, metric string, window time.Duration, now time.Time) (map[string]float64, error)

MeasureAllServices measures one alert metric for EVERY service in one query — the batched twin of the evaluator's per-service measure() (v0.8.352, perf P2-A). Requires a window the 5m MV grid serves (UseSummaryMV true); the evaluator keeps its per-service raw path for rarer sub-5m custom windows.

Absent map key = the service had no rows in the window (zero traffic). The evaluator's absentMeasure translates that to the exact value/skip the per-service query used to produce for an empty result.

func (*Store) MeasureCountAllServices added in v0.8.352

func (s *Store) MeasureCountAllServices(ctx context.Context, window time.Duration, now time.Time) (map[string]uint64, error)

MeasureCountAllServices returns the span count per service over the window in ONE query — the batched twin of the evaluator's per-service measureCount() that feeds the MinSamples gate (v0.8.352, perf P2-A: this single class was ~32k queries/hour). Counts are normalized to the nominal window exactly like the per-service read (v0.8.315). Absent key = zero spans, which is what countMerge over no buckets returned.

func (*Store) MetricAttrKeys added in v0.9.124

func (s *Store) MetricAttrKeys(ctx context.Context, metric, service string, since time.Duration) ([]string, error)

MetricAttrKeys returns the distinct DATAPOINT attribute keys observed on a metric (v0.9.124) — powers PromQL `without(L)`, which groups by every label EXCEPT L and so must first discover what the labels are. Bounded (LIMIT + max_execution_time + time-bounded WHERE); the key COUNT is tiny (a metric has a handful of attr keys). Resource attrs like service.name aren't in attr_keys; the caller adds them to the candidate set separately.

func (*Store) MetricExists added in v0.7.69

func (s *Store) MetricExists(ctx context.Context, metric string) (bool, error)

metricExists reports whether ANY point for `metric` landed in the window. The defensive Postgres/MySQL/Redis checks only run when their receiver is actually publishing, so an install with no such receiver never sees spurious Problems (nor pays for the read every tick once it knows the metric is absent — but we keep this cheap + stateless rather than caching, since it's one indexed point-existence probe).

func (*Store) MetricLabelValues

func (s *Store) MetricLabelValues(ctx context.Context, metric, key string, since time.Duration) ([]string, error)

MetricLabelValues returns distinct values for a single attribute key observed in the given metric — fuels the value-suggestions in the UI.

func (*Store) MetricSeriesFingerprints added in v0.8.432

func (s *Store) MetricSeriesFingerprints(ctx context.Context, f MetricQueryFilter) (map[string][]uint64, error)

MetricSeriesFingerprints resolves a metric chart's series (as drawn: same groupBy, same filters, same window) to the series_fingerprint sets behind them — the missing server half that kept the /api/exemplars?fingerprints= PK-scan mode unused (audit Faz B). Returns nil on installs where the fingerprint column never reached the shards (hasSeriesFpCol=false, external-Distributed fallback) — callers degrade to no-◆, exactly today's behavior.

func (*Store) MonitorStatsAll

func (s *Store) MonitorStatsAll(ctx context.Context) (map[string]MonitorStats, error)

MonitorStatsAll returns the rollup for every monitor that has at least one probe result in the last 24h, keyed by monitor ID. Map is keyed sparsely — monitors with no recent results don't appear, so callers should treat a missing key as "no data yet" rather than "down".

func (*Store) MonitorTimeline

func (s *Store) MonitorTimeline(ctx context.Context, monitorID string, limit int) ([]MonitorResult, error)

MonitorTimeline returns the result history for one monitor (newest first), capped at `limit` rows. Drives the per-monitor status timeline.

func (*Store) NoisyRules added in v0.5.131

func (s *Store) NoisyRules(ctx context.Context, from, to time.Time, limit int) ([]NoisyRule, error)

NoisyRules returns rules ranked by problem-open count over [from, to]. Median duration + total duration come from the resolved problems only (open problems contribute to the count but not the duration — their duration isn't bounded yet).

Read pattern: single GROUP BY rule_id on the problems table. ReplacingMergeTree FINAL pulls the latest version per id so the count doesn't double on a status flip. Partition pruning drops every date outside [from, to] before the GROUP BY hits. 30s execution-time guard keeps the worst case bounded.

func (*Store) OpenIncidentRollups added in v0.7.33

func (s *Store) OpenIncidentRollups(ctx context.Context) ([]OpenIncidentRollup, error)

OpenIncidentRollups returns, for every OPEN incident, the number of attached problems, how many are NOT yet resolved, and the latest problem-resolution time. The evaluator's cascade sweep uses this to auto-resolve incidents whose problems have ALL cleared — operator-reported: problems auto-resolve but incidents stayed open forever (CH ground truth: 214 problems resolved / 0 open, yet 57 incidents open / 0 resolved). One aggregate query, not N per-incident lookups, keeps the 1-minute sweep cheap; the state tables are small and the joins are LIMIT- + time-bounded.

func (*Store) OpenProblemsSnapshot added in v0.8.520

func (s *Store) OpenProblemsSnapshot(ctx context.Context) (*OpenProblems, error)

OpenProblemsSnapshot returns every open/acknowledged problem in ONE FINAL scan, indexed BOTH ways (bkz. OpenProblems). v0.8.520 (perf raporu #9): the evaluator called FindOpenProblem once per (rule, service) pair — ~657 nokta FINAL sorgusu/tick prod'da — hepsi aynı küçük state tablosunu okuyor. Tick başında tek snapshot + map lookup.

func (*Store) Ping

func (s *Store) Ping(ctx context.Context) error

Ping reports CH liveness. Used by /api/status — wraps the driver's own Ping so we don't expose the driver type to callers.

func (*Store) PodServiceMap added in v0.9.11

func (s *Store) PodServiceMap(ctx context.Context, cluster string, from, to time.Time) (map[string][]string, error)

func (*Store) PopulateServiceDeploymentsFromSpans added in v0.9.25

func (s *Store) PopulateServiceDeploymentsFromSpans(ctx context.Context, since time.Duration) (int, error)

PopulateServiceDeploymentsFromSpans — namespace populate'inin aynası.

func (*Store) PopulateServiceNamespacesFromSpans added in v0.8.436

func (s *Store) PopulateServiceNamespacesFromSpans(ctx context.Context, since time.Duration) (int, error)

PopulateServiceNamespacesFromSpans mirrors PopulateServiceTeamsFromSpans for the namespace field — read-merge-write per service, best-effort.

func (*Store) PopulateServiceTeamsFromSpans added in v0.8.95

func (s *Store) PopulateServiceTeamsFromSpans(ctx context.Context, since time.Duration) (int, error)

PopulateServiceTeamsFromSpans derives teams from span attributes and fills the empty owner_team / sre_team catalog fields (manual values are preserved, as are all other metadata fields — UpsertServiceMetadata is a full-row replace, so we read-merge-write). Best-effort: a single failed upsert doesn't abort the rest. Returns the number of services updated.

func (*Store) PruneServiceMapTopN added in v0.8.215

func (s *Store) PruneServiceMapTopN(m *ServiceMap, topN int)

PruneServiceMapTopN is the exported wrapper the api package calls; the logic lives in the pure pruneServiceMapTopN so the cap is unit-tested without a Store.

func (*Store) PurgeTelemetry added in v0.8.196

func (s *Store) PurgeTelemetry(ctx context.Context) (PurgeResult, error)

PurgeTelemetry empties every observability-DATA table (telemetryPurgeTables), preserving all configuration. Best-effort: a per-table failure is recorded and the purge continues. Each table's storage is resolved by engine so it works across deployment modes:

  • plain MergeTree/Replicated → TRUNCATE the table directly
  • combined MaterializedView → TRUNCATE its hidden `.inner_id.<uuid>`
  • Distributed wrapper → TRUNCATE the `<name>_local` shard table ON CLUSTER (cluster derived from the engine def, or cfg.ClusterName); if the local is itself an MV, its inner is truncated.

Carries the volume-guard overrides (max_table_size_to_drop / max_partition_size_to_drop = 0) so a huge spans table truncates regardless of accumulated size — same guard dropCombinedMV uses.

func (*Store) PutBranding added in v0.4.80

func (s *Store) PutBranding(ctx context.Context, b BrandingSettings) error

PutBranding overwrites the saved overlay. Admin-gated at the HTTP layer; the store side is unguarded so the boot-time seeder (future) can also call it.

func (*Store) PutCustomRolesRaw added in v0.5.251

func (s *Store) PutCustomRolesRaw(ctx context.Context, raw []byte) error

PutCustomRolesRaw overwrites the saved JSON blob. Caller marshals the typed slice so chstore stays untyped.

func (*Store) PutKibana added in v0.5.219

func (s *Store) PutKibana(ctx context.Context, k KibanaSettings) error

PutKibana overwrites the saved settings. Admin-gated at the HTTP layer.

func (*Store) PutLogstoreESSettingsRaw added in v0.8.232

func (s *Store) PutLogstoreESSettingsRaw(ctx context.Context, raw []byte) error

PutLogstoreESSettingsRaw overwrites the saved JSON blob. Caller marshals the typed ESSettings struct so chstore stays untyped.

func (*Store) PutPipelineRulesRaw added in v0.5.263

func (s *Store) PutPipelineRulesRaw(ctx context.Context, raw []byte) error

func (*Store) PutSetting

func (s *Store) PutSetting(ctx context.Context, key string, value []byte) error

PutSetting upserts the JSON-encoded value at key.

func (*Store) PutTeamAliases added in v0.9.427

func (s *Store) PutTeamAliases(ctx context.Context, ta TeamAliases) error

func (*Store) PutTeamContacts added in v0.8.429

func (s *Store) PutTeamContacts(ctx context.Context, tc TeamContacts) error

PutTeamContacts persists the blob (admin PUT path; audited at the API layer like every settings write).

func (*Store) PutTempoSettingsRaw added in v0.5.208

func (s *Store) PutTempoSettingsRaw(ctx context.Context, raw []byte) error

PutTempoSettingsRaw overwrites the saved JSON blob. Caller marshals the typed Settings struct so chstore stays untyped.

func (*Store) PutThanosSettingsRaw added in v0.8.575

func (s *Store) PutThanosSettingsRaw(ctx context.Context, raw []byte) error

PutThanosSettingsRaw overwrites the saved JSON blob. Caller marshals the typed Settings struct so chstore stays untyped.

func (*Store) PutTopologyHiddenRaw added in v0.8.241

func (s *Store) PutTopologyHiddenRaw(ctx context.Context, raw []byte) error

PutTopologyHiddenRaw overwrites the saved JSON blob.

func (*Store) QueryMetric

func (s *Store) QueryMetric(ctx context.Context, f MetricQueryFilter) ([]SpanMetricSeries, error)

QueryMetric runs a multi-series time-bucketed query against metric_points. Returns the same SpanMetricSeries shape so the UI can reuse MultiLineChart.

func (*Store) QueryMetricHistogram added in v0.6.56

func (s *Store) QueryMetricHistogram(ctx context.Context, f MetricQueryFilter) (*HistogramSeries, error)

QueryMetricHistogram reads explicit-histogram metric_points over a window and returns a time × bucket heatmap + per-time-bucket percentiles. Cumulative-temporality series are delta'd PER SERIES before binning so the heatmap shows per-interval density rather than a monotonically growing cumulative. v1 aggregates every matching attribute set into one heatmap; series whose bucket layout differs from the canonical one are skipped (Skipped surfaced to the UI). CH-bounded: metric-scoped + time-bounded WHERE, LIMIT, max_execution_time.

func (*Store) QueryMetricHistogramPercentile added in v0.9.107

func (s *Store) QueryMetricHistogramPercentile(ctx context.Context, f MetricQueryFilter, agg string) ([]SpanMetricSeries, error)

QueryMetricHistogramPercentile — the p50/p95/p99 agg-string entry (chart picker). Maps to a quantile ratio and delegates to the float core.

func (*Store) QueryMetricHistogramQuantile added in v0.9.119

func (s *Store) QueryMetricHistogramQuantile(ctx context.Context, f MetricQueryFilter, q float64) ([]SpanMetricSeries, error)

QueryMetricHistogramQuantile — arbitrary-quantile entry (v0.9.119, PromQL histogram_quantile with any q ∈ [0,1], not just 0.5/0.95/0.99).

func (*Store) QueryMetricRate added in v0.9.106

func (s *Store) QueryMetricRate(ctx context.Context, f MetricQueryFilter, mode string) ([]SpanMetricSeries, error)

QueryMetricRate — PromQL rate()/increase() muadili (F2). mode: "rate" (per-saniye) | "increase" (pencere-artışı, ham delta toplamı). Yalnız counter (instrument='sum'); gauge/histogram boş döner. Cumulative temporality → per-seri reset-korumalı cross-bucket delta; delta temporality → per-bucket sum (değer zaten interval-artışı). Aynı step disiplini (metricAutoStepPx + clampStepToExport). Sonuç SpanMetricSeries — UI aynı.

func (*Store) QueryRepeatedSpans added in v0.4.96

func (s *Store) QueryRepeatedSpans(ctx context.Context, f RepeatedSpanFilter) ([]RepeatedSpanRow, error)

QueryRepeatedSpans runs one GROUP BY (trace_id, <groupBy>) HAVING count >= MinRepeats pass over the spans table. Bounded at LIMIT 200 server-side so a wide window with many duplicate- heavy traces doesn't blow up the response.

Performance posture: GROUP BY on a hash of (trace_id + groupBy) is cheap at billion-span scale because the time filter prunes by partition first; uniqExact + sum aggregates fit in memory inside a single 20s max_execution_time budget. `min(time)` per group surfaces the earliest span so the UI can show "when did this duplicate burst start".

func (*Store) QueryRollupRED added in v0.9.385

func (s *Store) QueryRollupRED(ctx context.Context, plan RollupPlan, f RollupSeriesFilter, from, to time.Time) ([]RollupSeries, bool, error)

QueryRollupRED — plan.Table'dan step'e katlanmış RED serisi. İkinci dönüş: GroupBy kesmesi oldu mu (sessiz kesme yok — yanıt söyler).

func (*Store) QuerySpanMetric

func (s *Store) QuerySpanMetric(ctx context.Context, f SpanMetricFilter) ([]SpanMetricSeries, error)

QuerySpanMetric computes the requested aggregation over the matching spans, bucketed by step seconds, optionally split by 1+ group keys.

func (*Store) QuerySpanMetricMulti added in v0.5.54

func (s *Store) QuerySpanMetricMulti(ctx context.Context, f SpanMetricBatchFilter) (map[string][]SpanMetricSeries, int, error)

QuerySpanMetricMulti runs every aggregation in `f.Aggs` against the same WHERE + GROUP BY in ONE round trip. Returns a map keyed by spec.Name → series list. Empty result map on success is allowed (no spans matched the filter); per-spec failures (e.g. unknown aggregation) fail the whole call.

func (*Store) QuerySpanMetricTopN added in v0.8.144

func (s *Store) QuerySpanMetricTopN(ctx context.Context, f SpanMetricFilter) (series []SpanMetricSeries, total int, capped bool, err error)

QuerySpanMetricTopN runs QuerySpanMetric and, on a high-cardinality groupBy, trims the result to the spanMetricTopN biggest-by-area series — the exact set the frontend would render anyway (PanelStack ranks by the same area metric and caps at TOP_N_MAX). `total` is the series count BEFORE trimming so the UI's "+N more" stays accurate even though the wire payload is bounded.

Only the primary /api/spans/metric handler uses this. The resolver, DQL, RED and batch paths keep calling QuerySpanMetric directly (they either already bound cardinality or need every series), so their behaviour is unchanged.

func (*Store) RCAVerdictQualityStats added in v0.9.591

func (s *Store) RCAVerdictQualityStats(ctx context.Context, from, to time.Time) (RCAVerdictQuality, error)

RCAVerdictQualityStats — pencere içindeki verdict kalitesi.

ai_feedback ile LEFT JOIN: derecelendirilmemiş verdict'ler de sayılmalı. INNER olsaydı yalnız oylananlar görünür ve oran yapay şekilde anlamlı görünürdü — oylama seyrek bir jest.

Bounded: rca_verdicts 90g TTL'li küçük bir state tablosu, zaman pencereli WHERE + max_execution_time. FINAL şart (ReplacingMergeTree — yeniden yazılan bir verdict iki kez sayılmamalı).

func (*Store) RateGauge added in v0.7.69

func (s *Store) RateGauge(
	ctx context.Context, metric string,
) ([]CapacitySample, error)

rateGauge reads a single cumulative-counter metric per instance and derives its per-second rate over the window ((max-min)/windowSec), matching the queryOracleRates / queryRedisRates derivation. Used for the Redis eviction-rate check, where there is no cap gauge — a positive rate is itself the saturation signal (maxmemory-policy is evicting). A counter reset (negative delta) is suppressed to 0. Usage carries the rate; Limit is left 0 (the evaluator treats Limit==0 checks as raw-rate).

func (*Store) ReadRootFlowsAgg added in v0.5.112

func (s *Store) ReadRootFlowsAgg(ctx context.Context, from, to time.Time, limit int) ([]RootFlow, error)

ReadRootFlowsAgg reads pre-aggregated business flows for a window. trace_count is summed across buckets; services arrays are merged + deduplicated. Limit caps the number of flows returned to the heaviest by trace volume.

func (*Store) ReadServiceCallersAgg added in v0.5.368

func (s *Store) ReadServiceCallersAgg(
	ctx context.Context, service string, from, to time.Time, limit int,
) ([]CallerRow, error)

ReadServiceCallersAgg surfaces the same CallerRow shape from the MV. Bucket-aligns the lower bound (the standard *_5m idiom) and reads with FINAL so ReplacingMergeTree version dedup is honoured. Sub-second even at billion-span scale — the prior raw-spans self-join could exceed 30s.

func (*Store) ReadServiceTopologyAgg added in v0.5.108

func (s *Store) ReadServiceTopologyAgg(ctx context.Context, from, to time.Time, limit int) ([]ServiceTopologyEdge, error)

ReadServiceTopologyAgg reads pre-aggregated topology rows for a window from topology_edges_5m. Each row in the agg table is one 5-min bucket; we sum calls + merge top_labels arrays across buckets to give an aggregate over the requested window.

distinct_labels is approximated as the count of unique labels in the merged top_labels array — at 5 labels per bucket that's accurate up to a few dozen endpoints per strand, which is plenty for human-readable topology.

Window is rounded out to the surrounding 5-min boundaries so a partially-covered bucket isn't dropped silently.

func (*Store) ReadServiceTopologyAggForFocus added in v0.9.366

func (s *Store) ReadServiceTopologyAggForFocus(ctx context.Context, from, to time.Time, focus string, hops, limit int) ([]ServiceTopologyEdge, error)

ReadServiceTopologyAggForFocus returns the aggregated edges within `hops` of `focus` by walking hop-by-hop with IN-filtered MV reads. v0.9.366 — the neighborhood scope previously read the whole estate's top-20000 edges by calls and hop-walked in Go: past 20k estate edges the focused service's own QUIET dependencies fell out of the LIMIT window and silently vanished from its Topology tab. Truncation now happens inside the neighborhood (which is what the render budget means). ≤3 bounded MV queries (hops clamps at 3 in the api layer).

func (*Store) ReadTopologyOpEdgesAgg added in v0.5.109

func (s *Store) ReadTopologyOpEdgesAgg(ctx context.Context, from, to time.Time, limit int) ([]TopologyEdge, error)

ReadTopologyOpEdgesAgg reads per-op edges from the aggregated table for the requested window. Returns the full edge set; the API handler runs the BFS to extract the bounded subgraph.

func (*Store) RefreshExceptionGroups

func (s *Store) RefreshExceptionGroups(ctx context.Context, since time.Time) (int, error)

func (*Store) RemoveStatusSubscriber

func (s *Store) RemoveStatusSubscriber(ctx context.Context, email string) error

func (*Store) ReplaceDocumentChunks added in v0.8.441

func (s *Store) ReplaceDocumentChunks(ctx context.Context, chunks []RagChunk) error

ReplaceDocumentChunks — bir dokümanın TÜM chunk'larını tek version damgasıyla yazar. Eski yüklemenin daha uzun kuyruk chunk'ları (yeni içerik kısaldıysa) ayrıca temizlenir.

func (*Store) ResolveMetricQuery added in v0.8.51

func (s *Store) ResolveMetricQuery(ctx context.Context, q MetricResolveQuery) (MetricResolveResult, error)

ResolveMetricQuery turns a descriptor into a tier-selected metric read, dual-reading the legacy path for windows that predate the fine-grain rollups.

func (*Store) RevokeAPIToken added in v0.8.444

func (s *Store) RevokeAPIToken(ctx context.Context, id string) error

RevokeAPIToken — tombstone satırı (satır silinmez; audit izi kalır).

func (*Store) RevokeTraceSnapshot added in v0.5.85

func (s *Store) RevokeTraceSnapshot(ctx context.Context, token string) error

RevokeTraceSnapshot immediately invalidates a share token by setting its expires_at to now. The ReplacingMergeTree (version-keyed) takes the higher version on read, so the next GetTraceSnapshot returns nil → 404 on the public route. Idempotent — revoking an already-expired token is a no-op.

func (*Store) RollupTableReady added in v0.9.385

func (s *Store) RollupTableReady(ctx context.Context, table string) (bool, error)

RollupTableReady — dış katman (route) için probe: tablo var mı? serveCached ÖNCESİ çağrılır ki 424 cevabı cache'e yazılmasın.

func (*Store) RouterGaps added in v0.9.549

func (s *Store) RouterGaps(ctx context.Context, since time.Duration, limit int) ([]RouterGap, error)

RouterGaps — guided router'ın yakalayamadığı sorular, sıklığa göre.

surface='chat' = serbest tool döngüsü (guided 'chat-guided' yazar). Boş prompt_sample elenir: kayıt var ama soru yok demek, sıralamada gürültü.

func (*Store) SampleCorrelationIDs added in v0.9.580

func (s *Store) SampleCorrelationIDs(ctx context.Context, service string, from, to time.Time) ([]CorrelationSample, error)

SampleCorrelationIDs — bir servisin son hatalarından örnek istek kimlikleri.

Bounded: service+time WHERE, LIMIT, max_execution_time (spans sert kısıtı). Hata span'i yoksa boş döner — uydurma yok.

func (*Store) SaveAnnouncement added in v0.8.486

func (s *Store) SaveAnnouncement(ctx context.Context, a Announcement) (Announcement, error)

SaveAnnouncement persists the banner and stamps the revision — the stamp is what re-surfaces the banner for users who dismissed an older text.

func (*Store) SaveAnomalyPromotion added in v0.5.70

func (s *Store) SaveAnomalyPromotion(ctx context.Context, c AnomalyPromotionConfig) error

SaveAnomalyPromotion writes the config under system_settings. Backed by the same key/value table SMTP credentials and retention overrides use, so it survives restart without any new schema.

func (*Store) SaveProblemEscalation added in v0.9.248

func (s *Store) SaveProblemEscalation(ctx context.Context, c ProblemEscalationConfig) error

SaveProblemEscalation persists the config under system_settings — same key/value table every other operator setting uses, so it survives restart without new schema.

func (*Store) SaveRuntimeAlerts added in v0.9.485

func (s *Store) SaveRuntimeAlerts(ctx context.Context, c RuntimeAlertConfig) error

func (*Store) SeedPresetDashboards

func (s *Store) SeedPresetDashboards(ctx context.Context) error

func (*Store) ServiceCallers

func (s *Store) ServiceCallers(
	ctx context.Context, service string, from, to time.Time, limit int,
) ([]CallerRow, error)

ServiceCallers returns the inbound-callers backtrace for `service` over [from,to]. Each row identifies a distinct caller pod / instance + client IP combination so the operator can answer "who is hammering me right now". Self-join on (trace_id, parent_id) so caller pod / host comes from the calling span's resource attrs; client.address + user_agent.original come from the receiving (server) span itself, so even traces missing the parent edge still surface the IP-level identity.

Performance posture:

  • The receiving-side filter (service_name = ?) limits the LEFT side aggressively — typically a small fraction of the spans.
  • The right side is constrained by trace_id IN (LEFT.trace_id) so ClickHouse can use the trace_id skip index instead of a full window scan.
  • GROUP BY columns are all low-cardinality lookups extracted once in the LEFT subquery; aggregation happens on a few thousand rows in the typical case.

func (*Store) ServiceInstances added in v0.7.111

func (s *Store) ServiceInstances(ctx context.Context, service string, from, to time.Time) ([]ServiceInstance, error)

ServiceInstances returns one row per host_name emitting metrics for the service in the window, with the latest CPU / memory per pod. ONE bounded metric_points query (service + time prefix prune, LowCardinality host_name group, capped rows + wall-clock).

func (*Store) ServiceNeighbors

func (s *Store) ServiceNeighbors(
	ctx context.Context, service string, since time.Duration, sampleCount int,
) (upstream, downstream []NeighborStat, sampledFrom, totalSpans int, err error)

ServiceNeighbors returns the service-level upstream / downstream neighbours of `service` derived purely from trace topology — no peer.service reliance. We sample the same recent N traces the structure view uses and walk parent-child edges in memory: an edge from a span S whose service != `service` to a child whose service == `service` makes S's service an upstream caller; the reverse pattern makes the child's service a downstream callee.

Returns:

upstream     — services that called `service` (caller side of inbound edges)
downstream   — services `service` called (callee side of outbound edges)
sampledFrom  — number of traces actually inspected (≤ samples)
totalSpans   — span count across the sampled traces (header line)

func (*Store) ServiceREDSeries added in v0.8.332

func (s *Store) ServiceREDSeries(ctx context.Context, service string, from, to time.Time) []SpanMetricSeries

ServiceREDSeries fetches one service's three RED series (rate / error_rate / p99) over [from,to] via the SAME QuerySpanMetric path the live chart + RED panel + DQL hit — one cache + one MV story. Each series is grouped by service.name so the MV fast-path (service_summary_5m, step ≥ 5m) applies and the result is one line per metric. Soft-fails per-query: a missing series just drops out of the bundle. The GroupKey[0] is overwritten with the metric label so the three lines are self-describing without a side channel.

func (*Store) SetAlertRuleEnabled

func (s *Store) SetAlertRuleEnabled(ctx context.Context, id string, enabled bool) error

SetAlertRuleEnabled flips a rule's enabled flag. Used by both the disable (DELETE) and re-enable endpoints.

func (*Store) SetExceptionGroupState

func (s *Store) SetExceptionGroupState(ctx context.Context, fingerprint, newState string) error

func (*Store) SetIncidentPublished

func (s *Store) SetIncidentPublished(ctx context.Context, p PublishedIncident) error

func (*Store) SetNeighborProvider

func (s *Store) SetNeighborProvider(np NeighborProvider)

SetNeighborProvider registers the topology-aware lookup the auto-attach path uses for rule 3 (1-hop neighbour grouping). Call once at startup with the correlator.Correlator instance; nil disables topological clustering, leaving same-service grouping intact. The store keeps its own reference so the existing AttachProblemToIncident call sites (evaluator, anomaly, monitor) don't need to thread the provider through their constructors.

func (*Store) SetProblemAssignee added in v0.5.209

func (s *Store) SetProblemAssignee(ctx context.Context, id, assignee string) error

SetProblemAssignee overwrites the assignee on a single problem. Empty string clears the assignee — operator's explicit "take it back to unassigned" action. ReplacingMergeTree handles the dedupe; the upsert path is the same as every other write.

func (*Store) SetRetention

func (s *Store) SetRetention(ctx context.Context, sp RetentionSpec, actor string) error

SetRetention persists the new retention values + applies them via ALTER TABLE MODIFY TTL. Only fields with a non-empty value are touched; empty preserves the existing setting.

`actor` is the user email (or "system" on boot replay) recorded for audit. Per-table TTL is set against that table's primary time column — toDate(time) for spans/logs/metrics (day-precision is enough at high cardinality), `start_time` for profiles, plain `time` for metric_points.

func (*Store) SetRunbookEnabled added in v0.6.72

func (s *Store) SetRunbookEnabled(ctx context.Context, id string, enabled bool) error

func (*Store) SetUserTeam added in v0.5.15

func (s *Store) SetUserTeam(ctx context.Context, userID, team string) error

SetUserTeam updates the user's team label. Re-uses the upsert pipeline so ReplacingMergeTree picks up the new row as the latest version. Empty string clears the assignment (the UI groups those under "Unassigned").

func (*Store) ShardPolicy added in v0.5.419

func (s *Store) ShardPolicy() map[string]string

ShardPolicy returns the resolved (table → expression) map for every table that gets a Distributed wrapper in cluster mode. Exposed for /admin/clickhouse so the operator can audit which shard expression each table actually got — saves them from `SHOW CREATE TABLE` round-trips in CH.

func (*Store) ShardSkipSetting added in v0.8.209

func (s *Store) ShardSkipSetting() string

ShardSkipSetting is the exported form for callers in the api package.

func (*Store) StartExecution added in v0.6.73

func (s *Store) StartExecution(ctx context.Context, rb Runbook, execID, startedBy, problemID string) (*RunbookExecution, error)

func (*Store) StartRetentionEnforcer added in v0.5.320

func (s *Store) StartRetentionEnforcer(ctx context.Context, interval time.Duration, lock cache.Lock)

StartRetentionEnforcer runs EnforceRetention immediately, then every `interval` until ctx cancellation. Default interval is 1 hour when ≤ 0. Goroutine-friendly — caller is expected to invoke as `go s.StartRetentionEnforcer(ctx, 0, lock)`.

Singleton-by-Redis-lock: each tick acquires retentionLockKey with a short 5-minute TTL (the enforcement itself runs in seconds; 5 min is generous headroom for a slow CH metadata op). Lock released after the tick. If a holder crashes mid-tick, the lease expires within 5 min and the next replica picks up the work — no manual recovery needed.

func (*Store) TelemetryReadConn added in v0.9.504

func (s *Store) TelemetryReadConn() driver.Conn

TelemetryReadConn — telemetryReadConn'un paket DIŞI hali (v0.9.504). anomaly/evaluator gibi arka plan işçileri chstore paketinde değil ama okudukları şey tamamen telemetri; RoundRobin havuzunu kullanmaları gerekiyor. AYNI KURAL geçerli: yalnız Distributed sarmalayıcı / MV okumaları. Bir state tablosu (users, teams, system_settings, alert_rules, problems, incidents…) bu bağlantıdan okunursa v0.9.486'nın operatör bug'ı geri gelir. Yeni bir paket bunu kullanacaksa conn_strategy_test.go'daki paket beyaz listesine BİLİNÇLİ eklenir.

func (*Store) TopKRagChunks added in v0.8.441

func (s *Store) TopKRagChunks(ctx context.Context, queryEmbedding []float32, k int) ([]RagHit, error)

TopKRagChunks — en benzer k chunk (k [1,20] aralığına kelepçelenir).

func (*Store) TopKRagChunksByContent added in v0.9.161

func (s *Store) TopKRagChunksByContent(ctx context.Context, query string, k int) ([]RagHit, error)

func (*Store) TouchUserLogin added in v0.8.450

func (s *Store) TouchUserLogin(ctx context.Context, userID string) error

TouchUserLogin stamps last_login_at = now after a successful login (v0.8.450). Read-modify-write like UpdatePassword — ReplacingMergeTree replaces the whole row, so the fresh read carries photo/team/role through. Non-fatal for callers: a failed stamp must never block a valid login.

func (*Store) TraceExtras added in v0.9.195

func (s *Store) TraceExtras(ctx context.Context, ids, attrs []string, from, to time.Time) (map[string]map[string]string, error)

TraceExtras fetches the requested attribute keys for an EXPLICIT trace-id set within [from, to] (+slack) and returns them keyed by trace id. Every requested key is present in each returned trace's map (” when absent) so callers can distinguish "fetched, empty" from "not fetched". Id sets past traceExtrasChunkIDs run as sequential chunked queries (export path).

This is the single phase-2 implementation (FAZ 2): GetTraces' MV and raw paths reach it through fillTraceExtras (bounds derived from the page rows), and the /api/traces?traceIds= enrichment path calls it directly (bounds supplied by the client from the visible rows).

func (*Store) UpdatePassword

func (s *Store) UpdatePassword(ctx context.Context, userID, newHash string) error

UpdatePassword writes a new bcrypt hash for an existing user. Other fields are preserved by reading them first — needed because ReplacingMergeTree replaces the whole row on insert.

func (*Store) UpsertAIFeedback added in v0.8.399

func (s *Store) UpsertAIFeedback(ctx context.Context, f AIFeedback) error

UpsertAIFeedback inserts a verdict row. ReplacingMergeTree dedup by exchange_id means re-rating the same answer is a whole-row replace (all fields carried forward by the caller, house rule) — no ALTER UPDATE, no read-modify-write.

func (*Store) UpsertAlertRule

func (s *Store) UpsertAlertRule(ctx context.Context, r AlertRule) error

func (*Store) UpsertAnomalyEvent

func (s *Store) UpsertAnomalyEvent(ctx context.Context, e AnomalyEvent) error

UpsertAnomalyEvent records (or refreshes) an event. ReplacingMergeTree picks the latest version on read. peak_ratio is monotonic — we pass max(prev, new) in the application layer because CH lacks an atomic max-on-upsert primitive on this engine.

func (*Store) UpsertAnomalySilence

func (s *Store) UpsertAnomalySilence(ctx context.Context, sil AnomalySilence) error

func (*Store) UpsertChannel

func (s *Store) UpsertChannel(ctx context.Context, c NotificationChannel) error

func (*Store) UpsertDashboard

func (s *Store) UpsertDashboard(ctx context.Context, d Dashboard) error

UpsertDashboard inserts a new version row — ReplacingMergeTree merges on `id` keeping the row with the highest version (set to current ns).

func (*Store) UpsertEvent added in v0.5.476

func (s *Store) UpsertEvent(ctx context.Context, e Event) (Event, error)

func (*Store) UpsertExceptionGroup

func (s *Store) UpsertExceptionGroup(ctx context.Context, g ExceptionGroup) error

UpsertExceptionGroup — tekil yol (API çağrıları, tekil düzeltmeler). Sıcak yenileme döngüsü UpsertExceptionGroups kullanır.

func (*Store) UpsertExceptionGroupAISummary added in v0.9.415

func (s *Store) UpsertExceptionGroupAISummary(ctx context.Context, fingerprint, summary string) error

UpsertExceptionGroupAISummary (v0.9.415) — ExceptionExplainer'ın proaktif kök-sebep özetini yazar. UpsertProblemAISummary'nin ikizi: FINAL okuma + tam-satır yeniden yazım (writeExceptionGroup zaten tüm alanları taşır). Grup kaybolduysa sessizce düşer.

func (*Store) UpsertExceptionGroups added in v0.9.523

func (s *Store) UpsertExceptionGroups(ctx context.Context, gs []ExceptionGroup) error

UpsertExceptionGroups — sıcak yenileme yolu: N tekil okuma yerine BİR toplu okuma (v0.9.523).

Prod ölçümü (2026-08-02): tekil GetExceptionGroup şekli saatte ~1.3k çağrı — queryrow trafiğinin ikinci en büyük kalemi. exception_groups bir STATE tablosu, in-order ana bağlantıda kalmak zorunda; yani bu yük okuma havuzuna dağıtılamaz, yalnız AZALTILABİLİR. problems tarafındaki v0.9.522 düzeltmesinin ikizi.

func (*Store) UpsertExecution added in v0.6.73

func (s *Store) UpsertExecution(ctx context.Context, e RunbookExecution) error

func (*Store) UpsertHypothesis added in v0.8.168

func (s *Store) UpsertHypothesis(ctx context.Context, h RootCauseHypothesis) error

UpsertHypothesis records (or refreshes) the synthesized hypothesis for one anchor. ReplacingMergeTree(version) keeps the latest per (anchor_kind, anchor_id); the version column's DEFAULT stamps a monotonic ns timestamp so successive worker syntheses dedup to the newest. Candidates is marshalled to the json String column here. Explicit column list (the table also has a `version` DEFAULT) — same idiom as UpsertAnomalyEvent, so the DEFAULT does its job and we don't hand-craft a version value.

func (*Store) UpsertIncident

func (s *Store) UpsertIncident(ctx context.Context, i *Incident) error

UpsertIncident takes a pointer so auto-generated IDs flow back to the caller (matches the monitor/dashboard pattern).

func (*Store) UpsertLdapGroups added in v0.8.526

func (s *Store) UpsertLdapGroups(ctx context.Context, rows []LdapGroupRow, syncedAt time.Time) (written, tombstoned int, err error)

UpsertLdapGroups writes the FULL in-scope group set (deleted=0) and tombstones (deleted=1) every UID that was live before this sync but is absent now. One monotonic version stamps the whole batch so a later sync always wins the ReplacingMergeTree merge. Returns (written, tombstoned) counts for the sync stats.

func (*Store) UpsertLogTemplate added in v0.5.244

func (s *Store) UpsertLogTemplate(ctx context.Context, t LogTemplate) error

UpsertLogTemplate writes (or refreshes) one template row. ReplacingMergeTree(version) picks the highest version on merge so the latest batch wins. Caller supplies template_id; we keep first_seen sticky (never decrease) by reading the existing row before writing, similar to how UpsertAnomalyEvent guards started_at.

func (*Store) UpsertMaintenanceWindow added in v0.5.26

func (s *Store) UpsertMaintenanceWindow(ctx context.Context, w MaintenanceWindow) error

UpsertMaintenanceWindow writes / updates a window. ID set by caller (new rows generated upstream); ReplacingMergeTree keyed by id so re-upsert replaces.

func (*Store) UpsertMonitor

func (s *Store) UpsertMonitor(ctx context.Context, m *Monitor) error

UpsertMonitor takes a pointer so that auto-generated IDs / tokens (filled in for new monitors) flow back to the caller.

func (*Store) UpsertProblem

func (s *Store) UpsertProblem(ctx context.Context, p Problem) error

func (*Store) UpsertProblemAISummary added in v0.5.254

func (s *Store) UpsertProblemAISummary(ctx context.Context, problemID, summary string) error

UpsertProblemAISummary writes just the AI-explain blurb without touching any of the evaluator-owned fields. ReplacingMergeTree keeps the highest-version row at read time; this insert wins over a same-tick evaluator upsert because the explainer always runs after the problem has been opened (later wall-clock = newer version).

Reads other fields back from the existing row first so the resulting full row is consistent — otherwise the merge'd row would have value/threshold/etc collapsing to defaults on the "summary-only" version.

func (*Store) UpsertRCAVerdict added in v0.9.591

func (s *Store) UpsertRCAVerdict(ctx context.Context, v RCAVerdictRecord) error

UpsertRCAVerdict — bir verdict kaydını yazar.

ReplacingMergeTree(version) ORDER BY exchange_id: aynı exchange_id ile ikinci yazım TAM SATIR değişimidir (ev kuralı — çağıran tüm alanları taşır), ALTER UPDATE yok.

Yalnız MODEL GERÇEKTEN ÇAĞRILDIĞINDA yazılır (önbellek ıskası). Önbellekten sunulan her istekte yazsaydık aynı karar defalarca sayılır ve ölçüm şişerdi.

func (*Store) UpsertRunbook added in v0.6.72

func (s *Store) UpsertRunbook(ctx context.Context, rb Runbook) error

func (*Store) UpsertSLO

func (s *Store) UpsertSLO(ctx context.Context, o SLO) error

func (*Store) UpsertSavedView

func (s *Store) UpsertSavedView(ctx context.Context, v SavedView) error

func (*Store) UpsertServiceMetadata

func (s *Store) UpsertServiceMetadata(ctx context.Context, m ServiceMetadata) error

UpsertServiceMetadata writes a catalog row. Last-write-wins via the ReplacingMergeTree's version column; UpdatedAt is always stamped to now() so the operator sees fresh edit times in the list. Empty `service` is a no-op (you can't curate "no service").

Writes only the new chat_channel column. The legacy slack_channel column is left as-is so an upgrade-then- downgrade still surfaces the original value; the next edit after upgrade migrates the value into chat_channel via the read-time fallback.

func (*Store) UpsertStatusComponent

func (s *Store) UpsertStatusComponent(ctx context.Context, c *StatusComponent) error

func (*Store) UpsertStatusPageConfig

func (s *Store) UpsertStatusPageConfig(ctx context.Context, c StatusPageConfig) error

func (*Store) UpsertUser

func (s *Store) UpsertUser(ctx context.Context, u User) error

func (*Store) UsageLimit added in v0.7.69

func (s *Store) UsageLimit(
	ctx context.Context, usageMetric, limitMetric string,
) ([]CapacitySample, error)

UsageLimit reads a set of (usage-metric, limit-metric) gauge pairs per instance, latest value over the window. Returns one CapacitySample per instance that has BOTH gauges present (a usage with no limit can't be turned into a saturation %, so it's skipped — the dashboard shows it raw, but it isn't pageable). Subkey is always empty for these undimensioned checks.

usageMetric / limitMetric are literal metric names from the receiver semantic conventions (oracledb.sessions.usage, …) — never operator input — so the IN list is safe to build.

func (*Store) WatcherProblemSummaries added in v0.9.196

func (s *Store) WatcherProblemSummaries(ctx context.Context) (map[string]WatcherSummary, error)

WatcherProblemSummaries returns the per-rule rollup keyed by rule id. Rules that never opened a problem simply have no entry — the API layer zero-fills them against the alert_rules list.

func (*Store) WriteRootFlowsBucket added in v0.5.112

func (s *Store) WriteRootFlowsBucket(ctx context.Context, bucketStart time.Time) error

WriteRootFlowsBucket pre-aggregates business flows for one 5-min window. Counts traces + collects unique services per (root_service, root_op). Mirrors GetRootFlows but materialises the result into the agg table for cheap fan-out reads later.

func (*Store) WriteServiceCallersBucket added in v0.5.368

func (s *Store) WriteServiceCallersBucket(ctx context.Context, bucketStart time.Time) error

WriteServiceCallersBucket batch-aggregates the per-(receiver, caller pod / client / UA) rollup for one 5-min slice. Called by the topology aggregator alongside WriteTopologyBucket so the schedule + settle-delay semantics stay aligned.

Same shape as the prior raw-spans ServiceCallers query, just run ONCE per bucket instead of on every operator request. All the dimensional cardinality concerns we already accepted for the raw path (client_address can be high-card) carry forward into the MV unchanged — the TTL keeps storage bounded.

func (*Store) WriteTopologyBucket added in v0.5.108

func (s *Store) WriteTopologyBucket(ctx context.Context, bucketStart time.Time) error

func (*Store) WriteTopologyOpBucket added in v0.5.109

func (s *Store) WriteTopologyOpBucket(ctx context.Context, bucketStart time.Time) error

WriteTopologyOpBucket pre-aggregates per-op edges for a 5-min bucket. Same shape as WriteTopologyBucket but at op granularity — used by /api/topology (operation deep-dive view).

type SystemHealth added in v0.8.211

type SystemHealth struct {
	// ExternalDistributedSpansUnset is true when `spans` is an external
	// Distributed table but COREMETRY_CH_CLUSTER_NAME is unset — so adaptDDL
	// can't rewrite MV bodies to FROM spans_local ON CLUSTER, their per-shard
	// insert trigger never fires, and every summary MV (service_summary_5m,
	// trace_service_index_5m, …) stays EMPTY → reads return no/partial results.
	ExternalDistributedSpansUnset bool `json:"externalDistributedSpansUnset"`
	// SuggestedClusterName is the cluster the external `spans` Distributed table
	// fans to (parsed from its engine def) — set COREMETRY_CH_CLUSTER_NAME to
	// this to make the MVs populate. Empty if unparseable.
	SuggestedClusterName string `json:"suggestedClusterName,omitempty"`
	// LockDegraded is true when COREMETRY_REDIS_URL was set (the operator wants
	// a distributed leader lock for multi-pod HA) but the Redis connection
	// failed, so the pod fell back to the always-leader Noop lock. In a
	// multi-pod deployment EVERY pod then becomes leader and background jobs
	// (alerts, notifications, topology aggregation, retention) run DUPLICATED.
	// Populated by the API getSystemStats handler (main.go knows the lock state).
	LockDegraded bool `json:"lockDegraded"`
	// ESQueryErrors — cumulative failed Elasticsearch queries since process
	// start (transport + non-2xx), when the logs backend is external ES.
	// Non-zero = check /admin/elastic → Recent query errors for the exact
	// requests Coremetry sent. Populated by the API getSystemStats handler
	// (chstore stays free of any logstore dependency). v0.8.230.
	ESQueryErrors int64 `json:"esQueryErrors,omitempty"`
}

SystemHealth surfaces config/boot conditions that silently degrade reads, so the operator sees them on /admin/stats instead of debugging empty dashboards. v0.8.211.

type SystemSnapshot

type SystemSnapshot struct {
	Spans24h        uint64 `json:"spans24h"`
	Spans7d         uint64 `json:"spans7d"`
	SpansAllTime    uint64 `json:"spansAllTime"`
	Errors24h       uint64 `json:"errors24h"`
	Logs24h         uint64 `json:"logs24h"`
	LogsAllTime     uint64 `json:"logsAllTime"`
	Metrics24h      uint64 `json:"metrics24h"`
	MetricsAllTime  uint64 `json:"metricsAllTime"`
	Profiles24h     uint64 `json:"profiles24h"`
	ProfilesAllTime uint64 `json:"profilesAllTime"`
	Services24h     uint64 `json:"services24h"`
	Operations24h   uint64 `json:"operations24h"`
	TotalDiskBytes  uint64 `json:"totalDiskBytes"`
}

type SystemStats

type SystemStats struct {
	Snapshot SystemSnapshot `json:"snapshot"`
	Tables   []TableStat    `json:"tables"`
	// Disks (v0.9.289, operator ask) — the CAPACITY of the volumes
	// ClickHouse writes to, which is a different question from Tables
	// above. Tables says how much room Coremetry's data occupies;
	// Disks says how much room is left. Retention settings are only
	// meaningful against the second one, and until now the operator had
	// to ssh to the node to find it.
	Disks []DiskStat `json:"disks"`
	// Servers (v0.9.290, operator ask) — live memory/CPU pressure per
	// ClickHouse node, alongside the disk capacity above.
	Servers   []ServerStat   `json:"servers"`
	History   []DayStat      `json:"history"`
	Ingest    IngestRates    `json:"ingest"`
	Drops     IngestDrops    `json:"drops"`
	Health    SystemHealth   `json:"health"`
	Exemplars ExemplarIngest `json:"exemplars"`
	SpanLinks SpanLinkIngest `json:"spanLinks"`
}

SystemStats is the meta-observability snapshot the /admin/stats page renders: today's KPIs, per-table storage, and a 30-day history bar chart. All data points are read from cheap sources — system.parts metadata for storage, the service_summary_5m aggregate MV for daily span / error rollups, and bounded recent scans for distinct service / operation counts. Designed to stay sub-second even at 40M traces / day.

type TableStat

type TableStat struct {
	Table             string `json:"table"`
	Rows              uint64 `json:"rows"`
	BytesOnDisk       uint64 `json:"bytesOnDisk"`
	CompressedBytes   uint64 `json:"compressedBytes"`
	UncompressedBytes uint64 `json:"uncompressedBytes"`
	Parts             uint32 `json:"parts"`
	OldestNs          int64  `json:"oldestNs"`
	NewestNs          int64  `json:"newestNs"`
}

type TeamAliases added in v0.9.427

type TeamAliases struct {
	Aliases map[string]string `json:"aliases"`
}

TeamAliases — alias → kanonik ad. Anahtarlar da değerler de serbest yazımdır; TÜM karşılaştırmalar CanonTeam üzerinden normalize edilir (küçük harf + trim + Türkçe İ'nin combining-dot artığı temizliği).

func (TeamAliases) CanonTeam added in v0.9.427

func (ta TeamAliases) CanonTeam(name string) string

CanonTeam — adı kanonik forma indirir: alias tablosunda varsa hedefin normali, yoksa kendi normali. Tek seviyedir (alias'ın alias'ı yok — tablo zaten kanonik hedefe yazılır).

func (TeamAliases) TeamEqual added in v0.9.427

func (ta TeamAliases) TeamEqual(a, b string) bool

TeamEqual — iki takım adı aynı takım mı (alias + normalizasyon).

type TeamContacts added in v0.8.429

type TeamContacts struct {
	Enabled bool `json:"enabled"`
	// MinSeverity — info | warning | critical; "" defaults to warning
	// so a fresh install doesn't mail every info-level blip.
	MinSeverity string `json:"minSeverity,omitempty"`
	// Contacts maps a catalog team name → e-mail address(es). Values
	// may be comma-separated for multi-recipient teams. Lookup is
	// case-insensitive (mixed-casing team attrs, v0.8.330 lesson).
	Contacts map[string]string `json:"contacts"`
}

TeamContacts is the "team_contacts" system_settings value.

func (TeamContacts) EmailsForTeam added in v0.8.429

func (tc TeamContacts) EmailsForTeam(team string) []string

EmailsForTeam resolves one catalog team name to its configured addresses — case-insensitive key match, comma-split, trimmed. Missing / empty team or contact yields nil (callers skip silently; the Settings UI surfaces which catalog teams lack an address).

func (TeamContacts) SeverityAllows added in v0.8.429

func (tc TeamContacts) SeverityAllows(sev string) bool

SeverityAllows reports whether a problem of severity sev clears the blob's MinSeverity floor (default warning).

type TopRow

type TopRow struct {
	Name string `json:"name"`
	Rows uint64 `json:"rows"`
}

type TopologyEdge added in v0.5.100

type TopologyEdge struct {
	ParentService string `json:"parentService"`
	ParentOp      string `json:"parentOp"`
	ChildService  string `json:"childService"`
	ChildOp       string `json:"childOp"`
	Calls         uint64 `json:"calls"`
}

TopologyEdge is one parent→child operation invocation aggregated over a time window. Used by the op-level depth view; the service- level view consumes ServiceTopologyEdge below.

type TraceAggregateStub added in v0.6.34

type TraceAggregateStub struct {
	RootService string  `json:"rootService"`
	RootName    string  `json:"rootName"`
	StartTimeNs int64   `json:"startTimeNs"` // earliest span start in MV
	EndTimeNs   int64   `json:"endTimeNs"`   // latest span end in MV
	SpanCount   uint64  `json:"spanCount"`
	ErrorCount  uint64  `json:"errorCount"`
	DurationMs  float64 `json:"durationMs"`
}

TraceAggregateStub is the minimal "we have aggregates only" payload the UI uses to explain the missing-spans case. All fields come from the trace_summary_5m MV's *Merge() finalisers.

type TraceCount added in v0.9.638

type TraceCount struct {
	Value   uint64 `json:"value"`
	AtLeast bool   `json:"atLeast"`          // tavana değdi → "10.000+"
	Reason  string `json:"reason,omitempty"` // dolu ise Value anlamsız
}

TraceCount — sayım sonucu.

Reason != "" ise SAYI YOK ve sebebi var. Bu bilinçli: bazı şekiller (süre filtresi, servis+post-agg) MV'de ucuza sayılamıyor ve "yanlış sayı, sayı yokluğundan kötüdür" ilkesinin devamı olarak PAHALI sayı da dürüst bir retten kötüdür.

type TraceFilter

type TraceFilter struct {
	Service  string
	Search   string
	TraceID  string // exact 32-hex match only (prefix search removed v0.9.82)
	From, To time.Time
	HasError bool
	// RootOnly hides traces where the root span ((parent_id = ” OR parent_id = '0000000000000000')) was
	// never ingested — typically partial / fragmented traces where
	// only sub-spans landed in storage. The list view exposes this
	// as a "Root traces" checkbox alongside "Errors only".
	RootOnly bool
	// RequireServices restricts the result to traces that contain
	// spans from EVERY listed service — a trace-topology AND across
	// service involvement. The single Service filter, in contrast,
	// is a span-level WHERE that narrows to one service. When both
	// are set, RequireServices takes precedence (Service is dropped)
	// because the WHERE-narrowing approach can't co-exist with the
	// HAVING-based fan-in check. Used by the backtrace 'Traces'
	// drill-in to surface only traces where caller × callee
	// actually co-occur.
	RequireServices []string
	// TraceIDs restricts the result to this explicit set of trace IDs
	// (rendered as `trace_id IN (…)`). Used by the relations view
	// (relations.go): the bounded self-join resolves a page of trace
	// IDs, then GetTraces re-fetches their summary rows for the list
	// render. The `trace_id IN (…)` clause rides the idx_trace bloom
	// skip index so the re-fetch is bounded by the page size, not the
	// window. Caller caps the slice length (≤ page size). When set, the
	// trace_summary MV fast-path is disqualified (raw access required).
	TraceIDs []string
	MinMs    float64
	MaxMs    float64
	AttrKey  string
	AttrVal  string
	// ExtraAttrs is the user-selected list of attribute keys whose
	// values should be projected into TraceRow.Extras. Each key picks
	// up the first non-empty value among span-attributes and resource-
	// attributes for that trace. Sanitised by the HTTP layer to strict
	// dot/underscore/dash naming so the value can flow safely into the
	// SELECT clause.
	ExtraAttrs []string
	// Env narrows to spans emitted from ONE deployment environment
	// (spans.deploy_env — v0.8.383, env-separation Phase 1, the global
	// Topbar picker's ?env=). Deliberately a first-class field rather
	// than an injected FilterExpr: FilterRoot SUPERSEDES Filters, so an
	// appended env leaf would silently vanish whenever the operator has
	// a grouped OR/nested filter active — and wrapping the group one
	// level deeper would hit the depth cap (nested groups' own Groups
	// are ignored). As its own always-AND conjunct it composes with
	// every filter mode. Non-empty Env disqualifies the trace_summary
	// MV fast-path (the MV has no env dimension — cluster-style
	// raw-fallback, operator-approved, NO MV changes).
	Env     string
	Filters []FilterExpr // advanced filter chips (AND-joined)
	// FilterRoot is the optional grouped AND/OR builder (v0.8.x trace-query
	// gap-2). When non-nil it SUPERSEDES Filters: buildGetTracesWhere calls
	// ApplyFilterGroup instead of ApplyFilters. A flat-AND FilterRoot emits
	// byte-identical SQL to the legacy Filters path (pinned by
	// filtergroup_test.go), so existing callers that leave it nil are wholly
	// unaffected. An OR / nested group disqualifies the trace_summary MV
	// fast-path exactly like Search / custom-attr does (see GetTraces gate).
	FilterRoot *FilterGroup
	Sort       string // "time" | "duration"
	Order      string // "asc" | "desc"
	Limit      int
	Offset     int
	// RankedWithin, when non-nil, is an OUT param: the MV fast-path
	// writes traceRecencySliceN into it when a non-time sort was
	// ranked within the newest-N recency slice (v0.8.369,
	// Dynatrace-style). The HTTP layer forwards it so the UI can
	// show the "ranked within newest N" hint honestly — it stays 0
	// when the raw path (or an unsliced sort) served the request.
	RankedWithin *int
	// NarrowedFrom (v0.9.297) — OUT param. Set when Stage 2 ran out of
	// resources and the window had to be halved to answer at all. The
	// caller MUST surface it: a top-N over half the requested window is
	// a DIFFERENT answer, not a slower one, and presenting it as the
	// operator's question silently answered is the failure mode this
	// codebase keeps paying for.
	NarrowedFrom *time.Time
	// CountMode controls the cost/accuracy of the total-rows badge:
	//
	//	"skip"    no DISTINCT count at all — the cheapest path. The caller
	//	          gets `hasMore=true` when the result is full, so the UI
	//	          can paginate without ever paying the count.
	//	"approx"  count only the first Limit+1 trace_ids (LIMIT-bounded
	//	          subquery). Caps the scan so the worst case is bounded
	//	          even at scale.
	//	"exact"   full count(DISTINCT trace_id) — the legacy behaviour;
	//	          can be 10s+ on multi-billion-span tables.
	//
	// Empty string is treated as "exact" by GetTraces for back-compat
	// with internal callers; the HTTP layer overrides the default to
	// "skip".
	CountMode string
}

type TraceRow

type TraceRow struct {
	TraceID     string  `json:"traceId"`
	RootName    string  `json:"rootName"`
	ServiceName string  `json:"serviceName"`
	StartTime   int64   `json:"startTime"`
	DurationMs  float64 `json:"durationMs"`
	SpanCount   uint64  `json:"spanCount"`
	HasError    bool    `json:"hasError"`
	// Per-trace lookup of attribute values requested by the caller
	// via TraceFilter.ExtraAttrs. Keys mirror the requested list;
	// missing/empty values surface as "" so the UI can render a
	// "—" placeholder. Omitted entirely when no extras requested.
	Extras map[string]string `json:"extras,omitempty"`
}

type TraceShape added in v0.5.264

type TraceShape struct {
	// ShapeID is the stable hash key; useful as a React key.
	ShapeID string `json:"shapeId"`
	// Signature is the sorted-unique list of "service|operation"
	// strings. The frontend renders this as a multi-chip card so
	// the operator can see the cohort at a glance.
	Signature []string `json:"signature"`
	// TraceCount is the estimated count over the requested
	// window (sample count × sample-rate-inverse). The
	// frontend tags this as "~N traces" so operators don't
	// read it as an exact figure.
	TraceCount uint64 `json:"traceCount"`
	// AvgMs / P99Ms / ErrorRate are NOT extrapolated — they're
	// computed across the sample so the cohort's stats stay
	// representative.
	AvgMs     float64 `json:"avgMs"`
	P99Ms     float64 `json:"p99Ms"`
	ErrorRate float64 `json:"errorRate"`
	// SamplingRate is the trace_id hash divisor we used (e.g.
	// 0.1 for the default 10% sample). UI surfaces it as a
	// "estimated counts" tag so the operator knows the
	// numbers are sampled.
	SamplingRate float64 `json:"samplingRate"`
}

TraceShape (v0.5.264) — operator-facing "trace shape" cluster. A shape is the sorted-unique set of (service.name, operation) pairs that appear in a trace; two traces share a shape if and only if they exercise the exact same set of (service, op) touchpoints regardless of count / order. This collapses millions of traces into a few dozen distinct "what is this app doing" patterns, the Dynatrace OpenTelemetry "mass data analysis" workflow native to Coremetry.

Counts are estimated from a 10%-by-trace_id hash sample so the query stays under the 30s ceiling at billion-span scale. The p99 / avg / error_rate stats are computed across the sample (no extrapolation — they're representative of the cohort regardless of the multiplier).

type TraceShapesFilter added in v0.5.264

type TraceShapesFilter struct {
	From, To time.Time
	Service  string // optional — pins shapes to a single root service
	Limit    int    // default 30; capped at 100
}

TraceShapesFilter shapes the request to GetTraceShapes.

type TraceSnapshot

type TraceSnapshot struct {
	Token     string `json:"token"`
	TraceID   string `json:"traceId"`
	CreatedBy string `json:"createdBy,omitempty"`
	CreatedAt int64  `json:"createdAt"` // unix ns
	ExpiresAt int64  `json:"expiresAt"` // unix ns
	// LogsJSON — the trace's log lines captured AT SHARE TIME
	// (v0.8.252), pre-marshalled JSON array. The public viewer serves
	// this frozen copy: no live logstore query on the anonymous route,
	// and the share keeps its logs even after log-retention TTLs eat
	// the originals. Empty = shared before v0.8.252 or capture failed.
	LogsJSON string `json:"-"`
}

TraceSnapshot is a public, time-boxed share token for a trace. Created via POST /api/trace/{id}/share, resolved by the public route GET /api/public/trace/{token}.

type User

type User struct {
	ID           string `json:"id"`
	Email        string `json:"email"`
	PasswordHash string `json:"-"`
	Role         string `json:"role"` // admin | editor | viewer
	Disabled     bool   `json:"disabled"`
	AuthProvider string `json:"authProvider"` // local | oidc — drives "Change password" UI
	// Team — free-text grouping the admin picks (e.g. "platform-sre",
	// "fraud", "payments"). Backed by a LowCardinality column so
	// repeated values are cheap. Empty when unassigned; the
	// users-list UI groups "Unassigned" separately so an admin
	// can see who still needs labelling.
	Team string `json:"team"`
	// CustomRole — optional pointer into the auth.Service custom-role
	// catalog. Only meaningful when Role == viewer; otherwise ignored
	// (admin/editor get no further restriction). Empty = no custom
	// role, unrestricted viewer.
	CustomRole string `json:"customRole,omitempty"`
	CreatedAt  int64  `json:"createdAt"` // unix nanoseconds
	// Photo — LDAP thumbnailPhoto/jpegPhoto bytes (v0.8.238), refreshed
	// on each directory login; empty for local/OIDC accounts. Never
	// serialized into user JSON — served by the dedicated photo
	// endpoints. Get* loads the bytes (read-modify-write paths must
	// carry them through UpsertUser or the ReplacingMergeTree row
	// replace would wipe the photo); List* loads only HasPhoto.
	Photo    []byte `json:"-"`
	HasPhoto bool   `json:"hasPhoto"`
	// FullName / Org — directory identity (v0.8.266, operator: "ad
	// soyad + organizasyon da gelsin"). Refreshed on each LDAP login
	// (displayName → FullName, company/o → Org; department/ou lands
	// in Team above). Empty for local/OIDC accounts unless set.
	FullName string `json:"fullName,omitempty"`
	Org      string `json:"org,omitempty"`
	// LastLoginAt — son başarılı login (unix ns, 0 = hiç; v0.8.450).
	// Login yolları TouchUserLogin ile damgalar; whole-row replace
	// gereği her Upsert taşır.
	LastLoginAt int64 `json:"lastLoginAt"`
	// LdapUsername — directory sAMAccountName, lowercased (v0.8.526).
	// The join key between an LDAP group snapshot's members and this
	// user; email stays the canonical identity. Set by loginViaLDAP,
	// empty for local/OIDC accounts. Read-modify-write paths must carry
	// it forward (whole-row replace). Only persisted/read when the store
	// probed the column present (Store.hasLdapUsernameCol).
	LdapUsername string `json:"ldapUsername,omitempty"`
}

type WatcherSummary added in v0.9.196

type WatcherSummary struct {
	RuleID   string `json:"ruleId"`
	LastFire int64  `json:"lastFire"` // unix ns of the newest problem's started_at; 0 = never fired
	Fires24h uint64 `json:"fires24h"` // problems opened in the trailing 24h
	OpenNow  bool   `json:"openNow"`  // an open/acknowledged problem exists right now
	// FiresHourly — the same trailing-24h fires, distributed into 24
	// one-hour slots, oldest→newest (slot 0 = 24h ago … slot 23 = the
	// last hour). Granular-sparklines sweep (M4): drives the /watchers
	// list's per-row fire-distribution mini-bar instead of a bare count.
	FiresHourly []uint64 `json:"firesHourly,omitempty"`
}

WatcherSummary is one rule's problems rollup for the /watchers list.

Source Files

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL