]>
Commit | Line | Data |
---|---|---|
caea8d61 DM |
1 | use std::path::PathBuf; |
2 | use std::sync::Arc; | |
3 | ||
f9a5beaa | 4 | use anyhow::Error; |
dde18bbb | 5 | use serde::Serialize; |
f9a5beaa | 6 | use serde_json::Value; |
caea8d61 | 7 | |
6ef1b649 | 8 | use proxmox_router::{ |
dde18bbb | 9 | cli::{ |
f9a5beaa TL |
10 | default_table_format_options, format_and_print_result_full, get_output_format, |
11 | ColumnConfig, OUTPUT_FORMAT, | |
dde18bbb | 12 | }, |
f9a5beaa | 13 | ApiMethod, RpcEnvironment, |
dde18bbb | 14 | }; |
f9a5beaa | 15 | use proxmox_schema::{api, ApiType, ReturnType}; |
caea8d61 | 16 | |
8c74349b | 17 | use pbs_api_types::{BackupNamespace, BackupType}; |
2b7f8dd5 WB |
18 | use pbs_client::tools::key_source::get_encryption_key_password; |
19 | use pbs_client::{BackupRepository, BackupWriter}; | |
ed208076 | 20 | use pbs_datastore::data_blob::{DataBlob, DataChunkBuilder}; |
1104d2a2 | 21 | use pbs_key_config::{load_and_decrypt_key, KeyDerivationConfig}; |
f9a5beaa | 22 | use pbs_tools::crypt_config::CryptConfig; |
caea8d61 | 23 | |
caea8d61 | 24 | use crate::{ |
f9a5beaa | 25 | connect, extract_repository_from_value, record_repository, KEYFILE_SCHEMA, REPO_URL_SCHEMA, |
caea8d61 DM |
26 | }; |
27 | ||
dde18bbb DM |
28 | #[api()] |
29 | #[derive(Copy, Clone, Serialize)] | |
30 | /// Speed test result | |
31 | struct Speed { | |
32 | /// The meassured speed in Bytes/second | |
f9a5beaa | 33 | #[serde(skip_serializing_if = "Option::is_none")] |
dde18bbb DM |
34 | speed: Option<f64>, |
35 | /// Top result we want to compare with | |
36 | top: f64, | |
37 | } | |
38 | ||
39 | #[api( | |
40 | properties: { | |
41 | "tls": { | |
42 | type: Speed, | |
43 | }, | |
44 | "sha256": { | |
45 | type: Speed, | |
46 | }, | |
47 | "compress": { | |
48 | type: Speed, | |
49 | }, | |
50 | "decompress": { | |
51 | type: Speed, | |
52 | }, | |
53 | "aes256_gcm": { | |
54 | type: Speed, | |
55 | }, | |
baae780c DM |
56 | "verify": { |
57 | type: Speed, | |
58 | }, | |
dde18bbb DM |
59 | }, |
60 | )] | |
61 | #[derive(Copy, Clone, Serialize)] | |
62 | /// Benchmark Results | |
63 | struct BenchmarkResult { | |
64 | /// TLS upload speed | |
65 | tls: Speed, | |
3435f549 | 66 | /// SHA256 checksum computation speed |
dde18bbb DM |
67 | sha256: Speed, |
68 | /// ZStd level 1 compression speed | |
69 | compress: Speed, | |
70 | /// ZStd level 1 decompression speed | |
71 | decompress: Speed, | |
72 | /// AES256 GCM encryption speed | |
73 | aes256_gcm: Speed, | |
baae780c DM |
74 | /// Verify speed |
75 | verify: Speed, | |
dde18bbb DM |
76 | } |
77 | ||
f9a5beaa | 78 | static BENCHMARK_RESULT_2020_TOP: BenchmarkResult = BenchmarkResult { |
dde18bbb DM |
79 | tls: Speed { |
80 | speed: None, | |
62c74d77 | 81 | top: 1_000_000.0 * 1235.0, // TLS to localhost, AMD Ryzen 7 2700X |
dde18bbb DM |
82 | }, |
83 | sha256: Speed { | |
84 | speed: None, | |
baae780c | 85 | top: 1_000_000.0 * 2022.0, // AMD Ryzen 7 2700X |
dde18bbb DM |
86 | }, |
87 | compress: Speed { | |
88 | speed: None, | |
baae780c | 89 | top: 1_000_000.0 * 752.0, // AMD Ryzen 7 2700X |
dde18bbb DM |
90 | }, |
91 | decompress: Speed { | |
92 | speed: None, | |
baae780c | 93 | top: 1_000_000.0 * 1198.0, // AMD Ryzen 7 2700X |
dde18bbb DM |
94 | }, |
95 | aes256_gcm: Speed { | |
96 | speed: None, | |
baae780c DM |
97 | top: 1_000_000.0 * 3645.0, // AMD Ryzen 7 2700X |
98 | }, | |
99 | verify: Speed { | |
100 | speed: None, | |
101 | top: 1_000_000.0 * 758.0, // AMD Ryzen 7 2700X | |
dde18bbb DM |
102 | }, |
103 | }; | |
104 | ||
caea8d61 DM |
105 | #[api( |
106 | input: { | |
107 | properties: { | |
108 | repository: { | |
109 | schema: REPO_URL_SCHEMA, | |
110 | optional: true, | |
111 | }, | |
112 | keyfile: { | |
113 | schema: KEYFILE_SCHEMA, | |
114 | optional: true, | |
115 | }, | |
dde18bbb DM |
116 | "output-format": { |
117 | schema: OUTPUT_FORMAT, | |
118 | optional: true, | |
119 | }, | |
caea8d61 DM |
120 | } |
121 | } | |
122 | )] | |
123 | /// Run benchmark tests | |
124 | pub async fn benchmark( | |
125 | param: Value, | |
126 | _info: &ApiMethod, | |
127 | _rpcenv: &mut dyn RpcEnvironment, | |
128 | ) -> Result<(), Error> { | |
dde18bbb | 129 | let repo = extract_repository_from_value(¶m).ok(); |
caea8d61 DM |
130 | |
131 | let keyfile = param["keyfile"].as_str().map(PathBuf::from); | |
132 | ||
dde18bbb DM |
133 | let output_format = get_output_format(¶m); |
134 | ||
caea8d61 DM |
135 | let crypt_config = match keyfile { |
136 | None => None, | |
137 | Some(path) => { | |
ff8945fd | 138 | let (key, _, _) = load_and_decrypt_key(&path, &get_encryption_key_password)?; |
caea8d61 DM |
139 | let crypt_config = CryptConfig::new(key)?; |
140 | Some(Arc::new(crypt_config)) | |
141 | } | |
142 | }; | |
143 | ||
dde18bbb DM |
144 | let mut benchmark_result = BENCHMARK_RESULT_2020_TOP; |
145 | ||
146 | // do repo tests first, because this may prompt for a password | |
147 | if let Some(repo) = repo { | |
4a2e4467 | 148 | test_upload_speed(&mut benchmark_result, repo, crypt_config.clone()).await?; |
dde18bbb DM |
149 | } |
150 | ||
4a2e4467 | 151 | test_crypt_speed(&mut benchmark_result)?; |
dde18bbb DM |
152 | |
153 | render_result(&output_format, &benchmark_result)?; | |
154 | ||
155 | Ok(()) | |
156 | } | |
157 | ||
158 | // print comparison table | |
f9a5beaa | 159 | fn render_result(output_format: &str, benchmark_result: &BenchmarkResult) -> Result<(), Error> { |
dde18bbb | 160 | let mut data = serde_json::to_value(benchmark_result)?; |
b2362a12 | 161 | let return_type = ReturnType::new(false, &BenchmarkResult::API_SCHEMA); |
dde18bbb DM |
162 | |
163 | let render_speed = |value: &Value, _record: &Value| -> Result<String, Error> { | |
164 | match value["speed"].as_f64() { | |
165 | None => Ok(String::from("not tested")), | |
166 | Some(speed) => { | |
167 | let top = value["top"].as_f64().unwrap(); | |
f9a5beaa TL |
168 | Ok(format!( |
169 | "{:.2} MB/s ({:.0}%)", | |
170 | speed / 1_000_000.0, | |
171 | (speed * 100.0) / top | |
172 | )) | |
dde18bbb DM |
173 | } |
174 | } | |
175 | }; | |
176 | ||
177 | let options = default_table_format_options() | |
f9a5beaa TL |
178 | .column( |
179 | ColumnConfig::new("tls") | |
dde18bbb | 180 | .header("TLS (maximal backup upload speed)") |
f9a5beaa TL |
181 | .right_align(false) |
182 | .renderer(render_speed), | |
183 | ) | |
184 | .column( | |
185 | ColumnConfig::new("sha256") | |
3435f549 | 186 | .header("SHA256 checksum computation speed") |
f9a5beaa TL |
187 | .right_align(false) |
188 | .renderer(render_speed), | |
189 | ) | |
190 | .column( | |
191 | ColumnConfig::new("compress") | |
dde18bbb | 192 | .header("ZStd level 1 compression speed") |
f9a5beaa TL |
193 | .right_align(false) |
194 | .renderer(render_speed), | |
195 | ) | |
196 | .column( | |
197 | ColumnConfig::new("decompress") | |
dde18bbb | 198 | .header("ZStd level 1 decompression speed") |
f9a5beaa TL |
199 | .right_align(false) |
200 | .renderer(render_speed), | |
201 | ) | |
202 | .column( | |
203 | ColumnConfig::new("verify") | |
baae780c | 204 | .header("Chunk verification speed") |
f9a5beaa TL |
205 | .right_align(false) |
206 | .renderer(render_speed), | |
207 | ) | |
208 | .column( | |
209 | ColumnConfig::new("aes256_gcm") | |
dde18bbb | 210 | .header("AES256 GCM encryption speed") |
f9a5beaa TL |
211 | .right_align(false) |
212 | .renderer(render_speed), | |
213 | ); | |
dde18bbb | 214 | |
b2362a12 | 215 | format_and_print_result_full(&mut data, &return_type, output_format, &options); |
dde18bbb DM |
216 | |
217 | Ok(()) | |
218 | } | |
219 | ||
220 | async fn test_upload_speed( | |
221 | benchmark_result: &mut BenchmarkResult, | |
222 | repo: BackupRepository, | |
223 | crypt_config: Option<Arc<CryptConfig>>, | |
dde18bbb | 224 | ) -> Result<(), Error> { |
6ef1b649 | 225 | let backup_time = proxmox_time::epoch_i64(); |
caea8d61 | 226 | |
f3fde36b | 227 | let client = connect(&repo)?; |
caea8d61 DM |
228 | record_repository(&repo); |
229 | ||
4a2e4467 | 230 | log::debug!("Connecting to backup server"); |
caea8d61 | 231 | let client = BackupWriter::start( |
fcea0794 | 232 | &client, |
caea8d61 DM |
233 | crypt_config.clone(), |
234 | repo.store(), | |
133d718f WB |
235 | &BackupNamespace::root(), |
236 | &(BackupType::Host, "benchmark".to_string(), backup_time).into(), | |
caea8d61 | 237 | false, |
f9a5beaa TL |
238 | true, |
239 | ) | |
240 | .await?; | |
caea8d61 | 241 | |
4a2e4467 HL |
242 | log::debug!("Start TLS speed test"); |
243 | let speed = client.upload_speedtest().await?; | |
caea8d61 | 244 | |
4a2e4467 | 245 | log::info!("TLS speed: {:.2} MB/s", speed / 1_000_000.0); |
dde18bbb DM |
246 | |
247 | benchmark_result.tls.speed = Some(speed); | |
caea8d61 DM |
248 | |
249 | Ok(()) | |
250 | } | |
4327a846 | 251 | |
dde18bbb | 252 | // test hash/crypt/compress speed |
4a2e4467 | 253 | fn test_crypt_speed(benchmark_result: &mut BenchmarkResult) -> Result<(), Error> { |
4327a846 DM |
254 | let pw = b"test"; |
255 | ||
256 | let kdf = KeyDerivationConfig::Scrypt { | |
257 | n: 65536, | |
258 | r: 8, | |
259 | p: 1, | |
260 | salt: Vec::new(), | |
261 | }; | |
262 | ||
263 | let testkey = kdf.derive_key(pw)?; | |
264 | ||
265 | let crypt_config = CryptConfig::new(testkey)?; | |
266 | ||
25877d05 | 267 | //let random_data = proxmox_sys::linux::random_data(1024*1024)?; |
baae780c | 268 | let mut random_data = vec![]; |
f9a5beaa TL |
269 | // generate pseudo random byte sequence |
270 | for i in 0..256 * 1024 { | |
271 | for j in 0..4 { | |
272 | let byte = ((i >> (j << 3)) & 0xff) as u8; | |
273 | random_data.push(byte); | |
baae780c | 274 | } |
f9a5beaa | 275 | } |
baae780c | 276 | |
f9a5beaa | 277 | assert_eq!(random_data.len(), 1024 * 1024); |
4327a846 DM |
278 | |
279 | let start_time = std::time::Instant::now(); | |
280 | ||
281 | let mut bytes = 0; | |
f9a5beaa | 282 | loop { |
4327a846 DM |
283 | openssl::sha::sha256(&random_data); |
284 | bytes += random_data.len(); | |
f9a5beaa TL |
285 | if start_time.elapsed().as_micros() > 1_000_000 { |
286 | break; | |
287 | } | |
4327a846 | 288 | } |
f9a5beaa | 289 | let speed = (bytes as f64) / start_time.elapsed().as_secs_f64(); |
dde18bbb | 290 | benchmark_result.sha256.speed = Some(speed); |
4327a846 | 291 | |
4a2e4467 | 292 | log::info!("SHA256 speed: {:.2} MB/s", speed / 1_000_000.0); |
4327a846 DM |
293 | |
294 | let start_time = std::time::Instant::now(); | |
295 | ||
296 | let mut bytes = 0; | |
f9a5beaa | 297 | loop { |
4327a846 DM |
298 | let mut reader = &random_data[..]; |
299 | zstd::stream::encode_all(&mut reader, 1)?; | |
300 | bytes += random_data.len(); | |
f9a5beaa TL |
301 | if start_time.elapsed().as_micros() > 3_000_000 { |
302 | break; | |
303 | } | |
4327a846 | 304 | } |
f9a5beaa | 305 | let speed = (bytes as f64) / start_time.elapsed().as_secs_f64(); |
dde18bbb | 306 | benchmark_result.compress.speed = Some(speed); |
4327a846 | 307 | |
4a2e4467 | 308 | log::info!("Compression speed: {:.2} MB/s", speed / 1_000_000.0); |
4327a846 DM |
309 | |
310 | let start_time = std::time::Instant::now(); | |
311 | ||
312 | let compressed_data = { | |
313 | let mut reader = &random_data[..]; | |
314 | zstd::stream::encode_all(&mut reader, 1)? | |
315 | }; | |
316 | ||
317 | let mut bytes = 0; | |
f9a5beaa | 318 | loop { |
4327a846 DM |
319 | let mut reader = &compressed_data[..]; |
320 | let data = zstd::stream::decode_all(&mut reader)?; | |
321 | bytes += data.len(); | |
f9a5beaa TL |
322 | if start_time.elapsed().as_micros() > 1_000_000 { |
323 | break; | |
324 | } | |
4327a846 | 325 | } |
f9a5beaa | 326 | let speed = (bytes as f64) / start_time.elapsed().as_secs_f64(); |
dde18bbb | 327 | benchmark_result.decompress.speed = Some(speed); |
4327a846 | 328 | |
4a2e4467 | 329 | log::info!("Decompress speed: {:.2} MB/s", speed / 1_000_000.0); |
4327a846 DM |
330 | |
331 | let start_time = std::time::Instant::now(); | |
332 | ||
333 | let mut bytes = 0; | |
f9a5beaa | 334 | loop { |
4327a846 | 335 | let mut out = Vec::new(); |
ed208076 | 336 | DataBlob::encrypt_benchmark(&crypt_config, &random_data, &mut out)?; |
4327a846 | 337 | bytes += random_data.len(); |
f9a5beaa TL |
338 | if start_time.elapsed().as_micros() > 1_000_000 { |
339 | break; | |
340 | } | |
4327a846 | 341 | } |
f9a5beaa | 342 | let speed = (bytes as f64) / start_time.elapsed().as_secs_f64(); |
dde18bbb | 343 | benchmark_result.aes256_gcm.speed = Some(speed); |
4327a846 | 344 | |
4a2e4467 | 345 | log::info!("AES256/GCM speed: {:.2} MB/s", speed / 1_000_000.0); |
baae780c DM |
346 | |
347 | let start_time = std::time::Instant::now(); | |
348 | ||
f9a5beaa | 349 | let (chunk, digest) = DataChunkBuilder::new(&random_data).compress(true).build()?; |
baae780c DM |
350 | |
351 | let mut bytes = 0; | |
f9a5beaa | 352 | loop { |
baae780c DM |
353 | chunk.verify_unencrypted(random_data.len(), &digest)?; |
354 | bytes += random_data.len(); | |
f9a5beaa TL |
355 | if start_time.elapsed().as_micros() > 1_000_000 { |
356 | break; | |
357 | } | |
baae780c | 358 | } |
f9a5beaa | 359 | let speed = (bytes as f64) / start_time.elapsed().as_secs_f64(); |
baae780c DM |
360 | benchmark_result.verify.speed = Some(speed); |
361 | ||
4a2e4467 | 362 | log::info!("Verify speed: {:.2} MB/s", speed / 1_000_000.0); |
baae780c | 363 | |
4327a846 DM |
364 | Ok(()) |
365 | } |