Skip to content

Commit 5c6939d

Browse files
Merge pull request #623 from dfinity-berestovskyy/andriy/run-775-perf-counter
feat: RUN-775: Add `performance_counters` example
2 parents 349b5b7 + f22cd7a commit 5c6939d

File tree

6 files changed

+354
-0
lines changed

6 files changed

+354
-0
lines changed

rust/performance_counters/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "performance_counters"
3+
version = "1.0.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
candid = "0.9.11"
11+
ic-cdk = "0.11.3"
12+
ic-cdk-macros = "0.8.1"

rust/performance_counters/Makefile

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.PHONY: all
2+
all: build
3+
4+
.PHONY: build
5+
.SILENT: build
6+
build:
7+
dfx canister create --all
8+
dfx build
9+
10+
.PHONY: install
11+
.SILENT: install
12+
install: build
13+
dfx canister install performance_counters
14+
15+
.PHONY: upgrade
16+
.SILENT: upgrade
17+
upgrade: build
18+
dfx canister install performance_counters --mode=upgrade
19+
20+
.PHONY: deploy
21+
.SILENT: deploy
22+
deploy:
23+
# Deploy the canisters and run periodic tasks with 1s interval.
24+
dfx deploy performance_counters
25+
26+
.PHONY: test
27+
.SILENT: test
28+
test: deploy
29+
# Wait at least 2 seconds.
30+
sleep 2
31+
# Validate the counters are non-zero.
32+
dfx canister call performance_counters for_update | grep -vw '0' && echo 'PASS'
33+
dfx canister call performance_counters for_composite_query | grep -vw '0' && echo 'PASS'
34+
35+
.PHONY: clean
36+
.SILENT: clean
37+
clean:
38+
rm -fr .dfx

