mcp-samply 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +14 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/profile/analyze.d.ts +162 -0
  7. package/dist/profile/analyze.js +745 -0
  8. package/dist/profile/analyze.js.map +1 -0
  9. package/dist/profile/store.d.ts +104 -0
  10. package/dist/profile/store.js +238 -0
  11. package/dist/profile/store.js.map +1 -0
  12. package/dist/samply/executable.d.ts +3 -0
  13. package/dist/samply/executable.js +34 -0
  14. package/dist/samply/executable.js.map +1 -0
  15. package/dist/samply/process.d.ts +11 -0
  16. package/dist/samply/process.js +39 -0
  17. package/dist/samply/process.js.map +1 -0
  18. package/dist/samply/record.d.ts +36 -0
  19. package/dist/samply/record.js +180 -0
  20. package/dist/samply/record.js.map +1 -0
  21. package/dist/server.d.ts +2 -0
  22. package/dist/server.js +22 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/source/locate.d.ts +31 -0
  25. package/dist/source/locate.js +284 -0
  26. package/dist/source/locate.js.map +1 -0
  27. package/dist/tools/doctor.d.ts +2 -0
  28. package/dist/tools/doctor.js +81 -0
  29. package/dist/tools/doctor.js.map +1 -0
  30. package/dist/tools/locate.d.ts +2 -0
  31. package/dist/tools/locate.js +85 -0
  32. package/dist/tools/locate.js.map +1 -0
  33. package/dist/tools/profile.d.ts +3 -0
  34. package/dist/tools/profile.js +433 -0
  35. package/dist/tools/profile.js.map +1 -0
  36. package/dist/tools/record.d.ts +3 -0
  37. package/dist/tools/record.js +117 -0
  38. package/dist/tools/record.js.map +1 -0
  39. package/dist/tools/state.d.ts +6 -0
  40. package/dist/tools/state.js +11 -0
  41. package/dist/tools/state.js.map +1 -0
  42. package/package.json +51 -0
