This repo is a modified version of Canistergeek-IC-Motoko. This tool is an open-source project for Internet Computer to track Origyn NFT canisters cycles and memory status and collect log messages.
Canistergeek-Motoko can be integrated into your canisters as motoko library which exposes following components:
Monitor
- public class that collects the data for specific canister by 5 minutes intervals.Logger
- public class that collects log messages for specific canister.
Canistergeek-Motoko should be used together with Canistergeek-JS - Javascript library that fetches the data from canisters, performs all necessary calculations and displays it on a webpage
Monitor
- stored data for cycles and memory consumes ~6.5Mb per year per canister (assuming data points every 5 minutes).Logger
- depends on the length of messages and their number. (canister with e.g. 10000 messages with 4096 characters each consumes 120Mb after upgrade).
Data can be collected in two ways: automatically and manually
- Manually by calling
collectCanisterMetrics
public method of your canister. - Automatically by calling
canistergeekMonitor.collectMetrics();
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 ExperimentalCycles.balance()
.
Monitor collects how many memory bytes the canister consumes at particular time using Prim.rts_memory_size()
.
Monitor collects how many heap memory bytes the canister consumes at particular time using Prim.rts_heap_size()
.
Log messages can be collected by calling canistergeekLogger.logMessage(msg: Text)
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.
Vessel is a package manager for Motoko. Learn more.
- Add canistergeek to your
package-set.dhall
:
let
additions =
[{ name = "canistergeek"
, repo = "https://github.com/ORIGYN-SA/canistergeek-motoko"
, version = "v0.0.2"
, dependencies = ["base", "candy" ]
}] : List Package
- Add canistergeek to your
vessel.dhall
:
dependencies = [ ..., "canistergeek" ],
- Import library in your canister
import Canistergeek "mo:canistergeek/canistergeek";
- Copy
src
folder from this repository into your project, renaming it tocanistergeek
. - Import library in your canister
import Canistergeek "../canistergeek/canistergeek";
Please perform the following steps
Initialize Canistergeek Monitor as a private constant.
actor {
private let canistergeekMonitor = Canistergeek.Monitor();
}
Add stable variable and implement pre/post upgrade hooks. This step is necessary to save collected data between canister upgrades.
actor {
stable var _canistergeekMonitorUD: ? Canistergeek.UpgradeData = null;
system func preupgrade() {
_canistergeekMonitorUD := ? canistergeekMonitor.preupgrade();
};
system func postupgrade() {
canistergeekMonitor.postupgrade(_canistergeekMonitorUD);
_canistergeekMonitorUD := null;
};
}
Implement public methods in the canister in order to query collected data and optionally force collecting the data
actor {
// CANISTER MONITORING
/**
* Returns collected data based on passed parameters.
* Called from browser.
*/
public query ({caller}) func getCanisterMetrics(parameters: Canistergeek.GetMetricsParameters): async ?Canistergeek.CanisterMetrics {
validateCaller(caller);
canistergeekMonitor.getMetrics(parameters);
};
/**
* Force collecting the data at current time.
* Called from browser or any canister "update" method.
*/
public shared ({caller}) func collectCanisterMetrics(): async () {
validateCaller(caller);
canistergeekMonitor.collectMetrics();
};
private func validateCaller(principal: Principal) : () {
//limit access here!
};
}
Call canistergeekMonitor.collectMetrics()
method in all "update" methods in your canister in order to automatically collect all data.
actor {
public shared ({caller}) func doThis(): async () {
canistergeekMonitor.collectMetrics();
// rest part of the your method...
};
public shared ({caller}) func doThat(): async () {
canistergeekMonitor.collectMetrics();
// rest part of the your method...
};
}
Please perform the following steps
Initialize Canistergeek Logger as a private constant.
actor {
private let canistergeekLogger = Canistergeek.Logger();
}
Add stable variable and implement pre/post upgrade hooks. This step is necessary to save collected log messages between canister upgrades.
actor {
stable var _canistergeekLoggerUD: ? Canistergeek.LoggerUpgradeData = null;
system func preupgrade() {
_canistergeekLoggerUD := ? canistergeekLogger.preupgrade();
};
system func postupgrade() {
canistergeekLogger.postupgrade(_canistergeekLoggerUD);
_canistergeekLoggerUD := null;
//Optional: override default number of log messages to your value
canistergeekLogger.setMaxMessagesCount(3000);
};
}
Implement public methods in the canister in order to query collected log messages
actor {
// CANISTER LOGGER
/**
* Returns collected log messages based on passed parameters.
* Called from browser.
*/
public query ({caller}) func getCanisterLog(request: ?Canistergeek.CanisterLogRequest) : async ?Canistergeek.CanisterLogResponse {
validateCaller(caller);
canistergeekLogger.getLog(request);
};
private func validateCaller(principal: Principal) : () {
//limit access here!
};
}
Call canistergeekLogger.logMessage()
method where you want to log a message.
actor {
public shared ({caller}) func doThis(): async () {
canistergeekLogger.logMessage("doThis");
// rest part of the your method...
};
public shared ({caller}) func doThat(): async () {
canistergeekLogger.logMessage("doThat");
// rest part of the your method...
};
}
π΄π΄π΄ We highly recommend limiting access by checking caller principal π΄π΄π΄
actor {
private let adminPrincipal: Text = "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxx";
private func validateCaller(principal: Principal) : () {
//data is available only for specific principal
if (not (Principal.toText(principal) == adminPrincipal)) {
Prelude.unreachable();
}
};
}
import Time "mo:base/Time";
import Canistergeek "../canistergeek/canistergeek";
actor {
stable var _canistergeekMonitorUD: ? Canistergeek.UpgradeData = null;
private let canistergeekMonitor = Canistergeek.Monitor();
stable var _canistergeekLoggerUD: ? Canistergeek.LoggerUpgradeData = null;
private let canistergeekLogger = Canistergeek.Logger();
private let adminPrincipal: Text = "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxx";
system func preupgrade() {
_canistergeekMonitorUD := ? canistergeekMonitor.preupgrade();
_canistergeekLoggerUD := ? canistergeekLogger.preupgrade();
};
system func postupgrade() {
canistergeekMonitor.postupgrade(_canistergeekMonitorUD);
_canistergeekMonitorUD := null;
canistergeekLogger.postupgrade(_canistergeekLoggerUD);
_canistergeekLoggerUD := null;
canistergeekLogger.setMaxMessagesCount(3000);
canistergeekLogger.logMessage("postupgrade");
};
public query ({caller}) func getCanisterMetrics(parameters: Canistergeek.GetMetricsParameters): async ?Canistergeek.CanisterMetrics {
validateCaller(caller);
canistergeekMonitor.getMetrics(parameters);
};
public shared ({caller}) func collectCanisterMetrics(): async () {
validateCaller(caller);
canistergeekMonitor.collectMetrics();
};
public query ({caller}) func getCanisterLog(request: ?Canistergeek.CanisterLogRequest) : async ?Canistergeek.CanisterLogResponse {
validateCaller(caller);
return canistergeekLogger.getLog(request);
};
private func validateCaller(principal: Principal) : () {
//data is available only for specific principal
if (not (Principal.toText(principal) == adminPrincipal)) {
Prelude.unreachable();
}
};
public shared ({caller}) func doThis(): async () {
canistergeekMonitor.collectMetrics();
canistergeekLogger.logMessage("doThis");
// rest part of the your method...
};
public shared ({caller}) func doThat(): async () {
canistergeekMonitor.collectMetrics();
canistergeekLogger.logMessage("doThat");
// rest part of the your method...
};
public query ({caller}) func getThis(): async Text {
"this";
};
public query ({caller}) func getThat(): async Text {
"that";
};
}
edit to trigger clone