rust/performance_counters/README.md

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Performance counter
2+
3+
## Overview
4+
5+
The canister can query one of the "performance counters", which is a deterministic monotonically increasing integer approximating the amount of work the canister has done. Developers might use this data to profile and optimize the canister performance.
6+
7+
```Candid
8+
ic0.performance_counter : (counter_type : i32) -> i64
9+
````
10+
11+
The argument `type` decides which performance counter to return:
12+
13+
- 0 : current execution instruction counter.
14+
The number of WebAssembly instructions the canister has executed
15+
since the beginning of the current Message execution.
16+
17+
- 1 : call context instruction counter.
18+
19+
- For replicated message execution, it is the number of WebAssembly instructions
20+
the canister has executed within the call context of the current Message execution
21+
since Call context creation. The counter monotonically increases across all message
22+
executions in the call context until the corresponding call context is removed.
23+
24+
- For non-replicated message execution, it is the number of WebAssembly instructions
25+
the canister has executed within the corresponding `composite_query_helper`
26+
in Query call. The counter monotonically increases across the executions
27+
of the composite query method and the composite query callbacks
28+
until the corresponding `composite_query_helper` returns
29+
(ignoring WebAssembly instructions executed within any further downstream calls
30+
of `composite_query_helper`).
31+
32+
In the future, the IC might expose more performance counters.
33+
34+
## Tutorial
35+
36+
## Prerequisites
37+
38+
This example requires an installation of:
39+
40+
- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/setup/install/index.mdx).
41+
42+
### Step 1: Begin by opening a terminal window and navigating into the project's directory
43+
44+
```sh
45+
cd examples/rust/performance_counters
46+
```
47+
48+
### Step 2: Start a clean local Internet Computer replica and a web server
49+
50+
```sh
51+
dfx stop
52+
dfx start --clean
53+
```
54+
55+
This terminal will stay blocked, printing log messages, until the `Ctrl+C` is pressed or `dfx stop` command is run.
56+
57+
Example output:
58+
59+
```sh
60+
% dfx stop && dfx start --clean
61+
[...]
62+
Dashboard: http://localhost:63387/_/dashboard
63+
```
64+
65+
### Step 3: Open another terminal window in the same directory
66+
67+
```sh
68+
cd examples/rust/performance_counters
69+
```
70+
71+
### Step 4: Compile and deploy `performance_counters` canister
72+
73+
```sh
74+
dfx deploy
75+
```
76+
77+
Example output:
78+
79+
```sh
80+
% dfx deploy
81+
[...]
82+
Deployed canisters.
83+
URLs:
84+
Backend canister via Candid interface:
85+
performance_counters: http://127.0.0.1/...
86+
```
87+
88+
### Step 5: Call `performance_counters` canister `for_update` method
89+
90+
```sh
91+
dfx canister call performance_counters for_update
92+
```
93+
94+
Example output:
95+
96+
```sh
97+
% dfx canister call performance_counters for_update
98+
(6_618_678 : nat64, 19_886_107 : nat64)
99+
```
100+
101+
Note, how the current message execution counter (~6M instructions) is much different from the call context counter (~19M instructions).
102+
103+
### Step 6: Check the Internet Computer replica terminal window for more details
104+
105+
Example replica log output:
106+
107+
```text
108+
Performance counters for update call: current (0) call context (1)
109+
before the nested call: 6614001 6614201
110+
> inside the 1st nested call: 12425 12625
111+
after the 1st nested call: 6618836 13250387
112+
> inside the 2nd nested call: 12516 12716
113+
after the 2nd nested call: 6618678 19886107
114+
```
115+
116+
Note, how the current execution instruction counter (0) stays at ~6M instructions after each await point.
117+
By contrast, the call context performance counter (1) is monotonically increasing (~6M, ~13M, ~19M instructions).
118+
119+
Also note, that both counters start over for each nested execution (~12K instructions).
120+
121+
### Step 7: Repeat the steps above calling `for_composite_query` method
122+
123+
```sh
124+
dfx canister call performance_counters for_composite_query
125+
```
126+
127+
Example output:
128+
129+
```sh
130+
% dfx canister call performance_counters for_update
131+
(6_621_477 : nat64, 19_893_467 : nat64)
132+
```
133+
134+
Example replica log output:
135+
136+
```text
137+
Perf. counters for composite query call: current (0) call context (1)
138+
before the nested call: 6614001 6614201
139+
> inside the 1st nested call: 13567 13767
140+
after the 1st nested call: 6623158 13254766
141+
> inside the 2nd nested call: 13567 13767
142+
after the 2nd nested call: 6621477 19893467
143+
```
144+
145+
Note the same performance counters behavior for composite queries.
146+
147+
## Further learning
148+
149+
1. Have a look at the locally running dashboard. The URL is at the end of the `dfx start` command: `Dashboard: http://localhost/...`
150+
2. Check out the Candid user interface for `performance_counters` canister. The URL is at the end of the `dfx deploy` command: `performance_counters: http://127.0.0.1/...`
151+
152+
### Canister Interface
153+
154+
The `performance_counters` canisters provide the following interface:
155+
156+
- `for_update` — return all the performance counters values after two nested update calls.
157+
- `for_composite_query` — return all the performance counters values after two nested composite query calls.
158+
159+
## Conclusion
160+
161+
Performance counters is a great tool to optimize canister performance, both for update calls and queries.
162+
163+
## Security considerations and security best practices
164+
165+
If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the Internet Computer. This example may not implement all the best practices.

rust/performance_counters/dfx.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"canisters": {
3+
"performance_counters": {
4+
"candid": "performance_counters.did",
5+
"package": "performance_counters",
6+
"type": "rust"
7+
}
8+
},
9+
"version": 1
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
service : {
2+
"for_update" : () -> (nat64, nat64);
3+
"for_composite_query" : () -> (nat64, nat64) query;
4+
};

