Skip to content

Commit

Permalink
chore: add metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
sa-idx-admin committed Feb 4, 2025
1 parent fd7cd5a commit d17ee31
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 133 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rs/boundary_node/salt_sharing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ ic-canister-log = { path = "../../rust_canisters/canister_log" }
ic-cdk = { workspace = true }
ic-cdk-macros = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-nns-constants = { path = "../../nns/constants" }
ic-stable-structures = { workspace = true }
prometheus = {workspace = true }
salt-api = { path = "./api" }
serde = { workspace = true }
serde_cbor = { workspace = true }
Expand Down
11 changes: 10 additions & 1 deletion rs/boundary_node/salt_sharing/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use candid::{CandidType, Deserialize};
use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};

pub type GetSaltResponse = Result<SaltResponse, GetSaltError>;

Expand Down Expand Up @@ -26,3 +27,11 @@ pub enum GetSaltError {
Unauthorized,
Internal(String),
}

#[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Debug, Eq)]
pub struct ApiBoundaryNodeIdRecord {
pub id: Option<Principal>,
}

#[derive(CandidType, Deserialize, Clone, Copy, PartialEq, Eq)]
pub struct GetApiBoundaryNodeIdsRequest {}
150 changes: 20 additions & 130 deletions rs/boundary_node/salt_sharing/canister/canister.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
use std::time::Duration;

use crate::logs::{self, Log, LogEntry, Priority, P0};
use crate::storage::{StorableSalt, SALT, SALT_SIZE};
use crate::time::delay_till_next_month;
use candid::Principal;
use ic_canister_log::{export as export_logs, log};
use crate::helpers::init_async;
use crate::logs::export_logs_as_http_response;
use crate::metrics::{export_metrics_as_http_response, METRICS};
use crate::storage::SALT;
use ic_canisters_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_cdk::{api::time, spawn};
use ic_cdk_macros::{init, post_upgrade, query};
use ic_cdk_timers::set_timer;
use salt_api::{GetSaltError, GetSaltResponse, InitArg, SaltGenerationStrategy, SaltResponse};
use std::str::FromStr;
use salt_api::{GetSaltError, GetSaltResponse, InitArg, SaltResponse};
use std::time::Duration;

// Runs when canister is first installed
#[init]
fn init(init_arg: InitArg) {
set_timer(Duration::ZERO, || {
spawn(async { init_async(init_arg).await });
});
// Update metric.
let current_time = time() as i64;
METRICS.with(|cell| {
cell.borrow_mut()
.last_canister_change_time
.set(current_time);
});
}

