Skip to main content

fuchsiaperf/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use serde::{Deserialize, Serialize};
6
7/// A struct that can be json serialized to the fuchsiaperf.json format.
8#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
9#[serde(into = "JsonFuchsiaPerfBenchmarkResult", try_from = "JsonFuchsiaPerfBenchmarkResult")]
10pub struct FuchsiaPerfBenchmarkResult {
11    pub label: String,
12    pub test_suite: String,
13    pub unit: Unit,
14    pub direction: Direction,
15    pub values: Vec<f64>,
16}
17
18/// The unit of a benchmark result.
19#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
20pub enum Unit {
21    #[serde(rename = "ns")]
22    #[serde(alias = "nanoseconds")]
23    Nanoseconds,
24    #[serde(rename = "ms")]
25    #[serde(alias = "milliseconds")]
26    Milliseconds,
27    #[serde(rename = "bytes")]
28    Bytes,
29    #[serde(rename = "bytes/second")]
30    BytesPerSecond,
31    #[serde(rename = "frames/second")]
32    FramesPerSecond,
33    #[serde(rename = "percent")]
34    Percent,
35    #[serde(rename = "count")]
36    Count,
37    #[serde(rename = "W")]
38    Watts,
39}
40
41impl std::fmt::Display for Unit {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Unit::Nanoseconds => write!(f, "ns"),
45            Unit::Milliseconds => write!(f, "ms"),
46            Unit::Bytes => write!(f, "bytes"),
47            Unit::BytesPerSecond => write!(f, "bytes/second"),
48            Unit::FramesPerSecond => write!(f, "frames/second"),
49            Unit::Percent => write!(f, "percent"),
50            Unit::Count => write!(f, "count"),
51            Unit::Watts => write!(f, "W"),
52        }
53    }
54}
55
56impl std::str::FromStr for Unit {
57    type Err = String;
58
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        match s {
61            "ns" | "nanoseconds" => Ok(Unit::Nanoseconds),
62            "ms" | "milliseconds" => Ok(Unit::Milliseconds),
63            "bytes" => Ok(Unit::Bytes),
64            "bytes/second" => Ok(Unit::BytesPerSecond),
65            "frames/second" => Ok(Unit::FramesPerSecond),
66            "percent" => Ok(Unit::Percent),
67            "count" => Ok(Unit::Count),
68            "W" => Ok(Unit::Watts),
69            _ => Err(format!("Invalid benchmark unit: {}", s)),
70        }
71    }
72}
73
74/// The direction of a benchmark result. Controls which kinds of changes are considered
75/// regressions or improvements.
76#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
77pub enum Direction {
78    #[serde(rename = "biggerIsBetter")]
79    BiggerBetter,
80    #[serde(rename = "smallerIsBetter")]
81    SmallerBetter,
82}
83
84impl std::fmt::Display for Direction {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            Direction::BiggerBetter => write!(f, "biggerIsBetter"),
88            Direction::SmallerBetter => write!(f, "smallerIsBetter"),
89        }
90    }
91}
92
93impl std::str::FromStr for Direction {
94    type Err = String;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        match s {
98            "biggerIsBetter" => Ok(Direction::BiggerBetter),
99            "smallerIsBetter" => Ok(Direction::SmallerBetter),
100            _ => Err(format!("Invalid benchmark direction: {}", s)),
101        }
102    }
103}
104
105#[derive(Deserialize, Serialize)]
106struct JsonFuchsiaPerfBenchmarkResult {
107    label: String,
108    test_suite: String,
109    unit: String,
110    values: Vec<f64>,
111}
112
113impl From<FuchsiaPerfBenchmarkResult> for JsonFuchsiaPerfBenchmarkResult {
114    fn from(result: FuchsiaPerfBenchmarkResult) -> Self {
115        let unit = format!("{}_{}", result.unit, result.direction);
116        JsonFuchsiaPerfBenchmarkResult {
117            label: result.label,
118            test_suite: result.test_suite,
119            unit,
120            values: result.values,
121        }
122    }
123}
124
125impl TryFrom<JsonFuchsiaPerfBenchmarkResult> for FuchsiaPerfBenchmarkResult {
126    type Error = String;
127
128    fn try_from(json: JsonFuchsiaPerfBenchmarkResult) -> Result<Self, Self::Error> {
129        let (unit, direction) = if let Some((u, d)) = json.unit.rsplit_once('_') {
130            (u.parse()?, d.parse()?)
131        } else {
132            let u: Unit = json.unit.parse()?;
133            let d = match u {
134                Unit::Nanoseconds
135                | Unit::Milliseconds
136                | Unit::Bytes
137                | Unit::Count
138                | Unit::Watts => Direction::SmallerBetter,
139                Unit::BytesPerSecond | Unit::FramesPerSecond | Unit::Percent => {
140                    Direction::BiggerBetter
141                }
142            };
143            (u, d)
144        };
145
146        Ok(FuchsiaPerfBenchmarkResult {
147            label: json.label,
148            test_suite: json.test_suite,
149            unit,
150            direction,
151            values: json.values,
152        })
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use serde_json::json;
160
161    #[test]
162    fn test_serialization_with_direction() {
163        let result = FuchsiaPerfBenchmarkResult {
164            label: "test_label".to_string(),
165            test_suite: "test_suite".to_string(),
166            unit: Unit::Nanoseconds,
167            direction: Direction::SmallerBetter,
168            values: vec![1.0, 2.0],
169        };
170
171        let json = serde_json::to_value(&result).unwrap();
172        let expected = json!({
173            "label": "test_label",
174            "test_suite": "test_suite",
175            "unit": "ns_smallerIsBetter",
176            "values": [1.0, 2.0]
177        });
178
179        assert_eq!(json, expected);
180    }
181
182    #[test]
183    fn test_serialization_with_bigger_better() {
184        let result = FuchsiaPerfBenchmarkResult {
185            label: "test_label".to_string(),
186            test_suite: "test_suite".to_string(),
187            unit: Unit::BytesPerSecond,
188            direction: Direction::BiggerBetter,
189            values: vec![1.0, 2.0],
190        };
191
192        let json = serde_json::to_value(&result).unwrap();
193        let expected = json!({
194            "label": "test_label",
195            "test_suite": "test_suite",
196            "unit": "bytes/second_biggerIsBetter",
197            "values": [1.0, 2.0]
198        });
199
200        assert_eq!(json, expected);
201    }
202
203    #[test]
204    fn test_deserialization_with_direction() {
205        let json = json!({
206            "label": "test_label",
207            "test_suite": "test_suite",
208            "unit": "ns_smallerIsBetter",
209            "values": [1.0, 2.0]
210        });
211
212        let result: FuchsiaPerfBenchmarkResult = serde_json::from_value(json).unwrap();
213        assert_eq!(result.unit, Unit::Nanoseconds);
214        assert_eq!(result.direction, Direction::SmallerBetter);
215    }
216
217    #[test]
218    fn test_deserialization_without_direction() {
219        let json = json!({
220            "label": "test_label",
221            "test_suite": "test_suite",
222            "unit": "ns",
223            "values": [1.0, 2.0]
224        });
225
226        let result: FuchsiaPerfBenchmarkResult = serde_json::from_value(json).unwrap();
227        assert_eq!(result.unit, Unit::Nanoseconds);
228        assert_eq!(result.direction, Direction::SmallerBetter);
229    }
230}