tracemeld 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.
- package/build/analysis/bottleneck.d.ts +23 -0
- package/build/analysis/bottleneck.js +55 -0
- package/build/analysis/bottleneck.js.map +1 -0
- package/build/analysis/explain.d.ts +38 -0
- package/build/analysis/explain.js +139 -0
- package/build/analysis/explain.js.map +1 -0
- package/build/analysis/hotpaths.d.ts +19 -0
- package/build/analysis/hotpaths.js +55 -0
- package/build/analysis/hotpaths.js.map +1 -0
- package/build/analysis/hotspots.d.ts +24 -0
- package/build/analysis/hotspots.js +75 -0
- package/build/analysis/hotspots.js.map +1 -0
- package/build/analysis/query.d.ts +25 -0
- package/build/analysis/query.js +102 -0
- package/build/analysis/query.js.map +1 -0
- package/build/analysis/spinpaths.d.ts +17 -0
- package/build/analysis/spinpaths.js +48 -0
- package/build/analysis/spinpaths.js.map +1 -0
- package/build/analysis/starvations.d.ts +21 -0
- package/build/analysis/starvations.js +76 -0
- package/build/analysis/starvations.js.map +1 -0
- package/build/analysis/summary.d.ts +27 -0
- package/build/analysis/summary.js +130 -0
- package/build/analysis/summary.js.map +1 -0
- package/build/analysis/waste.d.ts +19 -0
- package/build/analysis/waste.js +33 -0
- package/build/analysis/waste.js.map +1 -0
- package/build/cli.d.ts +2 -0
- package/build/cli.js +4 -0
- package/build/cli.js.map +1 -0
- package/build/exporters/collapsed.d.ts +2 -0
- package/build/exporters/collapsed.js +34 -0
- package/build/exporters/collapsed.js.map +1 -0
- package/build/importers/chrome-trace.d.ts +2 -0
- package/build/importers/chrome-trace.js +146 -0
- package/build/importers/chrome-trace.js.map +1 -0
- package/build/importers/collapsed.d.ts +2 -0
- package/build/importers/collapsed.js +52 -0
- package/build/importers/collapsed.js.map +1 -0
- package/build/importers/detect.d.ts +2 -0
- package/build/importers/detect.js +60 -0
- package/build/importers/detect.js.map +1 -0
- package/build/importers/gecko.d.ts +2 -0
- package/build/importers/gecko.js +77 -0
- package/build/importers/gecko.js.map +1 -0
- package/build/importers/import.d.ts +11 -0
- package/build/importers/import.js +98 -0
- package/build/importers/import.js.map +1 -0
- package/build/importers/pprof.d.ts +2 -0
- package/build/importers/pprof.js +238 -0
- package/build/importers/pprof.js.map +1 -0
- package/build/importers/types.d.ts +6 -0
- package/build/importers/types.js +2 -0
- package/build/importers/types.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.js +6 -0
- package/build/index.js.map +1 -0
- package/build/instrument/mark.d.ts +11 -0
- package/build/instrument/mark.js +13 -0
- package/build/instrument/mark.js.map +1 -0
- package/build/instrument/trace.d.ts +16 -0
- package/build/instrument/trace.js +107 -0
- package/build/instrument/trace.js.map +1 -0
- package/build/model/frame-table.d.ts +10 -0
- package/build/model/frame-table.js +27 -0
- package/build/model/frame-table.js.map +1 -0
- package/build/model/profile.d.ts +20 -0
- package/build/model/profile.js +89 -0
- package/build/model/profile.js.map +1 -0
- package/build/model/state.d.ts +21 -0
- package/build/model/state.js +59 -0
- package/build/model/state.js.map +1 -0
- package/build/model/types.d.ts +73 -0
- package/build/model/types.js +10 -0
- package/build/model/types.js.map +1 -0
- package/build/patterns/blind-edit.d.ts +3 -0
- package/build/patterns/blind-edit.js +119 -0
- package/build/patterns/blind-edit.js.map +1 -0
- package/build/patterns/redundant-read.d.ts +3 -0
- package/build/patterns/redundant-read.js +72 -0
- package/build/patterns/redundant-read.js.map +1 -0
- package/build/patterns/registry.d.ts +10 -0
- package/build/patterns/registry.js +27 -0
- package/build/patterns/registry.js.map +1 -0
- package/build/patterns/retry-loop.d.ts +3 -0
- package/build/patterns/retry-loop.js +57 -0
- package/build/patterns/retry-loop.js.map +1 -0
- package/build/patterns/types.d.ts +14 -0
- package/build/patterns/types.js +2 -0
- package/build/patterns/types.js.map +1 -0
- package/build/server.d.ts +3 -0
- package/build/server.js +247 -0
- package/build/server.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Profile } from '../model/types.js';
|
|
2
|
+
import { type SourceLocation } from './query.js';
|
|
3
|
+
export interface BottleneckInput {
|
|
4
|
+
dimension: string;
|
|
5
|
+
top_n?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BottleneckEntry {
|
|
8
|
+
span_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
source?: SourceLocation;
|
|
12
|
+
ancestry: string[];
|
|
13
|
+
self_cost: Record<string, number>;
|
|
14
|
+
total_cost: Record<string, number>;
|
|
15
|
+
impact_score: number;
|
|
16
|
+
pct_of_total: number;
|
|
17
|
+
recommendation: string;
|
|
18
|
+
}
|
|
19
|
+
export interface BottleneckResult {
|
|
20
|
+
dimension: string;
|
|
21
|
+
entries: BottleneckEntry[];
|
|
22
|
+
}
|
|
23
|
+
export declare function findBottlenecks(profile: Profile, input: BottleneckInput): BottleneckResult;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getAllSpans, buildSpanIndex, getSpanAncestry, computeSelfCost, valuesToRecord, extractKind, getSpanSourceLocation, } from './query.js';
|
|
2
|
+
export function findBottlenecks(profile, input) {
|
|
3
|
+
const topN = input.top_n ?? 10;
|
|
4
|
+
const dim = input.dimension;
|
|
5
|
+
const dimIndex = profile.value_types.findIndex((vt) => vt.key === dim);
|
|
6
|
+
if (dimIndex < 0)
|
|
7
|
+
return { dimension: dim, entries: [] };
|
|
8
|
+
const allSpans = getAllSpans(profile);
|
|
9
|
+
const spanIndex = buildSpanIndex(profile);
|
|
10
|
+
let totalCost = 0;
|
|
11
|
+
for (const span of allSpans) {
|
|
12
|
+
totalCost += (computeSelfCost(profile, span, spanIndex)[dimIndex] ?? 0);
|
|
13
|
+
}
|
|
14
|
+
if (totalCost === 0)
|
|
15
|
+
return { dimension: dim, entries: [] };
|
|
16
|
+
const entries = [];
|
|
17
|
+
for (const span of allSpans) {
|
|
18
|
+
const selfCost = computeSelfCost(profile, span, spanIndex);
|
|
19
|
+
const selfVal = selfCost[dimIndex] ?? 0;
|
|
20
|
+
if (selfVal <= 0)
|
|
21
|
+
continue;
|
|
22
|
+
const frameName = profile.frames[span.frame_index]?.name ?? '<unknown>';
|
|
23
|
+
const pctOfTotal = (selfVal / totalCost) * 100;
|
|
24
|
+
const impactScore = selfVal * (selfVal / totalCost);
|
|
25
|
+
const source = getSpanSourceLocation(profile, span);
|
|
26
|
+
entries.push({
|
|
27
|
+
span_id: span.id,
|
|
28
|
+
name: frameName,
|
|
29
|
+
kind: extractKind(frameName),
|
|
30
|
+
source,
|
|
31
|
+
ancestry: getSpanAncestry(profile, span, spanIndex),
|
|
32
|
+
self_cost: valuesToRecord(profile, selfCost),
|
|
33
|
+
total_cost: valuesToRecord(profile, span.values),
|
|
34
|
+
impact_score: Math.round(impactScore * 100) / 100,
|
|
35
|
+
pct_of_total: Math.round(pctOfTotal * 100) / 100,
|
|
36
|
+
recommendation: generateRecommendation(frameName, pctOfTotal, source?.ref),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
entries.sort((a, b) => b.impact_score - a.impact_score);
|
|
40
|
+
return { dimension: dim, entries: entries.slice(0, topN) };
|
|
41
|
+
}
|
|
42
|
+
function generateRecommendation(frameName, pctOfTotal, sourceRef) {
|
|
43
|
+
const kind = extractKind(frameName);
|
|
44
|
+
const pctStr = `${Math.round(pctOfTotal)}%`;
|
|
45
|
+
const readHint = sourceRef ? ` Read ${sourceRef} to understand the implementation.` : '';
|
|
46
|
+
switch (kind) {
|
|
47
|
+
case 'bash': return `This command accounts for ${pctStr} of total cost. Consider scoping it more tightly or caching results.${readHint}`;
|
|
48
|
+
case 'file_read': return `This file read accounts for ${pctStr} of total cost. Consider reading only the relevant section.${readHint}`;
|
|
49
|
+
case 'file_write': return `This file write accounts for ${pctStr} of total cost. Consider batching changes.${readHint}`;
|
|
50
|
+
case 'thinking': return `Thinking accounts for ${pctStr} of total cost. Consider breaking the problem into smaller steps.`;
|
|
51
|
+
case 'validation': return `Validation accounts for ${pctStr} of total cost. Consider scoping tests to affected files.${readHint}`;
|
|
52
|
+
default: return `This operation accounts for ${pctStr} of total cost.${readHint || ' Consider whether it can be optimized or eliminated.'}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=bottleneck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bottleneck.js","sourceRoot":"","sources":["../../src/analysis/bottleneck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Profile, DetectedPattern } from '../model/types.js';
|
|
2
|
+
import type { PatternRegistry } from '../patterns/registry.js';
|
|
3
|
+
import { type SourceLocation } from './query.js';
|
|
4
|
+
export interface ExplainSpanInput {
|
|
5
|
+
span_id: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ExplainSpanResult {
|
|
8
|
+
span: {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
source?: SourceLocation;
|
|
12
|
+
start_time: number;
|
|
13
|
+
end_time: number;
|
|
14
|
+
duration_ms: number;
|
|
15
|
+
cost: Record<string, number>;
|
|
16
|
+
error?: string;
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
ancestry: string[];
|
|
20
|
+
children: Array<{
|
|
21
|
+
span_id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
source?: SourceLocation;
|
|
24
|
+
cost: Record<string, number>;
|
|
25
|
+
pct_of_parent: Record<string, number>;
|
|
26
|
+
error?: string;
|
|
27
|
+
}>;
|
|
28
|
+
causal_chain: Array<{
|
|
29
|
+
timestamp: number;
|
|
30
|
+
event: string;
|
|
31
|
+
kind: string;
|
|
32
|
+
cost: Record<string, number>;
|
|
33
|
+
outcome?: string;
|
|
34
|
+
}>;
|
|
35
|
+
patterns: DetectedPattern[];
|
|
36
|
+
recommendations: string[];
|
|
37
|
+
}
|
|
38
|
+
export declare function explainSpan(profile: Profile, input: ExplainSpanInput, registry?: PatternRegistry): ExplainSpanResult;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { getSpanById, buildSpanIndex, getSpanAncestry, extractKind, valuesToRecord, getSpanSourceLocation, } from './query.js';
|
|
2
|
+
export function explainSpan(profile, input, registry) {
|
|
3
|
+
const spanIndex = buildSpanIndex(profile);
|
|
4
|
+
const span = getSpanById(profile, input.span_id, spanIndex);
|
|
5
|
+
if (!span) {
|
|
6
|
+
return notFoundResult();
|
|
7
|
+
}
|
|
8
|
+
const frameName = profile.frames[span.frame_index]?.name ?? '<unknown>';
|
|
9
|
+
const kind = extractKind(frameName);
|
|
10
|
+
const ancestry = getSpanAncestry(profile, span, spanIndex);
|
|
11
|
+
const cost = valuesToRecord(profile, span.values);
|
|
12
|
+
const children = buildChildren(profile, span, spanIndex);
|
|
13
|
+
const causalChain = buildCausalChain(profile, span, spanIndex);
|
|
14
|
+
return {
|
|
15
|
+
span: {
|
|
16
|
+
name: frameName,
|
|
17
|
+
kind,
|
|
18
|
+
source: getSpanSourceLocation(profile, span),
|
|
19
|
+
start_time: span.start_time,
|
|
20
|
+
end_time: span.end_time,
|
|
21
|
+
duration_ms: span.end_time - span.start_time,
|
|
22
|
+
cost,
|
|
23
|
+
error: span.error,
|
|
24
|
+
args: span.args,
|
|
25
|
+
},
|
|
26
|
+
ancestry,
|
|
27
|
+
children,
|
|
28
|
+
causal_chain: causalChain,
|
|
29
|
+
patterns: registry
|
|
30
|
+
? registry.getMatchesForSpan(profile, span.id).map((m) => ({ ...m.pattern, span_ids: m.span_ids }))
|
|
31
|
+
: [],
|
|
32
|
+
recommendations: registry
|
|
33
|
+
? [...new Set(registry.getMatchesForSpan(profile, span.id).map((m) => m.recommendation))]
|
|
34
|
+
: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function buildChildren(profile, parent, index) {
|
|
38
|
+
const children = [];
|
|
39
|
+
for (const childId of parent.children) {
|
|
40
|
+
const child = getSpanById(profile, childId, index);
|
|
41
|
+
if (!child)
|
|
42
|
+
continue;
|
|
43
|
+
const childName = profile.frames[child.frame_index]?.name ?? '<unknown>';
|
|
44
|
+
const childCost = valuesToRecord(profile, child.values);
|
|
45
|
+
const pctOfParent = {};
|
|
46
|
+
for (let i = 0; i < profile.value_types.length; i++) {
|
|
47
|
+
const key = profile.value_types[i].key;
|
|
48
|
+
const parentVal = parent.values[i] ?? 0;
|
|
49
|
+
const childVal = child.values[i] ?? 0;
|
|
50
|
+
pctOfParent[key] =
|
|
51
|
+
parentVal > 0 ? Math.round((childVal / parentVal) * 10000) / 100 : 0;
|
|
52
|
+
}
|
|
53
|
+
children.push({
|
|
54
|
+
span_id: child.id,
|
|
55
|
+
name: childName,
|
|
56
|
+
source: getSpanSourceLocation(profile, child),
|
|
57
|
+
cost: childCost,
|
|
58
|
+
pct_of_parent: pctOfParent,
|
|
59
|
+
error: child.error,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Sort by the first value type dimension (typically wall_ms), descending
|
|
63
|
+
children.sort((a, b) => {
|
|
64
|
+
const key = profile.value_types[0]?.key;
|
|
65
|
+
if (!key)
|
|
66
|
+
return 0;
|
|
67
|
+
return (b.cost[key] ?? 0) - (a.cost[key] ?? 0);
|
|
68
|
+
});
|
|
69
|
+
return children;
|
|
70
|
+
}
|
|
71
|
+
function buildCausalChain(profile, parent, index) {
|
|
72
|
+
const events = [];
|
|
73
|
+
// Add child spans
|
|
74
|
+
for (const childId of parent.children) {
|
|
75
|
+
const child = getSpanById(profile, childId, index);
|
|
76
|
+
if (!child)
|
|
77
|
+
continue;
|
|
78
|
+
const childName = profile.frames[child.frame_index]?.name ?? '<unknown>';
|
|
79
|
+
const childKind = extractKind(childName);
|
|
80
|
+
const childCost = valuesToRecord(profile, child.values);
|
|
81
|
+
let eventDesc = childName;
|
|
82
|
+
const duration = child.end_time - child.start_time;
|
|
83
|
+
if (duration > 0) {
|
|
84
|
+
eventDesc += ` (${formatDuration(duration)}`;
|
|
85
|
+
const tokensIdx = profile.value_types.findIndex((vt) => vt.key === 'input_tokens');
|
|
86
|
+
const inputTokens = tokensIdx >= 0 ? (child.values[tokensIdx] ?? 0) : 0;
|
|
87
|
+
if (inputTokens > 0)
|
|
88
|
+
eventDesc += `, ${inputTokens} tokens`;
|
|
89
|
+
eventDesc += ')';
|
|
90
|
+
}
|
|
91
|
+
if (child.error)
|
|
92
|
+
eventDesc += ` [ERROR: ${child.error}]`;
|
|
93
|
+
events.push({
|
|
94
|
+
timestamp: child.start_time,
|
|
95
|
+
event: eventDesc,
|
|
96
|
+
kind: childKind,
|
|
97
|
+
cost: childCost,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Add markers that fall within the parent's time range
|
|
101
|
+
for (const lane of profile.lanes) {
|
|
102
|
+
for (const marker of lane.markers) {
|
|
103
|
+
if (marker.timestamp >= parent.start_time && marker.timestamp <= parent.end_time) {
|
|
104
|
+
events.push({
|
|
105
|
+
timestamp: marker.timestamp,
|
|
106
|
+
event: marker.name,
|
|
107
|
+
kind: 'marker',
|
|
108
|
+
cost: {},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
114
|
+
return events;
|
|
115
|
+
}
|
|
116
|
+
function formatDuration(ms) {
|
|
117
|
+
if (ms < 1000)
|
|
118
|
+
return `${Math.round(ms)}ms`;
|
|
119
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
120
|
+
}
|
|
121
|
+
function notFoundResult() {
|
|
122
|
+
return {
|
|
123
|
+
span: {
|
|
124
|
+
name: '<not found>',
|
|
125
|
+
kind: 'unknown',
|
|
126
|
+
start_time: 0,
|
|
127
|
+
end_time: 0,
|
|
128
|
+
duration_ms: 0,
|
|
129
|
+
cost: {},
|
|
130
|
+
args: {},
|
|
131
|
+
},
|
|
132
|
+
ancestry: [],
|
|
133
|
+
children: [],
|
|
134
|
+
causal_chain: [],
|
|
135
|
+
patterns: [],
|
|
136
|
+
recommendations: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../src/analysis/explain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Profile } from '../model/types.js';
|
|
2
|
+
import { type SourceLocation } from './query.js';
|
|
3
|
+
export interface HotpathsInput {
|
|
4
|
+
dimension: string;
|
|
5
|
+
top_n?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface HotpathEntry {
|
|
8
|
+
frames: string[];
|
|
9
|
+
leaf_source?: SourceLocation;
|
|
10
|
+
leaf_cost: number;
|
|
11
|
+
path_cost: Record<string, number>;
|
|
12
|
+
pct_of_total: number;
|
|
13
|
+
leaf_span_id: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface HotpathsResult {
|
|
16
|
+
dimension: string;
|
|
17
|
+
paths: HotpathEntry[];
|
|
18
|
+
}
|
|
19
|
+
export declare function findHotpaths(profile: Profile, input: HotpathsInput): HotpathsResult;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getAllSpans, buildSpanIndex, getSpanAncestry, computeSelfCost, valuesToRecord, getSpanSourceLocation, getSourceLocation, } from './query.js';
|
|
2
|
+
export function findHotpaths(profile, input) {
|
|
3
|
+
const topN = input.top_n ?? 10;
|
|
4
|
+
const dim = input.dimension;
|
|
5
|
+
const dimIndex = profile.value_types.findIndex((vt) => vt.key === dim);
|
|
6
|
+
if (dimIndex < 0)
|
|
7
|
+
return { dimension: dim, paths: [] };
|
|
8
|
+
const entries = [];
|
|
9
|
+
// From spans: leaf spans (no children) with their ancestry
|
|
10
|
+
const allSpans = getAllSpans(profile);
|
|
11
|
+
const spanIndex = buildSpanIndex(profile);
|
|
12
|
+
for (const span of allSpans) {
|
|
13
|
+
if (span.children.length > 0)
|
|
14
|
+
continue;
|
|
15
|
+
const selfCost = computeSelfCost(profile, span, spanIndex);
|
|
16
|
+
const leafCost = selfCost[dimIndex] ?? 0;
|
|
17
|
+
if (leafCost <= 0)
|
|
18
|
+
continue;
|
|
19
|
+
entries.push({
|
|
20
|
+
frames: getSpanAncestry(profile, span, spanIndex),
|
|
21
|
+
leaf_source: getSpanSourceLocation(profile, span),
|
|
22
|
+
leaf_cost: leafCost,
|
|
23
|
+
path_cost: valuesToRecord(profile, selfCost),
|
|
24
|
+
pct_of_total: 0,
|
|
25
|
+
leaf_span_id: span.id,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// From samples: each sample is a root-to-leaf path
|
|
29
|
+
for (const lane of profile.lanes) {
|
|
30
|
+
for (const sample of lane.samples) {
|
|
31
|
+
const cost = sample.values[dimIndex] ?? 0;
|
|
32
|
+
if (cost <= 0)
|
|
33
|
+
continue;
|
|
34
|
+
const leafFrameIdx = sample.stack[sample.stack.length - 1];
|
|
35
|
+
entries.push({
|
|
36
|
+
frames: sample.stack.map((idx) => profile.frames[idx]?.name ?? '<unknown>'),
|
|
37
|
+
leaf_source: sample.stack.length > 0 ? getSourceLocation(profile, leafFrameIdx) : undefined,
|
|
38
|
+
leaf_cost: cost,
|
|
39
|
+
path_cost: valuesToRecord(profile, sample.values),
|
|
40
|
+
pct_of_total: 0,
|
|
41
|
+
leaf_span_id: null,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Compute pct_of_total
|
|
46
|
+
const fullTotal = entries.reduce((sum, e) => sum + e.leaf_cost, 0);
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
entry.pct_of_total = fullTotal > 0
|
|
49
|
+
? Math.round((entry.leaf_cost / fullTotal) * 10000) / 100
|
|
50
|
+
: 0;
|
|
51
|
+
}
|
|
52
|
+
entries.sort((a, b) => b.leaf_cost - a.leaf_cost);
|
|
53
|
+
return { dimension: dim, paths: entries.slice(0, topN) };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=hotpaths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotpaths.js","sourceRoot":"","sources":["../../src/analysis/hotpaths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Profile, DetectedPattern } from '../model/types.js';
|
|
2
|
+
import type { PatternRegistry } from '../patterns/registry.js';
|
|
3
|
+
import { type SourceLocation } from './query.js';
|
|
4
|
+
export interface HotspotsInput {
|
|
5
|
+
dimension: string;
|
|
6
|
+
top_n?: number;
|
|
7
|
+
min_value?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface HotspotEntry {
|
|
10
|
+
span_id: string;
|
|
11
|
+
ancestry: string[];
|
|
12
|
+
name: string;
|
|
13
|
+
source?: SourceLocation;
|
|
14
|
+
total_cost: Record<string, number>;
|
|
15
|
+
self_cost: Record<string, number>;
|
|
16
|
+
pct_of_total: number;
|
|
17
|
+
patterns: DetectedPattern[];
|
|
18
|
+
investigate: string;
|
|
19
|
+
}
|
|
20
|
+
export interface HotspotsResult {
|
|
21
|
+
dimension: string;
|
|
22
|
+
entries: HotspotEntry[];
|
|
23
|
+
}
|
|
24
|
+
export declare function findHotspots(profile: Profile, input: HotspotsInput, registry?: PatternRegistry): HotspotsResult;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getAllSpans, buildSpanIndex, getSpanAncestry, computeSelfCost, valuesToRecord, getSpanSourceLocation, } from './query.js';
|
|
2
|
+
export function findHotspots(profile, input, registry) {
|
|
3
|
+
const topN = input.top_n ?? 10;
|
|
4
|
+
const minValue = input.min_value ?? 0;
|
|
5
|
+
const dim = input.dimension;
|
|
6
|
+
const isErrors = dim === 'errors';
|
|
7
|
+
const spans = getAllSpans(profile);
|
|
8
|
+
const spanIndex = buildSpanIndex(profile);
|
|
9
|
+
const dimIndex = isErrors ? -1 : profile.value_types.findIndex((vt) => vt.key === dim);
|
|
10
|
+
const ranked = [];
|
|
11
|
+
for (const span of spans) {
|
|
12
|
+
const selfCost = computeSelfCost(profile, span, spanIndex);
|
|
13
|
+
let rankValue;
|
|
14
|
+
if (isErrors) {
|
|
15
|
+
rankValue = countSubtreeErrors(span, spans);
|
|
16
|
+
}
|
|
17
|
+
else if (dimIndex >= 0) {
|
|
18
|
+
rankValue = selfCost[dimIndex] ?? 0;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
rankValue = 0;
|
|
22
|
+
}
|
|
23
|
+
if (rankValue >= minValue) {
|
|
24
|
+
ranked.push({ span, selfCost, rankValue });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
ranked.sort((a, b) => b.rankValue - a.rankValue);
|
|
28
|
+
let dimensionTotal = 0;
|
|
29
|
+
if (isErrors) {
|
|
30
|
+
for (const span of spans) {
|
|
31
|
+
if (span.error)
|
|
32
|
+
dimensionTotal++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (dimIndex >= 0) {
|
|
36
|
+
for (const item of ranked) {
|
|
37
|
+
dimensionTotal += item.selfCost[dimIndex] ?? 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const item of ranked.slice(0, topN)) {
|
|
42
|
+
const frameName = profile.frames[item.span.frame_index]?.name ?? '<unknown>';
|
|
43
|
+
const pctOfTotal = dimensionTotal > 0
|
|
44
|
+
? Math.round((item.rankValue / dimensionTotal) * 10000) / 100
|
|
45
|
+
: 0;
|
|
46
|
+
const source = getSpanSourceLocation(profile, item.span);
|
|
47
|
+
const investigateHint = source?.ref
|
|
48
|
+
? `Read ${source.ref} to understand this function, then call explain_span with span_id '${item.span.id}'`
|
|
49
|
+
: `call explain_span with span_id '${item.span.id}' to see the breakdown`;
|
|
50
|
+
entries.push({
|
|
51
|
+
span_id: item.span.id,
|
|
52
|
+
ancestry: getSpanAncestry(profile, item.span, spanIndex),
|
|
53
|
+
name: frameName,
|
|
54
|
+
source,
|
|
55
|
+
total_cost: valuesToRecord(profile, item.span.values),
|
|
56
|
+
self_cost: valuesToRecord(profile, item.selfCost),
|
|
57
|
+
pct_of_total: pctOfTotal,
|
|
58
|
+
patterns: registry
|
|
59
|
+
? registry.getMatchesForSpan(profile, item.span.id).map((m) => ({ ...m.pattern, span_ids: m.span_ids }))
|
|
60
|
+
: [],
|
|
61
|
+
investigate: investigateHint,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return { dimension: dim, entries };
|
|
65
|
+
}
|
|
66
|
+
function countSubtreeErrors(span, allSpans) {
|
|
67
|
+
let count = span.error ? 1 : 0;
|
|
68
|
+
for (const childId of span.children) {
|
|
69
|
+
const child = allSpans.find((s) => s.id === childId);
|
|
70
|
+
if (child)
|
|
71
|
+
count += countSubtreeErrors(child, allSpans);
|
|
72
|
+
}
|
|
73
|
+
return count;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=hotspots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspots.js","sourceRoot":"","sources":["../../src/analysis/hotspots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Profile, Span } from '../model/types.js';
|
|
2
|
+
export interface TimeRange {
|
|
3
|
+
start_ms: number;
|
|
4
|
+
end_ms: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function getAllSpans(profile: Profile): Span[];
|
|
7
|
+
/** Build a span lookup index for O(1) access by ID. */
|
|
8
|
+
export declare function buildSpanIndex(profile: Profile): Map<string, Span>;
|
|
9
|
+
export declare function getSpanById(profile: Profile, spanId: string, index?: Map<string, Span>): Span | undefined;
|
|
10
|
+
export declare function getSpanAncestry(profile: Profile, span: Span, index?: Map<string, Span>): string[];
|
|
11
|
+
export declare function computeSelfCost(profile: Profile, span: Span, index?: Map<string, Span>): number[];
|
|
12
|
+
export declare function extractKind(frameName: string): string;
|
|
13
|
+
export declare function filterSpansByTimeRange(spans: Span[], range: TimeRange | undefined): Span[];
|
|
14
|
+
/** Source location for LSP navigation. Format: "file:line" when available. */
|
|
15
|
+
export interface SourceLocation {
|
|
16
|
+
file?: string;
|
|
17
|
+
line?: number;
|
|
18
|
+
/** Pre-formatted for LLM navigation: "file:line" or undefined if no source info. */
|
|
19
|
+
ref?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Extract source location from a frame, formatted for LSP navigation. */
|
|
22
|
+
export declare function getSourceLocation(profile: Profile, frameIndex: number): SourceLocation | undefined;
|
|
23
|
+
/** Get source location for a span's frame. */
|
|
24
|
+
export declare function getSpanSourceLocation(profile: Profile, span: Span): SourceLocation | undefined;
|
|
25
|
+
export declare function valuesToRecord(profile: Profile, values: number[]): Record<string, number>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function getAllSpans(profile) {
|
|
2
|
+
const spans = [];
|
|
3
|
+
for (const lane of profile.lanes) {
|
|
4
|
+
for (const span of lane.spans) {
|
|
5
|
+
spans.push(span);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return spans;
|
|
9
|
+
}
|
|
10
|
+
/** Build a span lookup index for O(1) access by ID. */
|
|
11
|
+
export function buildSpanIndex(profile) {
|
|
12
|
+
const index = new Map();
|
|
13
|
+
for (const lane of profile.lanes) {
|
|
14
|
+
for (const span of lane.spans) {
|
|
15
|
+
index.set(span.id, span);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return index;
|
|
19
|
+
}
|
|
20
|
+
export function getSpanById(profile, spanId, index) {
|
|
21
|
+
if (index)
|
|
22
|
+
return index.get(spanId);
|
|
23
|
+
for (const lane of profile.lanes) {
|
|
24
|
+
const span = lane.spans.find((s) => s.id === spanId);
|
|
25
|
+
if (span)
|
|
26
|
+
return span;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
export function getSpanAncestry(profile, span, index) {
|
|
31
|
+
const chain = [];
|
|
32
|
+
let current = span;
|
|
33
|
+
while (current) {
|
|
34
|
+
const frameName = profile.frames[current.frame_index]?.name;
|
|
35
|
+
chain.push(frameName ?? `<unknown frame ${current.frame_index}>`);
|
|
36
|
+
if (current.parent_id) {
|
|
37
|
+
current = getSpanById(profile, current.parent_id, index);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
current = undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
chain.reverse();
|
|
44
|
+
return chain;
|
|
45
|
+
}
|
|
46
|
+
export function computeSelfCost(profile, span, index) {
|
|
47
|
+
if (span.children.length === 0) {
|
|
48
|
+
return [...span.values];
|
|
49
|
+
}
|
|
50
|
+
const selfCost = [...span.values];
|
|
51
|
+
for (const childId of span.children) {
|
|
52
|
+
const child = getSpanById(profile, childId, index);
|
|
53
|
+
if (!child)
|
|
54
|
+
continue;
|
|
55
|
+
for (let i = 0; i < selfCost.length; i++) {
|
|
56
|
+
selfCost[i] -= child.values[i] ?? 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < selfCost.length; i++) {
|
|
60
|
+
if (selfCost[i] < 0)
|
|
61
|
+
selfCost[i] = 0;
|
|
62
|
+
}
|
|
63
|
+
return selfCost;
|
|
64
|
+
}
|
|
65
|
+
export function extractKind(frameName) {
|
|
66
|
+
const colonIdx = frameName.indexOf(':');
|
|
67
|
+
return colonIdx >= 0 ? frameName.substring(0, colonIdx) : frameName;
|
|
68
|
+
}
|
|
69
|
+
export function filterSpansByTimeRange(spans, range) {
|
|
70
|
+
if (!range)
|
|
71
|
+
return spans;
|
|
72
|
+
return spans.filter((s) => s.end_time >= range.start_ms && s.start_time <= range.end_ms);
|
|
73
|
+
}
|
|
74
|
+
/** Extract source location from a frame, formatted for LSP navigation. */
|
|
75
|
+
export function getSourceLocation(profile, frameIndex) {
|
|
76
|
+
const frame = profile.frames[frameIndex];
|
|
77
|
+
if (!frame)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (!frame.file)
|
|
80
|
+
return undefined;
|
|
81
|
+
const loc = { file: frame.file };
|
|
82
|
+
if (frame.line != null) {
|
|
83
|
+
loc.line = frame.line;
|
|
84
|
+
loc.ref = `${frame.file}:${frame.line}`;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
loc.ref = frame.file;
|
|
88
|
+
}
|
|
89
|
+
return loc;
|
|
90
|
+
}
|
|
91
|
+
/** Get source location for a span's frame. */
|
|
92
|
+
export function getSpanSourceLocation(profile, span) {
|
|
93
|
+
return getSourceLocation(profile, span.frame_index);
|
|
94
|
+
}
|
|
95
|
+
export function valuesToRecord(profile, values) {
|
|
96
|
+
const record = {};
|
|
97
|
+
for (let i = 0; i < profile.value_types.length; i++) {
|
|
98
|
+
record[profile.value_types[i].key] = values[i] ?? 0;
|
|
99
|
+
}
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/analysis/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Profile } from '../model/types.js';
|
|
2
|
+
export interface SpinpathsInput {
|
|
3
|
+
min_wall_ms?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface SpinpathEntry {
|
|
6
|
+
span_id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
ancestry: string[];
|
|
9
|
+
wall_ms: number;
|
|
10
|
+
output_produced: Record<string, number>;
|
|
11
|
+
efficiency_ratio: number;
|
|
12
|
+
recommendation: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SpinpathsResult {
|
|
15
|
+
entries: SpinpathEntry[];
|
|
16
|
+
}
|
|
17
|
+
export declare function findSpinpaths(profile: Profile, input: SpinpathsInput): SpinpathsResult;
|