Skip to content
Open
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions rs/ledger_suite/common/ledger_canister_core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ rust_library(
"//rs/utils",
"@crate_index//:candid",
"@crate_index//:ic-cdk",
"@crate_index//:ic-metrics-encoder",
"@crate_index//:ic-stable-structures",
"@crate_index//:serde",
],
Expand Down
1 change: 1 addition & 0 deletions rs/ledger_suite/common/ledger_canister_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ic-limits = { path = "../../../limits" }
ic-ledger-core = { path = "../ledger_core" }
ic-ledger-hash-of = { path = "../../../../packages/ic-ledger-hash-of" }
ic-management-canister-types-private = { path = "../../../types/management_canister_types" }
ic-metrics-encoder = { workspace = true }
ic-stable-structures = { workspace = true }
ic-utils = { path = "../../../utils" }
serde = { workspace = true }
1 change: 1 addition & 0 deletions rs/ledger_suite/common/ledger_canister_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod archive;
pub mod blockchain;
pub mod ledger;
pub mod metrics;
pub mod range_utils;
pub mod runtime;
mod spawn;
88 changes: 88 additions & 0 deletions rs/ledger_suite/common/ledger_canister_core/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Metrics that both ledgers expose identically.

use crate::archive::{Archive, ArchiveCanisterWasm};
use crate::ledger::LedgerData;
use crate::runtime::Runtime;
use ic_metrics_encoder::MetricsEncoder;

/// Exposes the archiving configuration that is otherwise not observable from
/// outside the canister, with the defaults of the optional `ArchiveOptions`
/// fields already applied.
///
/// `trigger_threshold`, `num_blocks_to_archive` and `max_message_size_bytes`
/// govern the ledger's own behaviour and take effect immediately. The memory cap
/// and the creation cycles are only used when the ledger spawns a *new* archive:
/// an archive that already exists keeps the values it was installed with, and
/// reports them through its own metrics.
///
/// `effective_node_max_memory_size_bytes` is passed in because the two ledgers
/// resolve it differently: an ICRC archive clamps the cap it is given, an ICP
/// archive takes it as-is. The caller is the only one that knows which applies.
///
/// `max_transactions_per_response` is deliberately not reported here. Only the
/// ICRC archive has that setting, so the ICRC ledger emits it at its call site.
pub fn encode_archive_config_metrics<Rt, Wasm>(
w: &mut MetricsEncoder<Vec<u8>>,
archive: &Archive<Rt, Wasm>,
effective_node_max_memory_size_bytes: u64,
) -> std::io::Result<()>
where
Rt: Runtime,
Wasm: ArchiveCanisterWasm,
{
w.encode_gauge(
"ledger_archive_trigger_threshold",
archive.trigger_threshold as f64,
"The number of blocks which, when exceeded, triggers archiving.",
)?;
w.encode_gauge(
"ledger_archive_num_blocks_to_archive",
archive.num_blocks_to_archive as f64,
"The number of blocks archived when the trigger threshold is exceeded.",
)?;
w.encode_gauge(
"ledger_archive_node_max_memory_size_bytes",
effective_node_max_memory_size_bytes as f64,
"Maximum number of bytes an archive spawned from now on may store. Existing \
archives keep the cap they were created with, reported by their own metric.",
)?;
w.encode_gauge(
"ledger_archive_max_message_size_bytes",
archive.max_message_size_bytes as f64,
"Archive option limiting the size in bytes of a message sent to an archive. \
The size actually used is the smaller of this and the ledger's own \
ledger_max_message_size_bytes.",
)?;
w.encode_gauge(
"ledger_archive_cycles_for_archive_creation",
archive.cycles_for_archive_creation as f64,
"Cycles that will be attached to the call creating the next archive canister.",
)?;
Ok(())
}

