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
- Variables
- func DeclaredAuthzLanes() []string
- func OutboxScanObserver(rec *OutboxRecorder, table string, logger *slog.Logger, msg string) func(error)
- type AuthnHooksRecorder
- type AuthzObservation
- type BindingMaterializationRecorder
- type BootstrapAdminRecorder
- type CatalogSnapshotRecorder
- type CompensationRecorder
- type ExpiredCredentialSweepRecorder
- type IdentityGrowthCounts
- type InstrumentedAuthorizer
- type InstrumentedSubjectAuthorizer
- func (d *InstrumentedSubjectAuthorizer) BatchCheck(ctx context.Context, reqs []service.CheckRequest) ([]*service.CheckResult, error)
- func (d *InstrumentedSubjectAuthorizer) Check(ctx context.Context, req service.CheckRequest) (*service.CheckResult, error)
- func (d *InstrumentedSubjectAuthorizer) ExpandRelations(ctx context.Context, req service.ExpandRequest) (*service.ExpandResult, error)
- func (d *InstrumentedSubjectAuthorizer) ListSubjects(ctx context.Context, req service.ListSubjectsRequest) (*service.ListSubjectsResult, error)
- type IntrospectCounts
- type InviteActivationRecorder
- type InviteMailRecorder
- type JWKSMirrorCounts
- type LRORecorder
- func (l *LRORecorder) IncOrphansRecovered(outcome string)
- func (l *LRORecorder) IncReconcileErrors()
- func (l *LRORecorder) IncReconcileRuns()
- func (l *LRORecorder) IncTerminalWriteFailures(opType string)
- func (l *LRORecorder) IncTerminalWriteRetries(opType string)
- func (l *LRORecorder) SetInflight(n float64)
- type ListScanRecorder
- type ModuleCatalogRecorder
- type OutboxRecorder
- func (rec *OutboxRecorder) IncDeliveredByDirection(table, direction string)
- func (rec *OutboxRecorder) IncPoisoned(table string)
- func (rec *OutboxRecorder) InitDeliveredByDirection(table, direction string)
- func (rec *OutboxRecorder) InitScanOutcomes(table string)
- func (rec *OutboxRecorder) ObserveScanFailure(table string)
- func (rec *OutboxRecorder) SetBacklogDepth(table string, depth float64)
- func (rec *OutboxRecorder) SetBacklogDepthByDirection(table, direction string, depth float64)
- func (rec *OutboxRecorder) SetOldestPendingAgeByDirection(table, direction string, age float64)
- func (rec *OutboxRecorder) SetOldestPendingAgeSeconds(table string, age float64)
- func (rec *OutboxRecorder) SetPoisonedCount(table string, count float64)
- type OwnKeySetCounts
- type PresentedCredentialCounts
- type ReadinessRecorder
- type RegisterPostCommitRecorder
- type Registry
- func (r *Registry) AuthnHooksRecorder(routes, outcomes []string) *AuthnHooksRecorder
- func (r *Registry) CompensationRecorder() *CompensationRecorder
- func (r *Registry) ExpiredCredentialSweepRecorder(outcomes []string) *ExpiredCredentialSweepRecorder
- func (r *Registry) Handler() http.Handler
- func (r *Registry) InviteActivationRecorder() *InviteActivationRecorder
- func (r *Registry) InviteMailRecorder() *InviteMailRecorder
- func (r *Registry) NewBindingMaterializationRecorder() *BindingMaterializationRecorder
- func (r *Registry) NewBootstrapAdminRecorder() *BootstrapAdminRecorder
- func (r *Registry) NewCatalogSnapshotRecorder() *CatalogSnapshotRecorder
- func (r *Registry) NewClientTokenOutcomeCollector(outcomes []string, read func() map[string]uint64)
- func (r *Registry) NewCompensationRecorder() *CompensationRecorder
- func (r *Registry) NewIdentityGrowthCollector(read func() IdentityGrowthCounts)
- func (r *Registry) NewInviteActivationRecorder() *InviteActivationRecorder
- func (r *Registry) NewInviteMailRecorder() *InviteMailRecorder
- func (r *Registry) NewJWKSMirrorCollector(read func() JWKSMirrorCounts)
- func (r *Registry) NewLRORecorder() *LRORecorder
- func (r *Registry) NewListScanRecorder() *ListScanRecorder
- func (r *Registry) NewModuleCatalogRecorder() *ModuleCatalogRecorder
- func (r *Registry) NewOwnKeySetCollector(read func() OwnKeySetCounts)
- func (r *Registry) NewPresentedCredentialCollector(read func() PresentedCredentialCounts)
- func (r *Registry) NewRegisterPostCommitRecorder() *RegisterPostCommitRecorder
- func (r *Registry) NewRegistryTokenCredentialKindRecorder() *RegistryTokenCredentialKindRecorder
- func (r *Registry) NewRelationVerdictGroundsCollector(read func() RelationVerdictGrounds)
- func (r *Registry) NewRetentionCollector(read func() RetentionCounts)
- func (r *Registry) NewRoleVerbReseedRecorder() *RoleVerbReseedRecorder
- func (r *Registry) NewRuleRefReseedRecorder() *RuleRefReseedRecorder
- func (r *Registry) NewSigningKeyCollector(read func() SigningKeyCounts)
- func (r *Registry) NewTokenIntrospectionCollector(read func() IntrospectCounts)
- func (r *Registry) ObserveAuthz(o AuthzObservation)
- func (r *Registry) ObserveAuthzDecision(rpc string, allowed, failed bool)
- func (r *Registry) ObserveAuthzDuration(rpc string, allowed bool, seconds float64)
- func (r *Registry) ObserveAuthzStoreAttempt(op, outcome string, reused bool)
- func (r *Registry) OutboxRecorder() *OutboxRecorder
- func (r *Registry) ReadinessRecorder(dependencies []string) *ReadinessRecorder
- func (r *Registry) RegisterBuildInfo(version, revision string)
- func (r *Registry) RegisterPoolStats(poolName string, pool *pgxpool.Pool)
- func (r *Registry) Registerer() prometheus.Registerer
- func (r *Registry) RegistryTokenCredentialKindRecorder() *RegistryTokenCredentialKindRecorder
- type RegistryTokenCredentialKindRecorder
- type RelationVerdictGrounds
- type RetentionCounts
- type RoleVerbReseedRecorder
- type RuleRefReseedRecorder
- type SigningKeyCounts
Constants ¶
const ( // LaneCheck — полоса КРАЯ. LaneCheck = "Check" // LaneBatchCheck — полоса сужателя списочной выдачи. LaneBatchCheck = "BatchCheck" // LaneCheckRelation — полоса пообъектного звена модулей. LaneCheckRelation = "CheckRelation" )
Значения метки `rpc` — ЗАКРЫТЫЙ словарь. Именованные константы, а не литералы по месту: одно расхождение в написании завело бы четвёртую полосу, которую никто не заметит, потому что она всегда ноль.
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" )
Имена рядов второго уборщика. Собираются ИЗ КОНСТАНТЫ пространства имён.
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"}` растёт, ноль в первом ряду означает ноль. Отказ замера держится своей клеткой, потому что «замеров не было» и «замеры отказывают» требуют разных действий.
const ( JWKSMirrorOutcomesMetric = "kaname_jwks_mirror_outcomes_total" // JWKSMirrorOutcomeServed — обращение к верхнему хопу дало набор ключей. JWKSMirrorOutcomeServed = "served" JWKSMirrorOutcomeUnavailable = "unavailable" // JWKSMirrorOutcomeMisconfigured — по адресу не тот эндпоинт (временем не лечится). JWKSMirrorOutcomeMisconfigured = "misconfigured" )
JWKSMirrorOutcomesMetric / JWKSMirrorOutcomes — имя семейства и ЗАКРЫТЫЙ набор его клеток зеркала публичных ключей проверки.
const ( // PresentedCredentialOutcomesMetric — исходы приёма предъявленного. PresentedCredentialOutcomesMetric = "kaname_presented_credential_outcomes_total" // PresentedCredentialOutcomeAccepted — предъявленное принято, вызывающий назван. PresentedCredentialOutcomeAccepted = "accepted" // PresentedCredentialOutcomeRefused — предъявленное отвергнуто. PresentedCredentialOutcomeRefused = "refused" // либо авторитет отзыва недоступны. ТРЕТИЙ исход. PresentedCredentialOutcomeUnavailable = "unavailable" )
const ( // ReadinessOutcomeReady — зависимость ответила, что здорова. ReadinessOutcomeReady = "ready" // ReadinessOutcomeUnready — зависимость не ответила либо ответила отказом. // Что именно чинить, говорит метка `dependency`. ReadinessOutcomeUnready = "unready" )
Исходы оценки одной зависимости. Набор ЗАКРЫТ: носитель готовности сводит всякий отказ и всякий срок к «не готов», и третьего значения у него нет.
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`). Слитые в один ряд, они дали бы сумму, которая не равна ничему.
const ( // RelationVerdictLabelAxisMirror — метки спрошены в зеркале чужих объектов. RelationVerdictLabelAxisMirror = "mirror" // RelationVerdictLabelAxisIAMDirect — метки спрошены в собственной таблице iam. RelationVerdictLabelAxisIAMDirect = "iam_direct" )
Оси меточной ветви — ЗАКРЫТЫЙ набор клеток своего семейства.
Ось выбирается по типу объекта: у типов, чьи строки живут в собственных таблицах iam, метки спрашиваются там, у остальных — в зеркале. Разделено потому, что предикат равенства форм у осей РАЗНЫЙ, и число, названное для одной, доказывает половину и молчит про другую: ровно так дефект «ветвь отвечает на одной оси и молчит на второй» и дожил до находки.
const ( RetentionPassesMetric = Namespace + "_retention_passes_total" RetentionRowsRemovedMetric = Namespace + "_retention_rows_removed_total" RetentionPassFailuresMetric = Namespace + "_retention_pass_failures_total" )
Имена серий уборки. Собираются ИЗ КОНСТАНТЫ пространства имён, а не выписываются: имя серии — контракт с панелями и правилами тревог, и повторённое литералом оно не двигается вместе с константой (задача #2479).
const ( // KeySetOutcomesMetric — исходы НАШЕЙ записи публикуемого набора. KeySetOutcomesMetric = "kaname_own_keyset_outcomes_total" // KeySetOutcomeServed — набор отдан целиком. KeySetOutcomeServed = "served" KeySetOutcomeUnavailable = "unavailable" // KeySetOutcomeEmpty — ключей нет вовсе. Отдельная клетка, потому что // временем это НЕ лечится: нужен ключ, а не повтор. KeySetOutcomeEmpty = "empty" // IntrospectOutcomesMetric — исходы авторитета отзыва. IntrospectOutcomesMetric = "kaname_token_introspection_outcomes_total" // IntrospectOutcomeActive — токен признан действительным. IntrospectOutcomeActive = "active" // IntrospectOutcomeInactive — токен признан недействительным. IntrospectOutcomeInactive = "inactive" // оттенок второго: смешать его с «недействителен» значило бы сделать сбой // базы неотличимым от отзыва. 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) и ЗАКРЫТЫЕ наборы их клеток.
Величины выходят наружу по ТОЙ ЖЕ причине, по какой выходят величины зеркала: пока наружу выходят одни отказы, ноль в них отвечает сразу на два вопроса — «отказов не было» и «сюда никто не приходил», — а различие между ними и есть различие между работающим контролем и мёртвым.
const AuthnHookRequestsMetric = Namespace + "_authn_hook_requests_total"
AuthnHookRequestsMetric — исходы обращений поставщика личности к полосе хуков.
const BuildInfoUnstamped = "unstamped"
BuildInfoUnstamped — то, что метка версии сборки говорит, когда сборка её НЕ ПРОСТАВИЛА.
Пустая метка на витрине читается как «версии нет», а правдоподобное `dev` — как имя ветки; оба неотличимы от «величину не измеряли». Отдельное слово делает это состояние наблюдаемым: по нему пишется тревога, и оно не притворяется ответом.
const ClientTokenOutcomesMetric = Namespace + "_client_token_outcomes_total"
ClientTokenOutcomesMetric — исходы обращений за токеном по учётным данным клиента.
const InviteMailOutcomesMetric = "kaname_invite_mail_outcomes_total"
InviteMailOutcomesMetric — семейство исходов НАШЕЙ отправки письма приглашения.
Набор клеток ЗАКРЫТ и приходит из констант применителя (`clients.InviteMailOutcomes`), а не из ответа почтового узла: иначе кардинальность росла бы с трафиком, а имена клеток задавал бы чужой процесс.
const Namespace = "kaname"
Namespace — префикс имён ВСЕХ серий этого сервиса. Отдельная константа, а не литерал по месту: имя серии — контракт с панелями и правилами тревог, и собранное в двух местах оно разъедется на первом переименовании.
const ReadinessChecksMetric = Namespace + "_readiness_dependency_checks_total"
ReadinessChecksMetric — исходы оценки готовности по зависимостям.
Собирается ИЗ КОНСТАНТЫ пространства имён: имя ряда — контракт с панелями и правилами тревог, и повторённое литералом оно не двигается вместе с ней.
Variables ¶
var ( RegisterPostCommitSteps = []string{ "forward_additive", "forward_guarded", "residual_read", "residual_withdraw", } RegisterPostCommitOutcomes = []string{"ok", "error"} )
RegisterPostCommitSteps / RegisterPostCommitOutcomes — ЗАКРЫТЫЙ набор лейблов счётчика, объявленный ОДИН раз.
Значения приходят из констант use-case, никогда из данных запроса, поэтому кардинальность не растёт с трафиком. Набор объявлен здесь, а не пересказан в строке помощи: прежняя редакция перечисляла шаги прозой и уже разошлась с кодом — шагов стало шесть, а текст называл четыре. Перечень читают обе стороны: коллектор — чтобы инициализировать каждую клетку нулём, проба use-case — чтобы доказать, что его собственные константы этому набору равны в ОБЕ стороны (TestPostCommitStepConstantsMatchTheDeclaredLabelSet).
var ( KeySetOutcomes = []string{KeySetOutcomeServed, KeySetOutcomeUnavailable, KeySetOutcomeEmpty} IntrospectOutcomes = []string{IntrospectOutcomeActive, IntrospectOutcomeInactive, IntrospectOutcomeUnavailable} SigningKeyEvents = []string{ SigningKeyEventGenerated, SigningKeyEventActivated, SigningKeyEventRetired, SigningKeyEventRemoved, SigningKeyEventCompromised, SigningKeyEventFailure, } )
KeySetOutcomes / IntrospectOutcomes / SigningKeyEvents — закрытые наборы.
var IdentityLedgerSampleOutcomes = []string{ IdentityLedgerSampleOK, IdentityLedgerSampleError, }
IdentityLedgerSampleOutcomes — закрытый набор клеток исхода замера.
var JWKSMirrorOutcomes = []string{ JWKSMirrorOutcomeServed, JWKSMirrorOutcomeUnavailable, JWKSMirrorOutcomeMisconfigured, }
JWKSMirrorOutcomes — закрытый набор клеток семейства.
var PresentedCredentialOutcomes = []string{ PresentedCredentialOutcomeAccepted, PresentedCredentialOutcomeRefused, PresentedCredentialOutcomeUnavailable, }
PresentedCredentialOutcomes — закрытый набор исходов.
var ReadinessOutcomes = []string{ReadinessOutcomeReady, ReadinessOutcomeUnready}
ReadinessOutcomes — закрытый набор исходов.
var RelationVerdictLabelAxes = []string{ RelationVerdictLabelAxisMirror, RelationVerdictLabelAxisIAMDirect, }
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 ¶
func (d *InstrumentedAuthorizer) CheckRelation(ctx context.Context, req service.CheckRelationRequest) (*service.CheckResult, error)
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 ¶
func (d *InstrumentedSubjectAuthorizer) BatchCheck(ctx context.Context, reqs []service.CheckRequest) ([]*service.CheckResult, error)
BatchCheck — полоса сужателя: одно наблюдение ДЛИТЕЛЬНОСТИ на вызов и по решению на КАЖДЫЙ вопрос пачки.
Разделение не бухгалтерское. Длительность принадлежит вызову: делить её на вопросы значило бы утверждать про каждый то, чего никто не измерял. Решения принадлежат вопросам: страница контрактно бывает до тысячи объектов, и счёт по вызовам занизил бы нагрузку от списочной выдачи в тысячу раз — то есть сделал бы её невидимой ровно там, где она наибольшая.
На ошибке пачки вопросы не считаются поштучно: их исход неизвестен, и приписать им «отказ» значило бы выдумать решения, которых не принимали. Считается ОДИН сбой — столько же, сколько было вызовов.
func (*InstrumentedSubjectAuthorizer) Check ¶
func (d *InstrumentedSubjectAuthorizer) Check(ctx context.Context, req service.CheckRequest) (*service.CheckResult, error)
Check — полоса КРАЯ: один вопрос, одно наблюдение.
func (*InstrumentedSubjectAuthorizer) ExpandRelations ¶
func (d *InstrumentedSubjectAuthorizer) ExpandRelations(ctx context.Context, req service.ExpandRequest) (*service.ExpandResult, error)
func (*InstrumentedSubjectAuthorizer) ListSubjects ¶
func (d *InstrumentedSubjectAuthorizer) ListSubjects(ctx context.Context, req service.ListSubjectsRequest) (*service.ListSubjectsResult, error)
ListSubjects / ExpandRelations — сквозной проход БЕЗ наблюдения: у них своя цена и свой предмет.
type IntrospectCounts ¶
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 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 ¶
OwnKeySetCounts — величины нашей записи набора, прочитанные у публикатора.
type PresentedCredentialCounts ¶
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 ¶
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
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 ¶
ObserveAuthzDecision записывает ТОЛЬКО исход одного вопроса.
func (*Registry) ObserveAuthzDuration ¶
ObserveAuthzDuration записывает ТОЛЬКО длительность одного вызова.
Отделено от решения потому, что у пачки вопросов эти две величины считаются по-разному: длительность принадлежит ВЫЗОВУ (делить её на вопросы значило бы утверждать про каждый то, чего никто не измерял), а решения — ВОПРОСАМ (страница контрактно бывает до тысячи объектов, и счёт по вызовам занизил бы нагрузку от списочной выдачи в тысячу раз).
func (*Registry) ObserveAuthzStoreAttempt ¶
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 ¶
RegisterBuildInfo вешает на витрину постоянный ряд `kaname_build_info` с метками версии и ревизии работающего двоичного файла.
Зачем ¶
Первый вопрос дежурного — «какая версия у меня работает». До этого ряда служба не отвечала на него НИ ОДНОЙ величиной, и это смыкается с готовностью: её собственная проверка умеет отказать словами «образ не может обслужить эту схему», то есть назвать РАСХОЖДЕНИЕ, не дав ни одной из двух сравниваемых величин. Служба поставляется отдельно от платформы, где такие вопросы закрывает чужой инвентарь, — спросить больше некого.
Откуда приходят значения ¶
Со ШТАМПА СБОРКИ, а не с ручки профиля: величину, которую объявляет оператор, он вправе объявить любой, и тогда ряд отвечает не о том, что исполняется, а о том, что о нём написали. Штамп ставится там же, где клеймо образа, из ОДНОГО объявления (см. `provenance.mk` и `Dockerfile` службы), поэтому строка на витрине и строка на образе совпадают дословно.
Почему `version` и `revision`, а не `version` и `commit` ¶
Под этими именами величины уже стоят клеймом образа (`org.opencontainers.image.version` / `.revision`) и лежат файлом внутри него. Совпадение имён несущее: оператор сверяет витрину с образом без пересчёта, а расхождение читается как расхождение, а не как два способа назвать одно.
Почему gauge с постоянной единицей, а не метка на прочих рядах ¶
Метка версии, приклеенная к рабочим рядам, меняет их размерность при каждом раскате: суммы и доли по ним рвутся на границе выкатки, а `rate()` считает прибавку от нуля. Отдельный ряд с постоянным значением этой цены не несёт и склеивается с любым другим по времени.
Звать ОДИН раз на старте, из композиционного корня: повторный вызов уронит процесс на повторной регистрации — и это верно, потому что двух разных версий у одного двоичного файла не бывает.
func (*Registry) RegisterPoolStats ¶
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.
Source Files
¶
- authn_hooks_recorder.go
- authz_decorator.go
- authz_lanes.go
- binding_materialization_recorder.go
- bootstrap_admin_recorder.go
- build_info.go
- catalog_snapshot_recorder.go
- client_token_outcome_collector.go
- compensation_recorder.go
- expired_credential_sweep_recorder.go
- identity_growth_collector.go
- invite_activation_recorder.go
- invite_mail_recorder.go
- jwks_mirror_collector.go
- list_scan_recorder.go
- lro_recorder.go
- metrics.go
- module_catalog_recorder.go
- outbox_recorder.go
- outbox_scan_observer.go
- presented_credential_collector.go
- readiness_recorder.go
- register_postcommit_recorder.go
- registry_token_credential_kind_recorder.go
- relation_verdict_grounds_collector.go
- retention_collector.go
- role_rule_ref_reseed_recorder.go
- role_verb_reseed_recorder.go
- token_signing_collector.go