canistergeek_ic_rust
is the open-source tool for Internet Computer to track your project canisters cycles and memory status and collect log messages.
canistergeek_ic_rust
can be integrated into your canisters as rust library which exposes following modules:
- canistergeek_ic_rust::monitor
- public module that collects the data for specific canister by 5 minutes intervals.
- canistergeek_ic_rust::logger
- public module that collects log messages for specific canister.
canistergeek_ic_rust
should be used together with Canistergeek-IC-JS - Javascript library that fetches the data from canisters, performs all necessary calculations and displays it on a webpage
canistergeek_ic_rust::monitor
- stored data for cycles and memory consumes ~6.5Mb per year per canister (assuming data points every 5 minutes).canistergeek_ic_rust::logger
- depends on the length of messages and their number. (There is an issue with heap memory size after upgrade).Data can be collected in two ways: automatically and manually
collectCanisterMetrics
external public methodcanistergeek_ic_rust::monitor::collec_metrics();
in "update" methods in your canister to guarantee desired "Collect metrics" frequency. In some cases you may want to collect metrics in every "update" method to get the full picture in realtime and see how "update" methods influence canister price and capacity.Monitor collects the number of canister update calls
Monitor collects how many cycles left at particular time using ic_cdk::api::canister_balance()
.
Monitor collects how many memory bytes the canister consumes at particular time using ic_cdk::api::stable::stable64_size() * WASM_PAGE_SIZE + core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE
.
Monitor collects how many heap memory bytes the canister consumes at particular time using core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE
.
Log messages can be collected by calling canistergeek_ic_rust::logger::log_message(message: String);
method in "update" methods in your canister.
Logger collects time/message pairs with a maximum message length of 4096 characters.
Default number of messages (10000) can be overridden with corresponding method in realtime.
In file Cargo.toml
your project, add dependency on crate:
toml
canistergeek_ic_rust = "0.2.1"
Implement public methods in the canister in order to query collected log messages
```rust
// CANISTER LOGGER
/// Returns collected log messages based on passed parameters. Called from browser. ///
pub async fn getcanisterlog(request: canistergeekicrust::apitype::CanisterLogRequest) -> Option
fn validate_caller() -> () { // limit access here! } ```
Implement public methods in the canister in order to query collected data and optionally force collecting the data
```rust
// CANISTER MONITORING
/// Returns collected data based on passed parameters. Called from browser. ///
pub async fn getcanistermetrics(parameters: canistergeekicrust::apitype::GetMetricsParameters) -> Option
/// Force collecting the data at current time.
/// Called from browser or any canister update
method.
///
pub async fn collectcanistermetrics() -> () { validatecaller(); canistergeekicrust::monitor::collectmetrics(); }
fn validate_caller() -> () { // limit access here! } ```
Call canistergeek_ic_rust::monitor::collect_metrics()
method in all "update" methods in your canister in order to automatically collect all data.
Implement pre/post upgrade hooks. This step is necessary to save collected data between canister upgrades.
```rust
fn preupgradefunction() { let monitorstabledata = canistergeekicrust::monitor::preupgradestabledata(); let loggerstabledata = canistergeekicrust::logger::preupgradestabledata(); iccdk::storage::stablesave((monitorstabledata, loggerstabledata)); }
fn postupgradefunction() { let stabledata: Result<(canistergeekicrust::monitor::PostUpgradeStableData, canistergeekicrust::logger::PostUpgradeStableData), String> = iccdk::storage::stablerestore(); match stabledata { Ok((monitorstabledata, loggerstabledata)) => { canistergeekicrust::monitor::postupgradestabledata(monitorstabledata); canistergeekicrust::logger::postupgradestabledata(loggerstabledata); } Err(_) => {} } } ```
did
fileIn your canister did file your_canister.did
, add next declaration:
```candid ...
type UpdateCallsAggregatedData = vec nat64; type NumericEntity = record { avg: nat64; first: nat64; last: nat64; max: nat64; min: nat64; }; type Nanos = nat64; type MetricsGranularity = variant { daily; hourly; }; type LogMessagesData = record { message: text; timeNanos: Nanos; }; type HourlyMetricsData = record { canisterCycles: CanisterCyclesAggregatedData; canisterHeapMemorySize: CanisterHeapMemoryAggregatedData; canisterMemorySize: CanisterMemoryAggregatedData; timeMillis: int; updateCalls: UpdateCallsAggregatedData; }; type GetMetricsParameters = record { dateFromMillis: nat; dateToMillis: nat; granularity: MetricsGranularity; }; type GetLogMessagesParameters = record { count: nat32; filter: opt GetLogMessagesFilter; fromTimeNanos: opt Nanos; }; type GetLogMessagesFilter = record { analyzeCount: nat32; messageContains: opt text; messageRegex: opt text; }; type GetLatestLogMessagesParameters = record { count: nat32; filter: opt GetLogMessagesFilter; upToTimeNanos: opt Nanos; }; type DailyMetricsData = record { canisterCycles: NumericEntity; canisterHeapMemorySize: NumericEntity; canisterMemorySize: NumericEntity; timeMillis: int; updateCalls: nat64; }; type CanisterMetricsData = variant { daily: vec DailyMetricsData; hourly: vec HourlyMetricsData; }; type CanisterMetrics = record {data: CanisterMetricsData;}; type CanisterMemoryAggregatedData = vec nat64; type CanisterLogResponse = variant { messages: CanisterLogMessages; messagesInfo: CanisterLogMessagesInfo; }; type CanisterLogRequest = variant { getLatestMessages: GetLatestLogMessagesParameters; getMessages: GetLogMessagesParameters; getMessagesInfo; }; type CanisterLogMessagesInfo = record { count: nat32; features: vec opt CanisterLogFeature; firstTimeNanos: opt Nanos; lastTimeNanos: opt Nanos; }; type CanisterLogMessages = record { data: vec LogMessagesData; lastAnalyzedMessageTimeNanos: opt Nanos; }; type CanisterLogFeature = variant { filterMessageByContains; filterMessageByRegex; }; type CanisterHeapMemoryAggregatedData = vec nat64; type CanisterCyclesAggregatedData = vec nat64; service : { ... collectCanisterMetrics: () -> (); getCanisterLog: (opt CanisterLogRequest) -> (opt CanisterLogResponse) query; getCanisterMetrics: (GetMetricsParameters) -> (opt CanisterMetrics) query; }
```
🔴🔴🔴 We highly recommend limiting access by checking caller principal 🔴🔴🔴
rust
fn validate_caller() {
match ic_cdk::export::Principal::from_text("hozae-racaq-aaaaa-aaaaa-c") {
Ok(caller) if caller == ic_cdk::caller() => (),
_ => ic_cdk::trap("Invalid caller")
}
}
```rust
fn preupgradefunction() { let monitorstabledata = canistergeekicrust::monitor::preupgradestabledata(); let loggerstabledata = canistergeekicrust::logger::preupgradestabledata(); iccdk::storage::stablesave((monitorstabledata, loggerstabledata)); }
fn postupgradefunction() { let stabledata: Result<(canistergeekicrust::monitor::PostUpgradeStableData, canistergeekicrust::logger::PostUpgradeStableData), String> = iccdk::storage::stablerestore(); match stabledata { Ok((monitorstabledata, loggerstabledata)) => { canistergeekicrust::monitor::postupgradestabledata(monitorstabledata); canistergeekicrust::logger::postupgradestabledata(loggerstabledata); } Err(_) => {} } }
pub async fn getcanistermetrics(parameters: canistergeekicrust::apitype::GetMetricsParameters) -> Option
pub async fn collectcanistermetrics() -> () { validatecaller(); canistergeekicrust::monitor::collectmetrics(); }
pub async fn getcanisterlog(request: canistergeekicrust::apitype::CanisterLogRequest) -> Option
fn validatecaller() -> () { match iccdk::export::Principal::fromtext("hozae-racaq-aaaaa-aaaaa-c") { Ok(caller) if caller == iccdk::caller() => (), _ => ic_cdk::trap("Invalid caller") } }
pub async fn dothis() -> () { canistergeekicrust::monitor::collectmetrics(); canistergeekicrust::logger::logmessage(String::from("dothis")); // rest part of the your method... } ```