// Runs on every canister upgrade
Expand All @@ -29,95 +33,6 @@ fn post_upgrade(init_arg: InitArg) {

#[query]
fn get_salt() -> GetSaltResponse {
get_salt_response()
}

#[query(decoding_quota = 10000)]
fn http_request(request: HttpRequest) -> HttpResponse {
match request.path() {
"/logs" => {
use serde_json;

let max_skip_timestamp = match request.raw_query_param("time") {
Some(arg) => match u64::from_str(arg) {
Ok(value) => value,
Err(_) => {
return HttpResponseBuilder::bad_request()
.with_body_and_content_length("failed to parse the 'time' parameter")
.build()
}
},
None => 0,
};

let mut entries: Log = Default::default();

for entry in export_logs(&logs::P0) {
entries.entries.push(LogEntry {
timestamp: entry.timestamp,
counter: entry.counter,
priority: Priority::P0,
file: entry.file.to_string(),
line: entry.line,
message: entry.message,
});
}

for entry in export_logs(&logs::P1) {
entries.entries.push(LogEntry {
timestamp: entry.timestamp,
counter: entry.counter,
priority: Priority::P1,
file: entry.file.to_string(),
line: entry.line,
message: entry.message,
});
}

entries
.entries
.retain(|entry| entry.timestamp >= max_skip_timestamp);

HttpResponseBuilder::ok()
.header("Content-Type", "application/json; charset=utf-8")
.with_body_and_content_length(serde_json::to_string(&entries).unwrap_or_default())
.build()
}
_ => HttpResponseBuilder::not_found().build(),
}
}

async fn init_async(init_arg: InitArg) {
if !is_salt_init() || init_arg.regenerate_now {
if let Err(err) = try_regenerate_salt().await {
log!(P0, "[init_regenerate_salt_failed]: {err}");
}
}
// Start salt generation schedule based on the argument.
match init_arg.salt_generation_strategy {
SaltGenerationStrategy::StartOfMonth => schedule_monthly_salt_generation(),
}
}

// Sets an execution timer (delayed future task) and returns immediately.
fn schedule_monthly_salt_generation() {
let delay = delay_till_next_month(time());
set_timer(delay, || {
spawn(async {
if let Err(err) = try_regenerate_salt().await {
log!(P0, "[scheduled_regenerate_salt_failed]: {err}");
}
// Function is called recursively to schedule next execution
schedule_monthly_salt_generation();
});
});
}

fn is_salt_init() -> bool {
SALT.with(|cell| cell.borrow().get(&())).is_some()
}

fn get_salt_response() -> Result<SaltResponse, GetSaltError> {
let stored_salt = SALT
.with(|cell| cell.borrow().get(&()))
.ok_or(GetSaltError::SaltNotInitialized)?;
Expand All @@ -128,36 +43,11 @@ fn get_salt_response() -> Result<SaltResponse, GetSaltError> {
})
}

