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}