Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
08c3d55
feat(semconv): add the ourios.miner.* metric registry (RFC 0001 §6.8)
jensholdgaard Jun 8, 2026
b76eca1
feat(miner): instrument the generated ourios.miner.* telemetry constants
jensholdgaard Jun 8, 2026
ebe37af
docs(miner): align telemetry comments + RFC §5 to the ourios.miner.* …
jensholdgaard Jun 8, 2026
69cb8b1
perf(miner): compute service once per ingest
jensholdgaard Jun 8, 2026
acb029e
docs(miner): dot-namespace the alias counters in the §6.8 table
jensholdgaard Jun 8, 2026
e7fb5f9
feat(semconv): add ourios.miner.alias.* counters to the registry
jensholdgaard Jun 8, 2026
229d971
refactor(core): emit alias counters via generated ourios.miner.alias.…
jensholdgaard Jun 8, 2026
939c040
fix(miner): poison-safe metric writes + drop sentinel enum seed
jensholdgaard Jun 8, 2026
3ba0fcc
style(miner): rustfmt the wrapped lock-recovery lines
jensholdgaard Jun 8, 2026
cbd5fb2
refactor(miner): recover the metrics lock via a match helper
jensholdgaard Jun 8, 2026
2a908ed
refactor(miner): sort the confidence reservoir via f64::total_cmp
jensholdgaard Jun 8, 2026
8d54532
fix(miner): attribute-less metric seeds; fold empty service.name to u…
jensholdgaard Jun 8, 2026
065be5f
refactor(miner): §3.1.2 via registry + real data, drop zero-traffic f…
jensholdgaard Jun 8, 2026
0c1edd2
fix(miner): drop metric seeding; bump line denominator before ratio n…
jensholdgaard Jun 8, 2026
abe9e82
docs(miner): scope §3.1.2 to the §6.8 miner instruments; tighten rete…
jensholdgaard Jun 8, 2026
8bce3d8
refactor(miner): omit ourios.service when absent; rename record_durat…
jensholdgaard Jun 8, 2026
d19040f
docs(miner): correct the no-op cost model — gauge state updates run u…
jensholdgaard Jun 8, 2026
9c51d4f
perf(miner): compute confidence quantiles off the metrics lock
jensholdgaard Jun 8, 2026
ea2c06b
fix(miner): qualify quantile_of in the reservoir tests
jensholdgaard Jun 8, 2026
2dd6dbd
docs(rfc-0001): use ourios.tenant for the wal_replay_progress gauge a…
jensholdgaard Jun 8, 2026
64d3bbd
docs(miner): refresh stale cluster module doc + template.count brief
jensholdgaard Jun 8, 2026
b59b2c7
perf(miner): clone the tenant key only on first sight for per-tenant …
jensholdgaard Jun 8, 2026
12b6ef2
docs(miner): align ServiceTally/by_service docs to dotted names + the…
jensholdgaard Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions crates/ourios-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ serde_json = { version = "1", default-features = false, features = ["std"] }
# library crates depend on the lightweight API and resolve instruments
# through `global::meter`; the heavy SDK + OTLP exporter live in
# `ourios-telemetry`). Drives the alias map's
# `alias_assertions_total` / `alias_retractions_total` counters
# (§6.8 telemetry table).
# `ourios.miner.alias.assertions` / `ourios.miner.alias.retractions`
# counters (§6.8 telemetry table).
opentelemetry = { version = "0.32", default-features = false, features = ["metrics"] }
# Generated dotted-`ourios.*` semconv name constants for the alias
# counters' metric names + `ourios.tenant` attribute key (§6.8).
# Zero-dep leaf crate.
ourios-semconv = { path = "../ourios-semconv" }

[dev-dependencies]
# `init_in_memory` collects the alias counters' exported metric stream
Expand Down
69 changes: 34 additions & 35 deletions crates/ourios-core/src/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use std::time::SystemTime;

use opentelemetry::metrics::Counter;
use opentelemetry::{KeyValue, global};
use ourios_semconv as semconv;

use crate::audit::{AuditEvent, AuditPayload, AuditSink};
use crate::tenant::TenantId;
Expand All @@ -52,17 +53,6 @@ use crate::tenant::TenantId;
/// input.
pub const REASON_BYTE_LIMIT: usize = 256;

/// `alias_assertions_total` (RFC 0001 §6.8 telemetry table). Named with
/// the pre-redesign identifier the table pins — the dotted-`ourios.*`
/// semconv conversion is the deferred §6.8 redesign, not this slice.
const METRIC_ALIAS_ASSERTIONS_TOTAL: &str = "alias_assertions_total";
/// `alias_retractions_total` (RFC 0001 §6.8 telemetry table).
const METRIC_ALIAS_RETRACTIONS_TOTAL: &str = "alias_retractions_total";
/// The `tenant_id` data-point attribute key. Pre-redesign name per the
/// §6.8 table (the namespaced `ourios.tenant` key is the deferred
/// dotted-semconv redesign).
const ATTR_TENANT_ID: &str = "tenant_id";