// Regenerate salt and store it in the stable memory
// Can only fail, if the calls to management canister fail.
async fn try_regenerate_salt() -> Result<(), String> {
// Closure for getting random bytes from the IC.
let rnd_call = |attempt: u32| async move {
ic_cdk::call(Principal::management_canister(), "raw_rand", ())
.await
.map_err(|err| {
format!(
"Call {attempt} to raw_rand failed: code={:?}, err={}",
err.0, err.1
)
})
};

let (rnd_bytes_1,): ([u8; 32],) = rnd_call(1).await?;
let (rnd_bytes_2,): ([u8; 32],) = rnd_call(2).await?;

// Concatenate arrays to form an array of 64 random bytes.
let mut salt = [rnd_bytes_1, rnd_bytes_2].concat();
salt.truncate(SALT_SIZE);

let stored_salt = StorableSalt {
salt,
salt_id: time(),
};

SALT.with(|cell| {
cell.borrow_mut().insert((), stored_salt);
});

Ok(())
#[query(decoding_quota = 10000)]
fn http_request(request: HttpRequest) -> HttpResponse {
match request.path() {
"/metrics" => export_metrics_as_http_response(),
"/logs" => export_logs_as_http_response(request),
_ => HttpResponseBuilder::not_found().build(),
}
}
132 changes: 132 additions & 0 deletions rs/boundary_node/salt_sharing/canister/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::{collections::HashSet, time::Duration};

use crate::{
logs::P0,
metrics::METRICS,
storage::{StorableSalt, API_BOUNDARY_NODE_PRINCIPALS, SALT, SALT_SIZE},
time::delay_till_next_month,
};
use candid::Principal;
use ic_canister_log::log;
use ic_cdk::{api::time, call, spawn};
use ic_cdk_timers::{set_timer, set_timer_interval};
use ic_nns_constants::REGISTRY_CANISTER_ID;
use salt_api::{
ApiBoundaryNodeIdRecord, GetApiBoundaryNodeIdsRequest, InitArg, SaltGenerationStrategy,
};

const REGISTRY_CANISTER_METHOD: &str = "get_api_boundary_node_ids";

pub async fn init_async(init_arg: InitArg) {
if !is_salt_init() || init_arg.regenerate_now {
if let Err(err) = try_regenerate_salt().await {
log!(P0, "[init_regenerate_salt_failed]: {err}");
}
}
// Start salt generation schedule based on the argument.
match init_arg.salt_generation_strategy {
SaltGenerationStrategy::StartOfMonth => schedule_monthly_salt_generation(),
}
// Periodically poll API boundary nodes
let period = Duration::from_secs(init_arg.registry_polling_interval_secs);
set_timer_interval(period, || spawn(poll_api_boundary_nodes()));
}

// Sets an execution timer (delayed future task) and returns immediately.
pub fn schedule_monthly_salt_generation() {
let delay = delay_till_next_month(time());
set_timer(delay, || {
spawn(async {
if let Err(err) = try_regenerate_salt().await {
log!(P0, "[scheduled_regenerate_salt_failed]: {err}");
}
// Function is called recursively to schedule next execution
schedule_monthly_salt_generation();
});
});
}

pub fn is_salt_init() -> bool {
SALT.with(|cell| cell.borrow().get(&())).is_some()
}

// Regenerate salt and store it in the stable memory
// Can only fail, if the calls to management canister fail.
pub async fn try_regenerate_salt() -> Result<(), String> {
// Closure for getting random bytes from the IC.
let rnd_call = |attempt: u32| async move {
ic_cdk::call(Principal::management_canister(), "raw_rand", ())
.await
.map_err(|err| {
format!(
"Call {attempt} to raw_rand failed: code={:?}, err={}",
err.0, err.1
)
})
};

let (rnd_bytes_1,): ([u8; 32],) = rnd_call(1).await?;
let (rnd_bytes_2,): ([u8; 32],) = rnd_call(2).await?;

// Concatenate arrays to form an array of 64 random bytes.
let mut salt = [rnd_bytes_1, rnd_bytes_2].concat();
salt.truncate(SALT_SIZE);

let stored_salt = StorableSalt {
salt,
salt_id: time(),
};

SALT.with(|cell| {
cell.borrow_mut().insert((), stored_salt);
});

Ok(())
}

pub async fn poll_api_boundary_nodes() {
let canister_id = Principal::from(REGISTRY_CANISTER_ID);

let (call_status, message) = match call::<_, (Result<Vec<ApiBoundaryNodeIdRecord>, String>,)>(
canister_id,
REGISTRY_CANISTER_METHOD,
(&GetApiBoundaryNodeIdsRequest {},),
)
.await
{
Ok((Ok(api_bn_records),)) => {
// Set authorized readers of salt.
let principals: HashSet<_> = api_bn_records.into_iter().filter_map(|n| n.id).collect();
API_BOUNDARY_NODE_PRINCIPALS.with(|cell| *cell.borrow_mut() = principals);
// Update metric.
let current_time = time() as i64;
METRICS.with(|cell| {
cell.borrow_mut()
.last_successful_registry_poll_time
.set(current_time);
});
("success", "")
}
Ok((Err(err),)) => {
log!(
P0,
"[poll_api_boundary_nodes]: failed to fetch nodes from registry {err:?}",
);
("failure", "calling_canister_method_failed")
}
Err(err) => {
log!(
P0,
"[poll_api_boundary_nodes]: failed to fetch nodes from registry {err:?}",
);
("failure", "canister_call_rejected")
}
};
// Update metric.
METRICS.with(|cell| {
cell.borrow_mut()
.registry_poll_calls
.with_label_values(&[call_status, message])
.inc();
});
}
5 changes: 5 additions & 0 deletions rs/boundary_node/salt_sharing/canister/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#[cfg(any(target_family = "wasm", test))]
mod canister;
#[allow(dead_code)]
mod helpers;
#[allow(dead_code)]
mod logs;
#[allow(dead_code)]
mod metrics;
#[allow(dead_code)]
mod storage;
#[allow(dead_code)]
mod time;
Expand Down
Loading

0 comments on commit d17ee31

Please sign in to comment.