Skip to main content

ORK Metrics Reference

ORK nodes export performance metrics via OpenTelemetry SDK → OTLP gRPC → Azure Managed Prometheus → Azure Managed Grafana. This page documents the metric naming conventions, label vocabularies, and the full dictionary of metric series an ORK emits.

If you are debugging an ORK in production, reading these will let you write PromQL queries directly against Azure Managed Prometheus or interpret what the staging Grafana dashboards are showing.

Pipeline overview

.NET side: ORK (Cryptide, master-libs, Ork.Forseti, Tide.Ork.* meters)
→ OTel SDK, OTLP gRPC :4317 ─┐
├─► OTel Collector ─remote-write (Entra OAuth2)─► Azure Managed Prometheus ─PromQL─► Azure Managed Grafana
JVM side: TideCloak (override) + idp-extensions + Midgard's Java facade
→ /metrics:9000 ──scrape──────┘

The collector runs as an Azure Container App sidecar. ORKs push via OTLP gRPC; TideCloak is scraped from :9000. Cardinality is enforced at the collector edge (see Cardinality discipline below).

OTel → Prometheus naming

All metrics use seconds as the unit. On Prometheus export the standard OTel mapping applies:

  • dots become underscores: tide.crypto.durationtide_crypto_duration
  • histograms get the _seconds (or _bytes, etc.) unit suffix and produce three Prometheus series:
    • _count (cumulative observation count)
    • _sum (cumulative sum of observed values)
    • _bucket (with the le label, for percentile computation via histogram_quantile)
  • counters get an _errors or _total suffix where applicable

Example:

OTel name: tide.crypto.duration (Histogram<double>, unit: s)
Prometheus: tide_crypto_duration_seconds_count
tide_crypto_duration_seconds_sum
tide_crypto_duration_seconds_bucket{le="..."}

When writing PromQL or referencing metrics outside of code, always use the Prometheus form (underscores, _seconds suffix). Within ORK source the dotted OTel form is used because that is what the SDK consumes.

Cardinality discipline

This is a hard rule: never put unbounded values into metric labels. Use the centralized tag helpers in Cryptide.Metrics.* and Tide.Ork.* meter accessors — they accept only enums and validated strings from closed vocabularies.

Forbidden as labels (would explode AMP cardinality, drives up cost, breaks dashboards):

  • user id, vuid, vendorId, clientId
  • request id, session id, traceparent
  • raw URLs
  • realm (unless provably bounded in your environment)
  • error messages (use a code; the message goes in the log)

Allowed as labels (bounded enums or closed vocabularies):

  • type from the crypto vocabulary
  • op from the protocol op vocabulary (keygen, sign, check_payment, voucher, ...)
  • phase (e.g. GenShard, SendShard, Commit)
  • resultsuccess / error / skipped
  • outcomesuccess / error / gas_exhausted / crash / canceled / pool_timeout
  • scheme — when needed alongside type, but kept enumerable
  • sideserver / browser / vendor
  • status — HTTP status class or normalized outcome string

node and role are OTel resource attributes, not per-metric labels — they apply to every metric the SDK exports from an ORK process and are added once at startup. Do not duplicate them per metric.

The collector edge runs a Micrometer/OTel filter that drops Keycloak's high-cardinality base_* and http_server_requests_* defaults before they reach AMP — those are explicit denials in the collector config, not allowlist gaps.

Metric dictionary

All metric names below are shown in the dotted OTel form. To use them in PromQL, swap dots for underscores and add the appropriate suffix (see OTel → Prometheus naming).

Cryptographic primitives

MetricTypeLabelsEmitter
tide.crypto.durationHistogramtype, scheme, sideCryptide (in Key/TideSignature.cs dispatch + scheme delegates), Midgard pure-crypto, override (JWS / SignModel / SignWithVrk)
tide.crypto.countCountertype, scheme, sideCryptide
tide.crypto.errorsCountertype, scheme, sideCryptide