/// The operator / API principal that issued an alias assertion.
///
/// `[§3.1]` "explicit": aliasing is never anonymous, so every assertion
Expand Down Expand Up @@ -212,19 +202,20 @@ impl AliasMap {
/// provider is installed, so constructing and recording is always
/// safe).
///
/// `alias_assertions_total` / `alias_retractions_total` carry a
/// `tenant_id` attribute, so a per-tenant point appears on the first
/// real assertion / retraction (§6.8 collect-on-read) — they are not
/// zero-seeded, which would emit a spurious attribute-less series.
/// `ourios.miner.alias.assertions` / `ourios.miner.alias.retractions`
/// carry an `ourios.tenant` attribute, so a per-tenant point appears
/// on the first real assertion / retraction (§6.8 collect-on-read) —
/// they are not zero-seeded, which would emit a spurious
/// attribute-less series.
#[must_use]
pub fn new() -> Self {
let meter = global::meter("ourios.miner");
let assertions_total = meter
.u64_counter(METRIC_ALIAS_ASSERTIONS_TOTAL)
.u64_counter(semconv::OURIOS_MINER_ALIAS_ASSERTIONS)
.with_unit("{assertion}")
.build();
let retractions_total = meter
.u64_counter(METRIC_ALIAS_RETRACTIONS_TOTAL)
.u64_counter(semconv::OURIOS_MINER_ALIAS_RETRACTIONS)
.with_unit("{retraction}")
.build();
Self {
Expand All @@ -242,7 +233,7 @@ impl AliasMap {
/// ≥ 2 distinct ids), emits an [`AuditPayload::AliasAsserted`]
/// event through `sink`, folds the asserted set into the tenant's
/// classes (union-on-overlap: any pre-existing class sharing a
/// member merges in), and increments `alias_assertions_total`.
/// member merges in), and increments `ourios.miner.alias.assertions`.
/// `representative_id` is the operator's anchor id only — membership
/// is the union, independent of which id was named the anchor.
///
Expand Down Expand Up @@ -282,7 +273,10 @@ impl AliasMap {
self.union_in(tenant, &asserted);
self.assertions_total.add(
1,
&[KeyValue::new(ATTR_TENANT_ID, tenant.as_str().to_owned())],
&[KeyValue::new(
semconv::OURIOS_TENANT,
tenant.as_str().to_owned(),
)],
);
Ok(())
}
Expand All @@ -293,7 +287,7 @@ impl AliasMap {
/// derived canonical. Emits an [`AuditPayload::AliasRetracted`]
/// event through `sink` (with `representative_id = id` as the
/// operator's anchor and empty `member_ids`), removes `id` from its
/// class, and increments `alias_retractions_total`. A class that
/// class, and increments `ourios.miner.alias.retractions`. A class that
/// drops to a single member is no longer an alias set and is
/// dropped — that lone id then resolves only to itself
/// (RFC0001.16). The canonical re-derives as `min` of the remainder
Expand Down Expand Up @@ -330,7 +324,10 @@ impl AliasMap {
self.remove_id(tenant, id);
self.retractions_total.add(
1,
&[KeyValue::new(ATTR_TENANT_ID, tenant.as_str().to_owned())],
&[KeyValue::new(
semconv::OURIOS_TENANT,
tenant.as_str().to_owned(),
)],
);
Ok(())
}
Expand Down Expand Up @@ -566,12 +563,13 @@ mod tests {
assert_eq!(replayed.resolves(&t, 2), [2].into_iter().collect());
}

// RFC 0001 §6.8 telemetry table: `alias_assertions_total` /
// `alias_retractions_total` are mandatory counters with a
// `tenant_id` attribute. Collect the exported metric stream through
// an in-memory reader (no OTLP endpoint) and assert both surface,
// exactly as the compaction-metrics test does. `init_in_memory`
// installs the *global* provider, so this is a single-provider test.
// RFC 0001 §6.8 telemetry table: `ourios.miner.alias.assertions` /
// `ourios.miner.alias.retractions` are mandatory counters with an
// `ourios.tenant` attribute. Collect the exported metric stream
// through an in-memory reader (no OTLP endpoint) and assert both
// surface, exactly as the compaction-metrics test does.
// `init_in_memory` installs the *global* provider, so this is a
// single-provider test.
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn alias_counters_are_exported_with_tenant_attribute() {
use opentelemetry_sdk::metrics::data::{
Expand Down Expand Up @@ -600,8 +598,8 @@ mod tests {
.map(|m| m.name().to_string())
.collect();
for expected in [
METRIC_ALIAS_ASSERTIONS_TOTAL,
METRIC_ALIAS_RETRACTIONS_TOTAL,
semconv::OURIOS_MINER_ALIAS_ASSERTIONS,
semconv::OURIOS_MINER_ALIAS_RETRACTIONS,
] {
assert!(
names.iter().any(|n| n == expected),
Expand All @@ -621,17 +619,18 @@ mod tests {
panic!("{name} should be a u64 sum");
};
sum.data_points().any(|dp| {
dp.attributes()
.any(|kv| kv.key.as_str() == ATTR_TENANT_ID && kv.value.as_str() == "acme")
dp.attributes().any(|kv| {
kv.key.as_str() == semconv::OURIOS_TENANT && kv.value.as_str() == "acme"
})
})
};
assert!(
tenant_attr_present(METRIC_ALIAS_ASSERTIONS_TOTAL),
"alias_assertions_total must carry the tenant_id attribute",
tenant_attr_present(semconv::OURIOS_MINER_ALIAS_ASSERTIONS),
"ourios.miner.alias.assertions must carry the ourios.tenant attribute",
);
assert!(
tenant_attr_present(METRIC_ALIAS_RETRACTIONS_TOTAL),
"alias_retractions_total must carry the tenant_id attribute",
tenant_attr_present(semconv::OURIOS_MINER_ALIAS_RETRACTIONS),
"ourios.miner.alias.retractions must carry the ourios.tenant attribute",
);
}
}
20 changes: 17 additions & 3 deletions crates/ourios-core/src/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,10 @@ impl AuditPayload {
match self {
Self::Template { change, .. } => change.counts_as_merge(),
// Alias activity is counted separately via
// `alias_assertions_total` / `alias_retractions_total`
// (RFC 0001 §6.7); `merges_total` is reserved for the two
// structural widenings.
// `ourios.miner.alias.assertions` /
// `ourios.miner.alias.retractions` (RFC 0001 §6.7);
// `ourios.miner.merges` is reserved for the two structural
// widenings.
Self::AliasAsserted { .. } | Self::AliasRetracted { .. } | Self::Compaction { .. } => {
false
}
Expand All @@ -285,6 +286,19 @@ impl TemplateChange {
pub fn counts_as_merge(&self) -> bool {
matches!(self, Self::Widened { .. } | Self::TypeExpanded { .. })
}

/// The canonical `event_type` string for this change — the
/// `ourios.miner.merges` `ourios.miner.template_change` attribute
/// (RFC 0001 §6.8) reads it. Mirrors [`AuditPayload::event_type`]
/// for the `Template` payload arm.
#[must_use]
pub fn event_type(&self) -> &'static str {
match self {
Self::Widened { .. } => EVENT_TYPE_TEMPLATE_WIDENED,
Self::TypeExpanded { .. } => EVENT_TYPE_TEMPLATE_TYPE_EXPANDED,
Self::RejectedDegenerate { .. } => EVENT_TYPE_TEMPLATE_WIDENING_REJECTED_DEGENERATE,
}
}
}

/// Type tag for a masked parameter slot.
Expand Down
18 changes: 18 additions & 0 deletions crates/ourios-miner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,30 @@ publish = false

[dependencies]
ourios-core = { path = "../ourios-core" }
# Generated dotted-`ourios.miner.*` metric + attribute name constants
# (weaver registry, `semconv/registry/`). RFC 0001 §6.8 names.
ourios-semconv = { path = "../ourios-semconv" }
# RFC 0001 §6.5: `Param.value` for `ParamType::Overflow` carries a
# `(length, sha256_prefix: [u8; 8])` marker. The default-features
# = false build of `sha2` drops the asm backend and keeps the
# pure-Rust path — sufficient for an 8-byte-prefix hash on a
# per-param hot path, with no platform-specific build assumptions.
sha2 = { version = "0.10", default-features = false }
# The OTel metrics **API** only (RFC 0001 §6.8 "Export architecture":
# library crates depend on the lightweight API and resolve instruments
# through `global::meter`; the heavy SDK + OTLP exporter live in
# `ourios-telemetry`). Drives the §6.8 miner instrument set in
# `crate::metrics`.
opentelemetry = { version = "0.32", default-features = false, features = ["metrics"] }

[dev-dependencies]
# `init_in_memory` collects the §6.8 instrument stream through an
# in-memory reader (no OTLP endpoint) in the §3.1.2 / RFC0001.8 /
# H2.2 telemetry tests — same pattern as the alias + compaction
# metrics tests.
ourios-telemetry = { path = "../ourios-telemetry", features = ["testing"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "macros"] }
opentelemetry_sdk = { version = "0.32", default-features = false, features = ["metrics", "testing"] }

[lints]
workspace = true
Loading
Loading