metrics

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

authn_hooks_recorder.go — исходы полосы хуков поставщика личности (задача продукта #2495).

Почему клетки заводятся нулём при провязке

«Ноль обращений за всю жизнь полосы» здесь невидимо by construction: отсутствие строк журнала тревогой не бывает, а ряд, появляющийся вместе с первым обращением, не отличает «обращений не было» от «клетки нет». Заведение всех клеток при провязке делает условие тревоги «за час ноль обращений» выразимым — то есть делает выразимым состояние «поставщик не настроен звать хук», которое до этого не наблюдалось ничем.

Почему наборы приходят доводами

Маршруты и исходы объявляет пакет полосы — он один держит соответствие пути и обработчика и разбор состояния ответа. Второй перечень здесь разошёлся бы с первым молча, и разошёлся бы в сторону «маршрут без клетки». Переводит один набор в другой композиционный корень: он единственный знает обоих.

client_token_outcome_collector.go — читатель переписи исходов токен-эндпоинта платформы (задача продукта #2501).

Предмет

Обработчик нёс перепись с пред-засевом по каждому объявленному исходу и экспортированный аксессор — то есть работа была сделана на девять десятых. Читателя у переписи не было: разбивка отказов (склад однократности недоступен · перечень доверия недоступен · издатель не доверен · адресат не разрешён …) жила только в памяти процесса и уходила вместе с ним.

Через эту полосу в отдельной установке выдаётся ВСЯКОЕ арендаторское удостоверение, и «ноль отказов за всю жизнь полосы» здесь неотличимо от «полоса не исполнялась ни разу» — ровно то, что `security.md` §Hardening п. 8 требует сделать заметным.

Почему ряд печатается по ОБЪЯВЛЕННОМУ набору, а не по ключам снимка

Снимок сегодня полон: обработчик засевает словарь целиком. Но коллектор, печатающий ключи снимка, остался бы зелёным, перестань обработчик засевать, — и клетка снова появлялась бы при первом попадании. Объявленный набор приходит доводом, поэтому полнота витрины не зависит от чужого засева: отсутствующий в снимке исход печатается НУЛЁМ.

expired_credential_sweep_recorder.go — величины ВТОРОГО уборщика по сроку (задача продукта #2499).

Почему полосы двух уборщиков обязаны совпасть

Уборщиков одного вида два: уборка по сроку и снятие истёкших удостоверений. У первого три семейства величин, и справка первого прямо объявляет смысл нуля. У второго не было ни одной: наблюдение — строка на прогон и предупреждение при старте, когда уборщик выключен. Различие никем не решалось, а шапка самого файла провязки называет класс, ради которого его провязывали: «механизм, написанный и не позванный, — контроль, у которого нет возможности исполниться».

Какие ТРИ состояния различаются

выключен оператором           ряд включённости на нуле. До этих величин
                              состояние объявлялось единожды при старте и
                              уходило вместе со сроком хранения журнала;
включён и отказывает          клетка исхода `failed` растёт;
включён и находит ноль        клетка `ok` растёт, снятых строк нуль. Это
                              ЗАКОННОЕ состояние, и отличает его от мёртвой
                              петли именно ряд прогонов.

Почему найденное и снятое — ДВА ряда

«Снято 0» само по себе не отличает «нечего снимать» от «нашёл и не снял»: второе наступает при показе без снятия и при отказе посреди партии. Перепись прогона несёт два числа, и величины несут их оба.

Package metrics is the kaname Prometheus observability adapter.

It lives at the cmd/adapter boundary (Clean Architecture): the prometheus client is imported ONLY here and in the composition root (cmd/kaname) — never in domain/ or in the AuthorizeService use-case. The use-case stays a pure FGA-Check pipeline; instrumentation is layered on via the InstrumentedAuthorizer decorator (authz_decorator.go) and a gRPC server interceptor.

Surfaces:

  • Registry.Handler() — promhttp.Handler served on a SEPARATE internal port (KANAME_METRICS_ENDPOINT, default :9095). Never on the public tenant gRPC surface (it would expose internal cardinality — security.md).
  • Registry.ObserveAuthz — the authz Check hot-path histogram + decision counter (the documented ≤30ms p95 budget on AuthorizeService.Check / CheckRelation was previously un-instrumented).
  • Registry.UnaryServerInterceptor — per-RPC request count + latency + code, registered on BOTH gRPC listeners (public :9090 + internal :9091).

All metric names carry the `kaname_` prefix (the product names itself; the env domain segment stays IAM).

presented_credential_collector.go — исходы приёма удостоверения, ПРЕДЪЯВЛЕННОГО арендатором на публичном слушателе (задача продукта #2077).

Почему рядов ТРИ, а не один

Ряд отвергнутых без ряда принятых отвечает сразу на два вопроса: «отказов не было» и «сюда никто не приходил». Различить их по одному ряду нечем, а вопросы эти чинятся в разных местах: первый не чинится вовсе, второй означает, что арендатор до службы не доезжает.

Третий ряд — «ответить не смогли» — не оттенок отказа: негодный токен чинит предъявитель, недоступный авторитет чинит оператор. Смешать их значило бы сделать сбой хранилища неотличимым от подделанного удостоверения.

Почему ряды эмитируются ВСЕГДА, включая нулевые

Читатель отдаёт величины снимком, и коллектор печатает все три независимо от значений. Ряд, появляющийся только вместе с первым событием, делает ноль невыразимым: отсутствующий ряд и нулевой выглядят одинаково у того, кто смотрит на график.

readiness_recorder.go — исход готовности ПО КАЖДОЙ ЗАВИСИМОСТИ, а не одним битом (задача продукта #2494).

Почему один бит недостаточен

Зависимостей готовности три, и каждая означает СВОЮ починку: база недоступна — сломан продукт либо среда; образ не той версии, что схема — условие не создано (накат не прогнан либо откат поставил прежний образ на новую схему); исполнитель операций ещё не поднялся — идёт старт. Наружу выходил один бит «под не готов», а имена трёх зависимостей — только в теле пробы, поднятой по TLS на внутреннем порту. Чтобы их прочесть, нужен проброс порта и запрос в обход проверки сертификата; ни один документ поставки этого не называет.

Почему имя зависимости и есть различение трёх состояний

Класс починки привязан к ЗАВИСИМОСТИ, а не к тексту её отказа: имя в метке отвечает на «продукт сломан или условие не создано» без чтения кода. Разбирать текст ошибки было бы вторым разбором того же предмета — и разошёлся бы он молча, потому что текст свой у каждого чекера и меняется вместе с ним.

Почему ряды заводятся НУЛЁМ при регистрации

Клетка, появляющаяся при первом попадании, неотличима от «ещё не случалось»: правило тревоги, считающее долю, врёт ровно до первого события. Здесь заводится каждая клетка (зависимость × исход), поэтому «готовность не оценивалась ни разу» выразимо нулём во ВСЕХ рядах — и это отдельное состояние, а не оттенок исправности: пробы, которых никто не спрашивает, не снимают под из ротации.

Index

Constants

View Source
const (
	// LaneCheck — полоса КРАЯ.
	LaneCheck = "Check"
	// LaneBatchCheck — полоса сужателя списочной выдачи.
	LaneBatchCheck = "BatchCheck"
	// LaneCheckRelation — полоса пообъектного звена модулей.
	LaneCheckRelation = "CheckRelation"
)

Значения метки `rpc` — ЗАКРЫТЫЙ словарь. Именованные константы, а не литералы по месту: одно расхождение в написании завело бы четвёртую полосу, которую никто не заметит, потому что она всегда ноль.

View Source
const (
	// ExpiredCredentialReclaimEnabledMetric — включён ли уборщик (1/0).
	ExpiredCredentialReclaimEnabledMetric = Namespace + "_expired_credential_reclaim_enabled"
	// ExpiredCredentialReclaimPassesMetric — прогоны по исходам.
	ExpiredCredentialReclaimPassesMetric = Namespace + "_expired_credential_reclaim_passes_total"
	// ExpiredCredentialReclaimFoundMetric — строк найдено подлежащими снятию.
	ExpiredCredentialReclaimFoundMetric = Namespace + "_expired_credential_reclaim_rows_found_total"
	// ExpiredCredentialReclaimReclaimedMetric — строк снято.
	ExpiredCredentialReclaimReclaimedMetric = Namespace + "_expired_credential_reclaim_rows_reclaimed_total"
)

Имена рядов второго уборщика. Собираются ИЗ КОНСТАНТЫ пространства имён.

View Source
const (
	// IdentitiesTotalMetric — личности, которые платформа видела за всё время.
	IdentitiesTotalMetric = "kaname_identities_total"

	// IdentityLedgerSamplesMetric — исходы фонового замера журнала.
	IdentityLedgerSamplesMetric = "kaname_identity_ledger_samples_total"

	// IdentityLedgerSampleOK — замер прочитал журнал.
	IdentityLedgerSampleOK = "ok"
	// IdentityLedgerSampleError — замер не прочитал журнал.
	IdentityLedgerSampleError = "error"
)
  • Предмет
  • Почему величина накопительная, а не мгновенная
  • Почему рядом ОБЯЗАТЕЛЕН счётчик снятых замеров

Витрина РОСТА ЧИСЛА ЛИЧНОСТЕЙ — страховка платформы, а не мера.

Предмет

Потолок на число аккаунтов одной личности обходится заведением личностей: регистрация самообслуживаемая и стоит подтверждённого адреса. Потолок темпа удорожает автоматизацию, но не ловит МЕДЛЕННОЕ накопление, а его не производила ни одна величина: рост числа личностей не наблюдался ничем.

Отказ по такому порогу пришёл бы СЛЕДУЮЩЕМУ честному человеку, а не тому, кто исчерпал полку, — поэтому сначала ВИДНО, и только потом, отдельным решением, отказ.

Почему величина накопительная, а не мгновенная

Мгновенный счёт личностей немонотонен: человек уходит, и величина падает. На падающем ряде РОСТ не определён — `increase()` молчит там, где рост и был, — а «личностей ноль» перестаёт быть утверждением о всей жизни платформы. Журнал (`kaname.identity_journal`) рядов не снимает никогда, поэтому здесь величина объявлена СЧЁТЧИКОМ: над ней осмысленны и `increase()`, и `rate()`.

Почему рядом ОБЯЗАТЕЛЕН счётчик снятых замеров

Величина читается фоновым замером и до первого успешного замера равна нулю. То есть «личностей за всё время ноль» и «замер не работает» дают на витрине ОДНУ И ТУ ЖЕ картину — ровно то различие, ради которого величина и заводилась. Отличает их только второй ряд: пока `samples_total{outcome="ok"}` растёт, ноль в первом ряду означает ноль. Отказ замера держится своей клеткой, потому что «замеров не было» и «замеры отказывают» требуют разных действий.

View Source
const (
	JWKSMirrorOutcomesMetric = "kaname_jwks_mirror_outcomes_total"

	// JWKSMirrorOutcomeServed — обращение к верхнему хопу дало набор ключей.
	JWKSMirrorOutcomeServed = "served"
	// JWKSMirrorOutcomeUnavailable — верхний хоп не ответил полезно (лечится временем).
	JWKSMirrorOutcomeUnavailable = "unavailable"
	// JWKSMirrorOutcomeMisconfigured — по адресу не тот эндпоинт (временем не лечится).
	JWKSMirrorOutcomeMisconfigured = "misconfigured"
)

JWKSMirrorOutcomesMetric / JWKSMirrorOutcomes — имя семейства и ЗАКРЫТЫЙ набор его клеток зеркала публичных ключей проверки.

View Source
const (
	// PresentedCredentialOutcomesMetric — исходы приёма предъявленного.
	PresentedCredentialOutcomesMetric = "kaname_presented_credential_outcomes_total"
	// PresentedCredentialOutcomeAccepted — предъявленное принято, вызывающий назван.
	PresentedCredentialOutcomeAccepted = "accepted"
	// PresentedCredentialOutcomeRefused — предъявленное отвергнуто.
	PresentedCredentialOutcomeRefused = "refused"
	// PresentedCredentialOutcomeUnavailable — ответить не смогли: реестр ключей
	// либо авторитет отзыва недоступны. ТРЕТИЙ исход.
	PresentedCredentialOutcomeUnavailable = "unavailable"
)
View Source
const (
	// ReadinessOutcomeReady — зависимость ответила, что здорова.
	ReadinessOutcomeReady = "ready"
	// ReadinessOutcomeUnready — зависимость не ответила либо ответила отказом.
	// Что именно чинить, говорит метка `dependency`.
	ReadinessOutcomeUnready = "unready"
)

Исходы оценки одной зависимости. Набор ЗАКРЫТ: носитель готовности сводит всякий отказ и всякий срок к «не готов», и третьего значения у него нет.

View Source
const (
	// RelationVerdictLabelArmGroundsMetric — прямых вердиктов, чьим основанием
	// оказалась МЕТОЧНАЯ ВЕТВЬ, по каждой оси отдельно.
	RelationVerdictLabelArmGroundsMetric = "kaname_relation_verdict_label_arm_grounds_total"
	// RelationVerdictEarlyStopsMetric — прямых вердиктов, ответивших ДО того,
	// как набор источников дочитан. Знаменатель к ряду выше.
	RelationVerdictEarlyStopsMetric = "kaname_relation_verdict_early_stopped_total"
	// RelationVerdictUndeclaredTypeDenialsMetric — отказов, данных по основанию
	// «типа объекта нет в словаре модели».
	RelationVerdictUndeclaredTypeDenialsMetric = "kaname_relation_verdict_undeclared_type_denials_total"
)

Имена семейств разбора оснований вердикта о доступе.

Семейств ТРИ, а не одно с общим лейблом, и это не оформление. Клетки одного семейства читаются как РАЗБИЕНИЕ, а здесь разбиения нет: вердикт бывает одновременно и основанным на меточной ветви, и прекращённым досрочно (см. `relverdict.Grounds`). Слитые в один ряд, они дали бы сумму, которая не равна ничему.

View Source
const (
	// RelationVerdictLabelAxisMirror — метки спрошены в зеркале чужих объектов.
	RelationVerdictLabelAxisMirror = "mirror"
	// RelationVerdictLabelAxisIAMDirect — метки спрошены в собственной таблице iam.
	RelationVerdictLabelAxisIAMDirect = "iam_direct"
)

Оси меточной ветви — ЗАКРЫТЫЙ набор клеток своего семейства.

Ось выбирается по типу объекта: у типов, чьи строки живут в собственных таблицах iam, метки спрашиваются там, у остальных — в зеркале. Разделено потому, что предикат равенства форм у осей РАЗНЫЙ, и число, названное для одной, доказывает половину и молчит про другую: ровно так дефект «ветвь отвечает на одной оси и молчит на второй» и дожил до находки.

View Source
const (
	RetentionPassesMetric       = Namespace + "_retention_passes_total"
	RetentionRowsRemovedMetric  = Namespace + "_retention_rows_removed_total"
	RetentionPassFailuresMetric = Namespace + "_retention_pass_failures_total"
)

Имена серий уборки. Собираются ИЗ КОНСТАНТЫ пространства имён, а не выписываются: имя серии — контракт с панелями и правилами тревог, и повторённое литералом оно не двигается вместе с константой (задача #2479).

View Source
const (
	// KeySetOutcomesMetric — исходы НАШЕЙ записи публикуемого набора.
	KeySetOutcomesMetric = "kaname_own_keyset_outcomes_total"
	// KeySetOutcomeServed — набор отдан целиком.
	KeySetOutcomeServed = "served"
	// KeySetOutcomeUnavailable — источник набора не ответил (лечится временем).
	KeySetOutcomeUnavailable = "unavailable"
	// KeySetOutcomeEmpty — ключей нет вовсе. Отдельная клетка, потому что
	// временем это НЕ лечится: нужен ключ, а не повтор.
	KeySetOutcomeEmpty = "empty"

	// IntrospectOutcomesMetric — исходы авторитета отзыва.
	IntrospectOutcomesMetric = "kaname_token_introspection_outcomes_total"
	// IntrospectOutcomeActive — токен признан действительным.
	IntrospectOutcomeActive = "active"
	// IntrospectOutcomeInactive — токен признан недействительным.
	IntrospectOutcomeInactive = "inactive"
	// IntrospectOutcomeUnavailable — ответить не смогли. ТРЕТИЙ исход, а не
	// оттенок второго: смешать его с «недействителен» значило бы сделать сбой
	// базы неотличимым от отзыва.
	IntrospectOutcomeUnavailable = "unavailable"

	// SigningKeyEventsMetric — события жизненного цикла подписного ключа.
	SigningKeyEventsMetric = "kaname_signing_key_events_total"
	// SigningKeyEventGenerated — ключ порождён и положен в набор.
	SigningKeyEventGenerated = "generated"
	// SigningKeyEventActivated — ключ стал подписывающим.
	SigningKeyEventActivated = "activated"
	// SigningKeyEventRetired — ключ выведен из подписи, но остаётся в наборе.
	SigningKeyEventRetired = "retired"
	// SigningKeyEventRemoved — отсрочка истекла, ключ снят из набора.
	SigningKeyEventRemoved = "removed"
	// SigningKeyEventCompromised — ключ объявлен утёкшим; отдельная клетка,
	// потому что это решение другой цены, чем вывод из ротации.
	SigningKeyEventCompromised = "compromised"
	// SigningKeyEventFailure — ключница не смогла выполнить действие.
	SigningKeyEventFailure = "failure"
)

Семейства своей чеканки токенов (задача #897) и ЗАКРЫТЫЕ наборы их клеток.

Величины выходят наружу по ТОЙ ЖЕ причине, по какой выходят величины зеркала: пока наружу выходят одни отказы, ноль в них отвечает сразу на два вопроса — «отказов не было» и «сюда никто не приходил», — а различие между ними и есть различие между работающим контролем и мёртвым.

View Source
const AuthnHookRequestsMetric = Namespace + "_authn_hook_requests_total"

AuthnHookRequestsMetric — исходы обращений поставщика личности к полосе хуков.

View Source
const BuildInfoUnstamped = "unstamped"

BuildInfoUnstamped — то, что метка версии сборки говорит, когда сборка её НЕ ПРОСТАВИЛА.

Пустая метка на витрине читается как «версии нет», а правдоподобное `dev` — как имя ветки; оба неотличимы от «величину не измеряли». Отдельное слово делает это состояние наблюдаемым: по нему пишется тревога, и оно не притворяется ответом.

View Source
const ClientTokenOutcomesMetric = Namespace + "_client_token_outcomes_total"

ClientTokenOutcomesMetric — исходы обращений за токеном по учётным данным клиента.

View Source
const InviteMailOutcomesMetric = "kaname_invite_mail_outcomes_total"

InviteMailOutcomesMetric — семейство исходов НАШЕЙ отправки письма приглашения.

Набор клеток ЗАКРЫТ и приходит из констант применителя (`clients.InviteMailOutcomes`), а не из ответа почтового узла: иначе кардинальность росла бы с трафиком, а имена клеток задавал бы чужой процесс.

View Source
const Namespace = "kaname"

Namespace — префикс имён ВСЕХ серий этого сервиса. Отдельная константа, а не литерал по месту: имя серии — контракт с панелями и правилами тревог, и собранное в двух местах оно разъедется на первом переименовании.

View Source
const ReadinessChecksMetric = Namespace + "_readiness_dependency_checks_total"

ReadinessChecksMetric — исходы оценки готовности по зависимостям.

Собирается ИЗ КОНСТАНТЫ пространства имён: имя ряда — контракт с панелями и правилами тревог, и повторённое литералом оно не двигается вместе с ней.

Variables

View Source
var (
	RegisterPostCommitSteps = []string{
		"forward_additive", "forward_guarded",
		"residual_read", "residual_withdraw",
	}
	RegisterPostCommitOutcomes = []string{"ok", "error"}
)

RegisterPostCommitSteps / RegisterPostCommitOutcomes — ЗАКРЫТЫЙ набор лейблов счётчика, объявленный ОДИН раз.

Значения приходят из констант use-case, никогда из данных запроса, поэтому кардинальность не растёт с трафиком. Набор объявлен здесь, а не пересказан в строке помощи: прежняя редакция перечисляла шаги прозой и уже разошлась с кодом — шагов стало шесть, а текст называл четыре. Перечень читают обе стороны: коллектор — чтобы инициализировать каждую клетку нулём, проба use-case — чтобы доказать, что его собственные константы этому набору равны в ОБЕ стороны (TestPostCommitStepConstantsMatchTheDeclaredLabelSet).

KeySetOutcomes / IntrospectOutcomes / SigningKeyEvents — закрытые наборы.

View Source
var IdentityLedgerSampleOutcomes = []string{
	IdentityLedgerSampleOK,
	IdentityLedgerSampleError,
}

IdentityLedgerSampleOutcomes — закрытый набор клеток исхода замера.

JWKSMirrorOutcomes — закрытый набор клеток семейства.

PresentedCredentialOutcomes — закрытый набор исходов.

ReadinessOutcomes — закрытый набор исходов.

RelationVerdictLabelAxes — закрытый набор клеток семейства оснований.

Functions

func DeclaredAuthzLanes

func DeclaredAuthzLanes() []string

DeclaredAuthzLanes — полосы, которые обязаны иметь производителя.

Отдаётся копия: перечень — контракт с панелями и правилами тревог, и вызывающий, получивший срез пакета, мог бы подвинуть его у всех сразу.

func OutboxScanObserver

func OutboxScanObserver(rec *OutboxRecorder, table string, logger *slog.Logger, msg string) func(error)

OutboxScanObserver — ЕДИНСТВЕННЫЙ производитель реакции на исход скана очереди. Композиционный корень провязывает им КАЖДЫЙ сканер сервиса.

Зачем один производитель, а не по замыканию на сайт (#2062)

Реакция на отказ скана — решение об наблюдаемости, и оно обязано быть одним для всех очередей сервиса, иначе это второе место об одном предмете: очередь, чей сайт забыли поправить, молчала бы на отказе, а её молчание было бы неотличимо от исправной работы. Сайты сверяются с этим производителем гейтом дерева (`outbox_scan_observer_wiring_test.go`), а не вниманием.

Types

type AuthnHooksRecorder added in v0.2.0

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

AuthnHooksRecorder — приёмник исходов полосы. Форма метода [HookServed] совпадает с портом полосы (`iamhooks.LaneObserver`), поэтому корень отдаёт приёмник ей напрямую.

func (*AuthnHooksRecorder) HookServed added in v0.2.0

func (a *AuthnHooksRecorder) HookServed(route, outcome string)

HookServed принимает исход ОДНОГО обращения.

type AuthzObservation

type AuthzObservation struct {
	// RPC — полоса, которой задан вопрос. Значения — ЗАКРЫТЫЙ словарь
	// [DeclaredAuthzLanes]; каждое обязано иметь производителя, и это держит
	// проба `TestEveryDeclaredLaneHasAProducer`. Прежняя редакция называла
	// значения в этом комментарии, и одно из двух названных не производилось
	// ничем: полоса присутствовала нулём и выглядела исправным наблюдением.
	RPC      string
	Allowed  bool    // decision allowed
	Err      bool    // backend/validation error (overrides allow/deny in the decision counter)
	Duration float64 // seconds
}

AuthzObservation is a single recorded authz Check outcome.

type BindingMaterializationRecorder

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

BindingMaterializationRecorder measures HOW BIG one binding's materialization is: how many objects its selectors matched, and how many tuples those objects produce.

WHY THIS IS MEASURED AT ALL. Access below the three cascading levels is materialized PER OBJECT, so a single grant expands into as many tuples as the selector matches objects, times the verbs the rule carries. Nothing counted that expansion, which made "this grant grew large" indistinguishable from "this grant is ordinary" — right up until the size stops fitting whatever the model can hold. And it does not stop there where the grant was issued: the expansion is written by the reconciler, so the wall is hit later, on somebody else's request, by a path that never mentions the binding.

WHY NO CEILING IS ENFORCED HERE. The size a binding may reach has not been measured, and a limit picked before the measurement would either refuse legitimate grants or sit above everything and refuse nothing — the second being the more expensive mistake, because it looks like a control. So: measure first, then decide the number. This recorder is the measurement; it refuses nothing.

WHY HISTOGRAMS AND NOT GAUGES. The question is a distribution — «how large do bindings get, and is the tail moving?» — not a last-value. A gauge would report whichever binding the reconciler happened to touch last, which answers nothing about the tail that matters. Buckets are exponential and reach four figures, so a binding an order of magnitude past the ordinary lands in its own bucket rather than in `+Inf` together with everything else large.

The label set is EMPTY on purpose: binding id, scope and role would be unbounded cardinality straight from tenant data. The distribution answers the question; naming the individual binding is the job of the trail the reconciler already writes.

func (*BindingMaterializationRecorder) ObserveBindingMaterialization

func (rec *BindingMaterializationRecorder) ObserveBindingMaterialization(objects, tuples int)

ObserveBindingMaterialization records the size of ONE binding's desired set.

A pass that matched nothing is recorded too, as a zero: «this binding materializes nothing» is a real and interesting state — a selector that stopped matching looks exactly like a binding nobody reconciles unless the empty pass is counted.

type BootstrapAdminRecorder

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

BootstrapAdminRecorder — исходы попыток посева доступа кластерного администратора, по одной попытке согласователя старта.

Зачем это существует

Согласователь посева объявлен незавершающим ПО КОНТРАКТУ: он повторяет попытку бессрочно, подъём не задерживает и на неисполнимом посеве печатает `Debug`. При рабочем уровне `Info` это не печатается вовсе — то есть «доступ администратора не выдан НИ РАЗУ за всю жизнь стенда» снаружи неотличимо от «выдан на первой же попытке».

Цена неразличимости здесь выше обычной: кластерный администратор — тот единственный, кто мог бы починить остальное. Стенд, поднявшийся без него, выглядит исправным ровно до первого действия, которое требует его прав.

Почему ряды заводятся ЗАРАНЕЕ, а не вместе с первым событием

Счётчик, у которого полоса появляется вместе с первым попаданием, на стенде без единой выдачи ряда `granted` НЕ ИМЕЕТ. Отсутствующий ряд снаружи неотличим от отсутствующего согласователя — то есть от той самой тишины, ради снятия которой счётчик заведён. Поэтому все полосы объявленного набора инициализируются нулём при регистрации: «ноль выдач» становится НАБЛЮДАЕМЫМ фактом, а не отсутствием факта.

Почему числитель считается вместе со знаменателем

Счётчик одних отказов не отличает «отказов не было» от «попыток не было». Полосы пропуска и полоса выдачи растут в одном счётчике, поэтому вопрос «работает ли посев вообще» отвечается их суммой, а вопрос «выдан ли доступ» — одной полосой.

Набор меток ЗАКРЫТ: `outcome` приходит из констант согласователя (seed.BootstrapOutcomes), никогда из запроса, поэтому кардинальность не растёт ни с трафиком, ни с числом арендаторов.

func (*BootstrapAdminRecorder) IncBootstrapAdminAttempt

func (rec *BootstrapAdminRecorder) IncBootstrapAdminAttempt(outcome string)

IncBootstrapAdminAttempt — исход одной попытки посева. Реализует порт seed.BootstrapAttemptObserver.

type CatalogSnapshotRecorder

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

CatalogSnapshotRecorder — исходы ОБНОВЛЕНИЯ снимка каталога модуля (kacho#1816, IAM-CT-2-03 / -04).

Зачем считать УСПЕХИ, а не только отказы

Счётчик одних отказов не отличает «отказов не было» от «обновлений не было вовсе»: и там и там ноль, и путь, умерший целиком, выглядел бы здоровее всех. Снимок при этом продолжает отвечать — прежним, сколь угодно старым множеством, — поэтому снаружи мёртвое обновление неотличимо от исправного. Знаменатель считается наравне с числителем.

Зачем считать отказы, если успехи уже считаются

Обратное тоже неверно: растущий ноль удавшихся не говорит, отказывало ли обновление или его не запускали. Одна величина отвечает на один вопрос; их два.

Чем это отличается от заполнения на СТАРТЕ

Заполнение снимка на старте здесь НЕ считается: оно идёт тем же чтением, что и страж паритета, и его отказ фатален — служба не поднимается. Зачти его — и «ноль обновлений за жизнь процесса» станет неотличимо от одного.

Набор меток ЗАКРЫТ: outcome приходит из констант пакета `catalog` (refreshed|failed), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*CatalogSnapshotRecorder) IncCatalogSnapshotRefresh

func (rec *CatalogSnapshotRecorder) IncCatalogSnapshotRefresh(outcome string)

IncCatalogSnapshotRefresh — исход одного обновления. Реализует порт catalog.RefreshObserver.

type CompensationRecorder

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

CompensationRecorder — счётчики компенсаций частично исполненной саги «зарегистрировать клиента у провайдера → закоммитить свою строку».

Состояние самой очереди (глубина, возраст головы, отравленные) здесь БОЛЬШЕ НЕ живёт: гейджи размечены лейблом `table` и годятся любой очереди сервиса, а очередей у сервиса НЕСКОЛЬКО — держать их в типе с именем «компенсация» значило бы объявлять областью действия одну. Числа здесь нет намеренно: оно росло молча (в день заведения комментарий говорил «три»), а перечень выводится предикатом — см. шапку OutboxRecorder в outbox_recorder.go. Они переехали в OutboxRecorder (outbox_recorder.go), который наблюдает все три.

ЗАЧЕМ СЧЁТЧИК, А НЕ ТОЛЬКО ЛОГ. Компенсация срабатывает редко и только на неудачном пути, поэтому «ноль компенсаций за всю жизнь» — нормальное состояние здорового облака И одновременно ровно то, что видно у мёртвого механизма: очередь не провязана, дренаж не поднят, applier никогда не звали. Отличить их можно только если считать ЗАПИСАННЫЕ намерения отдельно от ИСПОЛНЕННЫХ: серия, которой нет вовсе, отвечает на «провязано ли», а расхождение записанных и исполненных — на «доезжает ли». То же требование, что и «ноль доставленных строк за всю жизнь очереди обязано быть заметно».

На второй обязательный вопрос — «висит ли ЭТА строка дольше N» — отвечает возраст головы очереди из OutboxRecorder. Обе величины нужны, ни одна не заменяет другую: расхождение записанных и исполненных говорит «доезжает ли вообще», возраст — «застряла ли конкретная».

Набор меток ЗАКРЫТ: origin приходит из констант use-case'ов (sa_key|user_token|interactive_client), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*CompensationRecorder) IncCompensationApplied

func (rec *CompensationRecorder) IncCompensationApplied(origin string)

IncCompensationApplied — компенсация исполнена. Реализует clients.CompensationObserver.

func (*CompensationRecorder) IncCompensationEmitted

func (rec *CompensationRecorder) IncCompensationEmitted(origin, outcome string)

IncCompensationEmitted — намерение записано (outcome "ok") либо записать не удалось (outcome "error", путь деградировал в прямое снятие).

type ExpiredCredentialSweepRecorder added in v0.2.0

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

ExpiredCredentialSweepRecorder — приёмник величин второго уборщика. Форма метода [SweepObserved] совпадает с портом уборщика (`expiredcredsweep.Observer`), поэтому корень отдаёт приёмник ему напрямую.

func (*ExpiredCredentialSweepRecorder) SetEnabled added in v0.2.0

func (x *ExpiredCredentialSweepRecorder) SetEnabled(on bool)

SetEnabled объявляет состояние выключателя. Зовётся ОДИН раз при старте, до первого прогона: выключенный уборщик обязан говорить о себе величиной.

func (*ExpiredCredentialSweepRecorder) SweepObserved added in v0.2.0

func (x *ExpiredCredentialSweepRecorder) SweepObserved(outcome string, found, reclaimed int)

SweepObserved принимает исход ОДНОГО прогона вместе с двумя числами переписи.

type IdentityGrowthCounts

type IdentityGrowthCounts struct {
	// IdentitiesEverSeen — рядов в накопительном журнале.
	IdentitiesEverSeen int64
	// SamplesOK — успешных замеров за жизнь процесса.
	SamplesOK uint64
	// SamplesFailed — отказавших замеров за жизнь процесса.
	SamplesFailed uint64
}

IdentityGrowthCounts — то, что витрина читает у замерщика.

type InstrumentedAuthorizer

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

InstrumentedAuthorizer decorates a relationAuthorizer, recording the authz Check hot-path histogram + decision counter for every CheckRelation call. It is a pass-through: the wrapped result/error are returned verbatim, so the decision logic in the use-case is unaffected.

func NewInstrumentedAuthorizer

func NewInstrumentedAuthorizer(inner relationAuthorizer, reg *Registry) *InstrumentedAuthorizer

NewInstrumentedAuthorizer wraps inner with metrics recording onto reg. Wire it in the composition root so the InternalIAMService gate (the documented ≤30ms p95 hot path) is observed without the use-case knowing about prometheus.

func (*InstrumentedAuthorizer) CheckRelation

CheckRelation delegates to the wrapped authorizer and records the outcome.

type InstrumentedSubjectAuthorizer

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

InstrumentedSubjectAuthorizer — сквозной декоратор публичного решателя.

Наблюдает ДВЕ полосы (край и сужатель) и не трогает ничего больше: результат и ошибка возвращаются дословно, поэтому решение остаётся решением use-case. Остальные методы порта проходят насквозь без наблюдения — у них своя цена и свой предмет, и приписывать их к «проверкам» значило бы завысить число ровно так же, как раньше оно было занижено.

func NewInstrumentedSubjectAuthorizer

func NewInstrumentedSubjectAuthorizer(inner subjectAuthorizer, reg *Registry) *InstrumentedSubjectAuthorizer

NewInstrumentedSubjectAuthorizer оборачивает решатель публичной службы. Провязывается в композиционном корне — единственном месте, которое знает и решатель, и реестр величин.

func (*InstrumentedSubjectAuthorizer) BatchCheck

BatchCheck — полоса сужателя: одно наблюдение ДЛИТЕЛЬНОСТИ на вызов и по решению на КАЖДЫЙ вопрос пачки.

Разделение не бухгалтерское. Длительность принадлежит вызову: делить её на вопросы значило бы утверждать про каждый то, чего никто не измерял. Решения принадлежат вопросам: страница контрактно бывает до тысячи объектов, и счёт по вызовам занизил бы нагрузку от списочной выдачи в тысячу раз — то есть сделал бы её невидимой ровно там, где она наибольшая.

На ошибке пачки вопросы не считаются поштучно: их исход неизвестен, и приписать им «отказ» значило бы выдумать решения, которых не принимали. Считается ОДИН сбой — столько же, сколько было вызовов.

func (*InstrumentedSubjectAuthorizer) Check

Check — полоса КРАЯ: один вопрос, одно наблюдение.

func (*InstrumentedSubjectAuthorizer) ExpandRelations

func (*InstrumentedSubjectAuthorizer) ListSubjects

ListSubjects / ExpandRelations — сквозной проход БЕЗ наблюдения: у них своя цена и свой предмет.

type IntrospectCounts

type IntrospectCounts struct {
	Active      uint64
	Inactive    uint64
	Unavailable uint64
}

IntrospectCounts — величины авторитета отзыва.

type InviteActivationRecorder

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

InviteActivationRecorder — исходы активации приглашения на первом входе.

Зачем считать УСПЕХИ, а не только отказы

Счётчик одних отказов не отличает «отказов не было» от «активаций не было вовсе»: и там и там ноль. Путь, умерший целиком, выглядел бы здоровее всех (security.md §Hardening-инварианты п.8 — «ноль за всю жизнь» обязано быть заметно). Поэтому исходов три, и знаменатель считается наравне с числителем.

Почему гонка — отдельный исход, а не отказ

Строку, которую уже активировал конкурент, активировать нечем: это ожидаемый исход гонки первого входа. Смешав её с отказами, мы получили бы счётчик, который всегда не ноль, — то есть тревогу, которую перестают читать.

Набор меток ЗАКРЫТ: outcome приходит из констант use-case'а (activated|already_active|failed), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*InviteActivationRecorder) IncInviteActivation

func (rec *InviteActivationRecorder) IncInviteActivation(outcome string)

IncInviteActivation — исход одной попытки активации. Реализует порт user.ActivationObserver.

type InviteMailRecorder

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

InviteMailRecorder — счётчик исходов отправки письма приглашения.

ПОЧЕМУ СДАННЫЕ СЧИТАЮТСЯ НАРАВНЕ С ОТКАЗАМИ

Форма взята у зеркала набора ключей вместе с её обоснованием (приёмка ID-MAIL-1, Р25). Пока наружу выходят одни отказы, ноль в них отвечает сразу на два вопроса — «отказов не было» и «сюда никто не приходил», — а различие между ними и есть различие между работающим отправителем и мёртвым. Реальный прецедент проекта: очередь регистраций, у которой за всю жизнь не доехало ни одной строки, и это было ненаблюдаемо, потому что синхронный путь работал.

ПОЧЕМУ НАСТРОЙКА — СВОЯ КЛЕТКА, А НЕ ВИД ОТКАЗА

Недоступность узла проходит со временем; ответ не по протоколу почты по объявленному адресу не проходит никогда. Смешать их в одном ряду значит спрятать настройку под сбой и сделать постоянную неверную настройку штатным режимом (`security.md` §Hardening п. 8).

ГРАНИЦА УТВЕРЖДЕНИЯ НАЗВАНА ЗДЕСЬ, А НЕ ВЫВЕДЕНА ЧИТАТЕЛЕМ

Счётчик считает НАШИ отправки — письма приглашения. О письмах подтверждения адреса и восстановления доступа он не утверждает НИЧЕГО: их отправляет чужой процесс, чьи величины из нашего дерева не измеримы вовсе. Счётчик, названный «письма продукта» и считающий одну полосу, — то же «принято-и-проигнорировано», только заведённое собственными руками: читатель получил бы величину, которая говорит меньше, чем её имя. Поэтому имя семейства называет ПРИГЛАШЕНИЕ.

ЧТО ЗНАЧИТ «СДАНО»

Сдано РЕТРАНСЛЯТОРУ, а не получено адресатом. Дальше ретранслятора наш вердикт не идёт, и продукт нигде не утверждает доставку (Р15). Читать эту клетку как «доставлено» — ошибка, которую этот текст и закрывает.

ВТОРАЯ ОБЯЗАТЕЛЬНАЯ ВЕЛИЧИНА ЖИВЁТ НЕ ЗДЕСЬ

«Висит ли ЭТА строка дольше N» отвечает возраст головы очереди из OutboxRecorder (`outbox_oldest_pending_age_seconds{table}`), который ведёт периодический скан очереди. Обе величины нужны, ни одна не заменяет другую: расхождение исходов говорит «доезжает ли вообще», возраст — «застряла ли конкретная».

func (*InviteMailRecorder) IncInviteMailOutcome

func (rec *InviteMailRecorder) IncInviteMailOutcome(outcome string)

IncInviteMailOutcome — исход одной попытки отправки. Реализует clients.InviteMailObserver.

type JWKSMirrorCounts

type JWKSMirrorCounts struct {
	// Served — обращений к верхнему хопу, давших набор ключей.
	Served uint64
	// Unavailable — отказов, потому что верхний хоп не ответил полезно.
	Unavailable uint64
	// Misconfigured — отказов, потому что по адресу не набор ключей.
	Misconfigured uint64
}

JWKSMirrorCounts — три числа зеркала ключей, прочитанные у самого зеркала.

type LRORecorder

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

LRORecorder is the Prometheus implementation of operations.Recorder. Without it the operations default-registry keeps a NopRecorder, so the live LRO-worker signals (terminal-write retries/failures, the in-flight gauge, orphan recovery, reconcile runs/errors) — the exact signals for a stranding operation — are invisible on /metrics. Wired once at the composition root via ConfigureDefault.

func (*LRORecorder) IncOrphansRecovered

func (l *LRORecorder) IncOrphansRecovered(outcome string)

IncOrphansRecovered — operations.Recorder.

func (*LRORecorder) IncReconcileErrors

func (l *LRORecorder) IncReconcileErrors()

IncReconcileErrors — operations.Recorder.

func (*LRORecorder) IncReconcileRuns

func (l *LRORecorder) IncReconcileRuns()

IncReconcileRuns — operations.Recorder.

func (*LRORecorder) IncTerminalWriteFailures

func (l *LRORecorder) IncTerminalWriteFailures(opType string)

IncTerminalWriteFailures — operations.Recorder.

func (*LRORecorder) IncTerminalWriteRetries

func (l *LRORecorder) IncTerminalWriteRetries(opType string)

IncTerminalWriteRetries — operations.Recorder.

func (*LRORecorder) SetInflight

func (l *LRORecorder) SetInflight(n float64)

SetInflight — operations.Recorder.

type ListScanRecorder

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

ListScanRecorder снимает стоимость одной отданной страницы списка.

func (*ListScanRecorder) ObserveListScan

func (r *ListScanRecorder) ObserveListScan(_ context.Context, resource string, rows, checks int)

ObserveListScan принимает стоимость страницы: сколько строк рассмотрено всеми догрузками вместе и сколько раз спрошена модель прав.

type ModuleCatalogRecorder

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

ModuleCatalogRecorder — что сделало ПРИМЕНЕНИЕ каталога модуля (`modulecatalog.Applier`, задача продукта #1963).

Зачем метрика, если перепись уже печатается журналом

Перепись печатается на старте, и число оператору достаётся — если он этот журнал читает. В три часа ночи вопрос стоит иначе, чем при разборе: «каталог отстал» или «продукт сломан». Журнал отвечает после того, как найден нужный под, нужный старт и нужная строка; счётчик отвечает сразу.

Величин ТРИ, и одной не хватает ни на один из трёх вопросов

применений ноль    → применитель не ходил вовсе (знаменатель)
снятий ноль        → каталог не менялся
переселений ноль   → снятие БЫЛО, но ни одной роли не задело

Оставь одни переселения — и ноль будет означать сразу три разных состояния, из которых одно (мёртвый применитель) выглядело бы здоровее всех. Сложи снятия с переселениями — и потеряешь различие, ради которого перепись применителя и держит две популяции порознь: «право отобрано» (`role_verb`) и «правило перестало резолвиться» (`rule_ref`) — разные события для того, кто разбирает последствия.

Граница названа: отказ на пути СТАРТА этой метрикой не наблюдаем

Исход `failed` производителя имеет — его эмитит всякий отказ применения, — но на пути старта он неснимаем by construction: отказ применения есть отказ пуска, а слушатель метрик поднимается позже применителя, и процесс до него не доживает. Наблюдают такой отказ по отсутствию процесса и по журналу; серия нужна второму вызывающему глагола, который отказ переживает.

Наборы меток ЗАКРЫТЫ

Все три приходят из констант `modulecatalog` (applied|failed, resource|verb, rule_ref|role_verb), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*ModuleCatalogRecorder) AddCatalogResettledProjections

func (rec *ModuleCatalogRecorder) AddCatalogResettledProjections(population string, n int)

AddCatalogResettledProjections — переселённых проекций, по популяции. Реализует порт modulecatalog.Observer.

func (*ModuleCatalogRecorder) AddCatalogRetiredRows

func (rec *ModuleCatalogRecorder) AddCatalogRetiredRows(kind string, n int)

AddCatalogRetiredRows — снятых строк каталога, по виду. Реализует порт modulecatalog.Observer.

func (*ModuleCatalogRecorder) IncCatalogApply

func (rec *ModuleCatalogRecorder) IncCatalogApply(outcome string)

IncCatalogApply — исход применения одного манифеста. Реализует порт modulecatalog.Observer.

type OutboxRecorder

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

OutboxRecorder — состояние ВСЕХ очередей kaname, снимаемое периодическим сканом таблицы, а не логом дренажа.

Почему это отдельный тип, а не поля в CompensationRecorder

Гейджи ниже размечены лейблом `table` и потому годятся любой очереди сервиса — но жили они в типе с именем «компенсация», хотя компенсация — ОДНА из очередей сервиса, а не все они. Числа очередей здесь намеренно НЕТ: оно росло у этого комментария молча (в день заведения он говорил «три», и это перестало быть верным задолго до того, как кто-нибудь перечитал строку). Перечень ВЫВОДИТСЯ:

git grep -c 'CREATE TABLE kaname\..*outbox' -- services/iam/internal/migrations
git grep -l 'outboxmetrics.NewCollector' -- services/iam/cmd ':!*_test.go'

Первое даёт объявленные очереди, второе — наблюдаемые; расхождение между ними и есть вопрос, ради которого число вообще называют.

Второй предикат сужен до композиционного корня НЕ для краткости: без сужения он находит ЭТОТ ЖЕ комментарий и считает собственное объяснение за наблюдателя (замерено: 4 файла вместо 3). Проверка, считающая собственную прозу, — тот самый класс, ради которого предикат вообще пишут рядом с числом; поэтому он смотрит туда, где наблюдателей ПРОВЯЗЫВАЮТ, а не туда, где о них пишут. Имя типа читается как область его действия: следующий, кому понадобится наблюдать fga_outbox, увидел бы «это про компенсации» и завёл бы вторые гейджи с теми же именами — то есть уронил бы старт на duplicate-register ровно тогда, когда механизм наконец провязали целиком. Поэтому счётчики компенсации остались у компенсации, а состояние очередей вынесено сюда и берётся одним экземпляром на реестр.

Что здесь есть и почему именно это

Сводные серии по таблице:

kaname_outbox_backlog_depth{table}              — недоставленных строк
kaname_outbox_oldest_pending_age_seconds{table} — возраст головы очереди
kaname_outbox_poisoned_count{table}             — отравленных сейчас

Плюс разложение той же очереди ПО НАПРАВЛЕНИЮ:

kaname_outbox_backlog_depth_by_direction{table,direction}
kaname_outbox_oldest_pending_age_by_direction_seconds{table,direction}
kaname_outbox_delivered_total{table,direction}

Разложение нужно потому, что сводные серии на очереди с двумя половинами остаются здоровыми при полностью мёртвой второй: выдачи прав идут непрерывно — ресурсы создают всё время, — поэтому и глубина мала, и голова молода, что бы ни происходило со снятием. «Работает» и «ни разу не отозвано» дают ОДИНАКОВУЮ картину, и различает их только `delivered_total{direction="withdrawal"}`: единственная из четырёх величин, которая отличает «их не было» от «они не доезжают».

Набор меток ЗАКРЫТ: имена таблиц и направлений приходят из констант composition root, никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*OutboxRecorder) IncDeliveredByDirection

func (rec *OutboxRecorder) IncDeliveredByDirection(table, direction string)

IncDeliveredByDirection — ОДНА доставленная строка направления.

СЧЁТЧИК, инкрементируемый наблюдателем дренажа, а не измеритель, ставящийся сканом (#1714): величина объявлена «за всё время», и счёт по живым строкам совпадал с этим ровно до появления уборки доставленных строк.

func (*OutboxRecorder) IncPoisoned

func (rec *OutboxRecorder) IncPoisoned(table string)

IncPoisoned реализует outbox/metrics.Recorder.

func (*OutboxRecorder) InitDeliveredByDirection

func (rec *OutboxRecorder) InitDeliveredByDirection(table, direction string)

InitDeliveredByDirection заводит серию направления с нулём, не увеличивая её: дочерняя серия счётчика иначе появилась бы только после ПЕРВОЙ доставки, и «ни одного отзыва не доставлено» выражалось бы отсутствием ряда вместо нуля.

func (*OutboxRecorder) InitScanOutcomes

func (rec *OutboxRecorder) InitScanOutcomes(table string)

InitScanOutcomes заводит клетки исходов скана нулём, не увеличивая их.

Зовётся при ПРОВЯЗКЕ сканера, а не при первом его проходе: иначе ряды появились бы только после первого исхода, и «сканер не сделал ни одного прохода» снова выражалось бы отсутствием ряда — тем самым, из-за чего эти счётчики и заведены.

func (*OutboxRecorder) ObserveScanFailure

func (rec *OutboxRecorder) ObserveScanFailure(table string)

ObserveScanFailure — ОДИН отказавший проход сканера.

func (*OutboxRecorder) SetBacklogDepth

func (rec *OutboxRecorder) SetBacklogDepth(table string, depth float64)

SetBacklogDepth реализует outbox/metrics.Recorder.

Здесь же считается УДАЧНЫЙ скан, и это не побочный эффект, а построение: сканер зовёт этот метод ровно один раз за проход и ровно тогда, когда значения получены (pkg/outbox/metrics, Collector.Scan — три Set-вызова после единственного чтения). Отдельный явный вызов «скан удался» был бы вторым местом об одном предмете и разошёлся бы с измерителями молча; счётчик, двигающийся ВМЕСТЕ со значением, разойтись с ним не может.

func (*OutboxRecorder) SetBacklogDepthByDirection

func (rec *OutboxRecorder) SetBacklogDepthByDirection(table, direction string, depth float64)

SetBacklogDepthByDirection реализует outbox/metrics.DirectionRecorder.

func (*OutboxRecorder) SetOldestPendingAgeByDirection

func (rec *OutboxRecorder) SetOldestPendingAgeByDirection(table, direction string, age float64)

SetOldestPendingAgeByDirection реализует outbox/metrics.DirectionRecorder.

func (*OutboxRecorder) SetOldestPendingAgeSeconds

func (rec *OutboxRecorder) SetOldestPendingAgeSeconds(table string, age float64)

SetOldestPendingAgeSeconds реализует outbox/metrics.Recorder.

func (*OutboxRecorder) SetPoisonedCount

func (rec *OutboxRecorder) SetPoisonedCount(table string, count float64)

SetPoisonedCount реализует outbox/metrics.Recorder.

type OwnKeySetCounts

type OwnKeySetCounts struct {
	Served      uint64
	Unavailable uint64
	Empty       uint64
}

OwnKeySetCounts — величины нашей записи набора, прочитанные у публикатора.

type PresentedCredentialCounts

type PresentedCredentialCounts struct {
	Accepted    uint64
	Refused     uint64
	Unavailable uint64
}

PresentedCredentialCounts — величины читателя предъявленного.

type ReadinessRecorder added in v0.2.0

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

ReadinessRecorder — приёмник исхода готовности. Форма метода [Observe] совпадает с объявленной опцией носителя (`health.WithResultObserver`), поэтому корень отдаёт приёмник ей напрямую и своего переходника не заводит.

func (*ReadinessRecorder) Observe added in v0.2.0

func (o *ReadinessRecorder) Observe(dependency string, up bool)

Observe принимает исход ОДНОЙ зависимости за одну оценку готовности.

type RegisterPostCommitRecorder

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

RegisterPostCommitRecorder counts the POST-COMMIT accelerators of the cross-service registration path — the forward reconcile that materializes the owner's per-object access, and the direct apply of the containment pointer.

WHY A COUNTER AND NOT JUST THE LOG LINE. Both steps are best-effort by design: they front a durable queue, so a failure costs latency and never the change itself. That is exactly what makes a permanently broken one invisible — one WARN per occurrence, a product that keeps working more slowly, and nothing that says so. A control that has never refused in its whole life is indistinguishable from one that was never reached unless RUNS are counted alongside OUTCOMES (security.md §Hardening-инвариант 8, the same requirement as "zero rows ever delivered" for a queue).

WHY EVERY CELL STARTS AT ZERO RATHER THAN APPEARING ON FIRST USE. An absent series answered two different questions at once — "this step never ran" and "this collector is not wired at all" — and telling those apart is the whole point. Measured on a stand: the ADDITIVE forward entry fired ZERO times across 367 registrations, and that was establishable only because its sibling had a series; on its own the proven entry looked like code that does not exist. With the closed label set initialised at construction, presence answers "is it wired?" and the VALUE answers "was it ever reached?" — so `forward_additive == 0` beside a growing `forward_guarded` is a FINDING stated by the metric, not a silence somebody has to interpret.

The `step` label additionally exposes WHICH materialization path each registration took. That is not decoration: `forward_additive` is the fast path a redelivery is supposed to stay on, and `forward_guarded` is the one that may escalate to the FULL EXCLUSIVE recompute every object of an account queues behind. A regression that pushes registrations back onto the guarded path shows up as a shift between two series, rather than as a latency somebody has to notice.

The label set is CLOSED and declared once above — the count is not repeated in prose here, because the prose already drifted from the code once.

func (*RegisterPostCommitRecorder) ObserveRegisterPostCommit

func (rec *RegisterPostCommitRecorder) ObserveRegisterPostCommit(step, outcome string)

ObserveRegisterPostCommit records one post-commit step outcome.

type Registry

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

Registry owns a private *prometheus.Registry and the kaname collectors. It is created once in the composition root and shared by the metrics HTTP listener, the authz decorator and the gRPC interceptors. A private registry (not the global default) keeps tests hermetic and avoids duplicate-register panics across server restarts in the same process (integration tests).

func NewRegistry

func NewRegistry() *Registry

NewRegistry constructs the registry, registers the Go + process runtime collectors and the kaname collectors.

func (*Registry) AuthnHooksRecorder added in v0.2.0

func (r *Registry) AuthnHooksRecorder(routes, outcomes []string) *AuthnHooksRecorder

AuthnHooksRecorder заводит приёмник и СЕЙЧАС ЖЕ клетку на каждую пару (маршрут × исход).

Пустой любой из наборов — ОТКАЗ: клеток не будет ни одной, и молчащая витрина неотличима от полосы, к которой никто не приходил. Это ровно то неразличение, ради устранения которого приёмник заведён.

Повторный вызов возвращает ТОГО ЖЕ приёмника: полоса собирается в прогоне не единожды, а второй экземпляр уронил бы старт на повторной регистрации.

func (*Registry) CompensationRecorder

func (r *Registry) CompensationRecorder() *CompensationRecorder

CompensationRecorder возвращает ЕДИНСТВЕННЫЙ экземпляр коллекторов компенсации этого реестра, создавая его при первом обращении.

Потребителей двое и собираются они в разных местах: writer намерений (внутри buildServices) и применитель компенсаций (в runServe). Два независимых вызова NewCompensationRecorder уронили бы старт на duplicate-register — и уронили бы именно тогда, когда механизм наконец провязали целиком.

func (*Registry) ExpiredCredentialSweepRecorder added in v0.2.0

func (r *Registry) ExpiredCredentialSweepRecorder(outcomes []string) *ExpiredCredentialSweepRecorder

ExpiredCredentialSweepRecorder заводит приёмник и СЕЙЧАС ЖЕ клетку на каждый объявленный исход.

Набор исходов приходит доводом из объявляющего пакета: второй перечень разошёлся бы с производителем молча. Пустой — ОТКАЗ: клеток не будет ни одной, и «отказов не было» останется неотличимым от «клетки нет».

Ряд включённости заводится нулём и здесь: он обязан существовать ДО первого прогона, потому что у выключенного уборщика прогонов не будет вовсе — а отсутствие ряда читается как отсутствие уборщика.

func (*Registry) Handler

func (r *Registry) Handler() http.Handler

Handler returns the promhttp handler exposing this registry. Mount it on the dedicated internal metrics listener only.

func (*Registry) InviteActivationRecorder

func (r *Registry) InviteActivationRecorder() *InviteActivationRecorder

InviteActivationRecorder возвращает ЕДИНСТВЕННЫЙ экземпляр счётчика этого реестра, создавая его при первом обращении.

Потребителей два и собираются они в разных местах: gRPC-путь (InternalUserService, buildServices) и ЖИВОЙ путь первого входа (провизион-хук, buildHooksMux). Второй из них и есть тот, ради которого счётчик заводится, — пропустить его значило бы получить метрику, всегда равную нулю на настоящем трафике.

func (*Registry) InviteMailRecorder

func (r *Registry) InviteMailRecorder() *InviteMailRecorder

InviteMailRecorder возвращает ЕДИНСТВЕННЫЙ экземпляр счётчика этого реестра, создавая его при первом обращении.

Единственность нужна по той же причине, что и у соседа: два независимых вызова конструктора уронили бы старт на повторной регистрации — и уронили бы именно тогда, когда механизм наконец провязали целиком.

func (*Registry) NewBindingMaterializationRecorder

func (r *Registry) NewBindingMaterializationRecorder() *BindingMaterializationRecorder

NewBindingMaterializationRecorder registers the collectors in this registry and returns the adapter the reconciler consumes through its narrow port. Call once at boot.

func (*Registry) NewBootstrapAdminRecorder

func (r *Registry) NewBootstrapAdminRecorder() *BootstrapAdminRecorder

NewBootstrapAdminRecorder регистрирует коллектор в этом реестре и заводит ряд по каждой объявленной полосе. Звать один раз на старте.

func (*Registry) NewCatalogSnapshotRecorder

func (r *Registry) NewCatalogSnapshotRecorder() *CatalogSnapshotRecorder

NewCatalogSnapshotRecorder регистрирует коллектор в этом реестре. Звать один раз на старте.

func (*Registry) NewClientTokenOutcomeCollector added in v0.2.0

func (r *Registry) NewClientTokenOutcomeCollector(outcomes []string, read func() map[string]uint64)

NewClientTokenOutcomeCollector регистрирует читателя переписи.

Источник обязателен: вечный ноль неотличим от непровязанного читателя, а именно «читатель не провязан» и есть то состояние, ради обнаружения которого на этот график смотрят. Пустой набор исходов — тот же отказ с другой стороны: на витрину не вышло бы ни одного ряда.

func (*Registry) NewCompensationRecorder

func (r *Registry) NewCompensationRecorder() *CompensationRecorder

NewCompensationRecorder регистрирует коллекторы в этом реестре. Звать один раз на старте.

func (*Registry) NewIdentityGrowthCollector

func (r *Registry) NewIdentityGrowthCollector(read func() IdentityGrowthCounts)

NewIdentityGrowthCollector регистрирует читателя величин роста числа личностей.

nil-источник — ОТКАЗ, по той же причине, что и у соседних коллекторов: вечный ноль выглядит работающим наблюдением и утверждает о платформе неправду — «личностей не появлялось», — тогда как на деле замерщика просто забыли подключить. Отказ при сборке дешевле молчаливой лжи на витрине.

func (*Registry) NewInviteActivationRecorder

func (r *Registry) NewInviteActivationRecorder() *InviteActivationRecorder

NewInviteActivationRecorder регистрирует коллектор в этом реестре. Звать один раз на старте.

func (*Registry) NewInviteMailRecorder

func (r *Registry) NewInviteMailRecorder() *InviteMailRecorder

NewInviteMailRecorder регистрирует счётчик в этом реестре.

Все клетки закрытого набора инициализируются нулём СРАЗУ: ряд, которого нет вовсе, отвечает на «провязан ли механизм», и без предварительной инициализации «отказов не было» неотличимо от «отказ ни разу не классифицировали».

func (*Registry) NewJWKSMirrorCollector

func (r *Registry) NewJWKSMirrorCollector(read func() JWKSMirrorCounts)

NewJWKSMirrorCollector регистрирует читателя счётчиков зеркала ключей.

ПОЧЕМУ ВЫДАННЫЕ СЧИТАЮТСЯ НАРАВНЕ С ОТКАЗАМИ. Зеркало отказывает закрыто: не ответил верхний хоп — плоскость данных отвергает каждый токен. Пока наружу выходят одни отказы, ноль в них отвечает сразу на два вопроса — «отказов не было» и «сюда никто не приходил», — а различие между ними и есть различие между работающим зеркалом и мёртвым. Отдельно держатся и две ПРИЧИНЫ отказа: «не ответил» проходит со временем, «по адресу не то» — никогда, и смешивать их в одном ряду значит прятать настройку под сбой.

nil-источник — ОТКАЗ по той же причине, что и у соседнего коллектора: вечный ноль выглядит как работающее наблюдение и утверждает неправду о зеркале, которое просто забыли подключить.

func (*Registry) NewLRORecorder

func (r *Registry) NewLRORecorder() *LRORecorder

NewLRORecorder registers the LRO collectors in this registry and returns the operations.Recorder adapter. Call once at boot.

func (*Registry) NewListScanRecorder

func (r *Registry) NewListScanRecorder() *ListScanRecorder

NewListScanRecorder заводит съём и регистрирует его в реестре сервиса.

func (*Registry) NewModuleCatalogRecorder

func (r *Registry) NewModuleCatalogRecorder() *ModuleCatalogRecorder

NewModuleCatalogRecorder регистрирует коллекторы в этом реестре. Звать один раз на старте.

func (*Registry) NewOwnKeySetCollector

func (r *Registry) NewOwnKeySetCollector(read func() OwnKeySetCounts)

NewOwnKeySetCollector регистрирует читателя величин нашей записи набора.

nil-источник — ОТКАЗ: вечный ноль выглядит как работающее наблюдение и утверждает неправду о подсистеме, которую просто забыли подключить.

func (*Registry) NewPresentedCredentialCollector

func (r *Registry) NewPresentedCredentialCollector(read func() PresentedCredentialCounts)

NewPresentedCredentialCollector регистрирует читателя величин.

Источник обязателен: вечный ноль неотличим от непровязанного читателя, а именно «читатель не провязан» и есть то состояние, ради обнаружения которого на этот график смотрят.

func (*Registry) NewRegisterPostCommitRecorder

func (r *Registry) NewRegisterPostCommitRecorder() *RegisterPostCommitRecorder

NewRegisterPostCommitRecorder registers the collector in this registry and returns the adapter the register use-case consumes through its narrow port. Call once at boot.

func (*Registry) NewRegistryTokenCredentialKindRecorder

func (r *Registry) NewRegistryTokenCredentialKindRecorder() *RegistryTokenCredentialKindRecorder

NewRegistryTokenCredentialKindRecorder регистрирует коллектор в этом реестре. Звать один раз на старте.

func (*Registry) NewRelationVerdictGroundsCollector

func (r *Registry) NewRelationVerdictGroundsCollector(read func() RelationVerdictGrounds)

NewRelationVerdictGroundsCollector регистрирует читателя величин, которые источник вердикта копит на живом пути решения о доступе.

ПОЧЕМУ ЭТИ ВЕЛИЧИНЫ ВООБЩЕ НАДО ПРЕДЪЯВЛЯТЬ

Копились они с тех пор, когда их читало теневое сравнение форм: тогда «ноль оснований меточной ветви» надо было отличать от «ветвь ни разу не спросили», иначе «расхождений с движком нет» ничего не означало. Сравнения больше нет — оно снято вместе с внешним движком прав, — но предмет не отпал, а стал шире: форма из теневой сделалась ЕДИНСТВЕННЫМ источником вердикта, и те же числа считаются теперь на живом пути решения о доступе.

Каждое из них закрывает свой ТИХИЙ отказ, у которого нет иного признака:

  • меточная ветвь перестала спрашиваться на одной из осей — выдачи по меткам на этой оси не действуют, и арендатор видит «прав не выдали», а не ошибку;
  • отказы по неизвестному типу растут — значит тип называют с опечаткой, доступ пропадает, и выглядит это ровно так же.

Оба состояния снаружи неотличимы от исправной работы. Пока величины не выходят наружу, их ноль отвечает сразу и «события не было», и «код, который его считает, не исполнялся» (`security.md` §Hardening-инвариант 8).

ЧТО ЭТИ РЯДЫ НЕ ЗАКРЫВАЮТ — названо, а не скрыто

Знаменатель здесь ЧАСТИЧНЫЙ. Ранний выход прекращает чтение на первом безусловном основании, поэтому ненулевые ранние выходы доказывают, что путь исполнялся, а вот сочетание «нулевые основания И нулевые ранние выходы» по-прежнему означает либо «ветвь спрашивали и она молчала», либо «сюда не приходили вовсе». Полное число потребовало бы второго обращения к базе на КАЖДОМ вопросе; неопределённость названа прямо, потому что скрыть её было бы хуже, чем назвать.

nil-источник — ОТКАЗ по той же причине, что и у соседних коллекторов: вечный ноль выглядит как работающее наблюдение и утверждает неправду о разборе, который просто забыли подключить.

func (*Registry) NewRetentionCollector

func (r *Registry) NewRetentionCollector(read func() RetentionCounts)

NewRetentionCollector заводит съём величин уборки.

Величина обязана иметь ЧИТАТЕЛЯ: накопитель, чьё число наружу не выходит, считает в никуда, и его ноль не утверждает ничего.

func (*Registry) NewRoleVerbReseedRecorder

func (r *Registry) NewRoleVerbReseedRecorder() *RoleVerbReseedRecorder

NewRoleVerbReseedRecorder регистрирует коллектор в этом реестре. Звать один раз на старте.

func (*Registry) NewRuleRefReseedRecorder

func (r *Registry) NewRuleRefReseedRecorder() *RuleRefReseedRecorder

NewRuleRefReseedRecorder регистрирует коллектор в этом реестре. Звать один раз на старте.

func (*Registry) NewSigningKeyCollector

func (r *Registry) NewSigningKeyCollector(read func() SigningKeyCounts)

NewSigningKeyCollector регистрирует читателя величин ключницы.

func (*Registry) NewTokenIntrospectionCollector

func (r *Registry) NewTokenIntrospectionCollector(read func() IntrospectCounts)

NewTokenIntrospectionCollector регистрирует читателя величин авторитета отзыва.

func (*Registry) ObserveAuthz

func (r *Registry) ObserveAuthz(o AuthzObservation)

ObserveAuthz records one authz Check outcome: the duration histogram (labelled rpc + allowed) plus the decision counter (allow|deny|error).

func (*Registry) ObserveAuthzDecision

func (r *Registry) ObserveAuthzDecision(rpc string, allowed, failed bool)

ObserveAuthzDecision записывает ТОЛЬКО исход одного вопроса.

func (*Registry) ObserveAuthzDuration

func (r *Registry) ObserveAuthzDuration(rpc string, allowed bool, seconds float64)

ObserveAuthzDuration записывает ТОЛЬКО длительность одного вызова.

Отделено от решения потому, что у пачки вопросов эти две величины считаются по-разному: длительность принадлежит ВЫЗОВУ (делить её на вопросы значило бы утверждать про каждый то, чего никто не измерял), а решения — ВОПРОСАМ (страница контрактно бывает до тысячи объектов, и счёт по вызовам занизил бы нагрузку от списочной выдачи в тысячу раз).

func (*Registry) ObserveAuthzStoreAttempt

func (r *Registry) ObserveAuthzStoreAttempt(op, outcome string, reused bool)

ObserveAuthzStoreAttempt records ONE attempt against the authorization store.

Принимает плоские значения, а не тип адаптера хранилища: иначе один адаптер импортировал бы другой ради метки счётчика (dependency-rule). Перевод делает композиционный корень — единственное место, которое знает обоих.

func (*Registry) OutboxRecorder

func (r *Registry) OutboxRecorder() *OutboxRecorder

OutboxRecorder возвращает ЕДИНСТВЕННЫЙ экземпляр коллекторов состояния очередей этого реестра, создавая его при первом обращении.

Потребителей столько же, сколько очередей, и собираются они в разных местах композиционного корня. Два независимых конструктора уронили бы старт на duplicate-register — поэтому экземпляр один и берётся отсюда.

func (*Registry) ReadinessRecorder added in v0.2.0

func (r *Registry) ReadinessRecorder(dependencies []string) *ReadinessRecorder

ReadinessRecorder заводит приёмник и СЕЙЧАС ЖЕ клетки по названным зависимостям.

Набор зависимостей приходит доводом, а не выписывается здесь: его знает композиционный корень — он один строит чекеры. Выведенный из того же среза, которым построен носитель, он не может от него отстать: добавление чекера заводит ряды само.

Пустой набор — ОТКАЗ, а не тихая регистрация: витрина без рядов выглядит точно так же, как витрина при исправной готовности, и это ровно то неразличение, ради которого приёмник заведён.

Повторный вызов возвращает ТОГО ЖЕ приёмника и досеивает названные клетки: носитель готовности собирается в прогоне не единожды, а второй экземпляр разложил бы ряды по двум семействам с одним именем и уронил бы старт на повторной регистрации.

func (*Registry) RegisterBuildInfo

func (r *Registry) RegisterBuildInfo(version, revision string)

RegisterBuildInfo вешает на витрину постоянный ряд `kaname_build_info` с метками версии и ревизии работающего двоичного файла.

Зачем

Первый вопрос дежурного — «какая версия у меня работает». До этого ряда служба не отвечала на него НИ ОДНОЙ величиной, и это смыкается с готовностью: её собственная проверка умеет отказать словами «образ не может обслужить эту схему», то есть назвать РАСХОЖДЕНИЕ, не дав ни одной из двух сравниваемых величин. Служба поставляется отдельно от платформы, где такие вопросы закрывает чужой инвентарь, — спросить больше некого.

Откуда приходят значения

Со ШТАМПА СБОРКИ, а не с ручки профиля: величину, которую объявляет оператор, он вправе объявить любой, и тогда ряд отвечает не о том, что исполняется, а о том, что о нём написали. Штамп ставится там же, где клеймо образа, из ОДНОГО объявления (см. `provenance.mk` и `Dockerfile` службы), поэтому строка на витрине и строка на образе совпадают дословно.

Почему `version` и `revision`, а не `version` и `commit`

Под этими именами величины уже стоят клеймом образа (`org.opencontainers.image.version` / `.revision`) и лежат файлом внутри него. Совпадение имён несущее: оператор сверяет витрину с образом без пересчёта, а расхождение читается как расхождение, а не как два способа назвать одно.

Почему gauge с постоянной единицей, а не метка на прочих рядах

Метка версии, приклеенная к рабочим рядам, меняет их размерность при каждом раскате: суммы и доли по ним рвутся на границе выкатки, а `rate()` считает прибавку от нуля. Отдельный ряд с постоянным значением этой цены не несёт и склеивается с любым другим по времени.

Звать ОДИН раз на старте, из композиционного корня: повторный вызов уронит процесс на повторной регистрации — и это верно, потому что двух разных версий у одного двоичного файла не бывает.

func (*Registry) RegisterPoolStats

func (r *Registry) RegisterPoolStats(poolName string, pool *pgxpool.Pool)

RegisterPoolStats подключает к этому реестру состояние ОДНОГО пула соединений под именем `poolName` (`primary`, `replica`).

Зачем

Насыщение пула до этого не наблюдалось ничем: снаружи «запрос ждал свободного соединения» и «запрос сам по себе медленный» дают одну и ту же растянутую задержку RPC, а лечатся противоположным. Разбор величин и того, какая пара из них различает эти два случая, — у самого коллектора (`pkg/db`); здесь только провязка.

Почему НЕ MustRegister

Повторная регистрация того же пула — ошибка сборки, а не причина ронять процесс: наблюдение, убивающее сервис, который оно наблюдает, хуже отсутствующего. Повтор поэтому проглатывается, а серии остаются от первой регистрации — они те же самые.

Всякий ДРУГОЙ отказ регистрации означает несогласованное объявление (то же имя с другой размерностью), и он остаётся паникой: пропустить его молча значило бы поднять процесс с семейством, которого на /metrics не будет никогда.

`pool == nil` допустим: коллектор просто не отдаёт ни одной серии — см. его разбор. Ветка «а есть ли пул» поэтому не нужна вызывающему.

func (*Registry) Registerer

func (r *Registry) Registerer() prometheus.Registerer

Registerer отдаёт реестр этого сервиса как ПРИЁМНИК регистрации.

Зачем окно наружу, если рядом есть именованные

Соседние окна (`RegisterAuthzCache`, `RegisterListNarrow`, `RegisterPoolStats`) принимают ЧИТАТЕЛЯ готовых величин: они существуют, чтобы этот пакет не импортировал доменные. Здесь предмет обратный — носителю входящего пути надо ЗАВЕСТИ своё семейство серий, а не отдать читателя, и заводит он его СВОИМИ руками. Тогда несогласованное объявление (то же имя с другой размерностью) становится отказом подъёма, а не молчаливой пропажей семейства со скрейпа.

Разбор решения — у поля, ради которого окно открыто: `pkg/servicecontract.Spec.Metrics` (отказ старта О13). Здесь он не пересказывается: два места об одном предмете расходятся на первом уточнении.

Отдаётся именно `Registerer`, а не сам реестр: собирать величины через это окно нельзя, и сузить его до одной операции дешевле, чем потом выяснять, кто ещё им воспользовался.

func (*Registry) RegistryTokenCredentialKindRecorder

func (r *Registry) RegistryTokenCredentialKindRecorder() *RegistryTokenCredentialKindRecorder

RegistryTokenCredentialKindRecorder возвращает ЕДИНСТВЕННЫЙ экземпляр счётчика этого реестра, создавая его при первом обращении.

Экземпляр один, потому что prometheus.MustRegister падает на повторной регистрации, а полоса собирается в композиционном корне, где второй вызов стоит одной строки невнимательности и роняет старт процесса целиком.

type RegistryTokenCredentialKindRecorder

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

RegistryTokenCredentialKindRecorder — исходы полос предъявленного удостоверения докерной полосы `/iam/token`.

Зачем счётчик, а не строка журнала

Ломающее изменение #1143 сняло приём ключевого материала в поле пароля. Причина отказа уходила в предупреждение журнала — и на вопросы, которые оператор задаёт во время перехода, предупреждение не отвечает, потому что оба вопроса КОЛИЧЕСТВЕННЫЕ:

«скольких я сломаю (сломал), если окно закрыто» → key_material_refused;
«можно ли уже закрывать окно»                   → key_material_accepted_in_window.

По строкам журнала это считают глазами и уже после того, как отказ истолкован; на прогоне из тысяч входов найти нужные строки нельзя.

Почему исходов ТРИ, а не один

Счётчик одних отказов не отличает «отказов не было» от «входов не было вовсе»: и там и там ноль, и полоса, умершая целиком, выглядела бы здоровее всех (security.md §Hardening-инварианты п.8 — «ноль за всю жизнь» обязано быть заметно). Поэтому знаменатель — basic_accepted — считается наравне с числителями.

Принятое ОКНОМ считается отдельно от принятого штатно, потому что это разные вопросы: первое — предикат закрытия окна, второе — признак жизни полосы. Слив их, оператор получил бы величину, не отвечающую ни на один из них.

Кардинальность

Набор меток ЗАКРЫТ: outcome приходит из констант use-case'а (registry_token.Outcome*), никогда из запроса, — поэтому кардинальность не растёт с трафиком. Ни имени предъявителя, ни идентификатора удостоверения здесь нет: в метрику не уходит то, чего не отдают клиенту.

func (*RegistryTokenCredentialKindRecorder) ObserveCredentialKind

func (rec *RegistryTokenCredentialKindRecorder) ObserveCredentialKind(outcome string)

ObserveCredentialKind — один исход полосы. Реализует порт registry_token.CredentialKindObserver.

type RelationVerdictGrounds

type RelationVerdictGrounds struct {
	// LabelArmMirror / LabelArmIAMDirect — оснований, данных меточной ветвью,
	// по осям.
	LabelArmMirror    int64
	LabelArmIAMDirect int64
	// EarlyStops — вердиктов, ответивших до того, как набор источников дочитан.
	EarlyStops int64
	// UndeclaredTypeDenials — отказов по основанию «тип не объявлен моделью».
	UndeclaredTypeDenials int64
}

RelationVerdictGrounds — снимок разбора оснований, прочитанный у самого источника вердикта.

Все четыре числа снимаются с ОДНОГО носителя и одним сбором: разнесённые по двум коллекторам, они читались бы в разные моменты, и знаменатель относился бы не к тому числителю.

type RetentionCounts

type RetentionCounts struct {
	// Passes — сколько проходов исполнено за жизнь процесса.
	Passes int64
	// Removed — снято строк по каждому предмету.
	Removed map[string]int64
	// Failures — отказов прохода по каждому предмету.
	Failures map[string]int64
}

RetentionCounts — снимок накопленного уборщиком.

Зеркалит `retention.Counts`; отдельный тип здесь потому, что слой наблюдаемости не импортирует прикладной слой.

type RoleVerbReseedRecorder

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

RoleVerbReseedRecorder — исходы пересчёта проекции «роль → тип объекта × глагол» на старте, по одной системной роли.

Зачем считать УСПЕХИ, а не только отказы

Счётчик одних отказов не отличает «отказов не было» от «пересчёта не было вовсе»: и там и там ноль, и путь, умерший целиком, выглядел бы здоровее всех. Знаменатель считается наравне с числителем.

Почему это отдельная полоса, а не часть досева выдач

Проекция — то, из чего цепь вердикта собирает ответ «разрешено ли действие». Пока её отказ приезжал обёрнутым в чужую ошибку досева, он печатался уровнем чужой полосы, и различить «база не ответила» от «механизм не работает» было нечем. Метрика даёт ту же различимость машинно: постоянный ненулевой `failed` при нулевом `reseeded` — механизм, а не моргание базы.

Набор меток ЗАКРЫТ: outcome приходит из констант досева (reseeded|failed), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*RoleVerbReseedRecorder) IncRoleVerbReseed

func (rec *RoleVerbReseedRecorder) IncRoleVerbReseed(outcome string)

IncRoleVerbReseed — исход пересчёта одной роли. Реализует порт seed.RoleVerbReseedObserver.

type RuleRefReseedRecorder

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

RuleRefReseedRecorder — исходы пересчёта проекции ОБЪЯВЛЕННЫХ СЕГМЕНТОВ правила (`kaname.role_rule_ref`) на старте, по одной системной роли.

Зачем считать УСПЕХИ, а не только отказы

Счётчик одних отказов не отличает «отказов не было» от «пересчёта не было вовсе»: и там и там ноль, и путь, умерший целиком, выглядел бы здоровее всех. Знаменатель считается наравне с числителем.

Почему это ОТДЕЛЬНЫЙ счётчик, а не метка у счётчика глаголов

У полос разные предметы, и смешение сделало бы неразличимым именно то, ради чего счётчик заводится. Проекция глаголов — то, из чего собирается ответ «разрешено ли действие»: её отказ отнимает доступ. Проекция сегментов — страж целостности правила: её отказ доступа не отнимает, он снимает проверку. Одна метка на два предмета означала бы, что тревога поднимается одинаково там, где арендатор получает отказ, и там, где перестаёт работать наша собственная проверка.

Набор меток ЗАКРЫТ: outcome приходит из констант досева (reseeded|failed), никогда из запроса, поэтому кардинальность не растёт с трафиком.

func (*RuleRefReseedRecorder) IncRuleRefReseed

func (rec *RuleRefReseedRecorder) IncRuleRefReseed(outcome string)

IncRuleRefReseed — исход пересчёта одной роли. Реализует порт seed.RuleRefReseedObserver.

type SigningKeyCounts

type SigningKeyCounts struct {
	Generated   uint64
	Activated   uint64
	Retired     uint64
	Removed     uint64
	Compromised uint64
	Failures    uint64
}

SigningKeyCounts — величины ключницы.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL