Skip to content

Commit

Permalink
[feature] hyperledger-iroha#3964: application/x-parity-scale respon…
Browse files Browse the repository at this point in the history
…se for `/status` endpoint

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>
  • Loading branch information
0x009922 committed Oct 23, 2023
1 parent 0e13452 commit 5b2d50d
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 28 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ tempfile = { workspace = true }
dashmap = { workspace = true }

thread-local-panic-hook = { version = "0.1.0", optional = true }
hyper = "0.14.27"

[dev-dependencies]
serial_test = "2.0.0"
Expand Down
4 changes: 4 additions & 0 deletions cli/src/torii/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub enum Error {
#[cfg(feature = "telemetry")]
/// Error while getting Prometheus metrics
Prometheus(#[source] eyre::Report),
/// Internal error while getting status
StatusFailure(#[source] eyre::Report),
/// Cannot find status segment by provided path
StatusBadSegment(#[source] eyre::Report),
}

impl Reply for Error {
Expand Down
74 changes: 48 additions & 26 deletions cli/src/torii/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use std::num::NonZeroUsize;

use eyre::WrapErr;
use eyre::{eyre, WrapErr};
use futures::TryStreamExt;
use iroha_config::{
base::proxy::Documented,
Expand Down Expand Up @@ -349,37 +349,59 @@ fn handle_metrics(sumeragi: &SumeragiHandle) -> Result<String> {
.map_err(Error::Prometheus)
}

#[cfg(feature = "telemetry")]
#[allow(clippy::unnecessary_wraps)]
fn handle_status(sumeragi: &SumeragiHandle) -> Result<warp::reply::Json, Infallible> {
fn update_metrics_gracefully(sumeragi: &SumeragiHandle) {
if let Err(error) = sumeragi.update_metrics() {
iroha_logger::error!(%error, "Error while calling `sumeragi::update_metrics`.");
}
}

#[cfg(feature = "telemetry")]
#[allow(clippy::unnecessary_wraps)]
fn handle_status(sumeragi: SumeragiHandle, accept: String) -> Result<reply::Response> {
const PARITY_SCALE_MIME: &'_ str = "application/x-parity-scale";

update_metrics_gracefully(&sumeragi);
let status = Status::from(&sumeragi.metrics());
Ok(reply::json(&status))

if accept == PARITY_SCALE_MIME {
let body: hyper::Body = status.encode().into();

warp::http::Response::builder()
.status(StatusCode::OK)
.header(warp::http::header::CONTENT_TYPE, PARITY_SCALE_MIME)
.body(body)
.wrap_err("Failed to build response body")
.map_err(Error::StatusFailure)
} else {
Ok(reply::json(&status).into_response())
}
}

#[cfg(feature = "telemetry")]
#[allow(clippy::unused_async)]
async fn handle_status_precise(sumeragi: SumeragiHandle, segment: String) -> Result<Json> {
if let Err(error) = sumeragi.update_metrics() {
iroha_logger::error!(%error, "Error while calling `sumeragi::update_metrics`.");
}
async fn handle_status_precise(sumeragi: SumeragiHandle, path: warp::path::Tail) -> Result<Json> {
use eyre::ContextCompat;

update_metrics_gracefully(&sumeragi);

// TODO: This probably can be optimised to elide the full
// structure. Ideally there should remain a list of fields and
// field aliases somewhere in `serde` macro output, which can
// elide the creation of the value, and directly read the value
// behind the mutex.
let status = Status::from(&sumeragi.metrics());
match serde_json::to_value(status) {
Ok(value) => Ok(value
.get(segment)
.map_or_else(|| reply::json(&value), reply::json)),
Err(err) => {
iroha_logger::error!(%err, "Error while converting to JSON value");
Ok(reply::json(&None::<String>))
}
}
let value = serde_json::to_value(Status::from(&sumeragi.metrics()))
.wrap_err("Failed to serialize JSON")
.map_err(Error::StatusFailure)?;

let reply = path
.as_str()
.split("/")
.try_fold(&value, |value, path| value.get(path))
.wrap_err_with(|| eyre!("Path not found: \"{}\"", path.as_str()))
.map(reply::json)
.map_err(Error::StatusBadSegment)?;

Ok(reply)
}

impl Torii {
Expand Down Expand Up @@ -432,14 +454,14 @@ impl Torii {
handle_status_precise,
status_path
.and(add_state!(self.sumeragi.clone()))
.and(warp::path::param()),
.and(warp::path::tail()),
);
let get_router_status_bare =
status_path
.and(add_state!(self.sumeragi.clone()))
.and_then(|sumeragi| async move {
Ok::<_, Infallible>(WarpResult(handle_status(&sumeragi)))
});
let get_router_status_bare = status_path
.and(add_state!(self.sumeragi.clone()))
.and(warp::header(warp::http::header::ACCEPT.as_str()))
.and_then(|sumeragi, accept| async move {
Ok::<_, Infallible>(WarpResult(handle_status(sumeragi, accept)))
});
let get_router_metrics = warp::path(uri::METRICS)
.and(add_state!(self.sumeragi))
.and_then(|sumeragi| async move {
Expand Down
6 changes: 5 additions & 1 deletion telemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ tokio-stream = { workspace = true, features = ["fs"] }
tokio-tungstenite = { workspace = true }
url = { workspace = true, features = ["serde"] }
prometheus = { workspace = true }

parity-scale-codec = { workspace = true }

[build-dependencies]
eyre = { workspace = true }
vergen = { workspace = true, features = ["cargo", "git", "gitoxide"] }

[dev-dependencies]
expect-test = { workspace = true }
hex = { workspace = true }

66 changes: 65 additions & 1 deletion telemetry/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
time::{Duration, SystemTime},
};

use parity_scale_codec::{Compact, Encode};
use prometheus::{
core::{AtomicU64, GenericGauge, GenericGaugeVec},
Encoder, Histogram, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, Opts, Registry,
Expand All @@ -21,22 +22,36 @@ impl Default for Uptime {
}
}

impl Encode for Uptime {
fn encode(&self) -> Vec<u8> {
let secs = self.0.as_secs();
let nanos = self.0.subsec_nanos();
(Compact(secs), Compact(nanos)).encode()
}
}

/// Response body for GET status request
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Encode)]
pub struct Status {
/// Number of connected peers, except for the reporting peer itself
#[codec(compact)]
pub peers: u64,
/// Number of committed blocks
#[codec(compact)]
pub blocks: u64,
/// Number of accepted transactions
#[codec(compact)]
pub txs_accepted: u64,
/// Number of rejected transactions
#[codec(compact)]
pub txs_rejected: u64,
/// Uptime since genesis block creation
pub uptime: Uptime,
/// Number of view changes in the current round
#[codec(compact)]
pub view_changes: u64,
/// Number of the transactions in the queue
#[codec(compact)]
pub queue_size: u64,
}

Expand Down Expand Up @@ -205,6 +220,8 @@ impl Metrics {

#[cfg(test)]
mod test {
#![allow(clippy::restriction)]

use super::*;

#[test]
Expand All @@ -219,4 +236,51 @@ mod test {
println!("{:?}", Status::from(&Box::new(metrics)));
println!("{:?}", Status::default());
}

fn sample_status() -> Status {
Status {
peers: 4,
blocks: 5,
txs_accepted: 31,
txs_rejected: 3,
uptime: Uptime(Duration::new(5, 937000000)),
view_changes: 2,
queue_size: 18,
}
}

#[test]
fn serialize_status_json() {
let value = sample_status();

let actual = serde_json::to_string_pretty(&value).expect("Sample is valid");
// CAUTION: if this is outdated, make sure to update the documentation:
// https://hyperledger.github.io/iroha-2-docs/api/torii-endpoints#status
let expected = expect_test::expect![[r#"
{
"peers": 4,
"blocks": 5,
"txs_accepted": 31,
"txs_rejected": 3,
"uptime": {
"secs": 5,
"nanos": 937000000
},
"view_changes": 2,
"queue_size": 18
}"#]];
expected.assert_eq(&actual);
}

#[test]
fn serialize_status_scale() {
let value = sample_status();
let bytes = value.encode();

let actual = hex::encode(&bytes);
// CAUTION: if this is outdated, make sure to update the documentation:
// https://hyperledger.github.io/iroha-2-docs/api/torii-endpoints#status
let expected = expect_test::expect!["10147c0c1402f165df0848"];
expected.assert_eq(&actual);
}
}

0 comments on commit 5b2d50d

Please sign in to comment.