hotshot_query_service/data_source/metrics.rs
1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the HotShot Query Service library.
3//
4// This program is free software: you can redistribute it and/or modify it under the terms of the GNU
5// General Public License as published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
8// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10// You should have received a copy of the GNU General Public License along with this program. If not,
11// see <https://www.gnu.org/licenses/>.
12
13#![cfg(feature = "metrics-data-source")]
14
15use async_trait::async_trait;
16
17use crate::{
18 metrics::PrometheusMetrics,
19 status::{HasMetrics, StatusDataSource},
20 QueryError, QueryResult,
21};
22
23/// A minimal data source for the status API provided in this crate, with no persistent storage.
24///
25/// [`MetricsDataSource`] uses the metrics provided by HotShot to implement [`StatusDataSource`]. It
26/// updates automatically whenever HotShot updates its metrics. All of the state for the metrics
27/// data source is kept in memory, so it does not require a persistent storage backend. This makes
28/// [`MetricsDataSource`] an attractive choice when only the status API is desired, not the
29/// availability API.
30///
31/// Since all the state required by [`MetricsDataSource`] is updated automatically by HotShot, there
32/// is no need to spawn an update loop to update the data source with new events, as is required
33/// with full archival data sources like [`SqlDataSource`](super::SqlDataSource). Instead,
34/// [`MetricsDataSource`] will be populated with useful data as long as its
35/// [`populate_metrics`](crate::status::UpdateStatusData::populate_metrics) is used to initialize
36/// HotShot:
37///
38/// ```
39/// # use hotshot::SystemContext;
40/// # use hotshot_query_service::{
41/// # data_source::MetricsDataSource,
42/// # status::UpdateStatusData,
43/// # testing::mocks::{MockNodeImpl as AppNodeImpl, MockTypes as AppTypes, MockVersions as AppVersions},
44/// # Error,
45/// # };
46/// # use hotshot_types::consensus::ConsensusMetricsValue;
47/// # use hotshot_example_types::node_types::TestVersions;
48/// # async fn doc() -> Result<(), hotshot_query_service::Error> {
49/// let data_source = MetricsDataSource::default();
50/// let hotshot = SystemContext::<AppTypes, AppNodeImpl, AppVersions>::init(
51/// # panic!(), panic!(), panic!(), panic!(), panic!(), panic!(), panic!(),
52/// ConsensusMetricsValue::new(&*data_source.populate_metrics()), panic!(),
53/// panic!(),
54/// // Other fields omitted
55/// ).await.map_err(Error::internal)?.0;
56/// # Ok(())
57/// # }
58/// ```
59#[derive(Clone, Debug, Default)]
60pub struct MetricsDataSource {
61 metrics: PrometheusMetrics,
62}
63
64impl HasMetrics for MetricsDataSource {
65 fn metrics(&self) -> &PrometheusMetrics {
66 &self.metrics
67 }
68}
69
70#[async_trait]
71impl StatusDataSource for MetricsDataSource {
72 async fn block_height(&self) -> QueryResult<usize> {
73 let last_synced_height = self
74 .consensus_metrics()?
75 .get_gauge("last_synced_block_height")
76 .map_err(|err| QueryError::Error {
77 message: err.to_string(),
78 })?
79 .get();
80 Ok(last_synced_height)
81 }
82}
83
84#[cfg(any(test, feature = "testing"))]
85mod impl_testable_data_source {
86 use hotshot::types::Event;
87
88 use super::*;
89 use crate::testing::{consensus::DataSourceLifeCycle, mocks::MockTypes};
90
91 #[async_trait]
92 impl DataSourceLifeCycle for MetricsDataSource {
93 type Storage = PrometheusMetrics;
94
95 async fn create(_node_id: usize) -> Self::Storage {
96 Default::default()
97 }
98
99 async fn connect(storage: &Self::Storage) -> Self {
100 Self {
101 metrics: storage.clone(),
102 }
103 }
104
105 async fn reset(storage: &Self::Storage) -> Self {
106 Self {
107 metrics: storage.clone(),
108 }
109 }
110
111 async fn handle_event(&self, _event: &Event<MockTypes>) {}
112 }
113}
114
115#[cfg(test)]
116mod test {
117 use super::{super::status_tests, MetricsDataSource};
118 // For some reason this is the only way to import the macro defined in another module of this
119 // crate.
120 use crate::*;
121
122 instantiate_status_tests!(MetricsDataSource);
123}