Simple pull-based metrics canister that reads and saves data from your application canisters on a specified schedule. Scheduling is currently done off-chain.
For example, you can:
- Track user count every day
- Track memory usage every 2 hours
- Track ICP balance every 15 minutes
Tracked data must be a Nat
or Int
. Currently, the minimum polling frequency is one minute. If you have more real-time data, such as markets data, this is probably not the most optimal solution.
The Metrics service makes all of its tracked data public and is consumed by ic.rocks.
Grab the candid or motoko types. In your application canister:
import T "Types";
...
// This is the single value you want to track
// Must be a shared query function returning Nat or Int
public shared query func get_user_count() : async Nat { ... };
let Metrics = actor "bsusq-diaaa-aaaah-qac5q-cai" : T.MetricsService;
let response = await Metrics.track({
// Track a new attribute
attributeId = null;
action = #Set({
name = "user_count";
// Optional description
description = ?"Number of users who signed up.";
// Getter function to read the data value
getter = get_user_count;
// If frequency is specified, the Metrics service will run on this schedule
polling_frequency = ?{
n = 5;
period = #Minute;
}
})
});
// Save the attributeId
let attributeId = switch(response) {
case (#ok(id)) ?id;
case (#err(error)) null;
};
A rust example can be found here.
let track_args = TrackerRequest {
attributeId: None,
action: Action::Set(AttributeDescription {
name: String::from("rust-counter"),
description: Some(String::from("A demo from rust")),
polling_frequency: Some(Frequency {
n: Nat::from(5),
period: Period::Minute,
}),
getter: Func {
principal: Principal::from_text("r7inp-6aaaa-aaaaa-aaabq-cai").unwrap(),
method: String::from("get"),
},
}),
};
let result: CallResult<(MetricsResponse,)> = ic_cdk::api::call::call(
Principal::from_text("bsusq-diaaa-aaaah-qac5q-cai").unwrap(),
"track",
(&track_args,),
)
.await;
You can read raw data or get data by minute/hour/day/week. If period
is specified, this will return the latest data point that is before each time period. Returns a maximum of 200 data points. You may specify a before
timestamp for pagination.
Metrics.recordById({
attributeId = someId;
before = null;
limit = null;
period = ?(#Day);
})
List all tracked data attributes for this Principal.
let myId : Principal = "...";
Metrics.attributesByPrincipal(myId);
You can modify details like name, description, or schedule. Only the principal that requested the tracking can modify it.
Metrics.track({
attributeId = ?attributeId;
action = #Set({
name = "user_count_hourly";
description = ?"Number of users who signed up, per hour.";
getter = get_user_count;
polling_frequency = ?{
n = 1;
period = #Hour;
}
})
})
You can pause tracking temporarily and resume later.
Metrics.track({
attributeId = ?attributeId;
action = #Pause;
})
Metrics.track({
attributeId = ?attributeId;
action = #Unpause;
})
You can also delete the entire data attribute and history by specifying the #Delete
action. WARNING: This is non-reversible!
Add these functions for a simple way to introspect cycles and memory.
import Prim "mo:prim";
import ExperimentalCycles "mo:base/ExperimentalCycles";
public query func memory() : async Nat {
Prim.rts_heap_size()
};
public query func cycles() : async Nat {
ExperimentalCycles.balance()
};
Then, track these attributes as usual.
You can deploy a metrics canister and run your own scheduler. The default implementation uses node-cron.
dfx canister create metrics
dfx build metrics
dfx deploy metrics
cd scheduler
npm i
npm start