rust/performance_counters/src/lib.rs

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! An example of performance counters usage.
2+
//!
3+
//! dfx deploy && dfx canister call performance_counters get
4+
5+
// The following performance counters supported:
6+
//
7+
// - 0 : current execution instruction counter.
8+
// The number of WebAssembly instructions the canister has executed
9+
// since the beginning of the current Message execution.
10+
//
11+
// - 1 : call context instruction counter.
12+
// - For replicated message execution, it is the number of WebAssembly instructions
13+
// the canister has executed within the call context of the current Message execution
14+
// since Call context creation. The counter monotonically increases across all message
15+
// executions in the call context until the corresponding call context is removed.
16+
// - For non-replicated message execution, it is the number of WebAssembly instructions
17+
// the canister has executed within the corresponding `composite_query_helper`
18+
// in Query call. The counter monotonically increases across the executions
19+
// of the composite query method and the composite query callbacks
20+
// until the corresponding `composite_query_helper` returns
21+
// (ignoring WebAssembly instructions executed within any further downstream calls
22+
// of `composite_query_helper`).
23+
//
24+
// In the future, the IC might expose more performance counters.
25+
use ic_cdk::api::{call_context_instruction_counter, instruction_counter};
26+
27+
/// Pretty print the `title` and a corresponding `tuple` with counters.
28+
fn pretty_print<N: std::fmt::Display, T: std::fmt::Display>(title: N, counters: (T, T)) {
29+
ic_cdk::println!("{:40} {:<15} {:<15}", title, counters.0, counters.1);
30+
}
31+
32+
/// Loop to simulate some amount of work.
33+
fn do_some_work() {
34+
for i in 0..1_000_000 {
35+
// The black box hint is to avoid compiler optimizations for the loop.
36+
std::hint::black_box(i);
37+
}
38+
}
39+
40+
/// Returns a tuple with all the performance counters.
41+
fn counters() -> (u64, u64) {
42+
(instruction_counter(), call_context_instruction_counter())
43+
}
44+
45+
/// Emulate a nested inter-canister update call.
46+
#[ic_cdk_macros::update]
47+
fn nested_update_call() -> (u64, u64) {
48+
counters()
49+
}
50+
51+
/// Emulate a nested inter-canister composite query call.
52+
#[ic_cdk_macros::query(composite = true)]
53+
fn nested_composite_query_call() -> (u64, u64) {
54+
counters()
55+
}
56+
57+
////////////////////////////////////////////////////////////////////////
58+
// Canister interface
59+
////////////////////////////////////////////////////////////////////////
60+
61+
/// Example usage: `dfx canister call performance_counters for_update`
62+
#[ic_cdk_macros::update]
63+
async fn for_update() -> (u64, u64) {
64+
do_some_work();
65+
let before = counters();
66+
67+
let inside_1st: (u64, u64) = ic_cdk::call(ic_cdk::id(), "nested_update_call", ())
68+
.await
69+
.unwrap();
70+
71+
do_some_work();
72+
let after_1st = counters();
73+
74+
let inside_2nd: (u64, u64) = ic_cdk::call(ic_cdk::id(), "nested_update_call", ())
75+
.await
76+
.unwrap();
77+
78+
do_some_work();
79+
let after_2nd = counters();
80+
81+
pretty_print(
82+
"Performance counters for update call:",
83+
("current (0)", "call context (1)"),
84+
);
85+
pretty_print(" before the nested call:", before);
86+
pretty_print(" > inside the 1st nested call:", inside_1st);
87+
pretty_print(" after the 1st nested call:", after_1st);
88+
pretty_print(" > inside the 2nd nested call:", inside_2nd);
89+
pretty_print(" after the 2nd nested call:", after_2nd);
90+
91+
after_2nd
92+
}
93+
94+
/// Example usage: `dfx canister call performance_counters for_composite_query`
95+
#[ic_cdk_macros::query(composite = true)]
96+
async fn for_composite_query() -> (u64, u64) {
97+
do_some_work();
98+
let before = counters();
99+
100+
let inside_1st: (u64, u64) = ic_cdk::call(ic_cdk::id(), "nested_composite_query_call", ())
101+
.await
102+
.unwrap();
103+
104+
do_some_work();
105+
let after_1st = counters();
106+
107+
let inside_2nd: (u64, u64) = ic_cdk::call(ic_cdk::id(), "nested_composite_query_call", ())
108+
.await
109+
.unwrap();
110+
111+
do_some_work();
112+
let after_2nd = counters();
113+
114+
pretty_print(
115+
"Perf. counters for composite query call:",
116+
("current (0)", "call context (1)"),
117+
);
118+
pretty_print(" before the nested call:", before);
119+
pretty_print(" > inside the 1st nested call:", inside_1st);
120+
pretty_print(" after the 1st nested call:", after_1st);
121+
pretty_print(" > inside the 2nd nested call:", inside_2nd);
122+
pretty_print(" after the 2nd nested call:", after_2nd);
123+
124+
after_2nd
125+
}

0 commit comments

Comments
 (0)