/// Exposes the transaction-deduplication configuration, which is not observable
/// from outside the canister. The window is a constant on the ICRC ledger and a
/// configured value on the ICP ledger; both are reported here as the effective
/// value in force.
pub fn encode_dedup_config_metrics<LD: LedgerData>(
w: &mut MetricsEncoder<Vec<u8>>,
ledger: &LD,
) -> std::io::Result<()> {
w.encode_gauge(
"ledger_transaction_window_seconds",
ledger.transaction_window().as_secs() as f64,
"Length of the transaction deduplication window in seconds.",
)?;
w.encode_gauge(
"ledger_max_transactions_in_window",
ledger.max_transactions_in_window() as f64,
"Maximum number of transactions retained in the deduplication window.",
)?;
w.encode_gauge(
"ledger_max_transactions_to_purge",
ledger.max_transactions_to_purge() as f64,
"Maximum number of transactions purged from the deduplication window per operation.",
)?;
Ok(())
}
7 changes: 7 additions & 0 deletions rs/ledger_suite/icp/ledger/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use ic_cdk::{post_upgrade, pre_upgrade, query, update};
use ic_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_icrc1::endpoints::{StandardRecord, convert_transfer_error};
use ic_ledger_canister_core::ledger::{LedgerContext, LedgerData};
use ic_ledger_canister_core::metrics::{
encode_archive_config_metrics, encode_dedup_config_metrics,
};
use ic_ledger_canister_core::runtime::heap_memory_size_bytes;
use ic_ledger_canister_core::{
archive::{Archive, ArchiveOptions},
Expand Down Expand Up @@ -1144,6 +1147,10 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
*MAX_MESSAGE_SIZE_BYTES.read().unwrap() as f64,
"Maximum inter-canister message size in bytes.",
)?;
if let Some(archive) = archive_guard.as_ref() {
encode_archive_config_metrics(w, archive, archive.node_max_memory_size_bytes)?;
}
encode_dedup_config_metrics(w, &*ledger)?;
w.encode_gauge(
"ledger_stable_memory_pages",
ic_cdk::stable::stable_size() as f64,
Expand Down
75 changes: 73 additions & 2 deletions rs/ledger_suite/icp/ledger/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use ic_icrc1_test_utils::minter_identity;
use ic_ledger_core::block::BlockIndex;
use ic_ledger_core::{Tokens, block::BlockType};
use ic_ledger_suite_state_machine_helpers::{
AllowanceProvider, balance_of, icrc21_consent_message, send_approval, send_transfer,
send_transfer_from, supported_standards, total_supply, transfer,
AllowanceProvider, balance_of, icrc21_consent_message, parse_metric, retrieve_metrics,
send_approval, send_transfer, send_transfer_from, supported_standards, total_supply, transfer,
};
use ic_ledger_suite_state_machine_tests::archiving::icp_archives;
use ic_ledger_suite_state_machine_tests::{
Expand Down Expand Up @@ -2624,6 +2624,77 @@ fn test_burn_whole_balance() {
assert_eq!(balance_of(&env, canister_id, p1.0), 0);
}

#[test]
fn test_archive_and_dedup_config_metrics() {
const TRIGGER_THRESHOLD: usize = 17;
const NUM_BLOCKS_TO_ARCHIVE: usize = 5;
const NODE_MAX_MEMORY_SIZE_BYTES: u64 = 123_456;
const MAX_MESSAGE_SIZE_BYTES: u64 = 64 * 1024;
const CYCLES_FOR_ARCHIVE_CREATION: u64 = 7_000_000_000;
const TRANSACTION_WINDOW: Duration = Duration::from_secs(3600);

let env = StateMachine::new();
let payload = LedgerCanisterInitPayload::builder()
.minting_account(MINTER.into())
.icrc1_minting_account(MINTER)
.transfer_fee(Tokens::from_e8s(10_000))
.token_symbol_and_name("ICP", "Internet Computer")
.transaction_window(TRANSACTION_WINDOW)
.archive_options(ArchiveOptions {
trigger_threshold: TRIGGER_THRESHOLD,
num_blocks_to_archive: NUM_BLOCKS_TO_ARCHIVE,
node_max_memory_size_bytes: Some(NODE_MAX_MEMORY_SIZE_BYTES),
max_message_size_bytes: Some(MAX_MESSAGE_SIZE_BYTES),
controller_id: PrincipalId::new_user_test_id(100),
more_controller_ids: None,
cycles_for_archive_creation: Some(CYCLES_FOR_ARCHIVE_CREATION),
max_transactions_per_response: None,
})
.build()
.unwrap();
let ledger_id = env
.install_canister(ledger_wasm(), Encode!(&payload).unwrap(), None)
.expect("Unable to install the Ledger canister");

let metric = |name: &str| parse_metric(&env, ledger_id, name);

assert_eq!(
metric("ledger_archive_trigger_threshold"),
TRIGGER_THRESHOLD as u64
);
assert_eq!(
metric("ledger_archive_num_blocks_to_archive"),
NUM_BLOCKS_TO_ARCHIVE as u64
);
assert_eq!(
metric("ledger_archive_node_max_memory_size_bytes"),
NODE_MAX_MEMORY_SIZE_BYTES
);
assert_eq!(
metric("ledger_archive_max_message_size_bytes"),
MAX_MESSAGE_SIZE_BYTES
);
assert_eq!(
metric("ledger_archive_cycles_for_archive_creation"),
CYCLES_FOR_ARCHIVE_CREATION
);
assert!(
!retrieve_metrics(&env, ledger_id)
.iter()
.any(|line| line.starts_with("ledger_archive_max_transactions_per_response")),
"the ICP archive has no max_transactions_per_response setting, so the ICP ledger must not \
advertise one"
);

assert_eq!(
metric("ledger_transaction_window_seconds"),
TRANSACTION_WINDOW.as_secs(),
"the ICP ledger's transaction window is configurable, so the metric must reflect the configured value"
);
assert!(metric("ledger_max_transactions_in_window") > 0);
assert!(metric("ledger_max_transactions_to_purge") > 0);
}

#[test]
fn test_change_initially_set_archive_options() {
const ARCHIVE_TRIGGER_THRESHOLD: usize = 10;
Expand Down
23 changes: 18 additions & 5 deletions rs/ledger_suite/icrc1/archive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ const WASM_PAGE_SIZE: u64 = 65536;
const GIB: u64 = 1024 * 1024 * 1024;

/// How much memory do we want to allocate for raw blocks.
const DEFAULT_MEMORY_LIMIT: u64 = 3 * GIB;
use ic_icrc1::archive_limits::ARCHIVE_MEMORY_LIMIT as DEFAULT_MEMORY_LIMIT;

/// The maximum number of blocks to return in a single get_transactions request.
const DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE: u64 = 2000;
use ic_icrc1::archive_limits::DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE;

/// The maximum number of Wasm pages that we allow to use for the stable storage.
const NUM_WASM_PAGES: u64 = 4 * GIB / WASM_PAGE_SIZE;
Expand Down Expand Up @@ -108,7 +108,7 @@ impl Default for ArchiveConfig {
max_memory_size_bytes: 0,
block_index_offset: 0,
ledger_id: Principal::management_canister(),
max_transactions_per_response: DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE,
max_transactions_per_response: DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE,
token_type: wasm_token_type(),
}
}
Expand Down Expand Up @@ -165,8 +165,8 @@ fn init(
let max_memory_size_bytes = max_memory_size_bytes
.unwrap_or(DEFAULT_MEMORY_LIMIT)
.min(DEFAULT_MEMORY_LIMIT);
let max_transactions_per_response = max_transactions_per_response
.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE);
let max_transactions_per_response =
max_transactions_per_response.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE);
cell.borrow_mut()
.set(ArchiveConfig {
max_memory_size_bytes,
Expand Down Expand Up @@ -423,6 +423,19 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
w.gauge_vec("cycle_balance", "Cycle balance on this canister.")?
.value(&[("canister", "icrc1-archive")], cycle_balance)?;

with_archive_opts(|opts| {
w.encode_gauge(
"archive_max_memory_size_bytes",
opts.max_memory_size_bytes as f64,
"Maximum number of bytes this archive can use to store encoded blocks.",
)?;
w.encode_gauge(
"archive_max_transactions_per_response",
opts.max_transactions_per_response as f64,
"Maximum number of transactions this archive returns per response.",
)
})?;

w.encode_gauge(
"archive_stored_blocks",
with_blocks(|blocks| blocks.len()) as f64,
Expand Down
27 changes: 27 additions & 0 deletions rs/ledger_suite/icrc1/ledger/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ic_cdk::init;
use ic_cdk::stable::StableReader;
use ic_cdk::{post_upgrade, pre_upgrade, query, update};
use ic_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_icrc1::archive_limits::{ARCHIVE_MEMORY_LIMIT, DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE};
use ic_icrc1::{
Operation, Transaction,
endpoints::{StandardRecord, convert_transfer_error},
Expand All @@ -21,6 +22,9 @@ use ic_ledger_canister_core::ledger::{
LedgerAccess, LedgerContext, LedgerData, TransferError as CoreTransferError, apply_transaction,
archive_blocks,
};
use ic_ledger_canister_core::metrics::{
encode_archive_config_metrics, encode_dedup_config_metrics,
};
use ic_ledger_canister_core::runtime::heap_memory_size_bytes;
use ic_ledger_core::block::BlockIndex;
use ic_ledger_core::timestamp::TimeStamp;
Expand Down Expand Up @@ -402,6 +406,23 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
num_archives as f64,
"Total number of archives.",
)?;
if let Some(archive) = archive_guard.as_ref() {
encode_archive_config_metrics(
w,
archive,
archive.node_max_memory_size_bytes.min(ARCHIVE_MEMORY_LIMIT),
)?;
w.encode_gauge(
"ledger_archive_max_transactions_per_response",
archive
.max_transactions_per_response
.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE)
as f64,
"Maximum number of transactions an archive spawned from now on will \
return per response. Existing archives keep the limit they were created \
with, reported by their own archive_max_transactions_per_response metric.",
)?;
}
}
Err(err) => Err(std::io::Error::other(format!(
"Failed to read number of archives: {err}"
Expand All @@ -412,6 +433,12 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
ledger.approvals().get_num_approvals() as f64,
"Total number of approvals.",
)?;
w.encode_gauge(
"ledger_max_message_size_bytes",
MAX_MESSAGE_SIZE as f64,
"Maximum inter-canister message size in bytes.",
)?;
encode_dedup_config_metrics(w, ledger)?;
Ok(())
})
}
Expand Down
16 changes: 16 additions & 0 deletions rs/ledger_suite/icrc1/ledger/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,22 @@ fn test_get_all_blocks_with_archiving_disabled() {
);
}

