canic/ops/model/memory/
cycles.rs1pub use crate::model::memory::cycles::CycleTrackerView;
2
3use crate::{
4 cdk::{
5 futures::spawn,
6 timers::{TimerId, clear_timer, set_timer, set_timer_interval},
7 },
8 interface::ic::canister_cycle_balance,
9 log,
10 log::Topic,
11 model::memory::cycles::CycleTracker,
12 ops::{config::ConfigOps, model::memory::EnvOps},
13 types::Cycles,
14 utils::time::now_secs,
15};
16use candid::CandidType;
17use serde::Serialize;
18use std::{cell::RefCell, time::Duration};
19
20thread_local! {
25 static TIMER: RefCell<Option<TimerId>> = const { RefCell::new(None) };
26}
27
28const TRACKER_INIT_DELAY: Duration = Duration::new(10, 0);
34
35const TRACKER_INTERVAL_SECS: Duration = Duration::from_secs(60 * 10);
37
38#[derive(CandidType, Serialize)]
43pub struct CycleTrackerPage {
44 pub entries: CycleTrackerView,
45 pub total: u64,
46}
47
48pub struct CycleTrackerOps;
53
54impl CycleTrackerOps {
55 pub fn start() {
58 TIMER.with_borrow_mut(|slot| {
59 if slot.is_some() {
60 return;
61 }
62
63 let init = set_timer(TRACKER_INIT_DELAY, async {
64 let _ = Self::track();
65
66 let interval = set_timer_interval(TRACKER_INTERVAL_SECS, || async {
67 let _ = Self::track();
68 });
69
70 TIMER.with_borrow_mut(|slot| *slot = Some(interval));
71 });
72
73 *slot = Some(init);
74 });
75 }
76
77 pub fn stop() {
79 TIMER.with_borrow_mut(|slot| {
80 if let Some(id) = slot.take() {
81 clear_timer(id);
82 }
83 });
84 }
85
86 #[must_use]
87 pub fn track() -> bool {
88 let ts = now_secs();
89 let cycles = canister_cycle_balance().to_u128();
90
91 if !EnvOps::is_root() {
93 Self::check_auto_topup();
94 }
95
96 CycleTracker::record(ts, cycles)
97 }
98
99 fn check_auto_topup() {
100 use crate::ops::request::cycles_request;
101
102 if let Ok(canister_cfg) = ConfigOps::current_canister()
103 && let Some(topup) = canister_cfg.topup
104 {
105 let cycles = canister_cycle_balance();
106
107 if cycles < topup.threshold {
108 spawn(async move {
109 match cycles_request(topup.amount.to_u128()).await {
110 Ok(res) => log!(
111 Topic::Cycles,
112 Ok,
113 "💫 requested {}, topped up by {}, now {}",
114 topup.amount,
115 Cycles::from(res.cycles_transferred),
116 canister_cycle_balance()
117 ),
118 Err(e) => log!(Topic::Cycles, Error, "💫 failed to request cycles: {e}"),
119 }
120 });
121 }
122 }
123 }
124
125 #[must_use]
126 pub fn page(offset: u64, limit: u64) -> CycleTrackerPage {
127 let entries = CycleTracker::entries(offset, limit);
128 let total = CycleTracker::len();
129
130 CycleTrackerPage { entries, total }
131 }
132}