Skip to main content

snarkos_node_metrics/
lib.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16pub mod names;
17
18// Expose the names at the crate level for easy access.
19pub use names::*;
20
21// Re-export the snarkVM metrics.
22pub use snarkvm::metrics::*;
23
24#[cfg(not(feature = "serial"))]
25use rayon::prelude::*;
26
27#[cfg(feature = "locktick")]
28use locktick::parking_lot::Mutex;
29#[cfg(not(feature = "locktick"))]
30use parking_lot::Mutex;
31use snarkvm::{
32    ledger::narwhal::TransmissionID,
33    prelude::{Block, Network, cfg_iter},
34};
35use std::{
36    collections::HashMap,
37    net::SocketAddr,
38    sync::{
39        Arc,
40        atomic::{AtomicUsize, Ordering},
41    },
42};
43use time::OffsetDateTime;
44
45/// Initializes the metrics and returns a handle to the task running the metrics exporter.
46pub fn initialize_metrics(ip: Option<SocketAddr>) {
47    // Build the Prometheus exporter.
48    let builder = metrics_exporter_prometheus::PrometheusBuilder::new();
49    if let Some(ip) = ip { builder.with_http_listener(ip) } else { builder }
50        .install()
51        .expect("can't build the prometheus exporter");
52
53    // Register the snarkVM metrics.
54    snarkvm::metrics::register_metrics();
55
56    // Register the metrics so they exist on init.
57    for name in crate::names::GAUGE_NAMES {
58        register_gauge(name);
59    }
60    for name in crate::names::COUNTER_NAMES {
61        register_counter(name);
62    }
63    for name in crate::names::HISTOGRAM_NAMES {
64        register_histogram(name);
65    }
66
67    // Set the build information metric
68    set_build_info();
69}
70
71pub fn update_block_metrics<N: Network>(block: &Block<N>) {
72    use snarkvm::ledger::ConfirmedTransaction;
73
74    let accepted_deploy = AtomicUsize::new(0);
75    let accepted_execute = AtomicUsize::new(0);
76    let rejected_deploy = AtomicUsize::new(0);
77    let rejected_execute = AtomicUsize::new(0);
78
79    // Add transaction to atomic counter based on enum type match.
80    cfg_iter!(block.transactions()).for_each(|tx| match tx {
81        ConfirmedTransaction::AcceptedDeploy(_, _, _) => {
82            accepted_deploy.fetch_add(1, Ordering::Relaxed);
83        }
84        ConfirmedTransaction::AcceptedExecute(_, _, _) => {
85            accepted_execute.fetch_add(1, Ordering::Relaxed);
86        }
87        ConfirmedTransaction::RejectedDeploy(_, _, _, _) => {
88            rejected_deploy.fetch_add(1, Ordering::Relaxed);
89        }
90        ConfirmedTransaction::RejectedExecute(_, _, _, _) => {
91            rejected_execute.fetch_add(1, Ordering::Relaxed);
92        }
93    });
94
95    increment_gauge(blocks::ACCEPTED_DEPLOY, accepted_deploy.load(Ordering::Relaxed) as f64);
96    increment_gauge(blocks::ACCEPTED_EXECUTE, accepted_execute.load(Ordering::Relaxed) as f64);
97    increment_gauge(blocks::REJECTED_DEPLOY, rejected_deploy.load(Ordering::Relaxed) as f64);
98    increment_gauge(blocks::REJECTED_EXECUTE, rejected_execute.load(Ordering::Relaxed) as f64);
99
100    // Update aborted transactions and solutions.
101    increment_gauge(blocks::ABORTED_TRANSACTIONS, block.aborted_transaction_ids().len() as f64);
102    increment_gauge(blocks::ABORTED_SOLUTIONS, block.aborted_solution_ids().len() as f64);
103}
104
105pub fn add_transmission_latency_metric<N: Network>(
106    transmissions_tracker: &Arc<Mutex<HashMap<TransmissionID<N>, i64>>>,
107    block: &Block<N>,
108) {
109    // The age at which we consider a transmission "stale".
110    const AGE_THRESHOLD_SECONDS: i32 = 30 * 60; // 30 minutes
111
112    // Retrieve all solution IDs (including aborted).
113    let solution_ids: std::collections::HashSet<_> =
114        block.solutions().solution_ids().chain(block.aborted_solution_ids()).collect();
115
116    // Retrieve all transaction IDs (including aborted).
117    let transaction_ids: std::collections::HashSet<_> =
118        block.transaction_ids().chain(block.aborted_transaction_ids()).collect();
119
120    let mut transmissions_tracker = transmissions_tracker.lock();
121    let ts_now = OffsetDateTime::now_utc().unix_timestamp();
122
123    // Determine which keys to remove.
124    let keys_to_remove = cfg_iter!(&*transmissions_tracker)
125        .flat_map(|(transmission_id, timestamp)| {
126            let elapsed_time = std::time::Duration::from_secs((ts_now - *timestamp) as u64);
127
128            if elapsed_time.as_secs() > AGE_THRESHOLD_SECONDS as u64 {
129                // This entry is stale-- remove it from transmission queue and record it as a stale transmission.
130                match transmission_id {
131                    TransmissionID::Solution(..) => increment_counter(consensus::STALE_UNCONFIRMED_SOLUTIONS),
132                    TransmissionID::Transaction(..) => increment_counter(consensus::STALE_UNCONFIRMED_TRANSACTIONS),
133                    TransmissionID::Ratification => {}
134                }
135                Some(*transmission_id)
136            } else {
137                let transmission_type = match transmission_id {
138                    TransmissionID::Solution(solution_id, _) if solution_ids.contains(solution_id) => Some("solution"),
139                    TransmissionID::Transaction(transaction_id, _) if transaction_ids.contains(transaction_id) => {
140                        Some("transaction")
141                    }
142                    // Either a ratification, or the transmission was not included in the block
143                    _ => None,
144                };
145
146                if let Some(transmission_type_string) = transmission_type {
147                    histogram_label(
148                        consensus::TRANSMISSION_LATENCY,
149                        "transmission_type",
150                        transmission_type_string.to_owned(),
151                        elapsed_time.as_secs_f64(),
152                    );
153                    Some(*transmission_id)
154                } else {
155                    None
156                }
157            }
158        })
159        .collect::<Vec<_>>();
160
161    // Remove keys of stale or seen transmissions.
162    for key in keys_to_remove {
163        transmissions_tracker.remove(&key);
164    }
165}
166
167// Include the generated build information
168mod built_info {
169    include!(concat!(env!("OUT_DIR"), "/built.rs"));
170}
171
172/// Sets the build information metric with version details as labels.
173/// The resulting metric will show as:
174/// snarkos_build_info{version="4.2.1",git_commit="abc123",git_branch="main",features="cuda,metrics"} 1
175pub fn set_build_info() {
176    let version = built_info::PKG_VERSION;
177    let git_commit = built_info::GIT_COMMIT_HASH.unwrap_or("unknown");
178    let git_branch = built_info::GIT_HEAD_REF.unwrap_or("unknown");
179    let features = built_info::FEATURES_LOWERCASE_STR.replace(' ', "");
180
181    ::metrics::gauge!(build::BUILD_INFO, "version" => version.to_string()).set(1.0);
182    ::metrics::gauge!(build::BUILD_INFO, "git_commit" => git_commit.to_string()).set(1.0);
183    ::metrics::gauge!(build::BUILD_INFO, "git_branch" => git_branch.to_string()).set(1.0);
184    ::metrics::gauge!(build::BUILD_INFO, "features" => features).set(1.0);
185}