utils.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use anyhow::Result;
  5. use serde::{Deserialize, Serialize};
  6. use serde_json::Value;
  7. use std::{
  8. collections::HashMap,
  9. fs,
  10. io::{BufRead, BufReader},
  11. path::PathBuf,
  12. process::{Command, Output, Stdio},
  13. };
  14. #[derive(Default, Clone, Serialize, Deserialize, Debug)]
  15. pub struct BenchResult {
  16. pub created_at: String,
  17. pub sha1: String,
  18. pub exec_time: HashMap<String, HashMap<String, f64>>,
  19. pub binary_size: HashMap<String, u64>,
  20. pub max_memory: HashMap<String, u64>,
  21. pub thread_count: HashMap<String, u64>,
  22. pub syscall_count: HashMap<String, u64>,
  23. pub cargo_deps: HashMap<String, usize>,
  24. }
  25. #[allow(dead_code)]
  26. #[derive(Debug, Clone, Serialize)]
  27. pub struct StraceOutput {
  28. pub percent_time: f64,
  29. pub seconds: f64,
  30. pub usecs_per_call: Option<u64>,
  31. pub calls: u64,
  32. pub errors: u64,
  33. }
  34. pub fn get_target() -> &'static str {
  35. #[cfg(target_os = "macos")]
  36. return if cfg!(target_arch = "aarch64") {
  37. "aarch64-apple-darwin"
  38. } else {
  39. "x86_64-apple-darwin"
  40. };
  41. #[cfg(target_os = "linux")]
  42. return "x86_64-unknown-linux-gnu";
  43. #[cfg(target_os = "windows")]
  44. unimplemented!();
  45. }
  46. pub fn target_dir() -> PathBuf {
  47. bench_root_path()
  48. .join("tests")
  49. .join("target")
  50. .join(get_target())
  51. .join("release")
  52. }
  53. pub fn bench_root_path() -> PathBuf {
  54. PathBuf::from(env!("CARGO_MANIFEST_DIR"))
  55. }
  56. #[allow(dead_code)]
  57. pub fn home_path() -> PathBuf {
  58. #[cfg(any(target_os = "macos", target_os = "linux"))]
  59. return PathBuf::from(env!("HOME"));
  60. #[cfg(any(target_os = "windows"))]
  61. return PathBuf::from(env!("HOMEPATH"));
  62. }
  63. #[allow(dead_code)]
  64. pub fn tauri_root_path() -> PathBuf {
  65. bench_root_path()
  66. .parent()
  67. .unwrap()
  68. .parent()
  69. .unwrap()
  70. .to_path_buf()
  71. }
  72. #[allow(dead_code)]
  73. pub fn run_collect(cmd: &[&str]) -> (String, String) {
  74. let mut process_builder = Command::new(cmd[0]);
  75. process_builder
  76. .args(&cmd[1..])
  77. .stdin(Stdio::piped())
  78. .stdout(Stdio::piped())
  79. .stderr(Stdio::piped());
  80. let prog = process_builder.spawn().expect("failed to spawn script");
  81. let Output {
  82. stdout,
  83. stderr,
  84. status,
  85. } = prog.wait_with_output().expect("failed to wait on child");
  86. let stdout = String::from_utf8(stdout).unwrap();
  87. let stderr = String::from_utf8(stderr).unwrap();
  88. if !status.success() {
  89. eprintln!("stdout: <<<{}>>>", stdout);
  90. eprintln!("stderr: <<<{}>>>", stderr);
  91. panic!("Unexpected exit code: {:?}", status.code());
  92. }
  93. (stdout, stderr)
  94. }
  95. #[allow(dead_code)]
  96. pub fn parse_max_mem(file_path: &str) -> Option<u64> {
  97. let file = fs::File::open(file_path).unwrap();
  98. let output = BufReader::new(file);
  99. let mut highest: u64 = 0;
  100. // MEM 203.437500 1621617192.4123
  101. for line in output.lines().flatten() {
  102. // split line by space
  103. let split = line.split(' ').collect::<Vec<_>>();
  104. if split.len() == 3 {
  105. // mprof generate result in MB
  106. let current_bytes = str::parse::<f64>(split[1]).unwrap() as u64 * 1024 * 1024;
  107. if current_bytes > highest {
  108. highest = current_bytes;
  109. }
  110. }
  111. }
  112. fs::remove_file(file_path).unwrap();
  113. if highest > 0 {
  114. return Some(highest);
  115. }
  116. None
  117. }
  118. #[allow(dead_code)]
  119. pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
  120. let mut summary = HashMap::new();
  121. let mut lines = output
  122. .lines()
  123. .filter(|line| !line.is_empty() && !line.contains("detached ..."));
  124. let count = lines.clone().count();
  125. if count < 4 {
  126. return summary;
  127. }
  128. let total_line = lines.next_back().unwrap();
  129. lines.next_back(); // Drop separator
  130. let data_lines = lines.skip(2);
  131. for line in data_lines {
  132. let syscall_fields = line.split_whitespace().collect::<Vec<_>>();
  133. let len = syscall_fields.len();
  134. let syscall_name = syscall_fields.last().unwrap();
  135. if (5..=6).contains(&len) {
  136. summary.insert(
  137. syscall_name.to_string(),
  138. StraceOutput {
  139. percent_time: str::parse::<f64>(syscall_fields[0]).unwrap(),
  140. seconds: str::parse::<f64>(syscall_fields[1]).unwrap(),
  141. usecs_per_call: Some(str::parse::<u64>(syscall_fields[2]).unwrap()),
  142. calls: str::parse::<u64>(syscall_fields[3]).unwrap(),
  143. errors: if syscall_fields.len() < 6 {
  144. 0
  145. } else {
  146. str::parse::<u64>(syscall_fields[4]).unwrap()
  147. },
  148. },
  149. );
  150. }
  151. }
  152. let total_fields = total_line.split_whitespace().collect::<Vec<_>>();
  153. summary.insert(
  154. "total".to_string(),
  155. match total_fields.len() {
  156. // Old format, has no usecs/call
  157. 5 => StraceOutput {
  158. percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
  159. seconds: str::parse::<f64>(total_fields[1]).unwrap(),
  160. usecs_per_call: None,
  161. calls: str::parse::<u64>(total_fields[2]).unwrap(),
  162. errors: str::parse::<u64>(total_fields[3]).unwrap(),
  163. },
  164. 6 => StraceOutput {
  165. percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
  166. seconds: str::parse::<f64>(total_fields[1]).unwrap(),
  167. usecs_per_call: Some(str::parse::<u64>(total_fields[2]).unwrap()),
  168. calls: str::parse::<u64>(total_fields[3]).unwrap(),
  169. errors: str::parse::<u64>(total_fields[4]).unwrap(),
  170. },
  171. _ => panic!("Unexpected total field count: {}", total_fields.len()),
  172. },
  173. );
  174. summary
  175. }
  176. #[allow(dead_code)]
  177. pub fn run(cmd: &[&str]) {
  178. let mut process_builder = Command::new(cmd[0]);
  179. process_builder.args(&cmd[1..]).stdin(Stdio::piped());
  180. let mut prog = process_builder.spawn().expect("failed to spawn script");
  181. let status = prog.wait().expect("failed to wait on child");
  182. if !status.success() {
  183. panic!("Unexpected exit code: {:?}", status.code());
  184. }
  185. }
  186. #[allow(dead_code)]
  187. pub fn read_json(filename: &str) -> Result<Value> {
  188. let f = fs::File::open(filename)?;
  189. Ok(serde_json::from_reader(f)?)
  190. }
  191. #[allow(dead_code)]
  192. pub fn write_json(filename: &str, value: &Value) -> Result<()> {
  193. let f = fs::File::create(filename)?;
  194. serde_json::to_writer(f, value)?;
  195. Ok(())
  196. }
  197. #[allow(dead_code)]
  198. pub fn download_file(url: &str, filename: PathBuf) {
  199. if !url.starts_with("http:") && !url.starts_with("https:") {
  200. fs::copy(url, filename).unwrap();
  201. return;
  202. }
  203. // Downloading with curl this saves us from adding
  204. // a Rust HTTP client dependency.
  205. println!("Downloading {}", url);
  206. let status = Command::new("curl")
  207. .arg("-L")
  208. .arg("-s")
  209. .arg("-o")
  210. .arg(&filename)
  211. .arg(url)
  212. .status()
  213. .unwrap();
  214. assert!(status.success());
  215. assert!(filename.exists());
  216. }