From f44210a5a18c873ccf33cbc25932cdf3e77b5fd2 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:03:57 +0900 Subject: [PATCH 01/10] add metrics --- .../src/canisters/attributes.rs | 16 ++ chainsight-cdk-macros/src/lib.rs | 5 + chainsight-cdk/src/lib.rs | 2 + chainsight-cdk/src/metric/mod.rs | 1 + chainsight-cdk/src/metric/types.rs | 229 ++++++++++++++++++ chainsight-cdk/src/time/mod.rs | 30 +++ 6 files changed, 283 insertions(+) create mode 100644 chainsight-cdk/src/metric/mod.rs create mode 100644 chainsight-cdk/src/metric/types.rs create mode 100644 chainsight-cdk/src/time/mod.rs diff --git a/chainsight-cdk-macros/src/canisters/attributes.rs b/chainsight-cdk-macros/src/canisters/attributes.rs index fd8cbc7e..82d5442e 100644 --- a/chainsight-cdk-macros/src/canisters/attributes.rs +++ b/chainsight-cdk-macros/src/canisters/attributes.rs @@ -31,3 +31,19 @@ pub fn only_proxy(_attr: TokenStream, item: TokenStream) -> TokenStream { } .into() } + +pub fn metric(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item_fn = parse_macro_input!(item as ItemFn); + let sig = item_fn.sig; + let block = item_fn.block.stmts; + quote! { + #sig { + let timestamper = chainsight_cdk::time::TimeStamper; + let start = timestamper::now_nanosec(); + #(#block);* + let end = timestamper::now_nanosec(); + chainsight_cdk::metric::metric(stringify!(#sig), TaskDuration::new(start, end)); + } + } + .into() +} diff --git a/chainsight-cdk-macros/src/lib.rs b/chainsight-cdk-macros/src/lib.rs index 0a5a09e3..d5e77793 100644 --- a/chainsight-cdk-macros/src/lib.rs +++ b/chainsight-cdk-macros/src/lib.rs @@ -214,3 +214,8 @@ pub fn only_controller(_attr: proc_macro::TokenStream, item: TokenStream) -> Tok pub fn only_proxy(_attr: TokenStream, item: TokenStream) -> TokenStream { canisters::attributes::only_proxy(_attr, item) } + +#[proc_macro_attribute] +pub fn metric(_attr: TokenStream, item: TokenStream) -> TokenStream { + canisters::attributes::metric(_attr, item) +} diff --git a/chainsight-cdk/src/lib.rs b/chainsight-cdk/src/lib.rs index 09a3f924..6f671da3 100644 --- a/chainsight-cdk/src/lib.rs +++ b/chainsight-cdk/src/lib.rs @@ -5,7 +5,9 @@ pub mod core; pub mod indexer; pub mod initializer; pub mod lens; +pub mod metric; pub mod rpc; pub mod storage; +pub mod time; pub mod web2; pub mod web3; diff --git a/chainsight-cdk/src/metric/mod.rs b/chainsight-cdk/src/metric/mod.rs new file mode 100644 index 00000000..cd408564 --- /dev/null +++ b/chainsight-cdk/src/metric/mod.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/chainsight-cdk/src/metric/types.rs b/chainsight-cdk/src/metric/types.rs new file mode 100644 index 00000000..6c12798c --- /dev/null +++ b/chainsight-cdk/src/metric/types.rs @@ -0,0 +1,229 @@ +use std::{cell::RefCell, collections::HashMap}; + +use candid::CandidType; + +use crate::time::TimeStamper; +// 7 days +const DATA_POINT_RETENTION_SECONDS: u64 = 7 * 24 * 60 * 60; +// 1 hour +const ONE_DATA_POINT_SECONDS: u64 = 60 * 60; + +#[derive(Clone, Debug, CandidType)] +pub struct Metric { + pub metric_type: MetricType, + pub value: f64, +} + +#[derive(Clone, Debug, CandidType)] +pub enum MetricType { + TimeMax, + TimeMin, + Count, +} + +#[derive(Clone, Debug)] +pub struct TaskDuration { + from_nanosec: u64, + to_nanosec: u64, +} + +impl TaskDuration { + pub fn new(from_nanosec: u64, to_nanosec: u64) -> Self { + Self { + from_nanosec, + to_nanosec, + } + } +} + +#[derive(Clone, Debug, CandidType)] +pub struct DataPoint { + pub metrics: Vec, + pub from_timestamp: u64, +} +struct MetricCollector { + data: HashMap>, +} +type MetricId = String; + +impl MetricCollector { + fn new() -> Self { + Self { + data: HashMap::new(), + } + } +} + +thread_local! { + static METRIC_COLLECTOR: RefCell = RefCell::new(MetricCollector::new()); +} + +#[ic_cdk::query] +fn metric_ids() -> Vec { + _metric_ids() +} + +#[ic_cdk::query] +fn metrics(id: String, count: u8) -> Vec { + METRIC_COLLECTOR.with(|collector| { + if let Some(data) = collector.borrow().data.get(&id) { + return data.iter().rev().take(count as usize).cloned().collect(); + } + return vec![]; + }) +} + +#[ic_cdk::query] +fn metrics_between(id: String, from: u64, to: u64) -> Vec { + METRIC_COLLECTOR.with(|collector| { + if let Some(data) = collector.borrow().data.get(&id) { + return data + .iter() + .filter(|d| d.from_timestamp >= from && d.from_timestamp <= to) + .cloned() + .collect(); + } + return vec![]; + }) +} + +fn _metric_ids() -> Vec { + METRIC_COLLECTOR.with(|collector| collector.borrow().data.keys().cloned().collect()) +} + +fn _enqueue(id: MetricId) { + let now = TimeStamper::now_sec(); + METRIC_COLLECTOR.with(|collector| { + let last = _last(id.clone()); + if last.is_none() { + collector.borrow_mut().data.insert(id, vec![]); + return; + } + let last = last.unwrap(); + if last.from_timestamp + ONE_DATA_POINT_SECONDS < now { + collector.borrow_mut().data.insert(id, vec![]); + } + }); +} + +fn _insert(id: MetricId, data_point: DataPoint) { + METRIC_COLLECTOR.with(|collector| { + let mut collector = collector.borrow_mut(); + if let Some(data) = collector.data.get_mut(&id) { + data.push(data_point); + } else { + collector.data.insert(id, vec![data_point]); + } + }); +} + +fn _dequeue(id: MetricId) { + METRIC_COLLECTOR.with(|collector| { + let mut collector = collector.borrow_mut(); + if let Some(data) = collector.data.get_mut(&id) { + data.remove(0); + } + }); +} + +fn _clean() { + let ids = _metric_ids(); + let now = TimeStamper::now_sec(); + + for id in ids { + let first = _first(id.clone()); + if first.is_none() { + continue; + } + let first = first.unwrap(); + if first.from_timestamp + DATA_POINT_RETENTION_SECONDS < now { + _dequeue(id); + } + } +} + +fn _first(id: MetricId) -> Option { + METRIC_COLLECTOR.with(|collector| { + if let Some(data) = collector.borrow().data.get(&id) { + return data.first().cloned(); + } + return None; + }) +} + +fn _last(id: MetricId) -> Option { + METRIC_COLLECTOR.with(|collector| { + if let Some(data) = collector.borrow().data.get(&id) { + return data.last().cloned(); + } + return None; + }) +} +pub fn metric(id: MetricId, duration: TaskDuration) { + _clean(); + _enqueue(id.clone()); + let mut last = match _last(id.clone()) { + Some(data) => data, + None => DataPoint { + metrics: vec![ + Metric { + metric_type: MetricType::TimeMax, + value: 0.0, + }, + Metric { + metric_type: MetricType::TimeMin, + value: f64::MAX, + }, + Metric { + metric_type: MetricType::Count, + value: 0.0, + }, + ], + from_timestamp: TimeStamper::now_sec(), + }, + }; + let task_dur = (duration.to_nanosec - duration.from_nanosec) as f64; + let new_metrics = last + .metrics + .iter() + .map(|m| { + let mut m = m.clone(); + match m.metric_type { + MetricType::TimeMax => { + m.value = m.value.max(task_dur); + } + MetricType::TimeMin => { + m.value = m.value.min(task_dur); + } + MetricType::Count => { + m.value += 1.0; + } + } + m + }) + .collect(); + last.metrics = new_metrics; + _insert(id, last); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_metric() { + let id = "test".to_string(); + let duration = TaskDuration::new(0, 100); + metric(id.clone(), duration); + let data = _last(id.clone()).unwrap(); + assert_eq!(data.metrics.len(), 3); + assert_eq!(data.metrics[0].value, 100.0); + assert_eq!(data.metrics[1].value, 100.0); + assert_eq!(data.metrics[2].value, 1.0); + metric(id.clone(), TaskDuration::new(0, 200)); + let data = _last(id.clone()).unwrap(); + assert_eq!(data.metrics.len(), 3); + assert_eq!(data.metrics[0].value, 200.0); + assert_eq!(data.metrics[1].value, 100.0); + assert_eq!(data.metrics[2].value, 2.0); + } +} diff --git a/chainsight-cdk/src/time/mod.rs b/chainsight-cdk/src/time/mod.rs new file mode 100644 index 00000000..e8614a29 --- /dev/null +++ b/chainsight-cdk/src/time/mod.rs @@ -0,0 +1,30 @@ +pub struct TimeStamper; + +impl TimeStamper { + /// returns current time nano seconds + #[cfg(not(target_arch = "wasm32"))] + fn _now() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_nanos() as u64 + } + #[cfg(target_arch = "wasm32")] + fn _now() -> u64 { + ic_cdk::api::time() + } + + pub fn now_nanosec() -> u64 { + Self::_now() + } + pub fn now_millisec() -> u64 { + Self::_now() / 1_000_000 + } + pub fn now_microsec() -> u64 { + Self::_now() / 1_000 + } + pub fn now_sec() -> u64 { + Self::_now() / 1_000_000_000 + } +} From 1f0fdedb62b69f9ef857b410463571b6efed2119 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:08:15 +0900 Subject: [PATCH 02/10] some refactor --- chainsight-cdk/src/metric/types.rs | 43 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/chainsight-cdk/src/metric/types.rs b/chainsight-cdk/src/metric/types.rs index 6c12798c..99cb8f50 100644 --- a/chainsight-cdk/src/metric/types.rs +++ b/chainsight-cdk/src/metric/types.rs @@ -41,6 +41,28 @@ pub struct DataPoint { pub metrics: Vec, pub from_timestamp: u64, } + +impl Default for DataPoint { + fn default() -> Self { + Self { + metrics: vec![ + Metric { + metric_type: MetricType::TimeMax, + value: 0.0, + }, + Metric { + metric_type: MetricType::TimeMin, + value: f64::MAX, + }, + Metric { + metric_type: MetricType::Count, + value: 0.0, + }, + ], + from_timestamp: TimeStamper::now_sec(), + } + } +} struct MetricCollector { data: HashMap>, } @@ -162,26 +184,7 @@ fn _last(id: MetricId) -> Option { pub fn metric(id: MetricId, duration: TaskDuration) { _clean(); _enqueue(id.clone()); - let mut last = match _last(id.clone()) { - Some(data) => data, - None => DataPoint { - metrics: vec![ - Metric { - metric_type: MetricType::TimeMax, - value: 0.0, - }, - Metric { - metric_type: MetricType::TimeMin, - value: f64::MAX, - }, - Metric { - metric_type: MetricType::Count, - value: 0.0, - }, - ], - from_timestamp: TimeStamper::now_sec(), - }, - }; + let mut last = match _last(id.clone()).unwrap_or_default(); let task_dur = (duration.to_nanosec - duration.from_nanosec) as f64; let new_metrics = last .metrics From 8b9cedf914c199afae202320aa26f0ec582dc51c Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:08:52 +0900 Subject: [PATCH 03/10] some refactor --- chainsight-cdk/src/metric/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainsight-cdk/src/metric/types.rs b/chainsight-cdk/src/metric/types.rs index 99cb8f50..ad64cd1f 100644 --- a/chainsight-cdk/src/metric/types.rs +++ b/chainsight-cdk/src/metric/types.rs @@ -184,7 +184,7 @@ fn _last(id: MetricId) -> Option { pub fn metric(id: MetricId, duration: TaskDuration) { _clean(); _enqueue(id.clone()); - let mut last = match _last(id.clone()).unwrap_or_default(); + let mut last = _last(id.clone()).unwrap_or_default(); let task_dur = (duration.to_nanosec - duration.from_nanosec) as f64; let new_metrics = last .metrics From 02f917e2f3c04c81c81c666c55483accda7dc736 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:49:54 +0900 Subject: [PATCH 04/10] update rust version --- .github/workflows/qa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 342ac633..fe1541a6 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -5,7 +5,7 @@ on: branches: - main env: - RUST_VERSION: 1.76.0 + RUST_VERSION: 1.79.0 jobs: format: runs-on: ubuntu-latest From e43c8b222f9b8c3c7d9669db7a438e3a34cc8cc3 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:50:53 +0900 Subject: [PATCH 05/10] update rust version --- .github/workflows/check-examples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml index e36fe53e..d5a2d6b4 100644 --- a/.github/workflows/check-examples.yml +++ b/.github/workflows/check-examples.yml @@ -5,7 +5,7 @@ on: branches: - main env: - RUST_VERSION: 1.76.0 + RUST_VERSION: 1.79.0 jobs: check-compilable: strategy: From 0084c0360ce72c449f339d6d015007cbceab9f74 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:53:22 +0900 Subject: [PATCH 06/10] lint --- chainsight-cdk/src/metric/types.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chainsight-cdk/src/metric/types.rs b/chainsight-cdk/src/metric/types.rs index ad64cd1f..e7935e53 100644 --- a/chainsight-cdk/src/metric/types.rs +++ b/chainsight-cdk/src/metric/types.rs @@ -91,7 +91,7 @@ fn metrics(id: String, count: u8) -> Vec { if let Some(data) = collector.borrow().data.get(&id) { return data.iter().rev().take(count as usize).cloned().collect(); } - return vec![]; + vec![] }) } @@ -105,7 +105,7 @@ fn metrics_between(id: String, from: u64, to: u64) -> Vec { .cloned() .collect(); } - return vec![]; + vec![] }) } @@ -169,7 +169,7 @@ fn _first(id: MetricId) -> Option { if let Some(data) = collector.borrow().data.get(&id) { return data.first().cloned(); } - return None; + None }) } @@ -178,7 +178,7 @@ fn _last(id: MetricId) -> Option { if let Some(data) = collector.borrow().data.get(&id) { return data.last().cloned(); } - return None; + None }) } pub fn metric(id: MetricId, duration: TaskDuration) { From b0865bee118522c77747211a969fd2ec9c76da63 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 14:57:56 +0900 Subject: [PATCH 07/10] update ci --- .github/workflows/qa.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index fe1541a6..7b401062 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -68,17 +68,13 @@ jobs: with: toolchain: nightly components: rustfmt - - uses: actions-rs/cargo@v1 - with: - toolchain: nightly - command: test - args: --all-features --no-fail-fast - env: - CARGO_INCREMENTAL: "0" - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Coverflow-checks=off" - RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Coverflow-checks=off" - - uses: actions-rs/grcov@v0.1 - id: coverage + - id: test + name: Run tests + run: cargo test --all --all-features + - id: coverage + name: Generate Coverage Report + uses: alekitto/grcov@v0.2 + - name: Coveralls upload uses: coverallsapp/github-action@master with: From 36d47df11732499f2287f95cf3ce60fc145859f3 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 15:01:24 +0900 Subject: [PATCH 08/10] lint --- chainsight-cdk/src/storage/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainsight-cdk/src/storage/storage.rs b/chainsight-cdk/src/storage/storage.rs index 5d6b61a2..b3cb43e8 100644 --- a/chainsight-cdk/src/storage/storage.rs +++ b/chainsight-cdk/src/storage/storage.rs @@ -154,7 +154,7 @@ thread_local! { MANAGER.with(|m|m.borrow().get(MemoryId::new(10))), ) ); - static LAST_KEY_STORE: RefCell = RefCell::new(String::new()); + static LAST_KEY_STORE: RefCell = const { RefCell::new(String::new()) } } From 3dc4d7dcabd630cf6ce1768733098ce035212d52 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 15:14:38 +0900 Subject: [PATCH 09/10] lint --- .github/workflows/qa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 7b401062..9ab58f5f 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -70,7 +70,7 @@ jobs: components: rustfmt - id: test name: Run tests - run: cargo test --all --all-features + run: cargo +nightly test --all --all-features - id: coverage name: Generate Coverage Report uses: alekitto/grcov@v0.2 From 8815c5687aac85d92766c672817f1f62d281f9b1 Mon Sep 17 00:00:00 2001 From: hide-yoshi Date: Thu, 28 Nov 2024 15:49:03 +0900 Subject: [PATCH 10/10] disable coverage temporarily --- .github/workflows/qa.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 9ab58f5f..d5a317d0 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -71,12 +71,12 @@ jobs: - id: test name: Run tests run: cargo +nightly test --all --all-features - - id: coverage - name: Generate Coverage Report - uses: alekitto/grcov@v0.2 - - - name: Coveralls upload - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ${{ steps.coverage.outputs.report }} + # - id: coverage + # name: Generate Coverage Report + # uses: alekitto/grcov@v0.2 + # + # - name: Coveralls upload + # uses: coverallsapp/github-action@master + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} + # path-to-lcov: ${{ steps.coverage.outputs.report }}