@@ -5,8 +5,9 @@ use super::subnet_call_context_manager::{
55} ;
66use super :: * ;
77use crate :: metadata_state:: testing:: SystemMetadataTesting ;
8+ use crate :: metrics:: ReplicatedStateMetrics ;
89use crate :: testing:: { CanisterQueuesTesting , StreamTesting } ;
9- use crate :: { CanisterPriority , InputQueueType } ;
10+ use crate :: { CanisterPriority , InputQueueType , ReplicatedState } ;
1011use assert_matches:: assert_matches;
1112use ic_crypto_test_utils_canister_threshold_sigs:: {
1213 CanisterThresholdSigTestEnvironment , IDkgParticipants , generate_ecdsa_presig_quadruple,
@@ -15,13 +16,16 @@ use ic_crypto_test_utils_canister_threshold_sigs::{
1516use ic_crypto_test_utils_reproducible_rng:: { ReproducibleRng , reproducible_rng} ;
1617use ic_error_types:: { ErrorCode , UserError } ;
1718use ic_limits:: MAX_INGRESS_TTL ;
19+ use ic_logger:: no_op_logger;
1820use ic_management_canister_types_private:: {
1921 EcdsaCurve , EcdsaKeyId , IC_00 , MasterPublicKeyId , SchnorrAlgorithm , SchnorrKeyId ,
2022} ;
23+ use ic_metrics:: MetricsRegistry ;
2124use ic_protobuf:: proxy:: ProxyDecodeError ;
2225use ic_protobuf:: state:: queues:: v1 as pb_queues;
2326use ic_protobuf:: state:: system_metadata:: v1 as pb_metadata;
2427use ic_registry_routing_table:: CanisterIdRange ;
28+ use ic_test_utilities_metrics:: fetch_gauge;
2529use ic_test_utilities_types:: ids:: {
2630 SUBNET_0 , SUBNET_1 , SUBNET_2 , canister_test_id, message_test_id, node_test_id, subnet_test_id,
2731 user_test_id,
@@ -2455,6 +2459,77 @@ fn consumed_cycles_total_calculates_the_right_amount() {
24552459 ) ;
24562460}
24572461
2462+ /// The `replicated_state_consumed_cycles_since_replica_started` gauge is
2463+ /// computed in `ReplicatedStateMetrics::observe` by summing the per-canister
2464+ /// totals with the subnet-level use cases. This test exercises every
2465+ /// subnet-level use case that contributes to the total, so that omitting any of
2466+ /// them (as the `SchnorrOutcalls`/`VetKd`/`DroppedMessages` use cases once were)
2467+ /// would change the reported value and fail the assertion. Distinct powers of
2468+ /// two are used so that a missing use case is always detectable in the total.
2469+ #[ test]
2470+ fn consumed_cycles_gauge_accounts_for_all_subnet_level_use_cases ( ) {
2471+ // The three use cases with a dedicated scalar field are also mirrored in the
2472+ // by-use-case map (as they are in production), while `SchnorrOutcalls`,
2473+ // `VetKd` and `DroppedMessages` live only in the map.
2474+ let mut consumed_cycles_by_use_case = BTreeMap :: new ( ) ;
2475+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: DeletedCanisters , NominalCycles :: new ( 1 ) ) ;
2476+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: ECDSAOutcalls , NominalCycles :: new ( 2 ) ) ;
2477+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: HTTPOutcalls , NominalCycles :: new ( 4 ) ) ;
2478+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: SchnorrOutcalls , NominalCycles :: new ( 8 ) ) ;
2479+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: VetKd , NominalCycles :: new ( 16 ) ) ;
2480+ consumed_cycles_by_use_case. insert ( CyclesUseCase :: DroppedMessages , NominalCycles :: new ( 32 ) ) ;
2481+
2482+ // The canister-level use cases are also present in the by-use-case map (in
2483+ // production they end up there via deleted canisters), but the gauge derives
2484+ // their contribution from the per-canister totals and the
2485+ // `consumed_cycles_by_deleted_canisters` scalar rather than from the map.
2486+ // Insert them with a large value to ensure they are *not* double-counted
2487+ // into the gauge total from the map.
2488+ for use_case in [
2489+ CyclesUseCase :: Memory ,
2490+ CyclesUseCase :: ComputeAllocation ,
2491+ CyclesUseCase :: IngressInduction ,
2492+ CyclesUseCase :: Instructions ,
2493+ CyclesUseCase :: RequestAndResponseTransmission ,
2494+ CyclesUseCase :: Uninstall ,
2495+ CyclesUseCase :: CanisterCreation ,
2496+ CyclesUseCase :: BurnedCycles ,
2497+ ] {
2498+ consumed_cycles_by_use_case. insert ( use_case, NominalCycles :: new ( 1024 ) ) ;
2499+ }
2500+
2501+ let subnet_metrics = SubnetMetrics {
2502+ consumed_cycles_by_deleted_canisters : NominalCycles :: new ( 1 ) ,
2503+ consumed_cycles_ecdsa_outcalls : NominalCycles :: new ( 2 ) ,
2504+ consumed_cycles_http_outcalls : NominalCycles :: new ( 4 ) ,
2505+ consumed_cycles_by_use_case,
2506+ ..Default :: default ( )
2507+ } ;
2508+
2509+ let mut state = ReplicatedState :: new ( subnet_test_id ( 1 ) , SubnetType :: Application ) ;
2510+ state. metadata . subnet_metrics = subnet_metrics;
2511+
2512+ let registry = MetricsRegistry :: new ( ) ;
2513+ let metrics = ReplicatedStateMetrics :: new ( & registry) ;
2514+ metrics. observe (
2515+ state. metadata . own_subnet_id ,
2516+ & state,
2517+ Height :: new ( 0 ) ,
2518+ & no_op_logger ( ) ,
2519+ ) ;
2520+
2521+ // Deleted canisters (1) + ECDSA (2) + HTTP (4) + Schnorr (8) + VetKd (16)
2522+ // + dropped messages (32) = 63. There are no canisters, so the per-canister
2523+ // contribution is zero, and the canister-level use cases inserted into the
2524+ // map above (each worth 1024) must not appear in the total.
2525+ let gauge = fetch_gauge (
2526+ & registry,
2527+ "replicated_state_consumed_cycles_since_replica_started" ,
2528+ )
2529+ . unwrap ( ) ;
2530+ assert_eq ! ( gauge, 63.0 ) ;
2531+ }
2532+
24582533#[ test]
24592534fn observe_use_case_migrates_outcalls_scalar_fields_into_use_cases ( ) {
24602535 let mut subnet_metrics = SubnetMetrics {
0 commit comments