]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_backup_client/benchmark.rs
proxmox-backup-client benchmark: improve output format
[proxmox-backup.git] / src / bin / proxmox_backup_client / benchmark.rs
1 use std::path::PathBuf;
2 use std::sync::Arc;
3
4 use anyhow::{Error};
5 use serde_json::Value;
6 use chrono::{TimeZone, Utc};
7 use serde::Serialize;
8
9 use proxmox::api::{ApiMethod, RpcEnvironment};
10 use proxmox::api::{
11 api,
12 cli::{
13 OUTPUT_FORMAT,
14 ColumnConfig,
15 get_output_format,
16 format_and_print_result_full,
17 default_table_format_options,
18 },
19 };
20
21 use proxmox_backup::backup::{
22 load_and_decrypt_key,
23 CryptConfig,
24 KeyDerivationConfig,
25 };
26
27 use proxmox_backup::client::*;
28
29 use crate::{
30 KEYFILE_SCHEMA, REPO_URL_SCHEMA,
31 extract_repository_from_value,
32 record_repository,
33 connect,
34 };
35
36 #[api()]
37 #[derive(Copy, Clone, Serialize)]
38 /// Speed test result
39 struct Speed {
40 /// The meassured speed in Bytes/second
41 #[serde(skip_serializing_if="Option::is_none")]
42 speed: Option<f64>,
43 /// Top result we want to compare with
44 top: f64,
45 }
46
47 #[api(
48 properties: {
49 "tls": {
50 type: Speed,
51 },
52 "sha256": {
53 type: Speed,
54 },
55 "compress": {
56 type: Speed,
57 },
58 "decompress": {
59 type: Speed,
60 },
61 "aes256_gcm": {
62 type: Speed,
63 },
64 },
65 )]
66 #[derive(Copy, Clone, Serialize)]
67 /// Benchmark Results
68 struct BenchmarkResult {
69 /// TLS upload speed
70 tls: Speed,
71 /// SHA256 checksum comptation speed
72 sha256: Speed,
73 /// ZStd level 1 compression speed
74 compress: Speed,
75 /// ZStd level 1 decompression speed
76 decompress: Speed,
77 /// AES256 GCM encryption speed
78 aes256_gcm: Speed,
79 }
80
81
82 static BENCHMARK_RESULT_2020_TOP: BenchmarkResult = BenchmarkResult {
83 tls: Speed {
84 speed: None,
85 top: 1_000_000.0 * 590.0, // TLS to localhost, AMD Ryzen 7 2700X
86 },
87 sha256: Speed {
88 speed: None,
89 top: 1_000_000.0 * 2120.0, // AMD Ryzen 7 2700X
90 },
91 compress: Speed {
92 speed: None,
93 top: 1_000_000.0 * 2158.0, // AMD Ryzen 7 2700X
94 },
95 decompress: Speed {
96 speed: None,
97 top: 1_000_000.0 * 8062.0, // AMD Ryzen 7 2700X
98 },
99 aes256_gcm: Speed {
100 speed: None,
101 top: 1_000_000.0 * 3803.0, // AMD Ryzen 7 2700X
102 },
103 };
104
105 #[api(
106 input: {
107 properties: {
108 repository: {
109 schema: REPO_URL_SCHEMA,
110 optional: true,
111 },
112 verbose: {
113 description: "Verbose output.",
114 type: bool,
115 optional: true,
116 },
117 keyfile: {
118 schema: KEYFILE_SCHEMA,
119 optional: true,
120 },
121 "output-format": {
122 schema: OUTPUT_FORMAT,
123 optional: true,
124 },
125 }
126 }
127 )]
128 /// Run benchmark tests
129 pub async fn benchmark(
130 param: Value,
131 _info: &ApiMethod,
132 _rpcenv: &mut dyn RpcEnvironment,
133 ) -> Result<(), Error> {
134
135 let repo = extract_repository_from_value(&param).ok();
136
137 let keyfile = param["keyfile"].as_str().map(PathBuf::from);
138
139 let verbose = param["verbose"].as_bool().unwrap_or(false);
140
141 let output_format = get_output_format(&param);
142
143 let crypt_config = match keyfile {
144 None => None,
145 Some(path) => {
146 let (key, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
147 let crypt_config = CryptConfig::new(key)?;
148 Some(Arc::new(crypt_config))
149 }
150 };
151
152 let mut benchmark_result = BENCHMARK_RESULT_2020_TOP;
153
154 // do repo tests first, because this may prompt for a password
155 if let Some(repo) = repo {
156 test_upload_speed(&mut benchmark_result, repo, crypt_config.clone(), verbose).await?;
157 }
158
159 test_crypt_speed(&mut benchmark_result, verbose)?;
160
161 render_result(&output_format, &benchmark_result)?;
162
163 Ok(())
164 }
165
166 // print comparison table
167 fn render_result(
168 output_format: &str,
169 benchmark_result: &BenchmarkResult,
170 ) -> Result<(), Error> {
171
172 let mut data = serde_json::to_value(benchmark_result)?;
173 let schema = BenchmarkResult::API_SCHEMA;
174
175 let render_speed = |value: &Value, _record: &Value| -> Result<String, Error> {
176 match value["speed"].as_f64() {
177 None => Ok(String::from("not tested")),
178 Some(speed) => {
179 let top = value["top"].as_f64().unwrap();
180 Ok(format!("{:.2} MB/s ({:.0}%)", speed/1_000_000.0, (speed*100.0)/top))
181 }
182 }
183 };
184
185 let options = default_table_format_options()
186 .column(ColumnConfig::new("tls")
187 .header("TLS (maximal backup upload speed)")
188 .right_align(false).renderer(render_speed))
189 .column(ColumnConfig::new("sha256")
190 .header("SHA256 checksum comptation speed")
191 .right_align(false).renderer(render_speed))
192 .column(ColumnConfig::new("compress")
193 .header("ZStd level 1 compression speed")
194 .right_align(false).renderer(render_speed))
195 .column(ColumnConfig::new("decompress")
196 .header("ZStd level 1 decompression speed")
197 .right_align(false).renderer(render_speed))
198 .column(ColumnConfig::new("aes256_gcm")
199 .header("AES256 GCM encryption speed")
200 .right_align(false).renderer(render_speed));
201
202
203 format_and_print_result_full(&mut data, schema, output_format, &options);
204
205 Ok(())
206 }
207
208 async fn test_upload_speed(
209 benchmark_result: &mut BenchmarkResult,
210 repo: BackupRepository,
211 crypt_config: Option<Arc<CryptConfig>>,
212 verbose: bool,
213 ) -> Result<(), Error> {
214
215 let backup_time = Utc.timestamp(Utc::now().timestamp(), 0);
216
217 let client = connect(repo.host(), repo.user())?;
218 record_repository(&repo);
219
220 if verbose { eprintln!("Connecting to backup server"); }
221 let client = BackupWriter::start(
222 client,
223 crypt_config.clone(),
224 repo.store(),
225 "host",
226 "benchmark",
227 backup_time,
228 false,
229 ).await?;
230
231 if verbose { eprintln!("Start TLS speed test"); }
232 let speed = client.upload_speedtest(verbose).await?;
233
234 eprintln!("TLS speed: {:.2} MB/s", speed/1_000_000.0);
235
236 benchmark_result.tls.speed = Some(speed);
237
238 Ok(())
239 }
240
241 // test hash/crypt/compress speed
242 fn test_crypt_speed(
243 benchmark_result: &mut BenchmarkResult,
244 _verbose: bool,
245 ) -> Result<(), Error> {
246
247 let pw = b"test";
248
249 let kdf = KeyDerivationConfig::Scrypt {
250 n: 65536,
251 r: 8,
252 p: 1,
253 salt: Vec::new(),
254 };
255
256 let testkey = kdf.derive_key(pw)?;
257
258 let crypt_config = CryptConfig::new(testkey)?;
259
260 let random_data = proxmox::sys::linux::random_data(1024*1024)?;
261
262 let start_time = std::time::Instant::now();
263
264 let mut bytes = 0;
265 loop {
266 openssl::sha::sha256(&random_data);
267 bytes += random_data.len();
268 if start_time.elapsed().as_micros() > 1_000_000 { break; }
269 }
270 let speed = (bytes as f64)/start_time.elapsed().as_secs_f64();
271 benchmark_result.sha256.speed = Some(speed);
272
273 eprintln!("SHA256 speed: {:.2} MB/s", speed/1_000_000_.0);
274
275
276 let start_time = std::time::Instant::now();
277
278 let mut bytes = 0;
279 loop {
280 let mut reader = &random_data[..];
281 zstd::stream::encode_all(&mut reader, 1)?;
282 bytes += random_data.len();
283 if start_time.elapsed().as_micros() > 3_000_000 { break; }
284 }
285 let speed = (bytes as f64)/start_time.elapsed().as_secs_f64();
286 benchmark_result.compress.speed = Some(speed);
287
288 eprintln!("Compression speed: {:.2} MB/s", speed/1_000_000_.0);
289
290
291 let start_time = std::time::Instant::now();
292
293 let compressed_data = {
294 let mut reader = &random_data[..];
295 zstd::stream::encode_all(&mut reader, 1)?
296 };
297
298 let mut bytes = 0;
299 loop {
300 let mut reader = &compressed_data[..];
301 let data = zstd::stream::decode_all(&mut reader)?;
302 bytes += data.len();
303 if start_time.elapsed().as_micros() > 1_000_000 { break; }
304 }
305 let speed = (bytes as f64)/start_time.elapsed().as_secs_f64();
306 benchmark_result.decompress.speed = Some(speed);
307
308 eprintln!("Decompress speed: {:.2} MB/s", speed/1_000_000_.0);
309
310
311 let start_time = std::time::Instant::now();
312
313 let mut bytes = 0;
314 loop {
315 let mut out = Vec::new();
316 crypt_config.encrypt_to(&random_data, &mut out)?;
317 bytes += random_data.len();
318 if start_time.elapsed().as_micros() > 1_000_000 { break; }
319 }
320 let speed = (bytes as f64)/start_time.elapsed().as_secs_f64();
321 benchmark_result.aes256_gcm.speed = Some(speed);
322
323 eprintln!("AES256/GCM speed: {:.2} MB/s", speed/1_000_000_.0);
324
325 Ok(())
326 }