testlint_sdk/profiler/
php.rs1#![allow(dead_code)]
2
3use crate::profiler::ProfileResult;
4use std::fs;
5use std::process::Command;
6
7pub struct PhpProfiler;
8
9impl Default for PhpProfiler {
10 fn default() -> Self {
11 Self::new()
12 }
13}
14
15impl PhpProfiler {
16 pub fn new() -> Self {
17 PhpProfiler
18 }
19
20 pub fn profile_continuous(&self, php_file: &str) -> Result<ProfileResult, String> {
22 println!("🐘 Starting PHP runtime profiling...");
23 println!("📝 Note: This uses Xdebug profiler mode");
24
25 if !self.is_xdebug_installed() {
27 return Err("Xdebug not found. Please install it:\n\
28 - PECL: pecl install xdebug\n\
29 - Or see: https://xdebug.org/docs/install"
30 .to_string());
31 }
32
33 self.profile_with_xdebug(php_file)
34 }
35
36 fn profile_with_xdebug(&self, php_file: &str) -> Result<ProfileResult, String> {
38 println!("🚀 Profiling PHP script: {}", php_file);
39
40 let output_dir = "xdebug_profile";
42 fs::create_dir_all(output_dir)
43 .map_err(|e| format!("Failed to create output directory: {}", e))?;
44
45 let mut cmd = Command::new("php");
47 cmd.args([
48 "-d",
49 "xdebug.mode=profile",
50 "-d",
51 &format!("xdebug.output_dir={}", output_dir),
52 "-d",
53 "xdebug.profiler_output_name=cachegrind.out.%p",
54 php_file,
55 ]);
56
57 println!("Running: php -d xdebug.mode=profile {}", php_file);
58 println!("Profiling until script exits...");
59
60 let output = cmd
61 .output()
62 .map_err(|e| format!("Failed to run PHP: {}", e))?;
63
64 if !output.status.success() {
65 return Err(format!(
66 "PHP script failed: {}",
67 String::from_utf8_lossy(&output.stderr)
68 ));
69 }
70
71 let stdout = String::from_utf8_lossy(&output.stdout);
73 if !stdout.is_empty() {
74 println!("\n--- Script Output ---");
75 println!("{}", stdout);
76 println!("--- End Output ---\n");
77 }
78
79 self.parse_xdebug_output(output_dir)
80 }
81
82 fn is_xdebug_installed(&self) -> bool {
84 let output = Command::new("php").args(["-m"]).output();
85
86 if let Ok(output) = output {
87 let modules = String::from_utf8_lossy(&output.stdout);
88 return modules.contains("Xdebug") || modules.contains("xdebug");
89 }
90
91 false
92 }
93
94 fn parse_xdebug_output(&self, output_dir: &str) -> Result<ProfileResult, String> {
96 let cachegrind_file = self.find_cachegrind_file(output_dir)?;
98
99 let file_size = fs::metadata(&cachegrind_file).map(|m| m.len()).unwrap_or(0);
100
101 let stats = self.analyze_cachegrind(&cachegrind_file)?;
103
104 let mut details = vec![
105 "✓ Profiling completed successfully".to_string(),
106 format!("📊 Cachegrind file generated: {}", cachegrind_file),
107 format!(" Size: {} bytes", file_size),
108 "".to_string(),
109 ];
110
111 details.extend(stats);
112
113 details.extend(vec![
114 "".to_string(),
115 "To analyze the profile:".to_string(),
116 "".to_string(),
117 "1. Using KCacheGrind (Linux/macOS):".to_string(),
118 format!(" kcachegrind {}", cachegrind_file),
119 "".to_string(),
120 "2. Using QCacheGrind (macOS):".to_string(),
121 " brew install qcachegrind".to_string(),
122 format!(" qcachegrind {}", cachegrind_file),
123 "".to_string(),
124 "3. Using WinCacheGrind (Windows):".to_string(),
125 " Download from: https://sourceforge.net/projects/wincachegrind/".to_string(),
126 "".to_string(),
127 "Cachegrind file contains:".to_string(),
128 " - Function call counts".to_string(),
129 " - Time spent in each function".to_string(),
130 " - Call graph relationships".to_string(),
131 " - Memory usage (if enabled)".to_string(),
132 ]);
133
134 Ok(ProfileResult {
135 language: "PHP".to_string(),
136 details,
137 })
138 }
139
140 fn find_cachegrind_file(&self, output_dir: &str) -> Result<String, String> {
142 let entries = fs::read_dir(output_dir)
143 .map_err(|e| format!("Failed to read output directory: {}", e))?;
144
145 for entry in entries.flatten() {
146 if let Some(name) = entry.file_name().to_str() {
147 if name.starts_with("cachegrind.out") {
148 return Ok(format!("{}/{}", output_dir, name));
149 }
150 }
151 }
152
153 Err("No cachegrind file found. Profiling may have failed.".to_string())
154 }
155
156 fn analyze_cachegrind(&self, cachegrind_file: &str) -> Result<Vec<String>, String> {
158 let content = fs::read_to_string(cachegrind_file)
159 .map_err(|e| format!("Failed to read cachegrind file: {}", e))?;
160
161 let mut function_count = 0;
162 let mut total_calls = 0;
163
164 for line in content.lines() {
165 if line.starts_with("fn=") {
166 function_count += 1;
167 } else if line.starts_with("calls=") {
168 if let Some(calls_str) = line.split('=').nth(1) {
169 if let Ok(calls) = calls_str
170 .split_whitespace()
171 .next()
172 .unwrap_or("0")
173 .parse::<u64>()
174 {
175 total_calls += calls;
176 }
177 }
178 }
179 }
180
181 Ok(vec![
182 "Profile Statistics:".to_string(),
183 format!(" - Functions profiled: {}", function_count),
184 format!(" - Total function calls: {}", total_calls),
185 ])
186 }
187
188 pub fn profile_pid(&self, _pid: u32) -> Result<ProfileResult, String> {
190 Err("PID profiling is not supported for PHP.\n\
191 PHP profiling requires running the script with Xdebug enabled from the start."
192 .to_string())
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_profiler_new() {
202 let profiler = PhpProfiler::new();
203 assert_eq!(std::mem::size_of_val(&profiler), 0);
204 }
205
206 #[test]
207 fn test_profiler_default() {
208 let profiler = PhpProfiler;
209 assert_eq!(std::mem::size_of_val(&profiler), 0);
210 }
211}