perf-skill 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +414 -0
- package/SKILL.md +238 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +353 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/options.d.ts +37 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +54 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/convert/converter.d.ts +39 -0
- package/dist/convert/converter.d.ts.map +1 -0
- package/dist/convert/converter.js +99 -0
- package/dist/convert/converter.js.map +1 -0
- package/dist/convert/extract.d.ts +32 -0
- package/dist/convert/extract.d.ts.map +1 -0
- package/dist/convert/extract.js +235 -0
- package/dist/convert/extract.js.map +1 -0
- package/dist/convert/index.d.ts +7 -0
- package/dist/convert/index.d.ts.map +1 -0
- package/dist/convert/index.js +7 -0
- package/dist/convert/index.js.map +1 -0
- package/dist/convert/sanitize.d.ts +60 -0
- package/dist/convert/sanitize.d.ts.map +1 -0
- package/dist/convert/sanitize.js +169 -0
- package/dist/convert/sanitize.js.map +1 -0
- package/dist/diff/engine.d.ts +76 -0
- package/dist/diff/engine.d.ts.map +1 -0
- package/dist/diff/engine.js +386 -0
- package/dist/diff/engine.js.map +1 -0
- package/dist/diff/index.d.ts +6 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +6 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/diff/markdown.d.ts +16 -0
- package/dist/diff/markdown.d.ts.map +1 -0
- package/dist/diff/markdown.js +342 -0
- package/dist/diff/markdown.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +247 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +39 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +270 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/index.d.ts +8 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/prompt.d.ts +32 -0
- package/dist/llm/prompt.d.ts.map +1 -0
- package/dist/llm/prompt.js +146 -0
- package/dist/llm/prompt.js.map +1 -0
- package/dist/llm/schema.d.ts +150 -0
- package/dist/llm/schema.d.ts.map +1 -0
- package/dist/llm/schema.js +131 -0
- package/dist/llm/schema.js.map +1 -0
- package/dist/llm/validate.d.ts +33 -0
- package/dist/llm/validate.d.ts.map +1 -0
- package/dist/llm/validate.js +241 -0
- package/dist/llm/validate.js.map +1 -0
- package/dist/profile/duration.d.ts +2 -0
- package/dist/profile/duration.d.ts.map +1 -0
- package/dist/profile/duration.js +24 -0
- package/dist/profile/duration.js.map +1 -0
- package/dist/profile/preload.d.ts +2 -0
- package/dist/profile/preload.d.ts.map +1 -0
- package/dist/profile/preload.js +100 -0
- package/dist/profile/preload.js.map +1 -0
- package/dist/profile/runner.d.ts +22 -0
- package/dist/profile/runner.d.ts.map +1 -0
- package/dist/profile/runner.js +88 -0
- package/dist/profile/runner.js.map +1 -0
- package/dist/server/http.d.ts +27 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +285 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/utils.d.ts +15 -0
- package/dist/server/utils.d.ts.map +1 -0
- package/dist/server/utils.js +71 -0
- package/dist/server/utils.js.map +1 -0
- package/dist/skill/handler.d.ts +77 -0
- package/dist/skill/handler.d.ts.map +1 -0
- package/dist/skill/handler.js +91 -0
- package/dist/skill/handler.js.map +1 -0
- package/dist/skill/index.d.ts +6 -0
- package/dist/skill/index.d.ts.map +1 -0
- package/dist/skill/index.js +6 -0
- package/dist/skill/index.js.map +1 -0
- package/dist/skill/manifest.d.ts +17 -0
- package/dist/skill/manifest.d.ts.map +1 -0
- package/dist/skill/manifest.js +231 -0
- package/dist/skill/manifest.js.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/fs.d.ts +52 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +106 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/limits.d.ts +38 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +86 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +82 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +70 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 skillsland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# perf-skill
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](http://badge.fury.io/js/perf-skill)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
AI‑assisted pprof toolkit for CPU/heap profiles: convert .pb.gz/.pprof to structured Markdown, compare profiles for regressions, and optionally generate evidence‑backed recommendations.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Convert**: Transform pprof profiles to structured Markdown
|
|
12
|
+
- **Analyze**: Get AI-powered optimization recommendations
|
|
13
|
+
- **Diff**: Compare two profiles to find regressions
|
|
14
|
+
- **Multi-format**: Library, CLI, and HTTP API
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install perf-skill
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or run directly with npx:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx perf-skill analyze profile.pb.gz
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### CLI Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Convert profile to markdown (fast, no LLM)
|
|
34
|
+
perf-skill convert cpu.pb.gz -o report.md
|
|
35
|
+
|
|
36
|
+
# Full analysis with AI recommendations
|
|
37
|
+
perf-skill analyze cpu.pb.gz --mode analyze
|
|
38
|
+
|
|
39
|
+
# Profile a Node entry (CPU, 10s) and analyze
|
|
40
|
+
perf-skill run slow.mjs --duration 10s
|
|
41
|
+
|
|
42
|
+
# CPU + Heap profiling (separate reports)
|
|
43
|
+
perf-skill run slow.mjs --heap --output cpu.md --heap-output heap.md
|
|
44
|
+
|
|
45
|
+
# Compare two profiles
|
|
46
|
+
perf-skill diff base.pb.gz current.pb.gz -o diff.md
|
|
47
|
+
|
|
48
|
+
# Start HTTP server
|
|
49
|
+
perf-skill server --port 3000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Programmatic Usage
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { analyze, diff } from "perf-skill-skill";
|
|
56
|
+
|
|
57
|
+
// Convert only (no LLM)
|
|
58
|
+
const result = await analyze("cpu.pb.gz", { mode: "convert-only" });
|
|
59
|
+
console.log(result.markdown);
|
|
60
|
+
console.log(result.hotspots);
|
|
61
|
+
|
|
62
|
+
// Full analysis with AI recommendations
|
|
63
|
+
const fullResult = await analyze("cpu.pb.gz", {
|
|
64
|
+
mode: "analyze",
|
|
65
|
+
context: {
|
|
66
|
+
serviceName: "api-server",
|
|
67
|
+
scenario: "load test",
|
|
68
|
+
targetSLO: "p99 < 100ms",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
console.log(fullResult.recommendations);
|
|
72
|
+
|
|
73
|
+
// Compare two profiles
|
|
74
|
+
const diffResult = await diff("base.pb.gz", "current.pb.gz", {
|
|
75
|
+
normalize: "scale-to-base-total",
|
|
76
|
+
});
|
|
77
|
+
console.log(diffResult.regressions);
|
|
78
|
+
console.log(diffResult.improvements);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### HTTP API
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Start server
|
|
85
|
+
perf-skill server
|
|
86
|
+
|
|
87
|
+
# Start server with security overrides
|
|
88
|
+
perf-skill server --no-cors --no-helmet --rate-limit --rate-limit-max 120 --rate-limit-window-ms 60000
|
|
89
|
+
|
|
90
|
+
# Analyze profile
|
|
91
|
+
curl -X POST http://localhost:3000/v1/pprof/analyze \
|
|
92
|
+
-F "file=@cpu.pb.gz"
|
|
93
|
+
|
|
94
|
+
# Compare profiles
|
|
95
|
+
curl -X POST http://localhost:3000/v1/pprof/diff \
|
|
96
|
+
-F "base=@base.pb.gz" \
|
|
97
|
+
-F "current=@current.pb.gz"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## CLI Options
|
|
101
|
+
|
|
102
|
+
### `perf-skill analyze <profile.pb.gz>`
|
|
103
|
+
|
|
104
|
+
| Option | Description | Default |
|
|
105
|
+
| ---------------------- | ------------------------------------------------ | ---------- |
|
|
106
|
+
| `-f, --format` | Output format: `summary`, `detailed`, `adaptive` | `adaptive` |
|
|
107
|
+
| `-t, --type` | Profile type: `cpu`, `heap`, `auto` | `auto` |
|
|
108
|
+
| `-o, --output` | Output markdown file | stdout |
|
|
109
|
+
| `-j, --json` | Output JSON results file | - |
|
|
110
|
+
| `-m, --mode` | `convert-only` or `analyze` | `analyze` |
|
|
111
|
+
| `-s, --source-dir` | Source directory for code context | - |
|
|
112
|
+
| `--max-hotspots` | Maximum hotspots to show | `10` |
|
|
113
|
+
| `--llm-provider` | LLM provider: `openai`, `anthropic`, etc. | `openai` |
|
|
114
|
+
| `--llm-model` | LLM model name | `gpt-5.2` |
|
|
115
|
+
| `--service` | Service name for context | - |
|
|
116
|
+
| `--scenario` | Scenario description | - |
|
|
117
|
+
| `--redact/--no-redact` | Redact sensitive information | `true` |
|
|
118
|
+
|
|
119
|
+
### `perf-skill run <entry> [entryArgs...]`
|
|
120
|
+
|
|
121
|
+
| Option | Description | Default |
|
|
122
|
+
| ----------------------- | ------------------------------------------------ | --------------------------- |
|
|
123
|
+
| `-d, --duration` | CPU profile duration (e.g. `10s`, `5000ms`) | `10s` |
|
|
124
|
+
| `--profile-out` | Profile output file | `cpu.pb.gz` |
|
|
125
|
+
| `--heap` | Also capture a heap profile | `false` |
|
|
126
|
+
| `--heap-profile-out` | Heap profile output file | `heap.pb.gz` |
|
|
127
|
+
| `--heap-interval-bytes` | Heap sampling interval (bytes) | `524288` |
|
|
128
|
+
| `--heap-stack-depth` | Heap sampling stack depth | `64` |
|
|
129
|
+
| `--heap-output` | Heap markdown output file | `heap.md` (if heap enabled) |
|
|
130
|
+
| `--heap-json` | Heap JSON output file | - |
|
|
131
|
+
| `-f, --format` | Output format: `summary`, `detailed`, `adaptive` | `adaptive` |
|
|
132
|
+
| `-t, --type` | Profile type: `cpu`, `heap`, `auto` | `auto` |
|
|
133
|
+
| `-o, --output` | Output markdown file | stdout |
|
|
134
|
+
| `-j, --json` | Output JSON results file | - |
|
|
135
|
+
| `-m, --mode` | `convert-only` or `analyze` | `analyze` |
|
|
136
|
+
| `-s, --source-dir` | Source directory for code context | - |
|
|
137
|
+
| `--max-hotspots` | Maximum hotspots to show | `10` |
|
|
138
|
+
| `--llm-provider` | LLM provider: `openai`, `anthropic`, etc. | `openai` |
|
|
139
|
+
| `--llm-model` | LLM model name | `gpt-5.2` |
|
|
140
|
+
| `--service` | Service name for context | - |
|
|
141
|
+
| `--scenario` | Scenario description | - |
|
|
142
|
+
| `--redact/--no-redact` | Redact sensitive information | `true` |
|
|
143
|
+
|
|
144
|
+
When `--heap` is enabled and `--output` is omitted, `perf-skill` writes `cpu.md` and `heap.md` instead of printing to stdout.
|
|
145
|
+
|
|
146
|
+
### `perf-skill profile <entry> [entryArgs...]`
|
|
147
|
+
|
|
148
|
+
| Option | Description | Default |
|
|
149
|
+
| ----------------------- | ------------------------------------------- | ------------ |
|
|
150
|
+
| `-d, --duration` | CPU profile duration (e.g. `10s`, `5000ms`) | `10s` |
|
|
151
|
+
| `-o, --output` | Profile output file | `cpu.pb.gz` |
|
|
152
|
+
| `--heap` | Also capture a heap profile | `false` |
|
|
153
|
+
| `--heap-profile-out` | Heap profile output file | `heap.pb.gz` |
|
|
154
|
+
| `--heap-interval-bytes` | Heap sampling interval (bytes) | `524288` |
|
|
155
|
+
| `--heap-stack-depth` | Heap sampling stack depth | `64` |
|
|
156
|
+
|
|
157
|
+
### `perf-skill diff <base.pb.gz> <current.pb.gz>`
|
|
158
|
+
|
|
159
|
+
| Option | Description | Default |
|
|
160
|
+
| -------------------------- | ------------------------------------------------ | --------------------- |
|
|
161
|
+
| `-f, --format` | `diff-summary`, `diff-detailed`, `diff-adaptive` | `diff-adaptive` |
|
|
162
|
+
| `-n, --normalize` | `none`, `scale-to-base-total`, `per-second` | `scale-to-base-total` |
|
|
163
|
+
| `--max-regressions` | Maximum regressions to show | `10` |
|
|
164
|
+
| `--max-improvements` | Maximum improvements to show | `5` |
|
|
165
|
+
| `--max-decompressed-bytes` | Maximum decompressed profile size (bytes) | - |
|
|
166
|
+
|
|
167
|
+
## Output Formats
|
|
168
|
+
|
|
169
|
+
### Summary
|
|
170
|
+
|
|
171
|
+
Compact format for quick triage:
|
|
172
|
+
|
|
173
|
+
```markdown
|
|
174
|
+
# PPROF Analysis: CPU
|
|
175
|
+
|
|
176
|
+
**Duration:** 30s | **Samples:** 45,231
|
|
177
|
+
|
|
178
|
+
## Top Hotspots
|
|
179
|
+
|
|
180
|
+
| Rank | Function | Self% | Cum% | Location |
|
|
181
|
+
| ---- | ---------------- | ----- | ----- | ---------------- |
|
|
182
|
+
| 1 | `JSON.parse` | 23.4% | 23.4% | `<native>` |
|
|
183
|
+
| 2 | `processRequest` | 15.2% | 67.8% | `handler.ts:142` |
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Detailed
|
|
187
|
+
|
|
188
|
+
Full context with call trees and source code.
|
|
189
|
+
|
|
190
|
+
### Adaptive (Default)
|
|
191
|
+
|
|
192
|
+
Summary with drill-down sections and anchor links for navigation.
|
|
193
|
+
|
|
194
|
+
## AI Recommendations
|
|
195
|
+
|
|
196
|
+
When using `--mode analyze`, the tool generates structured recommendations:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
interface Recommendation {
|
|
200
|
+
title: string; // Short action title
|
|
201
|
+
rationale: string; // Evidence-based explanation
|
|
202
|
+
steps: string[]; // Concrete action steps
|
|
203
|
+
expectedImpact: "high" | "medium" | "low";
|
|
204
|
+
risk: "high" | "medium" | "low";
|
|
205
|
+
confidence: number; // 0-1 based on evidence quality
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
All recommendations must reference evidence from the profile (function names, percentages, locations).
|
|
210
|
+
|
|
211
|
+
## Profile Diff
|
|
212
|
+
|
|
213
|
+
Compare two profiles to identify performance regressions:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const result = await diff("base.pb.gz", "current.pb.gz");
|
|
217
|
+
|
|
218
|
+
// Top regressions (got slower)
|
|
219
|
+
for (const reg of result.regressions) {
|
|
220
|
+
console.log(`${reg.function}: +${reg.deltaSelfPct.toFixed(1)}%`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Top improvements (got faster)
|
|
224
|
+
for (const imp of result.improvements) {
|
|
225
|
+
console.log(`${imp.function}: ${imp.deltaSelfPct.toFixed(1)}%`);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Normalization Modes
|
|
230
|
+
|
|
231
|
+
- **none**: Direct comparison (current - base)
|
|
232
|
+
- **scale-to-base-total**: Scale current to match base total (compare structure)
|
|
233
|
+
- **per-second**: Normalize by duration (compare rate)
|
|
234
|
+
|
|
235
|
+
## Collecting Profiles
|
|
236
|
+
|
|
237
|
+
### Node.js with @datadog/pprof
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import * as pprof from "@datadog/pprof";
|
|
241
|
+
import { writeFileSync } from "fs";
|
|
242
|
+
import { gzipSync } from "zlib";
|
|
243
|
+
|
|
244
|
+
// CPU profiling
|
|
245
|
+
pprof.time.start({ durationMillis: 30000 });
|
|
246
|
+
// ... run workload ...
|
|
247
|
+
const profile = await pprof.time.stop();
|
|
248
|
+
writeFileSync("cpu.pb.gz", gzipSync(profile.encode()));
|
|
249
|
+
|
|
250
|
+
// Heap profiling
|
|
251
|
+
pprof.heap.start(512 * 1024, 64);
|
|
252
|
+
// ... run workload ...
|
|
253
|
+
const heapProfile = await pprof.heap.profile();
|
|
254
|
+
writeFileSync("heap.pb.gz", gzipSync(heapProfile.encode()));
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Configuration
|
|
258
|
+
|
|
259
|
+
### Environment Variables
|
|
260
|
+
|
|
261
|
+
| Variable | Description |
|
|
262
|
+
| ---------------------- | ----------------------------------------------- |
|
|
263
|
+
| `OPENAI_API_KEY` | OpenAI API key for analysis |
|
|
264
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
265
|
+
| `LLM_PROVIDER` | Default LLM provider |
|
|
266
|
+
| `LLM_MODEL` | Default LLM model |
|
|
267
|
+
| `LLM_BASE_URL` | Custom LLM API endpoint |
|
|
268
|
+
| `LLM_TIMEOUT_MS` | LLM request timeout in ms |
|
|
269
|
+
| `LLM_MAX_RETRIES` | LLM retry count for transient failures |
|
|
270
|
+
| `LLM_RETRY_DELAY_MS` | Base retry delay in ms |
|
|
271
|
+
| `LOG_LEVEL` | Logging level: `debug`, `info`, `warn`, `error` |
|
|
272
|
+
| `LOG_FORMAT` | Log format: `text`, `json` |
|
|
273
|
+
| `CORS_ENABLED` | Enable CORS (`true`/`false`) |
|
|
274
|
+
| `CORS_ORIGIN` | CORS origin(s), comma-separated or `*` |
|
|
275
|
+
| `HELMET_ENABLED` | Enable Helmet (`true`/`false`) |
|
|
276
|
+
| `RATE_LIMIT_ENABLED` | Enable rate limiting (`true`/`false`) |
|
|
277
|
+
| `RATE_LIMIT_MAX` | Rate limit max requests per window |
|
|
278
|
+
| `RATE_LIMIT_WINDOW_MS` | Rate limit window size in ms |
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
export LLM_TIMEOUT_MS=30000
|
|
284
|
+
export LLM_MAX_RETRIES=2
|
|
285
|
+
export LLM_RETRY_DELAY_MS=500
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Resource Limits
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const result = await analyze("large-profile.pb.gz", {
|
|
292
|
+
limits: {
|
|
293
|
+
maxProfileBytes: 100 * 1024 * 1024, // 100MB
|
|
294
|
+
maxDecompressedBytes: 200 * 1024 * 1024, // 200MB uncompressed
|
|
295
|
+
maxMarkdownChars: 500_000, // 500k chars
|
|
296
|
+
maxSourceLinesPerFile: 100, // lines per snippet
|
|
297
|
+
timeoutMs: 120_000, // 2 minutes
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Security
|
|
303
|
+
|
|
304
|
+
### Redaction
|
|
305
|
+
|
|
306
|
+
By default, the tool redacts:
|
|
307
|
+
|
|
308
|
+
- AWS access keys
|
|
309
|
+
- Bearer tokens
|
|
310
|
+
- Private keys
|
|
311
|
+
- API keys and secrets
|
|
312
|
+
- Absolute paths (normalized to relative)
|
|
313
|
+
|
|
314
|
+
Disable with `--no-redact` or `redact: false`.
|
|
315
|
+
|
|
316
|
+
### Server Mode
|
|
317
|
+
|
|
318
|
+
In HTTP server mode:
|
|
319
|
+
|
|
320
|
+
- Source code inclusion is disabled by default
|
|
321
|
+
- File size limits are enforced
|
|
322
|
+
- Only `.pb.gz` files are accepted
|
|
323
|
+
|
|
324
|
+
Security defaults (configurable via env or server options):
|
|
325
|
+
|
|
326
|
+
- CORS enabled (set `CORS_ENABLED=false` to disable)
|
|
327
|
+
- Helmet enabled (set `HELMET_ENABLED=false` to disable)
|
|
328
|
+
- Rate limiting enabled (default 60 req/min, set `RATE_LIMIT_ENABLED=false` or `RATE_LIMIT_MAX=0` to disable)
|
|
329
|
+
|
|
330
|
+
Server CLI flags (override env defaults):
|
|
331
|
+
|
|
332
|
+
- `--cors/--no-cors`
|
|
333
|
+
- `--cors-origin <origin>` (comma-separated or `*`)
|
|
334
|
+
- `--helmet/--no-helmet`
|
|
335
|
+
- `--rate-limit/--no-rate-limit`
|
|
336
|
+
- `--rate-limit-max <n>`
|
|
337
|
+
- `--rate-limit-window-ms <ms>`
|
|
338
|
+
|
|
339
|
+
ServerOptions (programmatic):
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const server = await createServer({
|
|
343
|
+
enableCors: true,
|
|
344
|
+
corsOrigin: "https://example.com",
|
|
345
|
+
enableHelmet: true,
|
|
346
|
+
enableRateLimit: true,
|
|
347
|
+
rateLimitMax: 60,
|
|
348
|
+
rateLimitWindowMs: 60_000,
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Requirements
|
|
353
|
+
|
|
354
|
+
- Node.js >= 22.6.0
|
|
355
|
+
- For AI analysis: API key for OpenAI, Anthropic, or compatible provider
|
|
356
|
+
- CPU profiling uses bundled `@datadog/pprof` (native module) on supported platforms
|
|
357
|
+
|
|
358
|
+
## Architecture
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
perf-skill/
|
|
362
|
+
├── src/
|
|
363
|
+
│ ├── index.ts # Main exports
|
|
364
|
+
│ ├── types.ts # TypeScript types
|
|
365
|
+
│ ├── convert/ # pprof-to-md wrapper
|
|
366
|
+
│ │ ├── converter.ts # Core conversion
|
|
367
|
+
│ │ ├── sanitize.ts # Redaction & limits
|
|
368
|
+
│ │ └── extract.ts # Hotspot parsing
|
|
369
|
+
│ ├── llm/ # LLM integration
|
|
370
|
+
│ │ ├── client.ts # OpenAI/Anthropic clients
|
|
371
|
+
│ │ ├── prompt.ts # Prompt templates
|
|
372
|
+
│ │ ├── schema.ts # Zod schemas
|
|
373
|
+
│ │ └── validate.ts # Output validation
|
|
374
|
+
│ ├── diff/ # Profile comparison
|
|
375
|
+
│ │ ├── engine.ts # Pure TS diff engine
|
|
376
|
+
│ │ └── markdown.ts # Diff report generation
|
|
377
|
+
│ ├── cli/ # CLI implementation
|
|
378
|
+
│ ├── server/ # HTTP API
|
|
379
|
+
│ └── skill/ # Agent integration
|
|
380
|
+
│ ├── handler.ts # Skill handlers
|
|
381
|
+
│ └── manifest.ts # Tool schema
|
|
382
|
+
├── SKILL.md # Claude Code skill file
|
|
383
|
+
└── package.json
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## API Reference
|
|
387
|
+
|
|
388
|
+
### `analyze(profile, options): Promise<AnalyzeResult>`
|
|
389
|
+
|
|
390
|
+
Analyze a single profile.
|
|
391
|
+
|
|
392
|
+
### `diff(baseProfile, currentProfile, options): Promise<DiffResult>`
|
|
393
|
+
|
|
394
|
+
Compare two profiles.
|
|
395
|
+
|
|
396
|
+
### `convertProfileToMarkdown(buffer, options): Promise<ConvertResult>`
|
|
397
|
+
|
|
398
|
+
Low-level conversion function.
|
|
399
|
+
|
|
400
|
+
### `createLLMClient(config): LLMClient`
|
|
401
|
+
|
|
402
|
+
Create an LLM client for custom integrations.
|
|
403
|
+
|
|
404
|
+
## License
|
|
405
|
+
|
|
406
|
+
MIT
|
|
407
|
+
|
|
408
|
+
### Updating Prompt Fixtures
|
|
409
|
+
|
|
410
|
+
If you change prompt templates and need to refresh fixtures:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
npm run update-prompts
|
|
414
|
+
```
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-skill
|
|
3
|
+
description: Analyze pprof CPU and heap profiles with AI-powered recommendations, or profile a Node.js entry file and generate a full report. Use when the user has a .pb.gz profile file or wants to profile a Node.js script and get optimization suggestions.
|
|
4
|
+
argument-hint: [profile.pb.gz|entry.js] [options]
|
|
5
|
+
allowed-tools: Bash(node *), Bash(npx *), Read, Glob
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# perf-skill: Performance Profile Analysis Skill
|
|
9
|
+
|
|
10
|
+
Analyze pprof profiles (.pb.gz) to identify performance bottlenecks and generate actionable optimization recommendations.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
### Analyze a Single Profile
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Quick conversion to markdown (no LLM)
|
|
18
|
+
npx perf-skill convert $ARGUMENTS
|
|
19
|
+
|
|
20
|
+
# Full analysis with AI recommendations
|
|
21
|
+
npx perf-skill analyze $ARGUMENTS --mode analyze
|
|
22
|
+
|
|
23
|
+
# Output to file
|
|
24
|
+
npx perf-skill analyze $ARGUMENTS -o analysis.md -j results.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Profile a Node Entry and Analyze (One Command)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Default CPU profiling (10s) + analysis
|
|
31
|
+
npx perf-skill run slow.mjs
|
|
32
|
+
|
|
33
|
+
# Customize duration and output
|
|
34
|
+
npx perf-skill run slow.mjs --duration 10s -o analysis.md
|
|
35
|
+
|
|
36
|
+
# CPU + Heap profiling (separate reports)
|
|
37
|
+
npx perf-skill run slow.mjs --heap --output cpu.md --heap-output heap.md
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Compare Two Profiles (Diff)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Compare base vs current profile
|
|
44
|
+
npx perf-skill diff base.pb.gz current.pb.gz
|
|
45
|
+
|
|
46
|
+
# With specific format
|
|
47
|
+
npx perf-skill diff base.pb.gz current.pb.gz --format diff-detailed
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## When to Use This Skill
|
|
51
|
+
|
|
52
|
+
Use `perf-skill` when:
|
|
53
|
+
- User provides a `.pb.gz` pprof profile file
|
|
54
|
+
- User provides a Node.js entry file (`.js/.mjs/.cjs`) and wants an end-to-end performance report
|
|
55
|
+
- User asks "why is my app slow?" with a profile attached
|
|
56
|
+
- User wants to compare performance before/after a change
|
|
57
|
+
- User needs help interpreting profile data
|
|
58
|
+
- User asks about CPU or memory hotspots
|
|
59
|
+
|
|
60
|
+
## Routing
|
|
61
|
+
|
|
62
|
+
- If the argument ends with `.pb`, `.pb.gz`, or `.pprof`, run `analyze` or `diff` directly.
|
|
63
|
+
- If the argument ends with `.js`, `.mjs`, or `.cjs`, run `perf-skill run <entry>` to generate a CPU profile and analyze it.
|
|
64
|
+
- If the user asks for both CPU and heap (including misspellings like "heep" or terms like "memory"/"heap"), add `--heap` and save separate reports (`--output` + `--heap-output`).
|
|
65
|
+
|
|
66
|
+
## Available Commands
|
|
67
|
+
|
|
68
|
+
### `analyze` (default)
|
|
69
|
+
|
|
70
|
+
Analyze a single profile with optional AI recommendations.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
perf-skill analyze profile.pb.gz [options]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Options:**
|
|
77
|
+
- `-f, --format <format>`: Output format (`summary`, `detailed`, `adaptive`)
|
|
78
|
+
- `-t, --type <type>`: Profile type (`cpu`, `heap`, `auto`)
|
|
79
|
+
- `-o, --output <file>`: Save markdown to file
|
|
80
|
+
- `-j, --json <file>`: Save JSON results to file
|
|
81
|
+
- `-m, --mode <mode>`: `convert-only` (no LLM) or `analyze` (with LLM)
|
|
82
|
+
- `-s, --source-dir <path>`: Include source code context
|
|
83
|
+
- `--max-hotspots <n>`: Limit hotspots shown (default: 10)
|
|
84
|
+
- `--service <name>`: Service name for context
|
|
85
|
+
- `--scenario <desc>`: Scenario description
|
|
86
|
+
|
|
87
|
+
### `diff`
|
|
88
|
+
|
|
89
|
+
Compare two profiles to find performance regressions.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
perf-skill diff base.pb.gz current.pb.gz [options]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Options:**
|
|
96
|
+
- `-f, --format <format>`: `diff-summary`, `diff-detailed`, `diff-adaptive`
|
|
97
|
+
- `-n, --normalize <mode>`: `none`, `scale-to-base-total`, `per-second`
|
|
98
|
+
- `--max-regressions <n>`: Limit regressions shown (default: 10)
|
|
99
|
+
- `--max-improvements <n>`: Limit improvements shown (default: 5)
|
|
100
|
+
|
|
101
|
+
### `convert`
|
|
102
|
+
|
|
103
|
+
Convert profile to markdown without AI analysis (faster).
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
perf-skill convert profile.pb.gz -o report.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `run`
|
|
110
|
+
|
|
111
|
+
Profile a Node entry file (CPU) and analyze the resulting profile.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
perf-skill run entry.js [entryArgs...]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Options:**
|
|
118
|
+
- `-d, --duration <duration>`: Profiling duration (default: `10s`)
|
|
119
|
+
- `--profile-out <file>`: Profile output file (default: `cpu.pb.gz`)
|
|
120
|
+
- `--heap`: Also capture heap profile
|
|
121
|
+
- `--heap-profile-out <file>`: Heap profile output file (default: `heap.pb.gz`)
|
|
122
|
+
- `--heap-output <file>`: Heap markdown output file (default: derived from CPU output or `heap.md`)
|
|
123
|
+
- `--heap-json <file>`: Heap JSON output file (optional)
|
|
124
|
+
- `--heap-interval-bytes <n>`: Heap sampling interval (bytes, default: `524288`)
|
|
125
|
+
- `--heap-stack-depth <n>`: Heap sampling stack depth (default: `64`)
|
|
126
|
+
- All `analyze` options (`--format`, `--mode`, `--output`, etc.)
|
|
127
|
+
|
|
128
|
+
When `--heap` is enabled and `--output` is omitted, the CLI writes `cpu.md` and `heap.md` instead of printing to stdout.
|
|
129
|
+
|
|
130
|
+
### `profile`
|
|
131
|
+
|
|
132
|
+
Generate a CPU profile for a Node entry file without analysis.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
perf-skill profile entry.js [entryArgs...]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Options:**
|
|
139
|
+
- `-d, --duration <duration>`: Profiling duration (default: `10s`)
|
|
140
|
+
- `-o, --output <file>`: Profile output file (default: `cpu.pb.gz`)
|
|
141
|
+
- `--heap`: Also capture heap profile
|
|
142
|
+
- `--heap-profile-out <file>`: Heap profile output file (default: `heap.pb.gz`)
|
|
143
|
+
- `--heap-interval-bytes <n>`: Heap sampling interval (bytes, default: `524288`)
|
|
144
|
+
- `--heap-stack-depth <n>`: Heap sampling stack depth (default: `64`)
|
|
145
|
+
|
|
146
|
+
## Programmatic Usage
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { analyze, diff } from 'perf-skill';
|
|
150
|
+
|
|
151
|
+
// Analyze with AI
|
|
152
|
+
const result = await analyze('cpu.pb.gz', {
|
|
153
|
+
mode: 'analyze',
|
|
154
|
+
context: { serviceName: 'api-server' }
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(result.markdown);
|
|
158
|
+
console.log(result.recommendations);
|
|
159
|
+
|
|
160
|
+
// Compare profiles
|
|
161
|
+
const diffResult = await diff('base.pb.gz', 'current.pb.gz');
|
|
162
|
+
console.log(diffResult.regressions);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Understanding the Output
|
|
166
|
+
|
|
167
|
+
### Hotspots
|
|
168
|
+
|
|
169
|
+
Functions ranked by CPU time or memory allocation:
|
|
170
|
+
- **Self%**: Time spent in this function only
|
|
171
|
+
- **Cum%**: Time spent in this function + its callees
|
|
172
|
+
- **Location**: Source file and line number
|
|
173
|
+
|
|
174
|
+
### Recommendations (AI mode)
|
|
175
|
+
|
|
176
|
+
Each recommendation includes:
|
|
177
|
+
- **Title**: What to do
|
|
178
|
+
- **Rationale**: Why (with evidence from the profile)
|
|
179
|
+
- **Steps**: How to implement
|
|
180
|
+
- **Impact/Risk/Confidence**: Prioritization info
|
|
181
|
+
|
|
182
|
+
### Diff Analysis
|
|
183
|
+
|
|
184
|
+
When comparing profiles:
|
|
185
|
+
- **Regressions**: Functions that got slower
|
|
186
|
+
- **Improvements**: Functions that got faster
|
|
187
|
+
- **Call Path Δ**: Which paths contributed to the change
|
|
188
|
+
|
|
189
|
+
## Collecting Profiles
|
|
190
|
+
|
|
191
|
+
### Node.js with @datadog/pprof
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import * as pprof from '@datadog/pprof';
|
|
195
|
+
import { writeFileSync } from 'fs';
|
|
196
|
+
import { gzipSync } from 'zlib';
|
|
197
|
+
|
|
198
|
+
// CPU profiling (30 seconds)
|
|
199
|
+
pprof.time.start({ durationMillis: 30000 });
|
|
200
|
+
// ... run your workload ...
|
|
201
|
+
const profile = await pprof.time.stop();
|
|
202
|
+
writeFileSync('cpu.pb.gz', gzipSync(profile.encode()));
|
|
203
|
+
|
|
204
|
+
// Heap profiling
|
|
205
|
+
pprof.heap.start(512 * 1024, 64);
|
|
206
|
+
// ... run your workload ...
|
|
207
|
+
const heapProfile = await pprof.heap.profile();
|
|
208
|
+
writeFileSync('heap.pb.gz', gzipSync(heapProfile.encode()));
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Tips
|
|
212
|
+
|
|
213
|
+
1. **Start with `--format summary`** for quick triage
|
|
214
|
+
2. **Use `--mode convert-only`** when you just need the markdown
|
|
215
|
+
3. **Compare profiles** to find the cause of regressions
|
|
216
|
+
4. **Provide context** (`--service`, `--scenario`) for better AI recommendations
|
|
217
|
+
5. **High self%** = function is doing expensive work directly
|
|
218
|
+
6. **High cum%** = function is on a hot path (may be calling slow functions)
|
|
219
|
+
|
|
220
|
+
## Requirements
|
|
221
|
+
|
|
222
|
+
- Node.js >= 22.6.0
|
|
223
|
+
- For AI analysis: Set `OPENAI_API_KEY` or configure LLM provider
|
|
224
|
+
- CPU profiling uses bundled `@datadog/pprof` (native module); supported on common platforms
|
|
225
|
+
|
|
226
|
+
## Troubleshooting
|
|
227
|
+
|
|
228
|
+
### "No symbols found"
|
|
229
|
+
The profile may be from a production build without debug info. The analysis still works but function names may be mangled.
|
|
230
|
+
|
|
231
|
+
### "Native code dominates"
|
|
232
|
+
If native functions (like `JSON.parse`) are top hotspots, consider:
|
|
233
|
+
- Using streaming parsers for large JSON
|
|
234
|
+
- Caching parsed results
|
|
235
|
+
- Using binary formats like Protocol Buffers
|
|
236
|
+
|
|
237
|
+
### "Low sample count"
|
|
238
|
+
Run the profiler longer to get more samples for statistical significance.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AACA;;GAEG"}
|