Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Initial spec changes for query stats feature #183

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions spec/_attachments/ic.did
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ service ic : {
memory_size: nat;
cycles: nat;
idle_cycles_burned_per_day: nat;
query_stats: record {
num_calls_total: nat;
num_instructions_total: nat;
request_payload_bytes_total: nat;
response_payload_bytes_total: nat;
};
});
canister_info : (record {
canister_id : canister_id;
Expand Down
83 changes: 69 additions & 14 deletions spec/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,16 @@ Indicates various information about the canister. It contains:

- The cycle balance of the canister.

- Statistics regarding the query call execution of the canister, i.e., a record containing the following fields:

* `num_calls_total`: the total number of query and composite query methods evaluated on the canister,

* `num_instructions_total`: the total number of WebAssembly instructions executed during the evaluation of query and composite query methods and composite callbacks on the canister,

* `request_payload_bytes_total`: the total number of query and composite query method payload bytes, and

* `response_payload_bytes_total`: the total number of query and composite query response payload (reply data or reject message) bytes.

Only the controllers of the canister or the canister itself can request its status.

### IC method `canister_info` {#ic-canister-info}
Expand Down Expand Up @@ -2910,6 +2920,12 @@ Finally, we can describe the state of the IC as a record having the following fi
total_num_changes : Nat;
recent_changes : [Change];
}
QueryStats = {
timestamp : Timestamp;
num_instructions : Nat;
request_payload_bytes : Nat;
response_payload_bytes : Nat;
}
Subnet = {
subnet_id : Principal;
subnet_size : Nat;
Expand All @@ -2929,6 +2945,7 @@ Finally, we can describe the state of the IC as a record having the following fi
balances: CanisterId ↦ Nat;
certified_data: CanisterId ↦ Blob;
canister_history: CanisterId ↦ CanisterHistory;
query_stats: CanisterId ↦ [QueryStats];
system_time : Timestamp
call_contexts : CallId ↦ CallCtxt;
messages : List Message; // ordered!
Expand Down Expand Up @@ -2974,6 +2991,7 @@ The initial state of the IC is
balances = ();
certified_data = ();
canister_history = ();
query_stats = ();
system_time = T;
call_contexts = ();
messages = [];
Expand Down Expand Up @@ -3734,6 +3752,7 @@ S with
freezing_threshold[CanisterId] = New_freezing_threshold
balances[CanisterId] = M.transferred_cycles
certified_data[CanisterId] = ""
query_stats[CanisterId] = []
canister_history[CanisterId] = New_canister_history
messages = Older_messages · Younger_messages ·
ResponseMessage {
Expand Down Expand Up @@ -3899,12 +3918,21 @@ S with
S.freezing_threshold[A.canister_id],
S.canister_subnet[A.canister_id].subnet_size,
);
query_stats = noise(SUM {{num_calls_total: 1,
num_instructions_total: single_query_stats.num_instructions,
request_payload_bytes_total: single_query_stats.request_payload_bytes,
response_payload_bytes_total: single_query_stats.response_payload_bytes} |
single_query_stats <- S.query_stats[A.canister_id];
single_query_stats.timestamp <= S.time[A.canister_id] - T})
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
})
refunded_cycles = M.transferred_cycles
}

