kratactl/
format.rs

1use std::{collections::HashMap, time::Duration};
2
3use anyhow::Result;
4use fancy_duration::FancyDuration;
5use human_bytes::human_bytes;
6use prost_reflect::{DynamicMessage, ReflectMessage};
7use prost_types::Value;
8use termtree::Tree;
9
10use krata::v1::common::{Zone, ZoneMetricFormat, ZoneMetricNode, ZoneState};
11
12pub fn proto2dynamic(proto: impl ReflectMessage) -> Result<DynamicMessage> {
13    Ok(DynamicMessage::decode(
14        proto.descriptor(),
15        proto.encode_to_vec().as_slice(),
16    )?)
17}
18
19pub fn value2kv(value: serde_json::Value) -> Result<HashMap<String, String>> {
20    let mut map = HashMap::new();
21    fn crawl(prefix: String, map: &mut HashMap<String, String>, value: serde_json::Value) {
22        fn dot(prefix: &str, next: String) -> String {
23            if prefix.is_empty() {
24                next.to_string()
25            } else {
26                format!("{}.{}", prefix, next)
27            }
28        }
29
30        match value {
31            serde_json::Value::Null => {
32                map.insert(prefix, "null".to_string());
33            }
34
35            serde_json::Value::String(value) => {
36                map.insert(prefix, value);
37            }
38
39            serde_json::Value::Bool(value) => {
40                map.insert(prefix, value.to_string());
41            }
42
43            serde_json::Value::Number(value) => {
44                map.insert(prefix, value.to_string());
45            }
46
47            serde_json::Value::Array(value) => {
48                for (i, item) in value.into_iter().enumerate() {
49                    let next = dot(&prefix, i.to_string());
50                    crawl(next, map, item);
51                }
52            }
53
54            serde_json::Value::Object(value) => {
55                for (key, item) in value {
56                    let next = dot(&prefix, key);
57                    crawl(next, map, item);
58                }
59            }
60        }
61    }
62    crawl("".to_string(), &mut map, value);
63    Ok(map)
64}
65
66pub fn proto2kv(proto: impl ReflectMessage) -> Result<HashMap<String, String>> {
67    let message = proto2dynamic(proto)?;
68    let value = serde_json::to_value(message)?;
69    value2kv(value)
70}
71
72pub fn kv2line(map: HashMap<String, String>) -> String {
73    map.iter()
74        .map(|(k, v)| format!("{}=\"{}\"", k, v.replace('"', "\\\"")))
75        .collect::<Vec<_>>()
76        .join(" ")
77}
78
79pub fn zone_state_text(status: ZoneState) -> String {
80    match status {
81        ZoneState::Creating => "creating",
82        ZoneState::Created => "created",
83        ZoneState::Destroying => "destroying",
84        ZoneState::Destroyed => "destroyed",
85        ZoneState::Exited => "exited",
86        ZoneState::Failed => "failed",
87        _ => "unknown",
88    }
89    .to_string()
90}
91
92pub fn zone_simple_line(zone: &Zone) -> String {
93    let state = zone_state_text(
94        zone.status
95            .as_ref()
96            .map(|x| x.state())
97            .unwrap_or(ZoneState::Unknown),
98    );
99    let name = zone.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
100    let network_status = zone.status.as_ref().and_then(|x| x.network_status.as_ref());
101    let ipv4 = network_status.map(|x| x.zone_ipv4.as_str()).unwrap_or("");
102    let ipv6 = network_status.map(|x| x.zone_ipv6.as_str()).unwrap_or("");
103    format!("{}\t{}\t{}\t{}\t{}", zone.id, state, name, ipv4, ipv6)
104}
105
106fn metrics_value_string(value: Value) -> String {
107    proto2dynamic(value)
108        .map(|x| serde_json::to_string(&x).ok())
109        .ok()
110        .flatten()
111        .unwrap_or_default()
112}
113
114fn metrics_value_numeric(value: Value) -> f64 {
115    let string = metrics_value_string(value);
116    string.parse::<f64>().ok().unwrap_or(f64::NAN)
117}
118
119pub fn metrics_value_pretty(value: Value, format: ZoneMetricFormat) -> String {
120    match format {
121        ZoneMetricFormat::Bytes => human_bytes(metrics_value_numeric(value)),
122        ZoneMetricFormat::Integer => (metrics_value_numeric(value) as u64).to_string(),
123        ZoneMetricFormat::DurationSeconds => {
124            FancyDuration(Duration::from_secs_f64(metrics_value_numeric(value))).to_string()
125        }
126        _ => metrics_value_string(value),
127    }
128}
129
130fn metrics_flat_internal(prefix: &str, node: ZoneMetricNode, map: &mut HashMap<String, String>) {
131    if let Some(value) = node.value {
132        map.insert(prefix.to_string(), metrics_value_string(value));
133    }
134
135    for child in node.children {
136        let path = if prefix.is_empty() {
137            child.name.to_string()
138        } else {
139            format!("{}.{}", prefix, child.name)
140        };
141        metrics_flat_internal(&path, child, map);
142    }
143}
144
145pub fn metrics_flat(root: ZoneMetricNode) -> HashMap<String, String> {
146    let mut map = HashMap::new();
147    metrics_flat_internal("", root, &mut map);
148    map
149}
150
151pub fn metrics_tree(node: ZoneMetricNode) -> Tree<String> {
152    let mut name = node.name.to_string();
153    let format = node.format();
154    if let Some(value) = node.value {
155        let value_string = metrics_value_pretty(value, format);
156        name.push_str(&format!(": {}", value_string));
157    }
158
159    let mut tree = Tree::new(name);
160    for child in node.children {
161        tree.push(metrics_tree(child));
162    }
163    tree
164}