1use std::collections::{BTreeMap, HashMap};
2
3use num_format::{Locale, ToFormattedString};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Metric {
8 pub name: String,
9 pub value: f64,
10}
11
12impl Metric {
13 pub fn new(name: String, value: f64) -> Self {
14 Self { name, value }
15 }
16}
17
18#[derive(Debug, Clone, Eq)]
19pub struct Labels(pub Vec<(String, String)>);
20
21#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
22pub struct MdTableCell {
23 pub val: f64,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub diff: Option<f64>,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct BencherValue {
30 pub value: f64,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub lower_value: Option<f64>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub upper_value: Option<f64>,
35}
36
37#[derive(Clone, Debug, Default, Serialize, Deserialize)]
39pub struct BenchmarkOutput {
40 #[serde(flatten)]
42 pub by_name: HashMap<String, HashMap<String, BencherValue>>,
43}
44
45impl Labels {
46 pub fn get(&self, key: &str) -> Option<&str> {
47 self.0
48 .iter()
49 .find_map(|(k, v)| (k == key).then_some(v.as_str()))
50 }
51
52 pub fn remove(&mut self, key: &str) {
53 self.0.retain(|(k, _)| k != key);
54 }
55}
56
57impl PartialEq for Labels {
58 fn eq(&self, other: &Self) -> bool {
59 if self.0.len() != other.0.len() {
60 return false;
61 }
62 let mut self_sorted = self.0.clone();
63 let mut other_sorted = other.0.clone();
64 self_sorted.sort();
65 other_sorted.sort();
66 self_sorted == other_sorted
67 }
68}
69
70impl std::hash::Hash for Labels {
71 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
72 let mut sorted = self.0.clone();
73 sorted.sort();
74 sorted.hash(state);
75 }
76}
77
78impl From<Vec<[String; 2]>> for Labels {
79 fn from(v: Vec<[String; 2]>) -> Self {
80 Labels(v.into_iter().map(|[k, v]| (k, v)).collect())
81 }
82}
83
84#[derive(Debug, Default)]
85pub struct MetricDb {
86 pub flat_dict: HashMap<Labels, Vec<Metric>>,
87 pub dict_by_label_types: HashMap<Vec<String>, BTreeMap<Vec<String>, Vec<Metric>>>,
88}
89
90impl MetricDb {
91 pub fn format_number(value: f64) -> String {
92 let whole = value.trunc() as i64;
93 let decimal = (value.fract() * 100.0).abs().round() as i64;
94
95 if decimal == 0 {
96 whole.to_formatted_string(&Locale::en)
97 } else {
98 format!("{}.{:02}", whole.to_formatted_string(&Locale::en), decimal)
99 }
100 }
101}
102
103impl MdTableCell {
104 pub fn new(val: f64, diff: Option<f64>) -> Self {
105 Self { val, diff }
106 }
107}
108
109impl std::fmt::Display for MdTableCell {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 let div = MetricDb::format_number(self.val);
112 if let Some(diff) = self.diff {
113 let color = if diff > 0.0 { "red" } else { "green" };
114 let original_val = self.val - diff;
115 let diff_percent = diff / original_val;
116 let span = format!("{:+.0} [{:+.1}%]", diff, diff_percent * 100.0);
117 if diff_percent.abs() < 0.001 {
118 write!(f, "{}", format_cell(&div, None, None))
119 } else {
120 write!(f, "{}", format_cell(&div, Some(&span), Some(color)))
121 }
122 } else {
123 write!(f, "{}", format_cell(&div, None, None))
124 }
125 }
126}
127fn format_cell(div: &str, span: Option<&str>, span_color: Option<&str>) -> String {
128 let mut ret = String::new();
129 if let Some(span) = span {
130 if let Some(color) = span_color {
131 ret.push_str(&format!("<span style='color: {}'>({})</span>", color, span));
132 }
133 }
134 ret.push_str(&format!(" {div}"));
135 ret
136}
137
138impl BencherValue {
139 pub fn new(value: f64) -> Self {
140 Self {
141 value,
142 lower_value: None,
143 upper_value: None,
144 }
145 }
146}
147
148impl From<MdTableCell> for BencherValue {
149 fn from(cell: MdTableCell) -> Self {
150 Self::new(cell.val)
151 }
152}
153
154#[derive(Debug, Serialize, Deserialize)]
156pub struct MetricsFile {
157 #[serde(default)]
158 pub counter: Vec<MetricEntry>,
159 #[serde(default)]
160 pub gauge: Vec<MetricEntry>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct MetricEntry {
165 pub labels: Vec<[String; 2]>,
166 pub metric: String,
167 #[serde(deserialize_with = "deserialize_f64_from_string")]
168 pub value: f64,
169}
170
171pub fn deserialize_f64_from_string<'de, D>(deserializer: D) -> Result<f64, D::Error>
172where
173 D: serde::Deserializer<'de>,
174{
175 let s = String::deserialize(deserializer)?;
176 s.parse::<f64>().map_err(serde::de::Error::custom)
177}