Skip to content

Commit 30f6484

Browse files
andrewbattatIDX GitHub Automation
andauthored
chore: read_state metrics (#9235)
Adding `read_state` metrics before adding future optimizations, as described in NODE-1863 --------- Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
1 parent b553a8f commit 30f6484

File tree

5 files changed

+290
-23
lines changed

5 files changed

+290
-23
lines changed

rs/http_endpoints/public/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ pub fn start_server(
350350
let canister_read_state_router = |version| {
351351
CanisterReadStateServiceBuilder::builder(
352352
log.clone(),
353+
metrics.clone(),
353354
state_reader.clone(),
354355
registry_client.clone(),
355356
ingress_verifier.clone(),
@@ -369,6 +370,7 @@ pub fn start_server(
369370

370371
let subnet_read_state_router = |version| {
371372
SubnetReadStateServiceBuilder::builder(
373+
metrics.clone(),
372374
nns_delegation_reader.clone(),
373375
state_reader.clone(),
374376
nns_subnet_id,

rs/http_endpoints/public/src/metrics.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub struct HttpHandlerMetrics {
7171
// sync call handler metrics
7272
pub sync_call_early_response_trigger_total: IntCounterVec,
7373
pub sync_call_certificate_status_total: IntCounterVec,
74+
75+
// read_state metrics
76+
pub read_state_path_type_total: IntCounterVec,
7477
}
7578

7679
// There is a mismatch between the labels and the public spec.
@@ -207,6 +210,17 @@ impl HttpHandlerMetrics {
207210
"The count of early response triggers for the /{v3,v4}/.../call endpoint.",
208211
&[LABEL_SYNC_CALL_EARLY_RESPONSE_TRIGGER],
209212
),
213+
read_state_path_type_total: metrics_registry.int_counter_vec(
214+
"replica_http_read_state_path_type_total",
215+
"Count of read_state paths requested, by endpoint type and path type.",
216+
&["endpoint", "path_type"],
217+
),
210218
}
211219
}
220+
221+
pub fn observe_read_state_path(&self, endpoint_type: &str, path_type: &str) {
222+
self.read_state_path_type_total
223+
.with_label_values(&[endpoint_type, path_type])
224+
.inc()
225+
}
212226
}

rs/http_endpoints/public/src/read_state/canister.rs

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use super::{
55
use crate::{
66
HttpError, ReplicaHealthStatus,
77
common::{Cbor, WithTimeout, build_validator, validation_error_to_http_error},
8+
metrics::HttpHandlerMetrics,
89
};
910

1011
use axum::{
@@ -57,6 +58,7 @@ pub enum Version {
5758
pub struct CanisterReadStateService {
5859
log: ReplicaLogger,
5960
health_status: Arc<AtomicCell<ReplicaHealthStatus>>,
61+
metrics: HttpHandlerMetrics,
6062
nns_delegation_reader: NNSDelegationReader,
6163
state_reader: Arc<dyn StateReader<State = ReplicatedState>>,
6264
time_source: Arc<dyn TimeSource>,
@@ -69,6 +71,7 @@ pub struct CanisterReadStateService {
6971
pub struct CanisterReadStateServiceBuilder {
7072
log: ReplicaLogger,
7173
health_status: Option<Arc<AtomicCell<ReplicaHealthStatus>>>,
74+
metrics: HttpHandlerMetrics,
7275
malicious_flags: Option<MaliciousFlags>,
7376
nns_delegation_reader: NNSDelegationReader,
7477
state_reader: Arc<dyn StateReader<State = ReplicatedState>>,
@@ -91,6 +94,7 @@ impl CanisterReadStateService {
9194
impl CanisterReadStateServiceBuilder {
9295
pub fn builder(
9396
log: ReplicaLogger,
97+
metrics: HttpHandlerMetrics,
9498
state_reader: Arc<dyn StateReader<State = ReplicatedState>>,
9599
registry_client: Arc<dyn RegistryClient>,
96100
ingress_verifier: Arc<dyn IngressSigVerifier>,
@@ -101,6 +105,7 @@ impl CanisterReadStateServiceBuilder {
101105
Self {
102106
log,
103107
health_status: None,
108+
metrics,
104109
malicious_flags: None,
105110
nns_delegation_reader,
106111
state_reader,
@@ -136,6 +141,7 @@ impl CanisterReadStateServiceBuilder {
136141
health_status: self
137142
.health_status
138143
.unwrap_or_else(|| Arc::new(AtomicCell::new(ReplicaHealthStatus::Healthy))),
144+
metrics: self.metrics,
139145
nns_delegation_reader: self.nns_delegation_reader,
140146
state_reader: self.state_reader,
141147
time_source: self.time_source.unwrap_or(Arc::new(SysTimeSource::new())),
@@ -163,6 +169,7 @@ pub(crate) async fn canister_read_state(
163169
State(CanisterReadStateService {
164170
log,
165171
health_status,
172+
metrics,
166173
nns_delegation_reader,
167174
state_reader,
168175
time_source,
@@ -218,6 +225,7 @@ pub(crate) async fn canister_read_state(
218225

219226
// Verify authorization for requested paths.
220227
if let Err(HttpError { status, message }) = verify_paths(
228+
&metrics,
221229
version,
222230
certified_state_reader.get_state(),
223231
&read_state.source,
@@ -257,6 +265,7 @@ pub(crate) async fn canister_read_state(
257265

258266
// Verifies that the `user` is authorized to retrieve the `paths` requested.
259267
fn verify_paths(
268+
metrics: &HttpHandlerMetrics,
260269
version: Version,
261270
state: &ReplicatedState,
262271
user: &UserId,
@@ -265,6 +274,8 @@ fn verify_paths(
265274
effective_principal_id: PrincipalId,
266275
nns_subnet_id: SubnetId,
267276
) -> Result<(), HttpError> {
277+
const ENDPOINT: &str = "canister";
278+
268279
let mut last_request_status_id: Option<MessageId> = None;
269280

270281
// Convert the paths to slices to make it easier to match below.
@@ -275,10 +286,13 @@ fn verify_paths(
275286

276287
for path in paths {
277288
match path.as_slice() {
278-
[b"time"] => {}
289+
[b"time"] => {
290+
metrics.observe_read_state_path(ENDPOINT, "time");
291+
}
279292
[b"canister", canister_id, b"controllers" | b"module_hash"] => {
280293
let canister_id = parse_principal_id(canister_id)?;
281294
verify_principal_ids(&canister_id, &effective_principal_id)?;
295+
metrics.observe_read_state_path(ENDPOINT, "canister_info");
282296
}
283297
[b"canister", canister_id, b"metadata", name] => {
284298
let name = String::from_utf8(Vec::from(*name)).map_err(|err| HttpError {
@@ -295,26 +309,50 @@ fn verify_paths(
295309
&CanisterId::unchecked_from_principal(principal_id),
296310
&name,
297311
state,
298-
)?
312+
)?;
313+
metrics.observe_read_state_path(ENDPOINT, "canister_metadata");
314+
}
315+
[b"api_boundary_nodes"] => {
316+
metrics.observe_read_state_path(ENDPOINT, "api_boundary_nodes");
299317
}
300-
[b"api_boundary_nodes"] => {}
301318
[b"api_boundary_nodes", _node_id]
302319
| [
303320
b"api_boundary_nodes",
304321
_node_id,
305322
b"domain" | b"ipv4_address" | b"ipv6_address",
306-
] => {}
307-
[b"subnet"] => {}
308-
[b"subnet", _subnet_id] | [b"subnet", _subnet_id, b"public_key" | b"node"] => {}
323+
] => {
324+
metrics.observe_read_state_path(ENDPOINT, "api_boundary_nodes_info");
325+
}
326+
[b"subnet"] => {
327+
metrics.observe_read_state_path(ENDPOINT, "subnet");
328+
}
329+
[b"subnet", _subnet_id] => {
330+
metrics.observe_read_state_path(ENDPOINT, "subnet_info");
331+
}
332+
[b"subnet", _subnet_id, b"public_key"] => {
333+
metrics.observe_read_state_path(ENDPOINT, "subnet_public_key");
334+
}
335+
[b"subnet", _subnet_id, b"node"] => {
336+
metrics.observe_read_state_path(ENDPOINT, "subnet_node");
337+
}
338+
[b"subnet", _subnet_id, b"node", _node_id] => {
339+
metrics.observe_read_state_path(ENDPOINT, "subnet_node_info");
340+
}
341+
[b"subnet", _subnet_id, b"node", _node_id, b"public_key"] => {
342+
metrics.observe_read_state_path(ENDPOINT, "subnet_node_public_key");
343+
}
309344
// `/subnet/<subnet_id>/canister_ranges` is always allowed on the `/api/v2` endpoint
310-
[b"subnet", _subnet_id, b"canister_ranges"] if version == Version::V2 => {}
345+
[b"subnet", _subnet_id, b"canister_ranges"] if version == Version::V2 => {
346+
metrics.observe_read_state_path(ENDPOINT, "subnet_canister_ranges");
347+
}
311348
// `/subnet/<subnet_id>/canister_ranges` is allowed on the `/api/v3` endpoint
312349
// only when `subnet_id == nns_subnet_id`.
313350
[b"subnet", subnet_id, b"canister_ranges"]
314351
if version == Version::V3
315-
&& parse_principal_id(subnet_id)? == nns_subnet_id.get() => {}
316-
[b"subnet", _subnet_id, b"node", _node_id]
317-
| [b"subnet", _subnet_id, b"node", _node_id, b"public_key"] => {}
352+
&& parse_principal_id(subnet_id)? == nns_subnet_id.get() =>
353+
{
354+
metrics.observe_read_state_path(ENDPOINT, "subnet_canister_ranges");
355+
}
318356
[b"request_status", request_id]
319357
| [
320358
b"request_status",
@@ -365,6 +403,7 @@ fn verify_paths(
365403
.to_string(),
366404
});
367405
}
406+
metrics.observe_read_state_path(ENDPOINT, "request_status");
368407
}
369408
_ => {
370409
// All other paths are unsupported.
@@ -427,6 +466,7 @@ mod test {
427466
};
428467
use hyper::StatusCode;
429468
use ic_crypto_tree_hash::{Digest, Label, MixedHashTree, Path};
469+
use ic_metrics::MetricsRegistry;
430470
use ic_registry_subnet_type::SubnetType;
431471
use ic_replicated_state::{
432472
CanisterQueues, RefundPool, ReplicatedState, SystemMetadata,
@@ -442,6 +482,10 @@ mod test {
442482
const NNS_SUBNET_ID: SubnetId = SUBNET_0;
443483
const APP_SUBNET_ID: SubnetId = SUBNET_1;
444484

485+
fn test_metrics() -> HttpHandlerMetrics {
486+
HttpHandlerMetrics::new(&MetricsRegistry::new())
487+
}
488+
445489
#[test]
446490
fn encoding_read_state_tree_empty() {
447491
let tree = MixedHashTree::Empty;
@@ -565,9 +609,11 @@ mod test {
565609

566610
#[rstest]
567611
fn test_canister_ranges_are_not_allowed(#[values(Version::V2, Version::V3)] version: Version) {
612+
let metrics = test_metrics();
568613
let state = fake_replicated_state();
569614

570615
let error = verify_paths(
616+
&metrics,
571617
version,
572618
&state,
573619
&user_test_id(1),
@@ -586,9 +632,11 @@ mod test {
586632

587633
#[rstest]
588634
fn test_verify_path(#[values(Version::V2, Version::V3)] version: Version) {
635+
let metrics = test_metrics();
589636
let state = fake_replicated_state();
590637
assert_eq!(
591638
verify_paths(
639+
&metrics,
592640
version,
593641
&state,
594642
&user_test_id(1),
@@ -602,6 +650,7 @@ mod test {
602650

603651
assert_eq!(
604652
verify_paths(
653+
&metrics,
605654
version,
606655
&state,
607656
&user_test_id(1),
@@ -625,6 +674,7 @@ mod test {
625674
);
626675

627676
let err = verify_paths(
677+
&metrics,
628678
version,
629679
&state,
630680
&user_test_id(1),
@@ -643,8 +693,10 @@ mod test {
643693
#[test]
644694
fn deprecated_canister_ranges_path_is_not_allowed_on_the_v3_endpoint_except_for_the_nns_subnet()
645695
{
696+
let metrics = test_metrics();
646697
let state = fake_replicated_state();
647698
let err = verify_paths(
699+
&metrics,
648700
Version::V3,
649701
&state,
650702
&user_test_id(1),
@@ -662,6 +714,7 @@ mod test {
662714

663715
assert!(
664716
verify_paths(
717+
&metrics,
665718
Version::V3,
666719
&state,
667720
&user_test_id(1),
@@ -680,9 +733,11 @@ mod test {
680733

681734
#[test]
682735
fn deprecated_canister_ranges_path_is_allowed_on_the_v2_endpoint() {
736+
let metrics = test_metrics();
683737
let state = fake_replicated_state();
684738
assert!(
685739
verify_paths(
740+
&metrics,
686741
Version::V2,
687742
&state,
688743
&user_test_id(1),
@@ -698,4 +753,77 @@ mod test {
698753
.is_ok()
699754
);
700755
}
756+
757+
#[test]
758+
fn test_verify_paths_records_metrics() {
759+
let metrics = test_metrics();
760+
let state = fake_replicated_state();
761+
let canister_id = canister_test_id(1);
762+
763+
verify_paths(
764+
&metrics,
765+
Version::V2,
766+
&state,
767+
&user_test_id(1),
768+
&[
769+
Path::from(Label::from("time")),
770+
Path::new(vec![
771+
Label::from("canister"),
772+
canister_id.get().to_vec().into(),
773+
Label::from("module_hash"),
774+
]),
775+
Path::new(vec![Label::from("api_boundary_nodes")]),
776+
Path::new(vec![
777+
Label::from("subnet"),
778+
APP_SUBNET_ID.get().to_vec().into(),
779+
Label::from("public_key"),
780+
]),
781+
Path::new(vec![
782+
Label::from("request_status"),
783+
[0; 32].into(),
784+
Label::from("status"),
785+
]),
786+
],
787+
&CanisterIdSet::all(),
788+
canister_id.get(),
789+
NNS_SUBNET_ID,
790+
)
791+
.unwrap();
792+
793+
assert_eq!(
794+
metrics
795+
.read_state_path_type_total
796+
.with_label_values(&["canister", "time"])
797+
.get(),
798+
1
799+
);
800+
assert_eq!(
801+
metrics
802+
.read_state_path_type_total
803+
.with_label_values(&["canister", "canister_info"])
804+
.get(),
805+
1
806+
);
807+
assert_eq!(
808+
metrics
809+
.read_state_path_type_total
810+
.with_label_values(&["canister", "api_boundary_nodes"])
811+
.get(),
812+
1
813+
);
814+
assert_eq!(
815+
metrics
816+
.read_state_path_type_total
817+
.with_label_values(&["canister", "subnet_public_key"])
818+
.get(),
819+
1
820+
);
821+
assert_eq!(
822+
metrics
823+
.read_state_path_type_total
824+
.with_label_values(&["canister", "request_status"])
825+
.get(),
826+
1
827+
);
828+
}
701829
}

0 commit comments

Comments
 (0)