Per-Call Instruction Counter (performance counter type 1)

Hey folks,
Since the performance counter (type 0) has been introduced, there were a few requests for improvement. One of the main pain points was the difficulty of using the performance counter with async code. After each await point the counter gets reset, so the developer is responsible for storing the counter before the await point and then adding it to the number of instructions after the await

This per-call instruction counter is to facilitate the instrumentation of the async code. Let us know what do you think about the following proposal?

Proposal

Consider the following call graph: canister A calls canister B, which calls back canister A.

// Canister A
fn a_1() {
  // Block 1
  assert!(counter(1) > 0);
  assert!(counter(1) < 1);
  call(b_1).await;
  // Block 5
  assert!(counter(1) > 1 + 3);
  assert!(counter(1) < 1 + 3 + 5);
  reply();
}

fn a_2() {
  // Block 3
  assert!(counter(1) > 1);
  assert!(counter(1) < 1 + 3);
  reply();
}

// Canister B
fn b_1() {
  // Block 2
  assert!(counter(1) > 0);
  assert!(counter(1) < 2);
  call(a_2).await
  // Block 4
  assert!(counter(1) > 2);
  assert!(counter(1) < 2 + 4);
  reply();
}

Note, at the end of the execution of the call graph, the counter type 1 returns:

  1. In canister A – the number of instructions executed in blocks 1, 3 and 5.
  2. In canister B – the number of instructions executed in blocks 2 and 4.

Risk Considerations

Passing the executed number of instructions between the parent and the child call contexts is a potential security risk (side channel attack). The implementation should share the instructions information only when the caller and the callee canister IDs are the same.

References

  1. The Internet Computer Interface Specification: Performance Counter
  2. The Internet Computer Interface Specification: Call Contexts
  3. The Internet Computer Interface Specification: Call Context Creation
1 Like