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}