The type label values are drawn from the crypto / signing type vocabulary.

ORK protocol flows

MetricTypeLabelsWhat it measures
tide.ork.protocol.durationHistogramop, phaseEnd-to-end protocol flow duration per op (keygen GenShard / SendShard / Commit, sign PreSign / Sign, decrypt, offboard, anonsign, voucher)
tide.ork.request.durationHistogram (auto)route, statusASP.NET HTTP request handling, auto-instrumented via AddAspNetCoreInstrumentation()
tide.ork.outbound.durationHistogramtarget, endpoint, statusOutbound calls from this ORK to peers / master / payer. endpoint is a normalized logical name (never a raw URL).
tide.ork.throttle.rejectionsCounter(none)App-level shedding when the ORK refuses a request before doing work
tide.ork.sync_semaphore.waitHistogram(none)Time waiting for the SyncService semaphore

Master node coordination (master-libs)

Only emitted by master-role ORKs (sork1/sork2 in the staging topology).

MetricTypeLabelsWhat it measures
tide.master.op.durationHistogramop, phase, resultPer-operation duration. Phase splits: Register* → network / crypto / db; SubmitUser → validation / aggregation / lock / db
tide.master.op.countCounterop, resultTotal operation count
tide.master.lock.waitHistogramopTime waiting to acquire the master lock
tide.master.lock.holdHistogramopTime holding the master lock once acquired
tide.master.lock.in_flightUpDownCounteropCurrently-held locks (per op kind)
tide.master.registry.sizeObservableGaugekindSize of the in-memory registry (active rows by kind)

registry.size is hosted by an ORK-side hosted service rather than by master-libs itself — master-libs would otherwise need to capture a scoped DbContext, which is not its responsibility.

Forseti policy execution engine

Forseti is the in-ORK sandboxed C# policy executor. See the Forseti concept page for background.

MetricTypeLabelsWhat it measures
tide.ork.forseti.pool.queue_depthObservableGauge(none, gauge per pod)VmHost process pool: callers currently queued waiting for a free child process. Saturation signal. Unit: callers
tide.ork.forseti.compile.durationHistogramoutcomeSource-to-DLL compile time via the sandboxed VmHost compile path. outcomesuccess / error
tide.ork.forseti.execute.durationHistogramoutcome, stagesPer-policy-execution latency in the sandbox. outcomesuccess / error / gas_exhausted / crash / canceled / pool_timeout. stagesValidateData / ValidateApprovers / ValidateExecutor

Notes on Forseti histogram behaviour:

  • compile.duration exports only after the first observation. Fresh sork pods do not emit a compile_duration_* series until a contract is uploaded or the pod runs its startup warmup compile. A series being absent in AMP is not necessarily a bug; it may mean no compile has happened yet on that pod.
  • execute.duration follows the same rule. Until a PolicyAuthorizationFlow-driven request lands on the pod, execute_duration_* will not exist in AMP.
  • queue_depth is a gauge, so it exports 0 continuously even when idle — it is the only Forseti metric that is always visible.

TideCloak / IDP / Midgard (cross-tier, JVM)

These are emitted by the TideCloak + idp-extensions + Midgard Java code and reach AMP via the :9000 scrape path rather than OTLP gRPC.

MetricTypeLabelsEmitter
tide.midgard.op.durationHistogramop, composite, resultMidgard. Composite ops are end-to-end including ORK round-trip
tide.token.issue.durationHistogrampath, alg, resulttidecloak-override token issuance
tide.idp.login.durationTimerphase, sso, resulttidecloak-idp-extensions, stops at callback.authenticated() hand-off — token issuance itself is core Keycloak
tide.idp.login.callback.hop.durationTimerhopdecrypt / verify, resultidp-extensions, around the two callback Midgard calls
tide.idp.voucher.durationTimeraction, source, resultidp-extensions — action validated against a closed allow-list before recording
tide.idp.vrk_task.durationTimertask, resultidp-extensions scheduled VRK tasks