@@ -0,0 +1,745 @@
1
+ const analysisCache = new WeakMap();
2
+ const resolvedSampleCache = new WeakMap();
3
+ export function summarizeProfile(profile, options = {}) {
4
+ const maxThreads = options.maxThreads ?? 8;
5
+ const maxFunctions = options.maxFunctions ?? 8;
6
+ const maxMarkers = options.maxMarkers ?? 8;
7
+ const includeEmptyThreads = options.includeEmptyThreads ?? false;
8
+ const threadAnalyses = profile.threads
9
+ .map((thread) => getThreadAnalysis(profile, thread))
10
+ .filter((analysis) => includeEmptyThreads || analysis.sampleCount > 0)
11
+ .sort((left, right) => right.sampleCount - left.sampleCount);
12
+ const overallFunctions = aggregateFunctions(threadAnalyses);
13
+ const totalSamples = threadAnalyses.reduce((sum, analysis) => sum + analysis.sampleCount, 0);
14
+ let globalStartTime = null;
15
+ let globalEndTime = null;
16
+ for (const analysis of threadAnalyses) {
17
+ globalStartTime = takeMin(globalStartTime, analysis.startTimeMs);
18
+ globalEndTime = takeMax(globalEndTime, analysis.endTimeMs);
19
+ }
20
+ return {
21
+ profilePath: profile.profilePath,
22
+ sidecarPath: profile.sidecarPath,
23
+ presymbolicated: profile.sidecarPath !== null,
24
+ product: profile.meta.product ?? null,
25
+ oscpu: profile.meta.oscpu ?? null,
26
+ intervalMs: typeof profile.meta.interval === "number" ? profile.meta.interval : null,
27
+ processCount: profile.processCount,
28
+ threadCount: profile.threads.length,
29
+ totalSamples,
30
+ sampleTimeRangeMs: globalStartTime !== null && globalEndTime !== null
31
+ ? Math.max(0, globalEndTime - globalStartTime)
32
+ : null,
33
+ hottestSelfFunctionsOverall: sortFunctions(overallFunctions, "selfSamples", maxFunctions),
34
+ hottestStackFunctionsOverall: sortFunctions(overallFunctions, "stackSamples", maxFunctions),
35
+ threads: threadAnalyses
36
+ .slice(0, maxThreads)
37
+ .map((analysis) => toThreadSummary(analysis, {
38
+ maxFunctions,
39
+ maxMarkers,
40
+ })),
41
+ };
42
+ }
43
+ export function inspectThread(profile, selector, options = {}) {
44
+ const maxFunctions = options.maxFunctions ?? 10;
45
+ const maxMarkers = options.maxMarkers ?? 10;
46
+ const maxStacks = options.maxStacks ?? 8;
47
+ const selectedThread = selectThread(profile, selector);
48
+ const analysis = getThreadAnalysis(profile, selectedThread);
49
+ return {
50
+ profilePath: profile.profilePath,
51
+ sidecarPath: profile.sidecarPath,
52
+ presymbolicated: profile.sidecarPath !== null,
53
+ thread: {
54
+ ...toThreadSummary(analysis, {
55
+ maxFunctions,
56
+ maxMarkers,
57
+ }),
58
+ topStacks: [...analysis.stacks.values()]
59
+ .sort((left, right) => right.sampleCount - left.sampleCount)
60
+ .slice(0, maxStacks),
61
+ },
62
+ };
63
+ }
64
+ export function searchFunctions(profile, query, options = {}) {
65
+ const normalizedQuery = query.trim().toLowerCase();
66
+ if (normalizedQuery.length === 0) {
67
+ throw new Error("The search query must not be empty.");
68
+ }
69
+ const maxResults = options.maxResults ?? 10;
70
+ const maxThreadsPerResult = options.maxThreadsPerResult ?? 5;
71
+ const matches = new Map();
72
+ for (const thread of profile.threads) {
73
+ const analysis = getThreadAnalysis(profile, thread);
74
+ for (const functionStat of analysis.functions.values()) {
75
+ const haystack = `${functionStat.name}\n${functionStat.resourceName ?? ""}`.toLowerCase();
76
+ if (!haystack.includes(normalizedQuery)) {
77
+ continue;
78
+ }
79
+ const existing = matches.get(functionStat.key);
80
+ if (existing === undefined) {
81
+ matches.set(functionStat.key, {
82
+ ...functionStat,
83
+ threads: [
84
+ {
85
+ index: analysis.thread.index,
86
+ name: getThreadName(analysis.thread),
87
+ processName: analysis.thread.thread.processName ?? null,
88
+ selfSamples: functionStat.selfSamples,
89
+ stackSamples: functionStat.stackSamples,
90
+ },
91
+ ],
92
+ });
93
+ continue;
94
+ }
95
+ existing.selfSamples += functionStat.selfSamples;
96
+ existing.stackSamples += functionStat.stackSamples;
97
+ existing.threads.push({
98
+ index: analysis.thread.index,
99
+ name: getThreadName(analysis.thread),
100
+ processName: analysis.thread.thread.processName ?? null,
101
+ selfSamples: functionStat.selfSamples,
102
+ stackSamples: functionStat.stackSamples,
103
+ });
104
+ }
105
+ }
106
+ const sortedMatches = [...matches.values()]
107
+ .map((match) => ({
108
+ name: match.name,
109
+ resourceName: match.resourceName,
110
+ displayName: match.displayName,
111
+ selfSamples: match.selfSamples,
112
+ stackSamples: match.stackSamples,
113
+ threads: match.threads
114
+ .sort((left, right) => right.stackSamples - left.stackSamples)
115
+ .slice(0, maxThreadsPerResult),
116
+ }))
117
+ .sort((left, right) => {
118
+ if (right.stackSamples !== left.stackSamples) {
119
+ return right.stackSamples - left.stackSamples;
120
+ }
121
+ return right.selfSamples - left.selfSamples;
122
+ })
123
+ .slice(0, maxResults);
124
+ return {
125
+ profilePath: profile.profilePath,
126
+ sidecarPath: profile.sidecarPath,
127
+ presymbolicated: profile.sidecarPath !== null,
128
+ query,
129
+ matchCount: sortedMatches.length,
130
+ matches: sortedMatches,
131
+ };
132
+ }
133
+ export function breakdownBySubsystem(profile, options = {}) {
134
+ const prefixSegments = Math.max(1, options.prefixSegments ?? 2);
135
+ const maxGroups = options.maxGroups ?? 12;
136
+ const maxExamplesPerGroup = options.maxExamplesPerGroup ?? 5;
137
+ const maxThreadsPerGroup = options.maxThreadsPerGroup ?? 5;
138
+ const normalizedQuery = normalizeOptionalQuery(options.query);
139
+ const normalizedResourceQuery = normalizeOptionalQuery(options.resourceQuery);
140
+ const threads = selectThreads(profile, options.thread);
141
+ const groups = new Map();
142
+ for (const thread of threads) {
143
+ const analysis = getThreadAnalysis(profile, thread);
144
+ for (const functionStat of analysis.functions.values()) {
145
+ if (!matchesFunctionFilter(functionStat, normalizedQuery, normalizedResourceQuery)) {
146
+ continue;
147
+ }
148
+ const groupName = deriveSubsystemName(functionStat.name, prefixSegments, normalizedQuery);
149
+ const existingGroup = groups.get(groupName);
150
+ const group = existingGroup ??
151
+ {
152
+ name: groupName,
153
+ selfSamples: 0,
154
+ stackSamples: 0,
155
+ functions: new Map(),
156
+ threads: new Map(),
157
+ };
158
+ if (existingGroup === undefined) {
159
+ groups.set(groupName, group);
160
+ }
161
+ group.selfSamples += functionStat.selfSamples;
162
+ group.stackSamples += functionStat.stackSamples;
163
+ const existingFunction = group.functions.get(functionStat.key);
164
+ if (existingFunction === undefined) {
165
+ group.functions.set(functionStat.key, { ...functionStat });
166
+ }
167
+ else {
168
+ existingFunction.selfSamples += functionStat.selfSamples;
169
+ existingFunction.stackSamples += functionStat.stackSamples;
170
+ }
171
+ const existingThread = group.threads.get(thread.index);
172
+ if (existingThread === undefined) {
173
+ group.threads.set(thread.index, {
174
+ index: thread.index,
175
+ name: getThreadName(thread),
176
+ processName: thread.thread.processName ?? null,
177
+ selfSamples: functionStat.selfSamples,
178
+ stackSamples: functionStat.stackSamples,
179
+ });
180
+ }
181
+ else {
182
+ existingThread.selfSamples += functionStat.selfSamples;
183
+ existingThread.stackSamples += functionStat.stackSamples;
184
+ }
185
+ }
186
+ }
187
+ const sortedGroups = [...groups.values()]
188
+ .sort((left, right) => compareSampleSummaries(left, right))
189
+ .slice(0, maxGroups)
190
+ .map((group) => ({
191
+ name: group.name,
192
+ selfSamples: group.selfSamples,
193
+ stackSamples: group.stackSamples,
194
+ exampleFunctions: sortFunctions(group.functions, "stackSamples", maxExamplesPerGroup),
195
+ threads: [...group.threads.values()]
196
+ .sort((left, right) => {
197
+ if (right.stackSamples !== left.stackSamples) {
198
+ return right.stackSamples - left.stackSamples;
199
+ }
200
+ if (right.selfSamples !== left.selfSamples) {
201
+ return right.selfSamples - left.selfSamples;
202
+ }
203
+ return left.name.localeCompare(right.name);
204
+ })
205
+ .slice(0, maxThreadsPerGroup),
206
+ }));
207
+ return {
208
+ profilePath: profile.profilePath,
209
+ sidecarPath: profile.sidecarPath,
210
+ presymbolicated: profile.sidecarPath !== null,
211
+ query: options.query ?? null,
212
+ resourceQuery: options.resourceQuery ?? null,
213
+ prefixSegments,
214
+ groupCount: sortedGroups.length,
215
+ totalGroupedStackSamples: sortedGroups.reduce((sum, group) => sum + group.stackSamples, 0),
216
+ totalGroupedSelfSamples: sortedGroups.reduce((sum, group) => sum + group.selfSamples, 0),
217
+ groups: sortedGroups,
218
+ };
219
+ }
220
+ export function focusFunctions(profile, query, options = {}) {
221
+ const normalizedQuery = query.trim().toLowerCase();
222
+ if (normalizedQuery.length === 0) {
223
+ throw new Error("The focus query must not be empty.");
224
+ }
225
+ const normalizedResourceQuery = normalizeOptionalQuery(options.resourceQuery);
226
+ const beforeDepth = Math.max(0, options.beforeDepth ?? 4);
227
+ const afterDepth = Math.max(0, options.afterDepth ?? 2);
228
+ const maxMatches = options.maxMatches ?? 8;
229
+ const maxThreads = options.maxThreads ?? 5;
230
+ const maxContexts = options.maxContexts ?? 8;
231
+ const threads = selectThreads(profile, options.thread);
232
+ const matches = new Map();
233
+ const threadBreakdown = new Map();
234
+ const contexts = new Map();
235
+ const callers = new Map();
236
+ const callees = new Map();
237
+ let totalMatchedSamples = 0;
238
+ for (const thread of threads) {
239
+ const samples = getResolvedSamples(profile, thread);
240
+ for (const sample of samples) {
241
+ const matchIndex = findDeepestMatchingFrame(sample.frames, normalizedQuery, normalizedResourceQuery);
242
+ if (matchIndex < 0) {
243
+ continue;
244
+ }
245
+ totalMatchedSamples += 1;
246
+ const matchFrame = sample.frames[matchIndex];
247
+ const matchSummary = matches.get(matchFrame.key);
248
+ if (matchSummary === undefined) {
249
+ matches.set(matchFrame.key, {
250
+ name: matchFrame.name,
251
+ resourceName: matchFrame.resourceName,
252
+ displayName: matchFrame.displayName,
253
+ sampleCount: 1,
254
+ });
255
+ }
256
+ else {
257
+ matchSummary.sampleCount += 1;
258
+ }
259
+ const existingThread = threadBreakdown.get(thread.index);
260
+ if (existingThread === undefined) {
261
+ threadBreakdown.set(thread.index, {
262
+ index: thread.index,
263
+ name: getThreadName(thread),
264
+ processName: thread.thread.processName ?? null,
265
+ sampleCount: 1,
266
+ });
267
+ }
268
+ else {
269
+ existingThread.sampleCount += 1;
270
+ }
271
+ const before = sample.frames
272
+ .slice(Math.max(0, matchIndex - beforeDepth), matchIndex)
273
+ .map((frame) => frame.displayName);
274
+ const after = sample.frames
275
+ .slice(matchIndex + 1, matchIndex + 1 + afterDepth)
276
+ .map((frame) => frame.displayName);
277
+ incrementContextMap(contexts, `${matchFrame.key}\u0000${before.join("\u0001")}\u0000${after.join("\u0001")}`, {
278
+ match: matchFrame.displayName,
279
+ before,
280
+ after,
281
+ sampleCount: 0,
282
+ });
283
+ incrementContextMap(callers, `${matchFrame.key}\u0000${before.join("\u0001")}`, {
284
+ match: matchFrame.displayName,
285
+ path: before,
286
+ sampleCount: 0,
287
+ });
288
+ incrementContextMap(callees, `${matchFrame.key}\u0000${after.join("\u0001")}`, {
289
+ match: matchFrame.displayName,
290
+ path: after,
291
+ sampleCount: 0,
292
+ });
293
+ }
294
+ }
295
+ return {
296
+ profilePath: profile.profilePath,
297
+ sidecarPath: profile.sidecarPath,
298
+ presymbolicated: profile.sidecarPath !== null,
299
+ query,
300
+ resourceQuery: options.resourceQuery ?? null,
301
+ totalMatchedSamples,
302
+ matchedFunctionCount: matches.size,
303
+ matches: [...matches.values()]
304
+ .sort((left, right) => {
305
+ if (right.sampleCount !== left.sampleCount) {
306
+ return right.sampleCount - left.sampleCount;
307
+ }
308
+ return left.displayName.localeCompare(right.displayName);
309
+ })
310
+ .slice(0, maxMatches),
311
+ threads: [...threadBreakdown.values()]
312
+ .sort((left, right) => right.sampleCount - left.sampleCount)
313
+ .slice(0, maxThreads),
314
+ topContexts: sortSampleCountEntries(contexts, maxContexts),
315
+ topCallers: sortSampleCountEntries(callers, maxContexts),
316
+ topCallees: sortSampleCountEntries(callees, maxContexts),
317
+ };
318
+ }
319
+ function getThreadAnalysis(profile, thread) {
320
+ let cacheForProfile = analysisCache.get(profile);
321
+ if (cacheForProfile === undefined) {
322
+ cacheForProfile = new Map();
323
+ analysisCache.set(profile, cacheForProfile);
324
+ }
325
+ const cached = cacheForProfile.get(thread.index);
326
+ if (cached !== undefined) {
327
+ return cached;
328
+ }
329
+ const analysis = analyzeThread(profile, thread);
330
+ cacheForProfile.set(thread.index, analysis);
331
+ return analysis;
332
+ }
333
+ function analyzeThread(profile, thread) {
334
+ const functions = new Map();
335
+ const markers = new Map();
336
+ const stacks = new Map();
337
+ const sampleTable = thread.thread.samples;
338
+ const sampleCount = sampleTable?.length ?? sampleTable?.stack?.length ?? 0;
339
+ const samples = getResolvedSamples(profile, thread);
340
+ let startTimeMs = null;
341
+ let endTimeMs = null;
342
+ for (const sample of samples) {
343
+ if (sample.timeMs !== null) {
344
+ startTimeMs = takeMin(startTimeMs, sample.timeMs);
345
+ endTimeMs = takeMax(endTimeMs, sample.timeMs);
346
+ }
347
+ const frames = sample.frames;
348
+ if (frames.length === 0) {
349
+ continue;
350
+ }
351
+ const leafFrame = frames[frames.length - 1];
352
+ incrementFunctionStat(functions, leafFrame, "selfSamples");
353
+ const seenKeys = new Set();
354
+ for (const frame of frames) {
355
+ if (seenKeys.has(frame.key)) {
356
+ continue;
357
+ }
358
+ seenKeys.add(frame.key);
359
+ incrementFunctionStat(functions, frame, "stackSamples");
360
+ }
361
+ const stackLabels = frames.map((frame) => frame.displayName);
362
+ const stackKey = stackLabels.join(" -> ");
363
+ const existingStack = stacks.get(stackKey);
364
+ if (existingStack === undefined) {
365
+ stacks.set(stackKey, {
366
+ stack: stackLabels,
367
+ sampleCount: 1,
368
+ });
369
+ }
370
+ else {
371
+ existingStack.sampleCount += 1;
372
+ }
373
+ }
374
+ const markerNames = thread.thread.markers?.name ?? [];
375
+ const markerCount = thread.thread.markers?.length ?? markerNames.length;
376
+ for (let index = 0; index < markerCount; index += 1) {
377
+ const markerName = resolveMarkerName(thread.thread, markerNames[index]);
378
+ markers.set(markerName, (markers.get(markerName) ?? 0) + 1);
379
+ }
380
+ return {
381
+ thread,
382
+ sampleCount,
383
+ startTimeMs,
384
+ endTimeMs,
385
+ durationMs: startTimeMs !== null && endTimeMs !== null
386
+ ? Math.max(0, endTimeMs - startTimeMs)
387
+ : computeThreadDuration(thread.thread),
388
+ functions,
389
+ markers,
390
+ stacks,
391
+ };
392
+ }
393
+ function getResolvedSamples(profile, thread) {
394
+ let cacheForProfile = resolvedSampleCache.get(profile);
395
+ if (cacheForProfile === undefined) {
396
+ cacheForProfile = new Map();
397
+ resolvedSampleCache.set(profile, cacheForProfile);
398
+ }
399
+ const cached = cacheForProfile.get(thread.index);
400
+ if (cached !== undefined) {
401
+ return cached;
402
+ }
403
+ const stackCache = new Map();
404
+ const stackIndexes = thread.thread.samples?.stack ?? [];
405
+ const sampleTimes = thread.thread.samples?.time ?? [];
406
+ const sampleCount = thread.thread.samples?.length ?? stackIndexes.length;
407
+ const samples = [];
408
+ for (let index = 0; index < sampleCount; index += 1) {
409
+ const stackIndex = stackIndexes[index];
410
+ if (typeof stackIndex !== "number" || stackIndex < 0) {
411
+ continue;
412
+ }
413
+ samples.push({
414
+ timeMs: typeof sampleTimes[index] === "number" ? sampleTimes[index] : null,
415
+ frames: resolveStackFrames(profile, thread, stackIndex, stackCache),
416
+ });
417
+ }
418
+ cacheForProfile.set(thread.index, samples);
419
+ return samples;
420
+ }
421
+ function resolveStackFrames(profile, thread, stackIndex, cache) {
422
+ const cached = cache.get(stackIndex);
423
+ if (cached !== undefined) {
424
+ return cached;
425
+ }
426
+ const prefixIndex = getNumber(thread.thread.stackTable?.prefix, stackIndex);
427
+ const frameIndex = getNumber(thread.thread.stackTable?.frame, stackIndex);
428
+ const frames = prefixIndex !== null
429
+ ? [...resolveStackFrames(profile, thread, prefixIndex, cache)]
430
+ : [];
431
+ if (frameIndex !== null) {
432
+ frames.push(resolveFrame(profile, thread, frameIndex));
433
+ }
434
+ cache.set(stackIndex, frames);
435
+ return frames;
436
+ }
437
+ function resolveFrame(profile, thread, frameIndex) {
438
+ const strings = thread.thread.stringArray ?? [];
439
+ const funcIndex = getNumber(thread.thread.frameTable?.func, frameIndex);
440
+ const nameIndex = funcIndex !== null ? getNumber(thread.thread.funcTable?.name, funcIndex) : null;
441
+ const rawName = getString(strings, nameIndex);
442
+ const nativeName = resolveNativeSymbolName(thread.thread, frameIndex);
443
+ const resourceIndex = funcIndex !== null ? getNumber(thread.thread.funcTable?.resource, funcIndex) : null;
444
+ const resourceName = resolveResourceName(thread, resourceIndex);
445
+ const address = getNumber(thread.thread.frameTable?.address, frameIndex);
446
+ const lib = resolveResourceLib(thread, resourceIndex);
447
+ const sidecarName = profile.symbolicator.lookup(lib, address);
448
+ const chosenName = chooseFrameName(sidecarName, nativeName, rawName, address);
449
+ const displayName = resourceName !== null && !chosenName.includes(resourceName)
450
+ ? `${chosenName} [${resourceName}]`
451
+ : chosenName;
452
+ return {
453
+ key: `${chosenName}\u0000${resourceName ?? ""}`,
454
+ name: chosenName,
455
+ resourceName,
456
+ displayName,
457
+ };
458
+ }
459
+ function resolveNativeSymbolName(thread, frameIndex) {
460
+ const nativeSymbolIndex = getNumber(thread.frameTable?.nativeSymbol, frameIndex);
461
+ const nameIndex = nativeSymbolIndex !== null
462
+ ? getNumber(thread.nativeSymbols?.name, nativeSymbolIndex)
463
+ : null;
464
+ return getString(thread.stringArray ?? [], nameIndex);
465
+ }
466
+ function resolveResourceName(thread, resourceIndex) {
467
+ if (resourceIndex === null) {
468
+ return null;
469
+ }
470
+ const lib = resolveResourceLib(thread, resourceIndex);
471
+ if (lib !== undefined) {
472
+ return lib.debugName ?? lib.name ?? null;
473
+ }
474
+ const nameIndex = getNumber(thread.thread.resourceTable?.name, resourceIndex);
475
+ return getString(thread.thread.stringArray ?? [], nameIndex);
476
+ }
477
+ function resolveResourceLib(thread, resourceIndex) {
478
+ if (resourceIndex === null) {
479
+ return undefined;
480
+ }
481
+ const libIndex = getNumber(thread.thread.resourceTable?.lib, resourceIndex);
482
+ if (libIndex === null) {
483
+ return undefined;
484
+ }
485
+ return thread.libs[libIndex];
486
+ }
487
+ function chooseFrameName(sidecarName, nativeName, rawName, address) {
488
+ if (sidecarName !== null) {
489
+ return sidecarName;
490
+ }
491
+ if (nativeName !== null && !isProbablyAddress(nativeName)) {
492
+ return nativeName;
493
+ }
494
+ if (rawName !== null && !isProbablyAddress(rawName)) {
495
+ return rawName;
496
+ }
497
+ if (nativeName !== null) {
498
+ return nativeName;
499
+ }
500
+ if (rawName !== null) {
501
+ return rawName;
502
+ }
503
+ if (address !== null) {
504
+ return `0x${address.toString(16)}`;
505
+ }
506
+ return "UNKNOWN";
507
+ }
508
+ function incrementFunctionStat(stats, frame, field) {
509
+ const existing = stats.get(frame.key);
510
+ if (existing === undefined) {
511
+ stats.set(frame.key, {
512
+ key: frame.key,
513
+ name: frame.name,
514
+ resourceName: frame.resourceName,
515
+ displayName: frame.displayName,
516
+ selfSamples: field === "selfSamples" ? 1 : 0,
517
+ stackSamples: field === "stackSamples" ? 1 : 0,
518
+ });
519
+ return;
520
+ }
521
+ existing[field] += 1;
522
+ }
523
+ function sortFunctions(stats, field, limit) {
524
+ return [...stats.values()]
525
+ .sort((left, right) => {
526
+ if (right[field] !== left[field]) {
527
+ return right[field] - left[field];
528
+ }
529
+ if (right.stackSamples !== left.stackSamples) {
530
+ return right.stackSamples - left.stackSamples;
531
+ }
532
+ if (right.selfSamples !== left.selfSamples) {
533
+ return right.selfSamples - left.selfSamples;
534
+ }
535
+ return left.displayName.localeCompare(right.displayName);
536
+ })
537
+ .slice(0, limit)
538
+ .map(({ key: _key, ...rest }) => rest);
539
+ }
540
+ function aggregateFunctions(analyses) {
541
+ const aggregate = new Map();
542
+ for (const analysis of analyses) {
543
+ for (const functionStat of analysis.functions.values()) {
544
+ const existing = aggregate.get(functionStat.key);
545
+ if (existing === undefined) {
546
+ aggregate.set(functionStat.key, { ...functionStat });
547
+ continue;
548
+ }
549
+ existing.selfSamples += functionStat.selfSamples;
550
+ existing.stackSamples += functionStat.stackSamples;
551
+ }
552
+ }
553
+ return aggregate;
554
+ }
555
+ function compareSampleSummaries(left, right) {
556
+ if (right.stackSamples !== left.stackSamples) {
557
+ return right.stackSamples - left.stackSamples;
558
+ }
559
+ if (right.selfSamples !== left.selfSamples) {
560
+ return right.selfSamples - left.selfSamples;
561
+ }
562
+ return left.name.localeCompare(right.name);
563
+ }
564
+ function incrementContextMap(map, key, value) {
565
+ const existing = map.get(key);
566
+ if (existing === undefined) {
567
+ value.sampleCount = 1;
568
+ map.set(key, value);
569
+ return;
570
+ }
571
+ existing.sampleCount += 1;
572
+ }
573
+ function sortSampleCountEntries(map, limit) {
574
+ return [...map.values()]
575
+ .sort((left, right) => right.sampleCount - left.sampleCount)
576
+ .slice(0, limit);
577
+ }
578
+ function toThreadSummary(analysis, options) {
579
+ return {
580
+ index: analysis.thread.index,
581
+ name: getThreadName(analysis.thread),
582
+ processName: analysis.thread.thread.processName ?? null,
583
+ pid: typeof analysis.thread.thread.pid === "number"
584
+ ? analysis.thread.thread.pid
585
+ : null,
586
+ tid: typeof analysis.thread.thread.tid === "number"
587
+ ? analysis.thread.thread.tid
588
+ : null,
589
+ sampleCount: analysis.sampleCount,
590
+ startTimeMs: analysis.startTimeMs,
591
+ endTimeMs: analysis.endTimeMs,
592
+ durationMs: analysis.durationMs,
593
+ topSelfFunctions: sortFunctions(analysis.functions, "selfSamples", options.maxFunctions),
594
+ topStackFunctions: sortFunctions(analysis.functions, "stackSamples", options.maxFunctions),
595
+ topMarkers: [...analysis.markers.entries()]
596
+ .map(([name, count]) => ({ name, count }))
597
+ .sort((left, right) => right.count - left.count)
598
+ .slice(0, options.maxMarkers),
599
+ };
600
+ }
601
+ function selectThreads(profile, selector) {
602
+ if (selector === undefined) {
603
+ return profile.threads;
604
+ }
605
+ return [selectThread(profile, selector)];
606
+ }
607
+ function selectThread(profile, selector) {
608
+ if (typeof selector === "number") {
609
+ const exactMatch = profile.threads.find((thread) => thread.index === selector);
610
+ if (exactMatch !== undefined) {
611
+ return exactMatch;
612
+ }
613
+ throw new Error(`Could not find thread index ${selector}.`);
614
+ }
615
+ const normalizedSelector = selector.trim().toLowerCase();
616
+ const exactMatches = profile.threads.filter((thread) => {
617
+ const label = getThreadLabel(thread).toLowerCase();
618
+ return label === normalizedSelector || getThreadName(thread).toLowerCase() === normalizedSelector;
619
+ });
620
+ if (exactMatches.length > 0) {
621
+ return exactMatches[0];
622
+ }
623
+ const partialMatches = profile.threads
624
+ .filter((thread) => getThreadLabel(thread).toLowerCase().includes(normalizedSelector))
625
+ .sort((left, right) => {
626
+ const leftSamples = getThreadAnalysis(profile, left).sampleCount;
627
+ const rightSamples = getThreadAnalysis(profile, right).sampleCount;
628
+ return rightSamples - leftSamples;
629
+ });
630
+ if (partialMatches.length > 0) {
631
+ return partialMatches[0];
632
+ }
633
+ throw new Error(`Could not find a thread matching "${selector}".`);
634
+ }
635
+ function resolveMarkerName(thread, markerIndex) {
636
+ const name = getString(thread.stringArray ?? [], markerIndex ?? null);
637
+ return name ?? "UNKNOWN";
638
+ }
639
+ function getThreadName(thread) {
640
+ if (typeof thread.thread.name === "string" && thread.thread.name.length > 0) {
641
+ return thread.thread.name;
642
+ }
643
+ return `thread-${thread.index}`;
644
+ }
645
+ function getThreadLabel(thread) {
646
+ const processName = thread.thread.processName;
647
+ return processName ? `${getThreadName(thread)} (${processName})` : getThreadName(thread);
648
+ }
649
+ function normalizeOptionalQuery(value) {
650
+ const normalized = value?.trim().toLowerCase() ?? "";
651
+ return normalized.length > 0 ? normalized : null;
652
+ }
653
+ function matchesFunctionFilter(functionStat, normalizedQuery, normalizedResourceQuery) {
654
+ if (normalizedResourceQuery !== null &&
655
+ !(functionStat.resourceName ?? "")
656
+ .toLowerCase()
657
+ .includes(normalizedResourceQuery)) {
658
+ return false;
659
+ }
660
+ if (normalizedQuery === null) {
661
+ return true;
662
+ }
663
+ const haystack = `${functionStat.name}\n${functionStat.resourceName ?? ""}`.toLowerCase();
664
+ return haystack.includes(normalizedQuery);
665
+ }
666
+ function findDeepestMatchingFrame(frames, normalizedQuery, normalizedResourceQuery) {
667
+ for (let index = frames.length - 1; index >= 0; index -= 1) {
668
+ const frame = frames[index];
669
+ if (normalizedResourceQuery !== null &&
670
+ !(frame.resourceName ?? "").toLowerCase().includes(normalizedResourceQuery)) {
671
+ continue;
672
+ }
673
+ const haystack = `${frame.name}\n${frame.resourceName ?? ""}`.toLowerCase();
674
+ if (haystack.includes(normalizedQuery)) {
675
+ return index;
676
+ }
677
+ }
678
+ return -1;
679
+ }
680
+ function deriveSubsystemName(name, prefixSegments, normalizedQuery = null) {
681
+ const namespacePath = extractNamespacePath(name, normalizedQuery);
682
+ if (namespacePath === null) {
683
+ return name;
684
+ }
685
+ const segments = namespacePath.split("::").filter(Boolean);
686
+ if (segments.length === 0) {
687
+ return name;
688
+ }
689
+ return segments.slice(0, Math.min(prefixSegments, segments.length)).join("::");
690
+ }
691
+ function extractNamespacePath(name, normalizedQuery = null) {
692
+ const matches = [...name.matchAll(/[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)+/g)]
693
+ .map((match) => match[0])
694
+ .filter((match) => typeof match === "string");
695
+ if (matches.length === 0) {
696
+ return null;
697
+ }
698
+ if (normalizedQuery !== null) {
699
+ const preferredMatch = matches.find((match) => match.toLowerCase().includes(normalizedQuery));
700
+ if (preferredMatch !== undefined) {
701
+ return preferredMatch;
702
+ }
703
+ }
704
+ return matches[0] ?? null;
705
+ }
706
+ function computeThreadDuration(thread) {
707
+ const startTime = typeof thread.registerTime === "number" ? thread.registerTime : null;
708
+ const endTime = typeof thread.unregisterTime === "number" ? thread.unregisterTime : null;
709
+ if (startTime !== null && endTime !== null) {
710
+ return Math.max(0, endTime - startTime);
711
+ }
712
+ return null;
713
+ }
714
+ function getNumber(values, index) {
715
+ const value = values?.[index];
716
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
717
+ }
718
+ function getString(values, index) {
719
+ if (index === null) {
720
+ return null;
721
+ }
722
+ return values[index] ?? null;
723
+ }
724
+ function isProbablyAddress(value) {
725
+ return /^0x[0-9a-f]+$/i.test(value);
726
+ }
727
+ function takeMin(current, next) {
728
+ if (next === null) {
729
+ return current;
730
+ }
731
+ if (current === null) {
732
+ return next;
733
+ }
734
+ return Math.min(current, next);
735
+ }
736
+ function takeMax(current, next) {
737
+ if (next === null) {
738
+ return current;
739
+ }
740
+ if (current === null) {
741
+ return next;
742
+ }
743
+ return Math.max(current, next);
744
+ }
745
+ //# sourceMappingURL=analyze.js.map