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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +414 -0
  3. package/SKILL.md +238 -0
  4. package/dist/cli/main.d.ts +6 -0
  5. package/dist/cli/main.d.ts.map +1 -0
  6. package/dist/cli/main.js +353 -0
  7. package/dist/cli/main.js.map +1 -0
  8. package/dist/cli/options.d.ts +37 -0
  9. package/dist/cli/options.d.ts.map +1 -0
  10. package/dist/cli/options.js +54 -0
  11. package/dist/cli/options.js.map +1 -0
  12. package/dist/convert/converter.d.ts +39 -0
  13. package/dist/convert/converter.d.ts.map +1 -0
  14. package/dist/convert/converter.js +99 -0
  15. package/dist/convert/converter.js.map +1 -0
  16. package/dist/convert/extract.d.ts +32 -0
  17. package/dist/convert/extract.d.ts.map +1 -0
  18. package/dist/convert/extract.js +235 -0
  19. package/dist/convert/extract.js.map +1 -0
  20. package/dist/convert/index.d.ts +7 -0
  21. package/dist/convert/index.d.ts.map +1 -0
  22. package/dist/convert/index.js +7 -0
  23. package/dist/convert/index.js.map +1 -0
  24. package/dist/convert/sanitize.d.ts +60 -0
  25. package/dist/convert/sanitize.d.ts.map +1 -0
  26. package/dist/convert/sanitize.js +169 -0
  27. package/dist/convert/sanitize.js.map +1 -0
  28. package/dist/diff/engine.d.ts +76 -0
  29. package/dist/diff/engine.d.ts.map +1 -0
  30. package/dist/diff/engine.js +386 -0
  31. package/dist/diff/engine.js.map +1 -0
  32. package/dist/diff/index.d.ts +6 -0
  33. package/dist/diff/index.d.ts.map +1 -0
  34. package/dist/diff/index.js +6 -0
  35. package/dist/diff/index.js.map +1 -0
  36. package/dist/diff/markdown.d.ts +16 -0
  37. package/dist/diff/markdown.d.ts.map +1 -0
  38. package/dist/diff/markdown.js +342 -0
  39. package/dist/diff/markdown.js.map +1 -0
  40. package/dist/index.d.ts +52 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +247 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/llm/client.d.ts +39 -0
  45. package/dist/llm/client.d.ts.map +1 -0
  46. package/dist/llm/client.js +270 -0
  47. package/dist/llm/client.js.map +1 -0
  48. package/dist/llm/index.d.ts +8 -0
  49. package/dist/llm/index.d.ts.map +1 -0
  50. package/dist/llm/index.js +8 -0
  51. package/dist/llm/index.js.map +1 -0
  52. package/dist/llm/prompt.d.ts +32 -0
  53. package/dist/llm/prompt.d.ts.map +1 -0
  54. package/dist/llm/prompt.js +146 -0
  55. package/dist/llm/prompt.js.map +1 -0
  56. package/dist/llm/schema.d.ts +150 -0
  57. package/dist/llm/schema.d.ts.map +1 -0
  58. package/dist/llm/schema.js +131 -0
  59. package/dist/llm/schema.js.map +1 -0
  60. package/dist/llm/validate.d.ts +33 -0
  61. package/dist/llm/validate.d.ts.map +1 -0
  62. package/dist/llm/validate.js +241 -0
  63. package/dist/llm/validate.js.map +1 -0
  64. package/dist/profile/duration.d.ts +2 -0
  65. package/dist/profile/duration.d.ts.map +1 -0
  66. package/dist/profile/duration.js +24 -0
  67. package/dist/profile/duration.js.map +1 -0
  68. package/dist/profile/preload.d.ts +2 -0
  69. package/dist/profile/preload.d.ts.map +1 -0
  70. package/dist/profile/preload.js +100 -0
  71. package/dist/profile/preload.js.map +1 -0
  72. package/dist/profile/runner.d.ts +22 -0
  73. package/dist/profile/runner.d.ts.map +1 -0
  74. package/dist/profile/runner.js +88 -0
  75. package/dist/profile/runner.js.map +1 -0
  76. package/dist/server/http.d.ts +27 -0
  77. package/dist/server/http.d.ts.map +1 -0
  78. package/dist/server/http.js +285 -0
  79. package/dist/server/http.js.map +1 -0
  80. package/dist/server/utils.d.ts +15 -0
  81. package/dist/server/utils.d.ts.map +1 -0
  82. package/dist/server/utils.js +71 -0
  83. package/dist/server/utils.js.map +1 -0
  84. package/dist/skill/handler.d.ts +77 -0
  85. package/dist/skill/handler.d.ts.map +1 -0
  86. package/dist/skill/handler.js +91 -0
  87. package/dist/skill/handler.js.map +1 -0
  88. package/dist/skill/index.d.ts +6 -0
  89. package/dist/skill/index.d.ts.map +1 -0
  90. package/dist/skill/index.js +6 -0
  91. package/dist/skill/index.js.map +1 -0
  92. package/dist/skill/manifest.d.ts +17 -0
  93. package/dist/skill/manifest.d.ts.map +1 -0
  94. package/dist/skill/manifest.js +231 -0
  95. package/dist/skill/manifest.js.map +1 -0
  96. package/dist/types.d.ts +260 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +5 -0
  99. package/dist/types.js.map +1 -0
  100. package/dist/utils/fs.d.ts +52 -0
  101. package/dist/utils/fs.d.ts.map +1 -0
  102. package/dist/utils/fs.js +106 -0
  103. package/dist/utils/fs.js.map +1 -0
  104. package/dist/utils/index.d.ts +7 -0
  105. package/dist/utils/index.d.ts.map +1 -0
  106. package/dist/utils/index.js +7 -0
  107. package/dist/utils/index.js.map +1 -0
  108. package/dist/utils/limits.d.ts +38 -0
  109. package/dist/utils/limits.d.ts.map +1 -0
  110. package/dist/utils/limits.js +86 -0
  111. package/dist/utils/limits.js.map +1 -0
  112. package/dist/utils/logger.d.ts +21 -0
  113. package/dist/utils/logger.d.ts.map +1 -0
  114. package/dist/utils/logger.js +82 -0
  115. package/dist/utils/logger.js.map +1 -0
  116. 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
+ ![Node CI](https://github.com/skillsland/perf-skill/workflows/Node%20CI/badge.svg)
4
+ [![npm version](https://badge.fury.io/js/perf-skill.svg)](http://badge.fury.io/js/perf-skill)
5
+ ![license](https://img.shields.io/npm/l/perf-skill)
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,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * perf-skill CLI - Analyze pprof profiles with AI assistance
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AACA;;GAEG"}