1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use diem_logger::json_log::JsonLogEntry;
use reqwest::{blocking, Url};
use std::collections::HashMap;

pub mod node_debug_service;

/// Implement default utility client for NodeDebugInterface
pub struct NodeDebugClient {
    client: blocking::Client,
    url: Url,
}

impl NodeDebugClient {
    /// Create NodeDebugInterfaceClient from a valid socket address.
    pub fn new<A: AsRef<str>>(address: A, port: u16) -> Self {
        let url = Url::parse(&format!("http://{}:{}", address.as_ref(), port)).unwrap();

        Self::from_url(url)
    }

    pub fn from_url(url: Url) -> Self {
        let client = blocking::Client::new();

        Self { client, url }
    }

    /// Retrieves the individual node metric.  Requires all sub fields to match in alphabetical order.
    pub fn get_node_metric<S: AsRef<str>>(&self, metric: S) -> Result<Option<i64>> {
        let metrics = self.get_node_metrics()?;
        Ok(metrics.get(metric.as_ref()).cloned())
    }

    /// Retrieves all node metrics for a given metric name.  Allows for filtering metrics by fields afterwards.
    pub fn get_node_metric_with_name(&self, metric: &str) -> Result<Option<HashMap<String, i64>>> {
        let metrics = self.get_node_metrics()?;
        let search_string = format!("{}{{", metric);

        let result: HashMap<_, _> = metrics
            .iter()
            .filter_map(|(key, value)| {
                if key.starts_with(&search_string) {
                    Some((key.clone(), *value))
                } else {
                    None
                }
            })
            .collect();

        if result.is_empty() {
            Ok(None)
        } else {
            Ok(Some(result))
        }
    }

    pub fn get_node_metrics(&self) -> Result<HashMap<String, i64>> {
        let mut url = self.url.clone();
        url.set_path("metrics");
        let response = self.client.get(url).send()?;

        if !response.status().is_success() {
            anyhow::bail!("Error querying metrics: {}", response.status());
        }

        response
            .json::<HashMap<String, String>>()?
            .into_iter()
            .map(|(k, v)| match v.parse::<i64>() {
                Ok(v) => Ok((k, v)),
                Err(_) => Err(anyhow::format_err!(
                    "Failed to parse stat value to i64 {}: {}",
                    &k,
                    &v
                )),
            })
            .collect()
    }

    pub fn get_events(&self) -> Result<Vec<JsonLogEntry>> {
        let mut url = self.url.clone();
        url.set_path("events");
        let response = self.client.get(url).send()?;

        Ok(response.json()?)
    }
}

/// Implement default utility client for AsyncNodeDebugInterface
pub struct AsyncNodeDebugClient {
    client: reqwest::Client,
    addr: String,
}

impl AsyncNodeDebugClient {
    /// Create AsyncNodeDebugInterface from a valid socket address.
    pub fn new<A: AsRef<str>>(client: reqwest::Client, address: A, port: u16) -> Self {
        let addr = format!("http://{}:{}", address.as_ref(), port);

        Self { client, addr }
    }

    pub async fn get_node_metric<S: AsRef<str>>(&mut self, metric: S) -> Result<Option<i64>> {
        let metrics = self.get_node_metrics().await?;
        Ok(metrics.get(metric.as_ref()).cloned())
    }

    pub async fn get_node_metrics(&mut self) -> Result<HashMap<String, i64>> {
        let response = self
            .client
            .get(&format!("{}/metrics", self.addr))
            .send()
            .await?;

        response
            .json::<HashMap<String, String>>()
            .await?
            .into_iter()
            .map(|(k, v)| match v.parse::<i64>() {
                Ok(v) => Ok((k, v)),
                Err(_) => Err(anyhow::format_err!(
                    "Failed to parse stat value to i64 {}: {}",
                    &k,
                    &v
                )),
            })
            .collect()
    }

    pub async fn get_events(&mut self) -> Result<Vec<JsonLogEntry>> {
        let response = self
            .client
            .get(&format!("{}/events", self.addr))
            .send()
            .await?;

        Ok(response.json().await?)
    }
}