Skip to main content

tycho_core/block_strider/subscriber/
metrics_subscriber.rs

1use anyhow::Result;
2use futures_util::future::{BoxFuture, FutureExt};
3use tycho_block_util::block::BlockStuff;
4use tycho_types::models::*;
5use tycho_util::metrics::HistogramGuard;
6use tycho_util::sync::rayon_run;
7
8use crate::block_strider::{
9    BlockSubscriber, BlockSubscriberContext, StateSubscriber, StateSubscriberContext,
10};
11
12#[derive(Debug, Clone, Copy)]
13pub struct MetricsSubscriber;
14
15impl BlockSubscriber for MetricsSubscriber {
16    type Prepared = ();
17
18    type PrepareBlockFut<'a> = futures_util::future::Ready<Result<()>>;
19    type HandleBlockFut<'a> = BoxFuture<'static, Result<()>>;
20
21    fn prepare_block<'a>(&'a self, _: &'a BlockSubscriberContext) -> Self::PrepareBlockFut<'a> {
22        futures_util::future::ready(Ok(()))
23    }
24
25    fn handle_block(
26        &self,
27        cx: &BlockSubscriberContext,
28        _: Self::Prepared,
29    ) -> Self::HandleBlockFut<'_> {
30        handle_block_fut(cx.block.clone())
31    }
32}
33
34impl StateSubscriber for MetricsSubscriber {
35    type HandleStateFut<'a> = BoxFuture<'static, Result<()>>;
36
37    fn handle_state(&self, cx: &StateSubscriberContext) -> Self::HandleStateFut<'_> {
38        handle_block_fut(cx.block.clone())
39    }
40}
41
42fn handle_block_fut(block: BlockStuff) -> BoxFuture<'static, Result<()>> {
43    let histogram = HistogramGuard::begin("tycho_core_metrics_subscriber_handle_block_time");
44
45    rayon_run(move || {
46        // NOTE: Move histogram into the closure to ensure it's dropped
47        // after the block is handled. We started recording it earlier
48        // to also include the time spent on `rayon_run` overhead.
49        let _histogram = histogram;
50
51        if let Err(e) = handle_block(&block) {
52            tracing::error!("failed to handle block: {e:?}");
53        }
54        Ok(())
55    })
56    .boxed()
57}
58
59fn handle_block(block: &BlockStuff) -> Result<()> {
60    let block_id = block.id();
61    let info = block.load_info()?;
62    let extra = block.load_extra()?;
63
64    let mut in_msg_count: u32 = 0;
65    for descr in extra.in_msg_description.load()?.iter() {
66        let (_, _, in_msg) = descr?;
67        in_msg_count += matches!(
68            in_msg,
69            InMsg::External(_) | InMsg::Immediate(_) | InMsg::Final(_)
70        ) as u32;
71    }
72
73    let mut out_msgs_count: u32 = 0;
74    for descr in extra.out_msg_description.load()?.iter() {
75        let (_, _, out_msg) = descr?;
76        out_msgs_count += matches!(out_msg, OutMsg::New(_) | OutMsg::Immediate(_)) as u32;
77    }
78
79    let mut transaction_count = 0u32;
80    let mut message_count = 0u32;
81    let mut ext_message_count = 0u32;
82    let mut account_blocks_count = 0u32;
83    let mut contract_deployments = 0u32;
84    let mut contract_destructions = 0u32;
85    let mut total_gas_used = 0;
86
87    let account_blocks = extra.account_blocks.load()?;
88    for entry in account_blocks.iter() {
89        let (_, _, account_block) = entry?;
90        account_blocks_count += 1;
91
92        for entry in account_block.transactions.iter() {
93            let (_, _, tx) = entry?;
94            let tx = tx.load()?;
95
96            transaction_count += 1;
97            message_count += tx.in_msg.is_some() as u32 + tx.out_msg_count.into_inner() as u32;
98
99            if let Some(in_msg) = &tx.in_msg {
100                ext_message_count += in_msg.parse::<MsgType>()?.is_external_in() as u32;
101            }
102
103            let was_active = tx.orig_status == AccountStatus::Active;
104            let is_active = tx.end_status == AccountStatus::Active;
105
106            contract_deployments += (!was_active && is_active) as u32;
107            contract_destructions += (was_active && !is_active) as u32;
108
109            total_gas_used += 'gas: {
110                match tx.load_info()? {
111                    TxInfo::Ordinary(info) => {
112                        if let ComputePhase::Executed(phase) = &info.compute_phase {
113                            break 'gas phase.gas_used.into_inner();
114                        }
115                    }
116                    TxInfo::TickTock(info) => {
117                        if let ComputePhase::Executed(phase) = &info.compute_phase {
118                            break 'gas phase.gas_used.into_inner();
119                        }
120                    }
121                };
122
123                0
124            };
125        }
126    }
127
128    let out_in_message_ratio = if in_msg_count > 0 {
129        out_msgs_count as f64 / in_msg_count as f64
130    } else {
131        0.0
132    };
133    let out_message_account_ratio = if account_blocks_count > 0 {
134        out_msgs_count as f64 / account_blocks_count as f64
135    } else {
136        0.0
137    };
138
139    let labels = &[("workchain", block_id.shard.workchain().to_string())];
140    metrics::histogram!("tycho_bc_software_version", labels).record(info.gen_software.version);
141    metrics::histogram!("tycho_bc_in_msg_count", labels).record(in_msg_count);
142    metrics::histogram!("tycho_bc_out_msg_count", labels).record(out_msgs_count);
143
144    metrics::counter!("tycho_bc_txs_total", labels).increment(transaction_count as _);
145    metrics::counter!("tycho_bc_msgs_total", labels).increment(message_count as _);
146    metrics::counter!("tycho_bc_ext_msgs_total", labels).increment(ext_message_count as _);
147
148    metrics::counter!("tycho_bc_contract_deploy_total", labels)
149        .increment(contract_deployments as _);
150    metrics::counter!("tycho_bc_contract_delete_total", labels)
151        .increment(contract_destructions as _);
152
153    metrics::histogram!("tycho_bc_total_gas_used", labels).record(total_gas_used as f64);
154    metrics::histogram!("tycho_bc_out_in_msg_ratio", labels).record(out_in_message_ratio);
155    metrics::histogram!("tycho_bc_out_msg_acc_ratio", labels).record(out_message_account_ratio);
156
157    Ok(())
158}