#[test]
fn test_archive_reports_its_own_config_metrics() {
ic_ledger_suite_state_machine_tests::test_archive_reports_its_own_config_metrics(
ledger_wasm(),
encode_init_args,
);
}

#[test]
fn test_archive_and_dedup_config_metrics() {
ic_ledger_suite_state_machine_tests::test_archive_and_dedup_config_metrics(
ledger_wasm(),
encode_init_args,
);
}

#[test]
fn test_upgrade_archive_options() {
ic_ledger_suite_state_machine_tests::test_upgrade_archive_options(
Expand Down
10 changes: 10 additions & 0 deletions rs/ledger_suite/icrc1/src/archive_limits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Limits the ICRC archive canister applies to itself.

/// The default maximum number of transactions returned by an ICRC archive's
/// `get_transactions` endpoint, applied when `max_transactions_per_response` is
/// not set.
pub const DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE: u64 = 2000;

/// The hard upper bound an ICRC archive places on the number of bytes it will
/// use to store encoded blocks.
pub const ARCHIVE_MEMORY_LIMIT: u64 = 3 * 1024 * 1024 * 1024;
1 change: 1 addition & 0 deletions rs/ledger_suite/icrc1/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod archive_limits;
pub mod blocks;
mod compact_account;
pub mod endpoints;
Expand Down
Loading
Loading