@@ -5,6 +5,7 @@ use super::{
55use crate :: {
66 HttpError , ReplicaHealthStatus ,
77 common:: { Cbor , WithTimeout , build_validator, validation_error_to_http_error} ,
8+ metrics:: HttpHandlerMetrics ,
89} ;
910
1011use axum:: {
@@ -57,6 +58,7 @@ pub enum Version {
5758pub 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 {
6971pub 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 {
9194impl 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.
259267fn 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