profile.rs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. use std::{path::PathBuf, time::Duration};
  2. use anyhow::{anyhow, Result};
  3. use clap::{Parser, Subcommand};
  4. #[derive(Debug, Subcommand, Clone, PartialEq, Eq)]
  5. pub enum ProfileCommand {
  6. #[clap(about = "Sample the kernel")]
  7. Sample(ProfileSampleArgs),
  8. #[clap(about = "Parse the collected sample data")]
  9. Parse(ProfileParseArgs),
  10. }
  11. #[derive(Debug, Parser, Clone, PartialEq, Eq)]
  12. pub struct ProfileSampleArgs {
  13. #[clap(
  14. long = "kernel",
  15. help = "Path to the kernel image to use",
  16. default_value = "./bin/kernel/kernel.elf"
  17. )]
  18. pub kernel: PathBuf,
  19. #[clap(
  20. long = "interval",
  21. help = "Interval between samples (e.g., 200ms, 1s, 1m)",
  22. default_value = "200ms",
  23. value_parser = parse_time_interval
  24. )]
  25. interval: Duration,
  26. #[clap(
  27. long = "duration",
  28. help = "Duration of the sampling in seconds",
  29. default_value = "10s",
  30. value_parser = parse_time_interval
  31. )]
  32. duration: Duration,
  33. #[clap(long = "output", help = "Path of the output file")]
  34. pub output: PathBuf,
  35. #[clap(
  36. long = "format",
  37. help = "Output file format (flamegraph, json, folded)",
  38. default_value = "flamegraph",
  39. value_parser = parse_profile_file_type
  40. )]
  41. pub format: ProfileFileType,
  42. #[clap(
  43. long = "remote",
  44. help = "Remote address to connect to",
  45. default_value = "localhost:1234"
  46. )]
  47. pub remote: String,
  48. #[clap(
  49. long = "workers",
  50. help = "Number of worker threads to use",
  51. default_value = "3"
  52. )]
  53. pub workers: usize,
  54. #[clap(
  55. long = "cpu-mask",
  56. help = "CPU mask to filter",
  57. value_parser = parse_cpu_mask
  58. )]
  59. pub cpu_mask: Option<u128>,
  60. }
  61. impl ProfileSampleArgs {
  62. pub fn interval(&self) -> Duration {
  63. self.interval
  64. }
  65. pub fn duration(&self) -> Duration {
  66. self.duration
  67. }
  68. }
  69. fn parse_time_interval(interval: &str) -> Result<Duration> {
  70. let interval = interval
  71. .parse::<humantime::Duration>()
  72. .map_err(|e| anyhow!("Failed to parse interval: {}, error: {}", interval, e))?;
  73. Ok(interval.into())
  74. }
  75. fn parse_profile_file_type(format: &str) -> Result<ProfileFileType> {
  76. match format.trim().to_ascii_lowercase().as_str() {
  77. "json" => Ok(ProfileFileType::Json),
  78. "folded" => Ok(ProfileFileType::Folded),
  79. "flamegraph" => Ok(ProfileFileType::Flamegraph),
  80. _ => Err(anyhow!("Unknown profile file type: {}", format)),
  81. }
  82. }
  83. fn parse_cpu_mask(s: &str) -> Result<u128> {
  84. let mask = if s.starts_with("0x") || s.starts_with("0X") {
  85. u128::from_str_radix(&s[2..], 16)
  86. } else {
  87. s.parse::<u128>()
  88. };
  89. let mask = mask.map_err(|e| anyhow!("Failed to parse cpu mask: {}, error: {}", s, e))?;
  90. Ok(mask)
  91. }
  92. #[derive(Debug, Parser, Clone, PartialEq, Eq)]
  93. pub struct ProfileParseArgs {
  94. #[clap(
  95. long = "duration",
  96. help = "Duration of the sampling in seconds",
  97. default_value = "10s",
  98. value_parser = parse_time_interval
  99. )]
  100. duration: Duration,
  101. #[clap(long = "input", help = "Path of the input file")]
  102. pub input: PathBuf,
  103. #[clap(long = "output", help = "Path of the output file")]
  104. pub output: PathBuf,
  105. #[clap(
  106. long = "format",
  107. help = "Output file format (flamegraph, json, folded)",
  108. default_value = "flamegraph",
  109. value_parser = parse_profile_file_type
  110. )]
  111. pub format: ProfileFileType,
  112. #[clap(
  113. long = "cpu-mask",
  114. help = "CPU mask to filter",
  115. value_parser = parse_cpu_mask
  116. )]
  117. pub cpu_mask: Option<u128>,
  118. }
  119. /// 输出的文件类型
  120. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  121. pub enum ProfileFileType {
  122. /// Json格式
  123. Json,
  124. /// 栈帧折叠格式
  125. Folded,
  126. /// 火焰图
  127. Flamegraph,
  128. }
  129. #[cfg(test)]
  130. mod tests {
  131. use super::*;
  132. #[test]
  133. fn test_parse_time_interval() {
  134. assert_eq!(
  135. parse_time_interval("1ms").unwrap(),
  136. Duration::from_millis(1)
  137. );
  138. assert_eq!(parse_time_interval("1s").unwrap(), Duration::from_secs(1));
  139. assert_eq!(parse_time_interval("1m").unwrap(), Duration::from_secs(60));
  140. }
  141. #[test]
  142. fn test_parse_cpu_mask() {
  143. assert_eq!(parse_cpu_mask("1").unwrap(), 1);
  144. assert_eq!(parse_cpu_mask("0x1").unwrap(), 1);
  145. }
  146. }