.NET runtime saturation (auto-instrumented)

These come free from the .NET runtime instrumentation registered via AddRuntimeInstrumentation() in Program.cs. ASP.NET, HttpClient, GC, and ThreadPool are all instrumented automatically.

Notable series under the process_runtime_dotnet_* prefix:

  • process_runtime_dotnet_gc_heap_size_bytes (by generation ∈ gen0 / gen1 / gen2 / loh / poh)
  • process_runtime_dotnet_gc_collections_count_total (by generation)
  • process_runtime_dotnet_thread_pool_queue_length
  • process_runtime_dotnet_thread_pool_threads_count
  • process_runtime_dotnet_exceptions_count_total
  • process_runtime_dotnet_monitor_lock_contention_count_total

ASP.NET HTTP-side auto-instrumentation gives http_server_active_requests etc., scoped to the job=sork-env/ork label set by resource attributes.

Crypto / signing type vocabulary

Shared between browser and server crypto, so the same type label can be compared across SWE-side and ORK-side metrics in the same dashboard query.

Signing: eddsa_standard, eddsa_partial_threshold, eddsa_group, eddsa_blind_partial, vrk_ed25519, threshold_vvk (sign_model), anon_sign, jws_eddsa

Verify: eddsa_verify, blind_verify, doken_verify, commit_verify

Encryption: elgamal, elgamal_decentralized, aes_gcm, ecdh, asymmetric

Key derivation: nested_sss_keygen, sss_share, lagrange_eval, vrk_keygen, sha256_derive, hash_to_point

When adding a new crypto operation to Cryptide that needs instrumentation, extend this vocabulary in Cryptide/Metrics/CryptideMetrics.cs (do not introduce a new free-form string at the call site). The Prometheus dashboards build PromQL queries that filter and group by type, so the values need to be stable.

Grafana dashboards

The metrics above are surfaced in two staging/prod dashboards, tracked under version control in tidecloak-override/Tidified/grafana/dashboards/:

  • tide-metrics-dashboard.json — Tide Network Performance (crypto primitives, ORK protocol, ORK outbound, master, ORK HTTP, .NET runtime, TideCloak workflows, Forseti)
  • tide-ork-logs-dashboard.json — ORK logs (Serilog → Azure Monitor Logs)

Distinct from the local-only HA stack provisioning under Tidified/ha-tidecloak/grafana/. Edit the tracked JSON via PR; manually re-import into Azure Managed Grafana after merge (Grafana detects the existing UID and offers overwrite).

When adding a new dashboard panel for a newly-instrumented metric, edit the tracked JSON — do NOT edit the version in Grafana's UI and forget to push back. Conventions for new panels are documented in the Tidified/grafana/README.md alongside the dashboard JSON.

Adding a new metric (developer checklist)

  1. Pick the right meter. Cryptide primitives go in Cryptide.Metrics.CryptideMetrics. ORK-side protocol/outbound/Forseti go in Tide.Ork.*. Master coordination goes in Tide.Master.
  2. Choose the right instrument type — Histogram for latency, Counter for one-way totals, ObservableGauge for poll-style readings, UpDownCounter for in-flight gauges.
  3. Pick labels from a bounded vocabulary (see Cardinality discipline). If you find yourself wanting a per-user or per-request label, the answer is no — log it instead.
  4. Wire the meter into Program.csAddMeter("Tide.Ork.YourSubsystem") so OTel SDK actually exports it.
  5. Verify locally that the dotted OTel name maps to the expected Prometheus name (dots → underscores, _seconds suffix for time histograms).
  6. Add a Grafana panel to the tracked dashboard JSON if the metric should be on the standard dashboard. Use existing panels' fieldConfig + options blocks as a template.
  7. Document the metric in this dictionary (the file you are reading).