```

where `T` is an unspecified time delay of query statistics and `noise` is an unspecified probabilistic function
modelling information loss due to aggregating query statistics in a distributed system.

#### IC Management Canister: Canister information

Every canister can retrieve the canister history, current module hash, and current controllers of every other canister (including itself).
Expand Down Expand Up @@ -4611,6 +4639,7 @@ S with
balances[A.canister_id] = (deleted)
certified_data[A.canister_id] = (deleted)
canister_history[A.canister_id] = (deleted)
query_stats[A.canister_id] = (deleted)
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -4763,6 +4792,7 @@ S with
balances[Canister_id] = New_balance
certified_data[Canister_id] = ""
canister_history[Canister_id] = New_canister_history
query_stats[CanisterId] = []
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
Expand Down Expand Up @@ -5152,53 +5182,62 @@ We define an auxiliary method that handles calls from composite query methods by
let F = if Method_name ∈ dom(Mod.query_methods) then Mod.query_methods[Method_name] else Mod.composite_query_methods[Method_name]
let R = F(Arg, Caller, Env)(W)
if R = Trap trap
then Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - trap.cycles_used)
then Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - trap.cycles_used, S)
else if R = Return {new_state = W'; new_calls = Calls; response = Response; cycles_used = Cycles_used}
then
W := W'
if Cycles_used > MAX_CYCLES_PER_MESSAGE
then
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - MAX_CYCLES_PER_MESSAGE) // single message execution out of cycles
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - MAX_CYCLES_PER_MESSAGE, S) // single message execution out of cycles
Cycles := Cycles - Cycles_used
if Response = NoResponse
then
while Calls ≠ []
do
if Depth = MAX_CALL_DEPTH_COMPOSITE_QUERY
then
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles) // max call graph depth exceeded
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // max call graph depth exceeded
let Calls' · Call · Calls'' = Calls
Calls := Calls' · Calls''
if S.canister_subnet[Canister_id].subnet_id ≠ S.canister_subnet[Call.callee].subnet_id
then
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles) // calling to another subnet
let (Response', Cycles') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, Call.callee, Call.method_name, Call.arg)
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // calling to another subnet
let (Response', Cycles', S') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, Call.callee, Call.method_name, Call.arg)
Cycles := Cycles'
S := S' with
query_stats[Call.callee] = S'.query_stats[Call.callee] · {
timestamp = S'.time[Call.callee]
num_instructions = <implementation-specific>
request_payload_bytes = |Call.arg|
response_payload_bytes =
if Response' = Reject (RejectCode, RejectMsg) then |RejectMsg|
else if Response' = Reply Res then |Res|
}
if Cycles < MAX_CYCLES_PER_RESPONSE
then
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles) // composite query out of cycles
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // composite query out of cycles
Env.Cert = NoCertificate // no certificate available in composite query callbacks
let F' = Mod.composite_callbacks(Call.callback, Response', Env)
let R'' = F'(W')
if R'' = Trap trap''
then Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - trap''.cycles_used)
then Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - trap''.cycles_used, S)
else if R'' = Return {new_state = W''; new_calls = Calls''; response = Response''; cycles_used = Cycles_used''}
then
W := W''
if Cycles_used'' > MAX_CYCLES_PER_RESPONSE
then
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - MAX_CYCLES_PER_RESPONSE) // single message execution out of cycles
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles - MAX_CYCLES_PER_RESPONSE, S) // single message execution out of cycles
Cycles := Cycles - Cycles_used''
if Response'' = NoResponse
then
Calls := Calls'' · Calls
else
Return (Response'', Cycles)
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles) // canister did not respond
Return (Response'', Cycles, S)
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // canister did not respond
else
Return (Response, Cycles)
Return (Response, Cycles, S)
else
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles)
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S)

Submitted request
`E`
Expand All @@ -5216,11 +5255,11 @@ S.system_time <= Q.ingress_expiry

Query response `R`:

- if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reject (RejectCode, RejectMsg), _)` then
- if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reject (RejectCode, RejectMsg), _, S')` then

{status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: <implementation-specific>, signatures: Sigs}

- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reply Res, _)` then
- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reply Res, _, S')` then

{status: "replied"; reply: {arg: Res}, signatures: Sigs}

Expand All @@ -5232,6 +5271,22 @@ verify_response(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time /

```

State after

```html

S' with
query_stats[Q.receiver] = S'.query_stats[Q.receiver] · {
timestamp = S'.time[Q.receiver]
num_instructions = <implementation-specific>
request_payload_bytes = |Q.Arg|
response_payload_bytes =
if R.status = "rejected" then |R.reject_message|
else |R.reply.arg|
}

```

#### Certified state reads

:::note
Expand Down