#[cfg(feature = "host")]
use crate::runtime::RuntimeError;
use serde::Serialize;
use std::{fmt, str::FromStr};
#[cfg(feature = "host")]
use thiserror::Error as ThisError;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum IcMetricKind {
InstructionRate,
MessageExecutionRate,
CycleBurnRate,
BlockRate,
IcNodeCount,
IcSubnetTotal,
RegisteredCanistersCount,
TotalIcEnergyConsumptionRateKwh,
BoundaryNodesCount,
}
impl IcMetricKind {
#[must_use]
pub const fn all() -> [Self; 9] {
[
Self::InstructionRate,
Self::MessageExecutionRate,
Self::CycleBurnRate,
Self::BlockRate,
Self::IcNodeCount,
Self::IcSubnetTotal,
Self::RegisteredCanistersCount,
Self::TotalIcEnergyConsumptionRateKwh,
Self::BoundaryNodesCount,
]
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::InstructionRate => "instruction-rate",
Self::MessageExecutionRate => "message-execution-rate",
Self::CycleBurnRate => "cycle-burn-rate",
Self::BlockRate => "block-rate",
Self::IcNodeCount => "ic-node-count",
Self::IcSubnetTotal => "ic-subnet-total",
Self::RegisteredCanistersCount => "registered-canisters-count",
Self::TotalIcEnergyConsumptionRateKwh => "total-ic-energy-consumption-rate-kwh",
Self::BoundaryNodesCount => "boundary-nodes-count",
}
}
#[cfg(feature = "host")]
pub(crate) const fn series_names(self) -> &'static [&'static str] {
match self {
Self::InstructionRate => &["instruction_rate"],
Self::MessageExecutionRate => &["message_execution_rate"],
Self::CycleBurnRate => &["cycle_burn_rate"],
Self::BlockRate => &["block_rate"],
Self::IcNodeCount => &["total_nodes", "up_nodes"],
Self::IcSubnetTotal => &["ic_subnet_total"],
Self::RegisteredCanistersCount => &["running_canisters", "stopped_canisters"],
Self::TotalIcEnergyConsumptionRateKwh => &["energy_consumption_rate"],
Self::BoundaryNodesCount => &["boundary_nodes_count"],
}
}
}
impl fmt::Display for IcMetricKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for IcMetricKind {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::all()
.into_iter()
.find(|metric| metric.as_str() == value)
.ok_or_else(|| format!("unsupported IC Dashboard metric {value:?}"))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcMetricQuery {
pub metric: IcMetricKind,
pub start_unix_secs: u64,
pub end_unix_secs: u64,
pub step_secs: u32,
}
impl IcMetricQuery {
#[must_use]
pub const fn new(
metric: IcMetricKind,
start_unix_secs: u64,
end_unix_secs: u64,
step_secs: u32,
) -> Self {
Self {
metric,
start_unix_secs,
end_unix_secs,
step_secs,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcMetricRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
pub query: IcMetricQuery,
}
impl IcMetricRequest {
#[must_use]
pub fn new(
source_endpoint: impl Into<String>,
now_unix_secs: u64,
query: IcMetricQuery,
) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
query,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcBoundaryNodeDataCentersRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
}
impl IcBoundaryNodeDataCentersRequest {
#[must_use]
pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
pub canister_id: String,
}
impl IcCanisterRequest {
#[must_use]
pub fn new(
source_endpoint: impl Into<String>,
now_unix_secs: u64,
canister_id: impl Into<String>,
) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
canister_id: canister_id.into(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct IcCanisterFilters {
pub has_name: Option<bool>,
pub subnet_id: Option<String>,
pub controller_id: Option<String>,
pub languages: Vec<String>,
pub canister_types: Vec<String>,
pub query: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterCountRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
pub filters: IcCanisterFilters,
}
impl IcCanisterCountRequest {
#[must_use]
pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
filters: IcCanisterFilters::default(),
}
}
#[must_use]
pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
self.filters = filters;
self
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterPageRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
pub filters: IcCanisterFilters,
pub limit: u16,
pub after: Option<String>,
pub before: Option<String>,
}
impl IcCanisterPageRequest {
#[must_use]
pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
filters: IcCanisterFilters::default(),
limit: super::DEFAULT_IC_CANISTER_PAGE_LIMIT,
after: None,
before: None,
}
}
#[must_use]
pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
self.filters = filters;
self
}
#[must_use]
pub const fn with_limit(mut self, limit: u16) -> Self {
self.limit = limit;
self
}
#[must_use]
pub fn with_after(mut self, after: impl Into<String>) -> Self {
self.after = Some(after.into());
self
}
#[must_use]
pub fn with_before(mut self, before: impl Into<String>) -> Self {
self.before = Some(before.into());
self
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterUpgrade {
pub executed_timestamp_seconds: u64,
pub module_hash: String,
pub proposal_id: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcDashboardReportProvenance {
pub schema_version: u32,
pub network: String,
pub authority: String,
pub source_endpoint: String,
pub fetched_at: String,
pub fetched_by: String,
pub certified: bool,
pub point_in_time_guaranteed: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcMetricObservation {
pub timestamp_unix_secs: u64,
pub value: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcMetricSeries {
pub name: String,
pub observations: Vec<IcMetricObservation>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcMetricReport {
#[serde(flatten)]
pub provenance: IcDashboardReportProvenance,
#[serde(flatten)]
pub query: IcMetricQuery,
pub returned_series_count: usize,
pub returned_observation_count: usize,
pub series: Vec<IcMetricSeries>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcBoundaryNodeDataCenterRow {
pub dc_id: String,
pub name: String,
pub owner: String,
pub region: String,
pub latitude: String,
pub longitude: String,
pub total_nodes: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcBoundaryNodeDataCentersReport {
#[serde(flatten)]
pub provenance: IcDashboardReportProvenance,
pub data_center_count: usize,
pub total_node_count: u64,
pub rows: Vec<IcBoundaryNodeDataCenterRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterReport {
#[serde(flatten)]
pub provenance: IcDashboardReportProvenance,
pub canister_id: String,
pub dashboard_id: u64,
pub canister_type: Option<String>,
pub name: String,
pub subnet_id: String,
pub controllers: Vec<String>,
pub language: String,
pub module_hash: String,
pub dashboard_updated_at: String,
pub upgrade_count: Option<usize>,
pub upgrades: Option<Vec<IcCanisterUpgrade>>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterCountReport {
#[serde(flatten)]
pub provenance: IcDashboardReportProvenance,
pub filters: IcCanisterFilters,
pub total: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterPageController {
pub principal_id: String,
pub raw_metadata: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterPageRow {
pub canister_id: String,
pub dashboard_id: u64,
pub canister_type: Option<String>,
pub name: String,
pub subnet_id: String,
pub controllers: Vec<IcCanisterPageController>,
pub language: String,
pub module_hash: String,
pub dashboard_updated_at: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcCanisterPageReport {
#[serde(flatten)]
pub provenance: IcDashboardReportProvenance,
pub filters: IcCanisterFilters,
pub requested_limit: u16,
pub returned_count: usize,
pub after: Option<String>,
pub before: Option<String>,
pub previous_cursor: Option<String>,
pub next_cursor: Option<String>,
pub rows: Vec<IcCanisterPageRow>,
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcSourceRequest {
pub endpoint: String,
pub fetched_at: String,
pub fetched_by: String,
}
#[cfg(feature = "host")]
impl IcSourceRequest {
#[must_use]
pub fn new(
endpoint: impl Into<String>,
fetched_at: impl Into<String>,
fetched_by: impl Into<String>,
) -> Self {
Self {
endpoint: endpoint.into(),
fetched_at: fetched_at.into(),
fetched_by: fetched_by.into(),
}
}
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterSourceData {
pub source: IcSourceRequest,
pub canister_id: String,
pub dashboard_id: u64,
pub canister_type: Option<String>,
pub name: String,
pub subnet_id: String,
pub controllers: Vec<String>,
pub language: String,
pub module_hash: String,
pub dashboard_updated_at: String,
pub upgrades: Option<Vec<IcCanisterUpgrade>>,
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterCountSourceData {
pub source: IcSourceRequest,
pub filters: IcCanisterFilters,
pub total: u64,
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterPageSourceData {
pub source: IcSourceRequest,
pub filters: IcCanisterFilters,
pub requested_limit: u16,
pub after: Option<String>,
pub before: Option<String>,
pub previous_cursor: Option<String>,
pub next_cursor: Option<String>,
pub rows: Vec<IcCanisterPageRow>,
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcMetricSourceData {
pub source: IcSourceRequest,
pub query: IcMetricQuery,
pub series: Vec<IcMetricSeries>,
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcBoundaryNodeDataCentersSourceData {
pub source: IcSourceRequest,
pub rows: Vec<IcBoundaryNodeDataCenterRow>,
}
#[cfg(feature = "host")]
#[derive(Debug, ThisError)]
pub enum IcHostError {
#[error("failed to run IC Dashboard query: {0}")]
Runtime(#[from] RuntimeError),
#[error("invalid {field}: {reason}")]
InvalidPrincipal {
field: &'static str,
reason: String,
},
#[error("invalid {field}: {reason}")]
InvalidRequest {
field: &'static str,
reason: String,
},
#[error("invalid IC Dashboard endpoint {endpoint}: {reason}")]
InvalidEndpoint {
endpoint: String,
reason: String,
},
#[error("failed to build IC Dashboard HTTP client: {reason}")]
HttpClientBuild {
reason: String,
},
#[error("IC Dashboard request to {url} failed: {reason}")]
HttpRequest {
url: String,
reason: String,
},
#[error("IC Dashboard request to {url} returned HTTP status {status}")]
HttpStatus {
url: String,
status: u16,
},
#[error("failed to decode IC Dashboard response from {url}: {reason}")]
JsonDecode {
url: String,
reason: String,
},
#[error("invalid IC Dashboard source data: {reason}")]
InvalidSourceData {
reason: String,
},
}