profile_inspect/classify/
frame_classifier.rs1use crate::ir::{FrameCategory, FrameKind};
2
3pub struct FrameClassifier {
5 app_base_path: Option<String>,
7}
8
9impl FrameClassifier {
10 pub fn new(app_base_path: Option<String>) -> Self {
12 Self { app_base_path }
13 }
14
15 pub fn classify_kind(&self, name: &str, url: &str) -> FrameKind {
17 if name.starts_with('(') && name.ends_with(')') {
19 return match name {
20 "(garbage collector)" => FrameKind::GC,
21 "(idle)" => FrameKind::Idle,
22 "(program)" => FrameKind::Program,
23 _ => FrameKind::Native,
24 };
25 }
26
27 if name.contains("Builtin:") || name == "(native)" {
29 return FrameKind::Builtin;
30 }
31
32 if url.contains("eval at") || name.contains("eval") {
34 return FrameKind::Eval;
35 }
36
37 if url.starts_with("wasm://") || name.starts_with("wasm-") {
39 return FrameKind::Wasm;
40 }
41
42 if name.starts_with("RegExp:") {
44 return FrameKind::RegExp;
45 }
46
47 if url.is_empty() && !name.is_empty() {
49 return FrameKind::Native;
50 }
51
52 FrameKind::Function
54 }
55
56 pub fn classify_category(&self, url: &str, name: &str) -> FrameCategory {
58 if url.starts_with("node:") || url.contains("internal/") {
60 return FrameCategory::NodeInternal;
61 }
62
63 if name.starts_with('(') && name.ends_with(')') {
65 match name {
66 "(garbage collector)" | "(idle)" | "(program)" | "(root)" => {
67 return FrameCategory::V8Internal;
68 }
69 _ => {}
70 }
71 }
72
73 if url.is_empty() || name.contains("Builtin:") || name == "(native)" {
75 return FrameCategory::Native;
76 }
77
78 if url.contains("node_modules") {
80 return FrameCategory::Deps;
81 }
82
83 if let Some(base) = &self.app_base_path
85 && !url.is_empty()
86 && !url.starts_with(base)
87 && !url.starts_with("file://")
88 && (url.contains("/dist/") || url.contains("/build/"))
90 {
91 return FrameCategory::App; }
93
94 FrameCategory::App
96 }
97
98 pub fn classify(&self, url: &str, name: &str) -> (FrameKind, FrameCategory) {
100 (
101 self.classify_kind(name, url),
102 self.classify_category(url, name),
103 )
104 }
105}
106
107impl Default for FrameClassifier {
108 fn default() -> Self {
109 Self::new(None)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
122 fn test_gc_classification() {
123 let classifier = FrameClassifier::default();
124 assert_eq!(
125 classifier.classify_kind("(garbage collector)", ""),
126 FrameKind::GC
127 );
128 assert_eq!(
129 classifier.classify_category("", "(garbage collector)"),
130 FrameCategory::V8Internal
131 );
132 }
133
134 #[test]
135 fn test_idle_classification() {
136 let classifier = FrameClassifier::default();
137 assert_eq!(classifier.classify_kind("(idle)", ""), FrameKind::Idle);
138 assert_eq!(
139 classifier.classify_category("", "(idle)"),
140 FrameCategory::V8Internal
141 );
142 }
143
144 #[test]
145 fn test_program_classification() {
146 let classifier = FrameClassifier::default();
147 assert_eq!(
148 classifier.classify_kind("(program)", ""),
149 FrameKind::Program
150 );
151 assert_eq!(
152 classifier.classify_category("", "(program)"),
153 FrameCategory::V8Internal
154 );
155 }
156
157 #[test]
158 fn test_root_classification() {
159 let classifier = FrameClassifier::default();
160 assert_eq!(
161 classifier.classify_category("", "(root)"),
162 FrameCategory::V8Internal
163 );
164 }
165
166 #[test]
167 fn test_builtin_classification() {
168 let classifier = FrameClassifier::default();
169
170 assert_eq!(
171 classifier.classify_kind("Builtin:ArrayPush", ""),
172 FrameKind::Builtin
173 );
174 assert_eq!(classifier.classify_kind("(native)", ""), FrameKind::Native);
177 }
178
179 #[test]
180 fn test_eval_classification() {
181 let classifier = FrameClassifier::default();
182
183 assert_eq!(
184 classifier.classify_kind("anonymous", "eval at <anonymous>"),
185 FrameKind::Eval
186 );
187 assert_eq!(
188 classifier.classify_kind("eval", "/src/main.js"),
189 FrameKind::Eval
190 );
191 }
192
193 #[test]
194 fn test_wasm_classification() {
195 let classifier = FrameClassifier::default();
196
197 assert_eq!(
198 classifier.classify_kind("funcName", "wasm://wasm/123456"),
199 FrameKind::Wasm
200 );
201 assert_eq!(
202 classifier.classify_kind("wasm-function[42]", ""),
203 FrameKind::Wasm
204 );
205 }
206
207 #[test]
208 fn test_regexp_classification() {
209 let classifier = FrameClassifier::default();
210 assert_eq!(
211 classifier.classify_kind("RegExp: /\\d+/", ""),
212 FrameKind::RegExp
213 );
214 }
215
216 #[test]
217 fn test_native_no_url() {
218 let classifier = FrameClassifier::default();
219 assert_eq!(
221 classifier.classify_kind("nativeFunction", ""),
222 FrameKind::Native
223 );
224 }
225
226 #[test]
227 fn test_regular_function() {
228 let classifier = FrameClassifier::default();
229 assert_eq!(
230 classifier.classify_kind("myFunction", "/src/main.js"),
231 FrameKind::Function
232 );
233 }
234
235 #[test]
240 fn test_node_internal() {
241 let classifier = FrameClassifier::default();
242 assert_eq!(
243 classifier.classify_category("node:fs", "readFile"),
244 FrameCategory::NodeInternal
245 );
246 assert_eq!(
247 classifier.classify_category("node:internal/modules/cjs/loader", "load"),
248 FrameCategory::NodeInternal
249 );
250 assert_eq!(
251 classifier.classify_category("/internal/bootstrap.js", "startup"),
252 FrameCategory::NodeInternal
253 );
254 }
255
256 #[test]
257 fn test_deps_classification() {
258 let classifier = FrameClassifier::default();
259 assert_eq!(
260 classifier.classify_category("/project/node_modules/lodash/index.js", "map"),
261 FrameCategory::Deps
262 );
263 assert_eq!(
264 classifier.classify_category("/node_modules/@babel/core/lib/index.js", "transform"),
265 FrameCategory::Deps
266 );
267 assert_eq!(
268 classifier.classify_category(
269 "file:///Users/test/project/node_modules/vitest/dist/index.js",
270 "run"
271 ),
272 FrameCategory::Deps
273 );
274 }
275
276 #[test]
277 fn test_app_code() {
278 let classifier = FrameClassifier::default();
279 assert_eq!(
280 classifier.classify_category("/project/src/main.ts", "processData"),
281 FrameCategory::App
282 );
283 assert_eq!(
284 classifier.classify_category("file:///Users/test/project/src/utils.js", "helper"),
285 FrameCategory::App
286 );
287 }
288
289 #[test]
290 fn test_native_category() {
291 let classifier = FrameClassifier::default();
292
293 assert_eq!(
295 classifier.classify_category("", "someFunction"),
296 FrameCategory::Native
297 );
298
299 assert_eq!(
301 classifier.classify_category("", "Builtin:ArrayPush"),
302 FrameCategory::Native
303 );
304 }
305
306 #[test]
311 fn test_classify_combined() {
312 let classifier = FrameClassifier::default();
313
314 let (kind, category) = classifier.classify("", "(garbage collector)");
315 assert_eq!(kind, FrameKind::GC);
316 assert_eq!(category, FrameCategory::V8Internal);
317
318 let (kind, category) = classifier.classify("/src/main.js", "processData");
319 assert_eq!(kind, FrameKind::Function);
320 assert_eq!(category, FrameCategory::App);
321
322 let (kind, category) = classifier.classify("node:fs", "readFile");
323 assert_eq!(kind, FrameKind::Function);
324 assert_eq!(category, FrameCategory::NodeInternal);
325 }
326
327 #[test]
332 fn test_custom_app_base_path() {
333 let classifier = FrameClassifier::new(Some("/home/user/myproject".to_string()));
334
335 assert_eq!(
337 classifier.classify_category("/home/user/myproject/src/main.ts", "func"),
338 FrameCategory::App
339 );
340
341 assert_eq!(
343 classifier
344 .classify_category("/home/user/myproject/node_modules/lodash/index.js", "map"),
345 FrameCategory::Deps
346 );
347 }
348}