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 all 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 @@ -229,6 +229,12 @@ type canister_status_result = record {
cycles : nat;
reserved_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;
};
};

type canister_info_args = record {
Expand Down
77 changes: 62 additions & 15 deletions spec/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,16 @@ Indicates various information about the canister. It contains:

- The idle cycle consumption of the canister, i.e., the number of cycles burned by the canister per day due to its compute and memory allocation and actual memory usage.

- 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,

* `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 @@ -2988,6 +2998,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 @@ -3009,6 +3025,7 @@ Finally, we can describe the state of the IC as a record having the following fi
reserved_balance_limits: 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 @@ -3077,6 +3094,7 @@ The initial state of the IC is
reserved_balance_limits = ();
certified_data = ();
canister_history = ();
query_stats = ();
system_time = T;
call_contexts = ();
messages = [];
Expand Down Expand Up @@ -3902,6 +3920,7 @@ S with
reserved_balances[Canister_id] = New_reserved_balance
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
certified_data[Canister_id] = ""
query_stats[Canister_id] = []
canister_history[Canister_id] = New_canister_history
messages = Older_messages · Younger_messages ·
ResponseMessage {
Expand Down Expand Up @@ -4089,12 +4108,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 @@ -4862,6 +4890,7 @@ S with
reserved_balance_limits[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 @@ -5067,6 +5096,7 @@ S with
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
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 @@ -5482,56 +5512,57 @@ We define an auxiliary method that handles calls from composite query methods by
)
) < 0
then
Return (Reject (SYS_TRANSIENT, <implementation-specific>), Cycles)
Return (Reject (SYS_TRANSIENT, <implementation-specific>), Cycles, S)
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'
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 @@ -5550,11 +5581,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 @@ -5566,6 +5597,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
Loading