Add Guardian v2 approval review metrics (#39224)

## What changed

- Pass metrics bound to the active turn's model to approval-review contributors.
- Add counter support to `ExtensionMetrics` while preserving host session attribution.
- Record Guardian v2 tool-call score lag and count stale-score fallbacks with the `score_lag` reason.

## Testing

- Cover counter forwarding and session metadata tags.
- Verify Guardian v2 metric samples for current and stale scores.

GitOrigin-RevId: 0ec34736d29d2e37ed217a27e773b4f6230ae38a
This commit is contained in:
Dylan Hurd authored and copyberry committed 2026-08-18 16:27:54 +00:00
1 parent 2a30972fcb
commit e7e13c68e2
15 files changed
+230 -16

No files matched your search

+3
View File
@@ -330,6 +330,9 @@ async fn run_guardian_review(
&session.services.session_extension_data, &session.services.session_extension_data,
&session.services.thread_extension_data, &session.services.thread_extension_data,
&action.to_string(), &action.to_string(),
Some(crate::session::extension_metrics::from_session_telemetry(
turn.session_telemetry.clone(),
)),
) )
.await .await
{ {
+1
View File
@@ -2999,6 +2999,7 @@ async fn escalated_retry_bypasses_extension_approval_and_runs_guardian() -> anyh
_session_store: &'a codex_extension_api::ExtensionData, _session_store: &'a codex_extension_api::ExtensionData,
_thread_store: &'a codex_extension_api::ExtensionData, _thread_store: &'a codex_extension_api::ExtensionData,
_prompt: &'a str, _prompt: &'a str,
_extension_metrics: Option<Arc<dyn codex_extension_api::ExtensionMetrics>>,
) -> codex_extension_api::ExtensionFuture<'a, Option<ReviewDecision>> { ) -> codex_extension_api::ExtensionFuture<'a, Option<ReviewDecision>> {
Box::pin(async move { Some(ReviewDecision::Approved) }) Box::pin(async move { Some(ReviewDecision::Approved) })
} }
@@ -8,12 +8,16 @@ struct SessionTelemetryExtensionMetrics {
} }
impl ExtensionMetrics for SessionTelemetryExtensionMetrics { impl ExtensionMetrics for SessionTelemetryExtensionMetrics {
fn counter(&self, name: &str, inc: i64, tags: &[(&str, &str)]) {
self.session_telemetry.counter(name, inc, tags);
}
fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) { fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) {
self.session_telemetry.histogram(name, value, tags); self.session_telemetry.histogram(name, value, tags);
} }
} }
pub(super) fn from_session_telemetry( pub(crate) fn from_session_telemetry(
session_telemetry: SessionTelemetry, session_telemetry: SessionTelemetry,
) -> Arc<dyn ExtensionMetrics> { ) -> Arc<dyn ExtensionMetrics> {
Arc::new(SessionTelemetryExtensionMetrics { session_telemetry }) Arc::new(SessionTelemetryExtensionMetrics { session_telemetry })
+1 -1
View File
@@ -204,7 +204,7 @@ use codex_protocol::exec_output::StreamOutput;
mod code_mode_warning; mod code_mode_warning;
pub(crate) mod context_window; pub(crate) mod context_window;
mod environment; mod environment;
mod extension_metrics; pub(crate) mod extension_metrics;
mod handlers; mod handlers;
mod inject; mod inject;
mod input_queue; mod input_queue;
+23
View File
@@ -402,8 +402,31 @@ fn extension_metrics_preserve_session_metadata_tags() {
], ],
); );
extension_metrics.counter(
"codex.test.extension.counter",
/*inc*/ 2,
&[("component", "skills"), ("model", "extension-model")],
);
let snapshot = metrics.snapshot().expect("metrics snapshot"); let snapshot = metrics.snapshot().expect("metrics snapshot");
let attributes = single_histogram_attributes(&snapshot, "codex.test.extension"); let attributes = single_histogram_attributes(&snapshot, "codex.test.extension");
let counter = find_metric(&snapshot, "codex.test.extension.counter");
let AggregatedMetrics::U64(MetricData::Sum(sum)) = counter.data() else {
panic!("expected counter");
};
let points = sum.data_points().collect::<Vec<_>>();
assert_eq!(points.len(), 1);
assert_eq!(points[0].value(), 2);
assert_eq!(
points[0]
.attributes()
.map(|attribute| (
attribute.key.as_str().to_string(),
attribute.value.as_str().to_string(),
))
.collect::<BTreeMap<_, _>>(),
attributes,
);
assert_eq!( assert_eq!(
attributes, attributes,
BTreeMap::from([ BTreeMap::from([
+3
View File
@@ -6,6 +6,7 @@ use anyhow::Result;
use codex_extension_api::ApprovalReviewContributor; use codex_extension_api::ApprovalReviewContributor;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionFuture; use codex_extension_api::ExtensionFuture;
use codex_extension_api::ExtensionMetrics;
use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ExtensionRegistryBuilder;
use codex_features::Feature; use codex_features::Feature;
use codex_login::CodexAuth; use codex_login::CodexAuth;
@@ -64,8 +65,10 @@ impl ApprovalReviewContributor for ApprovedReviewContributor {
_session_store: &'a ExtensionData, _session_store: &'a ExtensionData,
_thread_store: &'a ExtensionData, _thread_store: &'a ExtensionData,
prompt: &'a str, prompt: &'a str,
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> ExtensionFuture<'a, Option<ReviewDecision>> { ) -> ExtensionFuture<'a, Option<ReviewDecision>> {
Box::pin(async move { Box::pin(async move {
assert!(extension_metrics.is_some());
assert!(prompt.contains("\"tool\":\"request_permissions\"")); assert!(prompt.contains("\"tool\":\"request_permissions\""));
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
}) })
@@ -3,6 +3,9 @@
/// Implementations are expected to attach the host's session attribution before /// Implementations are expected to attach the host's session attribution before
/// forwarding samples to the configured metrics backend. /// forwarding samples to the configured metrics backend.
pub trait ExtensionMetrics: Send + Sync { pub trait ExtensionMetrics: Send + Sync {
/// Increments a counter with optional extension-provided tags.
fn counter(&self, name: &str, inc: i64, tags: &[(&str, &str)]);
/// Records one histogram sample with optional extension-provided tags. /// Records one histogram sample with optional extension-provided tags.
fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]); fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]);
} }
@@ -318,11 +318,13 @@ pub trait ToolLifecycleContributor: Send + Sync {
/// Extension contribution that can claim rendered approval-review prompts. /// Extension contribution that can claim rendered approval-review prompts.
pub trait ApprovalReviewContributor: Send + Sync { pub trait ApprovalReviewContributor: Send + Sync {
/// Reviews one action using metrics bound to the active turn's model.
fn contribute<'a>( fn contribute<'a>(
&'a self, &'a self,
session_store: &'a ExtensionData, session_store: &'a ExtensionData,
thread_store: &'a ExtensionData, thread_store: &'a ExtensionData,
prompt: &'a str, prompt: &'a str,
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> ExtensionFuture<'a, Option<ReviewDecision>>; ) -> ExtensionFuture<'a, Option<ReviewDecision>>;
} }
+8 -1
View File
@@ -7,6 +7,7 @@ use crate::ConfigContributor;
use crate::ContextContributor; use crate::ContextContributor;
use crate::ExtensionData; use crate::ExtensionData;
use crate::ExtensionEventSink; use crate::ExtensionEventSink;
use crate::ExtensionMetrics;
use crate::McpServerContributor; use crate::McpServerContributor;
use crate::NoopExtensionEventSink; use crate::NoopExtensionEventSink;
use crate::SkillInvocationContributor; use crate::SkillInvocationContributor;
@@ -194,10 +195,16 @@ impl<C: Sync> ExtensionRegistry<C> {
session_store: &ExtensionData, session_store: &ExtensionData,
thread_store: &ExtensionData, thread_store: &ExtensionData,
prompt: &str, prompt: &str,
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> Option<ReviewDecision> { ) -> Option<ReviewDecision> {
for contributor in &self.approval_review_contributors { for contributor in &self.approval_review_contributors {
if let Some(decision) = contributor if let Some(decision) = contributor
.contribute(session_store, thread_store, prompt) .contribute(
session_store,
thread_store,
prompt,
extension_metrics.clone(),
)
.await .await
{ {
return Some(decision); return Some(decision);
@@ -135,6 +135,7 @@ impl ApprovalReviewContributor for AllContributors {
_session_store: &'a ExtensionData, _session_store: &'a ExtensionData,
_thread_store: &'a ExtensionData, _thread_store: &'a ExtensionData,
_prompt: &'a str, _prompt: &'a str,
_extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> ExtensionFuture<'a, Option<ReviewDecision>> { ) -> ExtensionFuture<'a, Option<ReviewDecision>> {
Box::pin(async move { Box::pin(async move {
let _self = self; let _self = self;
@@ -176,6 +177,7 @@ async fn build_round_trips_every_contributor_category() {
&ExtensionData::new("session"), &ExtensionData::new("session"),
&ExtensionData::new("thread"), &ExtensionData::new("thread"),
"review this", "review this",
/*extension_metrics*/ None,
) )
.await, .await,
Some(ReviewDecision::ApprovedForSession) Some(ReviewDecision::ApprovedForSession)
@@ -322,6 +324,7 @@ impl ApprovalReviewContributor for RecordingApprovalContributor {
session_store: &'a ExtensionData, session_store: &'a ExtensionData,
thread_store: &'a ExtensionData, thread_store: &'a ExtensionData,
prompt: &'a str, prompt: &'a str,
_extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> ExtensionFuture<'a, Option<ReviewDecision>> { ) -> ExtensionFuture<'a, Option<ReviewDecision>> {
Box::pin(async move { Box::pin(async move {
self.calls self.calls
@@ -363,6 +366,7 @@ async fn approval_review_returns_first_claim_and_short_circuits() {
&ExtensionData::new("session-1"), &ExtensionData::new("session-1"),
&ExtensionData::new("thread-1"), &ExtensionData::new("thread-1"),
"allow command?", "allow command?",
/*extension_metrics*/ None,
) )
.await; .await;
@@ -451,6 +455,7 @@ async fn empty_registry_does_not_claim_approval_review() {
&ExtensionData::new("session"), &ExtensionData::new("session"),
&ExtensionData::new("thread"), &ExtensionData::new("thread"),
"unclaimed", "unclaimed",
/*extension_metrics*/ None,
) )
.await, .await,
None None
+20
View File
@@ -11,6 +11,7 @@ use codex_extension_api::ApprovalReviewContributor;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionEventSink; use codex_extension_api::ExtensionEventSink;
use codex_extension_api::ExtensionFuture; use codex_extension_api::ExtensionFuture;
use codex_extension_api::ExtensionMetrics;
use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ExtensionWarning; use codex_extension_api::ExtensionWarning;
use codex_extension_api::ResponseItem; use codex_extension_api::ResponseItem;
@@ -190,6 +191,10 @@ pub enum StrictReviewReason {
struct GuardianV2Enabled; struct GuardianV2Enabled;
// Sampled when the approval contributor evaluates the cached Luna score.
const TOOL_CALL_LAG_METRIC: &str = "codex.guardian_v2.tool_call_lag";
const REVIEW_FALLBACK_METRIC: &str = "codex.guardian_v2.review_fallback";
#[derive(Default)] #[derive(Default)]
struct GuardianV2ScoreProgress { struct GuardianV2ScoreProgress {
latest_tool_call: AtomicUsize, latest_tool_call: AtomicUsize,
@@ -284,6 +289,7 @@ impl ApprovalReviewContributor for GuardianV2Extension {
_session_store: &'a ExtensionData, _session_store: &'a ExtensionData,
thread_store: &'a ExtensionData, thread_store: &'a ExtensionData,
_prompt: &'a str, _prompt: &'a str,
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
) -> ExtensionFuture<'a, Option<ReviewDecision>> { ) -> ExtensionFuture<'a, Option<ReviewDecision>> {
Box::pin(async move { Box::pin(async move {
thread_store.get::<GuardianV2Enabled>()?; thread_store.get::<GuardianV2Enabled>()?;
@@ -297,8 +303,22 @@ impl ApprovalReviewContributor for GuardianV2Extension {
.latest_scored_tool_call .latest_scored_tool_call
.load(Ordering::Acquire), .load(Ordering::Acquire),
); );
if let Some(metrics) = &extension_metrics {
metrics.histogram(
TOOL_CALL_LAG_METRIC,
i64::try_from(tool_call_lag).unwrap_or(i64::MAX),
&[],
);
}
if tool_call_lag > guardian_config.max_tool_call_lag { if tool_call_lag > guardian_config.max_tool_call_lag {
thread_store.insert(StrictReviewReason::StaleScore); thread_store.insert(StrictReviewReason::StaleScore);
if let Some(metrics) = &extension_metrics {
metrics.counter(
REVIEW_FALLBACK_METRIC,
/*inc*/ 1,
&[("fallback_reason", "score_lag")],
);
}
return None; return None;
} }
+144 -13
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::time::Duration; use std::time::Duration;
@@ -7,6 +8,7 @@ use anyhow::Result;
use codex_core::config::Config; use codex_core::config::Config;
use codex_extension_api::ConversationHistorySnapshot; use codex_extension_api::ConversationHistorySnapshot;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionMetrics;
use codex_extension_api::ExtensionRegistry; use codex_extension_api::ExtensionRegistry;
use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ResponseItem; use codex_extension_api::ResponseItem;
@@ -47,7 +49,9 @@ use pretty_assertions::assert_eq;
use serde_json::json; use serde_json::json;
use super::GuardianV2ScoreProgress; use super::GuardianV2ScoreProgress;
use super::REVIEW_FALLBACK_METRIC;
use super::StrictReviewReason; use super::StrictReviewReason;
use super::TOOL_CALL_LAG_METRIC;
use super::encrypted_parent_compaction; use super::encrypted_parent_compaction;
use crate::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS; use crate::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS;
use crate::config::DEFAULT_PARENT_COMPACTION_TOKENS; use crate::config::DEFAULT_PARENT_COMPACTION_TOKENS;
@@ -177,6 +181,37 @@ async fn installed_extension_reconnects_after_auth_refresh() -> Result<()> {
Ok(()) Ok(())
} }
#[derive(Debug, PartialEq)]
enum RecordedMetric {
Histogram(String, i64, Vec<(String, String)>),
Counter(String, i64, Vec<(String, String)>),
}
#[derive(Default)]
struct RecordingMetrics(Mutex<Vec<RecordedMetric>>);
impl ExtensionMetrics for RecordingMetrics {
fn counter(&self, name: &str, inc: i64, tags: &[(&str, &str)]) {
self.0.lock().unwrap().push(RecordedMetric::Counter(
name.to_owned(),
inc,
tags.iter()
.map(|(key, value)| ((*key).to_owned(), (*value).to_owned()))
.collect(),
));
}
fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) {
self.0.lock().unwrap().push(RecordedMetric::Histogram(
name.to_owned(),
value,
tags.iter()
.map(|(key, value)| ((*key).to_owned(), (*value).to_owned()))
.collect(),
));
}
}
struct TestConversationHistory(Vec<ResponseItem>); struct TestConversationHistory(Vec<ResponseItem>);
impl ConversationHistorySnapshot for TestConversationHistory { impl ConversationHistorySnapshot for TestConversationHistory {
@@ -425,6 +460,8 @@ async fn sample_configured_conversation_history(
let registry = builder.build(); let registry = builder.build();
let session_store = ExtensionData::new("session-1"); let session_store = ExtensionData::new("session-1");
let thread_store = test.codex.thread_extension_data(); let thread_store = test.codex.thread_extension_data();
thread_store.insert(RecordingMetrics::default());
let metrics = thread_store.get::<RecordingMetrics>().unwrap();
if has_model_defaults { if has_model_defaults {
let parent_model = test let parent_model = test
.thread_manager .thread_manager
@@ -435,7 +472,12 @@ async fn sample_configured_conversation_history(
} }
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
None None
); );
@@ -446,7 +488,7 @@ async fn sample_configured_conversation_history(
persistent_thread_state_available: false, persistent_thread_state_available: false,
environments: &[], environments: &[],
mcp_resource_client: None, mcp_resource_client: None,
extension_metrics: None, extension_metrics: Some(metrics),
session_store: &session_store, session_store: &session_store,
thread_store, thread_store,
}) })
@@ -607,7 +649,14 @@ max_recent_non_user_entries = 8
}); });
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
thread_store
.get::<RecordingMetrics>()
.map(|metrics| metrics as Arc<dyn ExtensionMetrics>),
)
.await, .await,
None None
); );
@@ -621,7 +670,14 @@ max_recent_non_user_entries = 8
}); });
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
thread_store
.get::<RecordingMetrics>()
.map(|metrics| metrics as Arc<dyn ExtensionMetrics>),
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
@@ -637,17 +693,33 @@ max_recent_non_user_entries = 8
.store(/*val*/ 3, Ordering::Release); .store(/*val*/ 3, Ordering::Release);
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
thread_store
.get::<RecordingMetrics>()
.map(|metrics| metrics as Arc<dyn ExtensionMetrics>),
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
let initial_metrics = thread_store.get::<RecordingMetrics>().unwrap();
thread_store.insert(RecordingMetrics::default());
score_progress score_progress
.latest_tool_call .latest_tool_call
.store(/*val*/ 4, Ordering::Release); .store(/*val*/ 4, Ordering::Release);
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
thread_store
.get::<RecordingMetrics>()
.map(|metrics| metrics as Arc<dyn ExtensionMetrics>),
)
.await, .await,
None None
); );
@@ -661,11 +733,40 @@ max_recent_non_user_entries = 8
.store(/*val*/ 2, Ordering::Release); .store(/*val*/ 2, Ordering::Release);
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
thread_store
.get::<RecordingMetrics>()
.map(|metrics| metrics as Arc<dyn ExtensionMetrics>),
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
assert_eq!(
*initial_metrics.0.lock().unwrap(),
vec![
RecordedMetric::Histogram(TOOL_CALL_LAG_METRIC.to_owned(), 0, vec![]),
RecordedMetric::Histogram(TOOL_CALL_LAG_METRIC.to_owned(), 0, vec![]),
RecordedMetric::Histogram(TOOL_CALL_LAG_METRIC.to_owned(), 2, vec![]),
]
);
let metrics = thread_store.get::<RecordingMetrics>().unwrap();
assert_eq!(
*metrics.0.lock().unwrap(),
vec![
RecordedMetric::Histogram(TOOL_CALL_LAG_METRIC.to_owned(), 3, vec![]),
RecordedMetric::Counter(
REVIEW_FALLBACK_METRIC.to_owned(),
1,
vec![("fallback_reason".to_owned(), "score_lag".to_owned())],
),
RecordedMetric::Histogram(TOOL_CALL_LAG_METRIC.to_owned(), 2, vec![]),
]
);
Ok(()) Ok(())
} }
@@ -847,7 +948,12 @@ async fn contributor_uses_model_defaults_and_preserves_local_overrides() -> Resu
}); });
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
@@ -980,7 +1086,12 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
assert!(score.sampled_at.is_some()); assert!(score.sampled_at.is_some());
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
None None
); );
@@ -1005,7 +1116,12 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
}); });
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
@@ -1017,7 +1133,12 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
}); });
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, &disabled_thread_store, "review action") .approval_review(
&session_store,
&disabled_thread_store,
"review action",
/*extension_metrics*/ None
)
.await, .await,
None None
); );
@@ -1309,7 +1430,12 @@ async fn contributor_reuses_the_latest_compatible_parent_compaction() -> Result<
assert_eq!(previous_score.scores.get("action_risk"), Some(&0.25)); assert_eq!(previous_score.scores.get("action_risk"), Some(&0.25));
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
Some(ReviewDecision::Approved) Some(ReviewDecision::Approved)
); );
@@ -1348,7 +1474,12 @@ async fn contributor_reuses_the_latest_compatible_parent_compaction() -> Result<
); );
assert_eq!( assert_eq!(
registry registry
.approval_review(&session_store, thread_store, "review action") .approval_review(
&session_store,
thread_store,
"review action",
/*extension_metrics*/ None,
)
.await, .await,
None None
); );
@@ -15,6 +15,10 @@ struct RecordingMetrics {
} }
impl ExtensionMetrics for RecordingMetrics { impl ExtensionMetrics for RecordingMetrics {
fn counter(&self, name: &str, _inc: i64, _tags: &[(&str, &str)]) {
panic!("unexpected counter: {name}");
}
fn histogram(&self, name: &str, value: i64, _tags: &[(&str, &str)]) { fn histogram(&self, name: &str, value: i64, _tags: &[(&str, &str)]) {
self.samples self.samples
.lock() .lock()
@@ -18,6 +18,10 @@ struct RecordingMetrics {
} }
impl ExtensionMetrics for RecordingMetrics { impl ExtensionMetrics for RecordingMetrics {
fn counter(&self, name: &str, _inc: i64, _tags: &[(&str, &str)]) {
panic!("unexpected counter: {name}");
}
fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) { fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) {
self.samples self.samples
.lock() .lock()
@@ -2294,6 +2294,10 @@ impl RecordingMetrics {
} }
impl ExtensionMetrics for RecordingMetrics { impl ExtensionMetrics for RecordingMetrics {
fn counter(&self, name: &str, _inc: i64, _tags: &[(&str, &str)]) {
panic!("unexpected counter: {name}");
}
fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) { fn histogram(&self, name: &str, value: i64, tags: &[(&str, &str)]) {
self.samples self.samples
.lock() .lock()