Skip to content

Commit

Permalink
chore: add /metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
IDX GitLab Automation committed Oct 24, 2024
1 parent 2c54dce commit 4dee538
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 1 deletion.
4 changes: 3 additions & 1 deletion rs/boundary_node/rate_limits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ anyhow = { workspace = true }
bincode = { workspace = true }
candid = { workspace = true }
hex = { workspace = true }
mockall = { workspace = true }
ic-canisters-http-types = { path = "../../rust_canisters/http_types" }
ic-cdk = { workspace = true }
ic-cdk-macros = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-metrics-encoder = "1"
ic-stable-structures = { workspace = true }
mockall = { workspace = true }
rate-limits-api = { path = "./api" }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions rs/boundary_node/rate_limits/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ documentation.workspace = true
candid = {workspace = true}
regex = { workspace = true }
serde = {workspace = true}
serde_bytes = { workspace = true }
serde_json = { workspace = true }

[lib]
Expand Down
12 changes: 12 additions & 0 deletions rs/boundary_node/rate_limits/canister/canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::add_config::{AddsConfig, ConfigAdder};
use crate::confidentiality_formatting::ConfidentialityFormatterFactory;
use crate::disclose::{DisclosesRules, RulesDiscloser};
use crate::fetcher::{ConfigFetcher, EntityFetcher, RuleFetcher};
use crate::metrics::{encode_metrics, serve_metrics};
use crate::state::{init_version_and_config, with_state};
use crate::storage::API_BOUNDARY_NODE_PRINCIPALS;
use candid::{candid_method, Principal};
use ic_canisters_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_cdk::api::call::call;
use ic_cdk_macros::{init, query, update};
use rate_limits_api::{
Expand Down Expand Up @@ -83,6 +85,16 @@ fn disclose_rules(args: DiscloseRulesArg) -> DiscloseRulesResponse {
Ok(())
}

// TODO: adjust quota
#[query(decoding_quota = 10000)]
#[candid_method(query)]
fn http_request(request: HttpRequest) -> HttpResponse {
match request.path() {
"/metrics" => serve_metrics(ic_cdk::api::time() as i64, encode_metrics),
_ => HttpResponseBuilder::not_found().build(),
}
}

fn periodically_poll_api_boundary_nodes(interval: Duration) {
ic_cdk_timers::set_timer_interval(interval, || {
ic_cdk::spawn(async {
Expand Down
18 changes: 18 additions & 0 deletions rs/boundary_node/rate_limits/canister/interface.did
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ type Timestamp = nat64; // Represents timestamp in nanoseconds since the
type RuleId = text; // Unique identifier for each rule
type SchemaVersion = nat64; // Version of the schema for encoding/decoding the rules
type IncidentId = text; // Unique identifier for each incident
type HeaderField = record { text; text; };


// Input structure for defining a rule with mandatory fields within a config
Expand Down Expand Up @@ -81,6 +82,20 @@ type InputConfig = record {
rules: vec InputRule;
};


type HttpRequest = record {
method: text;
url: text;
headers: vec HeaderField;
body: blob;
};

type HttpResponse = record {
status_code: nat16;
headers: vec HeaderField;
body: blob;
};

// Initialization arguments for the service
type InitArg = record {
registry_polling_period_secs: nat64; // IDs of existing API boundary nodes are polled from the registry with this periodicity
Expand All @@ -102,4 +117,7 @@ service : (InitArg) -> {

// Fetch all rules IDs related to an ID of the incident
get_rules_by_incident_id: (IncidentId) -> (GetRulesByIncidentIdResponse) query;

// Canister metrics (Http Interface)
http_request: (HttpRequest) -> (HttpResponse) query;
}
2 changes: 2 additions & 0 deletions rs/boundary_node/rate_limits/canister/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod disclose;
#[allow(dead_code)]
mod fetcher;
#[allow(dead_code)]
mod metrics;
#[allow(dead_code)]
mod state;
mod storage;
mod types;
38 changes: 38 additions & 0 deletions rs/boundary_node/rate_limits/canister/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::storage::API_BOUNDARY_NODE_PRINCIPALS;
use ic_canisters_http_types::{HttpResponse, HttpResponseBuilder};

/// Encode the metrics in a format that can be understood by Prometheus
pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::io::Result<()> {
// retrieve the count of boundary nodes with full access
let api_bns_count = API_BOUNDARY_NODE_PRINCIPALS.with(|cell| cell.borrow().len());

// Encode the gauge for Prometheus
w.encode_gauge(
"rate_limit_canister_api_boundary_nodes_total",
api_bns_count as f64,
"Number of API boundary nodes with full read access permission to rate-limit config",
)?;
Ok(())
}

/// Serve the encoded metrics as an HTTP response.
pub fn serve_metrics(
time: i64,
encode_metrics: impl FnOnce(&mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::io::Result<()>,
) -> HttpResponse {
let mut writer = ic_metrics_encoder::MetricsEncoder::new(vec![], time);

// TODO: Consider implementing metrics versioning

match encode_metrics(&mut writer) {
Ok(()) => HttpResponseBuilder::ok()
.header("Content-Type", "text/plain")
.with_body_and_content_length(writer.into_inner())
.build(),
Err(err) => {
// Return an HTTP 500 error with detailed error information
HttpResponseBuilder::server_error(format!("Failed to encode metrics: {:?}", err))
.build()
}
}
}

0 comments on commit 4dee538

Please sign in to comment.