@rog0x/mcp-perf-tools 1.0.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/README.md +76 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +210 -0
- package/dist/tools/benchmark.d.ts +27 -0
- package/dist/tools/benchmark.js +82 -0
- package/dist/tools/big-o-estimator.d.ts +13 -0
- package/dist/tools/big-o-estimator.js +80 -0
- package/dist/tools/bundle-analyzer.d.ts +25 -0
- package/dist/tools/bundle-analyzer.js +172 -0
- package/dist/tools/load-tester.d.ts +36 -0
- package/dist/tools/load-tester.js +185 -0
- package/dist/tools/memory-analyzer.d.ts +22 -0
- package/dist/tools/memory-analyzer.js +115 -0
- package/package.json +20 -0
- package/src/index.ts +257 -0
- package/src/tools/benchmark.ts +113 -0
- package/src/tools/big-o-estimator.ts +175 -0
- package/src/tools/bundle-analyzer.ts +195 -0
- package/src/tools/load-tester.ts +216 -0
- package/src/tools/memory-analyzer.ts +145 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# mcp-perf-tools
|
|
2
|
+
|
|
3
|
+
Performance analysis tools for AI agents, exposed via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### benchmark
|
|
8
|
+
|
|
9
|
+
Benchmark JavaScript code execution. Run a snippet N times and measure min/max/avg/median/p95/p99 latency and operations per second. Optionally compare two implementations side by side.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
- `code` (string) — JavaScript code to benchmark
|
|
13
|
+
- `iterations` (number, default 1000) — Number of iterations
|
|
14
|
+
- `compareCode` (string, optional) — Second implementation to compare
|
|
15
|
+
- `labelA` / `labelB` (string) — Labels for comparison output
|
|
16
|
+
|
|
17
|
+
### memory_analyze
|
|
18
|
+
|
|
19
|
+
Analyze Node.js memory usage: heap used/total, RSS, external memory, and array buffers. Takes snapshots over time and uses linear regression to detect trends and potential memory leaks.
|
|
20
|
+
|
|
21
|
+
**Parameters:**
|
|
22
|
+
- `action` — `"snapshot"` | `"analyze"` | `"clear"`
|
|
23
|
+
|
|
24
|
+
### big_o_estimate
|
|
25
|
+
|
|
26
|
+
Estimate Big O complexity from empirical timing data. Fits measurements against O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), and O(2^n) using R-squared scoring. Includes an ASCII growth curve visualization.
|
|
27
|
+
|
|
28
|
+
**Parameters:**
|
|
29
|
+
- `inputSizes` (number[]) — Array of input sizes
|
|
30
|
+
- `executionTimesMs` (number[]) — Corresponding execution times in ms
|
|
31
|
+
|
|
32
|
+
### bundle_analyze
|
|
33
|
+
|
|
34
|
+
Analyze a JavaScript bundle file: raw size, gzip compressed estimate, detected module count, largest modules, and tree-shaking opportunities (side effects, duplicates).
|
|
35
|
+
|
|
36
|
+
**Parameters:**
|
|
37
|
+
- `filePath` (string) — Absolute path to the bundle file
|
|
38
|
+
|
|
39
|
+
### load_test
|
|
40
|
+
|
|
41
|
+
Simple HTTP load tester. Sends N requests at a given concurrency level and reports response time percentiles, error rate, throughput (req/sec), and status code distribution.
|
|
42
|
+
|
|
43
|
+
**Parameters:**
|
|
44
|
+
- `url` (string) — Target URL
|
|
45
|
+
- `totalRequests` (number, default 100) — Total requests to send
|
|
46
|
+
- `concurrency` (number, default 10) — Concurrent request count
|
|
47
|
+
- `method` (string, default "GET") — HTTP method
|
|
48
|
+
- `headers` (object, optional) — HTTP headers
|
|
49
|
+
- `body` (string, optional) — Request body
|
|
50
|
+
- `timeoutMs` (number, default 30000) — Request timeout
|
|
51
|
+
|
|
52
|
+
## Setup
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage with Claude Desktop
|
|
60
|
+
|
|
61
|
+
Add to your `claude_desktop_config.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"perf-tools": {
|
|
67
|
+
"command": "node",
|
|
68
|
+
"args": ["D:/products/mcp-servers/mcp-perf-tools/dist/index.js"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const benchmark_js_1 = require("./tools/benchmark.js");
|
|
8
|
+
const memory_analyzer_js_1 = require("./tools/memory-analyzer.js");
|
|
9
|
+
const big_o_estimator_js_1 = require("./tools/big-o-estimator.js");
|
|
10
|
+
const bundle_analyzer_js_1 = require("./tools/bundle-analyzer.js");
|
|
11
|
+
const load_tester_js_1 = require("./tools/load-tester.js");
|
|
12
|
+
const server = new mcp_js_1.McpServer({
|
|
13
|
+
name: "mcp-perf-tools",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
});
|
|
16
|
+
// -------------------------------------------------------------------
|
|
17
|
+
// Tool 1: benchmark
|
|
18
|
+
// -------------------------------------------------------------------
|
|
19
|
+
server.tool("benchmark", "Benchmark JavaScript code execution. Run a snippet N times and measure min/max/avg/p95/p99 time and ops/sec. Optionally compare two implementations.", {
|
|
20
|
+
code: zod_1.z.string().describe("JavaScript code to benchmark"),
|
|
21
|
+
iterations: zod_1.z
|
|
22
|
+
.number()
|
|
23
|
+
.int()
|
|
24
|
+
.min(1)
|
|
25
|
+
.default(1000)
|
|
26
|
+
.describe("Number of iterations to run"),
|
|
27
|
+
compareCode: zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Optional second implementation to compare against"),
|
|
31
|
+
labelA: zod_1.z
|
|
32
|
+
.string()
|
|
33
|
+
.default("A")
|
|
34
|
+
.describe("Label for the first implementation"),
|
|
35
|
+
labelB: zod_1.z
|
|
36
|
+
.string()
|
|
37
|
+
.default("B")
|
|
38
|
+
.describe("Label for the second implementation"),
|
|
39
|
+
}, async ({ code, iterations, compareCode, labelA, labelB }) => {
|
|
40
|
+
try {
|
|
41
|
+
if (compareCode) {
|
|
42
|
+
const result = (0, benchmark_js_1.compareBenchmarks)(code, labelA, compareCode, labelB, iterations);
|
|
43
|
+
return { content: [{ type: "text", text: result.summary }] };
|
|
44
|
+
}
|
|
45
|
+
const result = (0, benchmark_js_1.runBenchmark)(code, iterations);
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: (0, benchmark_js_1.formatBenchmarkResult)(result) }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: `Benchmark error: ${msg}` }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// -------------------------------------------------------------------
|
|
59
|
+
// Tool 2: memory_analyze
|
|
60
|
+
// -------------------------------------------------------------------
|
|
61
|
+
server.tool("memory_analyze", "Analyze Node.js memory usage: heap used/total, RSS, external, array buffers. Tracks snapshots over time and detects potential memory leaks via growth trend analysis.", {
|
|
62
|
+
action: zod_1.z
|
|
63
|
+
.enum(["snapshot", "analyze", "clear"])
|
|
64
|
+
.default("analyze")
|
|
65
|
+
.describe("snapshot: take a single reading; analyze: full analysis with trend detection; clear: reset history"),
|
|
66
|
+
}, async ({ action }) => {
|
|
67
|
+
try {
|
|
68
|
+
if (action === "clear") {
|
|
69
|
+
(0, memory_analyzer_js_1.clearSnapshotHistory)();
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: "Memory snapshot history cleared." }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (action === "snapshot") {
|
|
75
|
+
const snap = (0, memory_analyzer_js_1.takeMemorySnapshot)();
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text: (0, memory_analyzer_js_1.formatSnapshot)(snap) }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// analyze
|
|
81
|
+
const analysis = (0, memory_analyzer_js_1.analyzeMemory)();
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: analysis.summary }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text", text: `Memory analysis error: ${msg}` }],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
// -------------------------------------------------------------------
|
|
95
|
+
// Tool 3: big_o_estimate
|
|
96
|
+
// -------------------------------------------------------------------
|
|
97
|
+
server.tool("big_o_estimate", "Estimate Big O complexity from empirical data. Provide input sizes and corresponding execution times to get the best-fit complexity class (O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), O(2^n)) with confidence scores and growth curve.", {
|
|
98
|
+
inputSizes: zod_1.z
|
|
99
|
+
.array(zod_1.z.number().positive())
|
|
100
|
+
.min(3)
|
|
101
|
+
.describe("Array of input sizes (e.g., [100, 500, 1000, 5000])"),
|
|
102
|
+
executionTimesMs: zod_1.z
|
|
103
|
+
.array(zod_1.z.number().nonnegative())
|
|
104
|
+
.min(3)
|
|
105
|
+
.describe("Array of execution times in milliseconds, corresponding to each input size"),
|
|
106
|
+
}, async ({ inputSizes, executionTimesMs }) => {
|
|
107
|
+
try {
|
|
108
|
+
const result = (0, big_o_estimator_js_1.estimateBigO)(inputSizes, executionTimesMs);
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: result.summary }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: `Big O estimation error: ${msg}` }],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// -------------------------------------------------------------------
|
|
122
|
+
// Tool 4: bundle_analyze
|
|
123
|
+
// -------------------------------------------------------------------
|
|
124
|
+
server.tool("bundle_analyze", "Analyze a JavaScript bundle file: raw size, gzip estimate, detected modules, largest modules, and tree-shaking opportunities.", {
|
|
125
|
+
filePath: zod_1.z
|
|
126
|
+
.string()
|
|
127
|
+
.describe("Absolute path to the JavaScript bundle file to analyze"),
|
|
128
|
+
}, async ({ filePath }) => {
|
|
129
|
+
try {
|
|
130
|
+
const result = (0, bundle_analyzer_js_1.analyzeBundle)(filePath);
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: result.summary }],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: `Bundle analysis error: ${msg}` }],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// -------------------------------------------------------------------
|
|
144
|
+
// Tool 5: load_test
|
|
145
|
+
// -------------------------------------------------------------------
|
|
146
|
+
server.tool("load_test", "Run a simple HTTP load test: send N concurrent requests to a URL, measure response times, error rate, throughput (req/sec), and percentile latencies.", {
|
|
147
|
+
url: zod_1.z.string().url().describe("Target URL to test"),
|
|
148
|
+
totalRequests: zod_1.z
|
|
149
|
+
.number()
|
|
150
|
+
.int()
|
|
151
|
+
.min(1)
|
|
152
|
+
.default(100)
|
|
153
|
+
.describe("Total number of requests to send"),
|
|
154
|
+
concurrency: zod_1.z
|
|
155
|
+
.number()
|
|
156
|
+
.int()
|
|
157
|
+
.min(1)
|
|
158
|
+
.default(10)
|
|
159
|
+
.describe("Number of concurrent requests"),
|
|
160
|
+
method: zod_1.z
|
|
161
|
+
.string()
|
|
162
|
+
.default("GET")
|
|
163
|
+
.describe("HTTP method (GET, POST, PUT, DELETE, etc.)"),
|
|
164
|
+
headers: zod_1.z
|
|
165
|
+
.record(zod_1.z.string(), zod_1.z.string())
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Optional HTTP headers as key-value pairs"),
|
|
168
|
+
body: zod_1.z.string().optional().describe("Optional request body"),
|
|
169
|
+
timeoutMs: zod_1.z
|
|
170
|
+
.number()
|
|
171
|
+
.int()
|
|
172
|
+
.min(1000)
|
|
173
|
+
.default(30000)
|
|
174
|
+
.describe("Request timeout in milliseconds"),
|
|
175
|
+
}, async ({ url, totalRequests, concurrency, method, headers, body, timeoutMs }) => {
|
|
176
|
+
try {
|
|
177
|
+
const result = await (0, load_tester_js_1.runLoadTest)({
|
|
178
|
+
url,
|
|
179
|
+
totalRequests,
|
|
180
|
+
concurrency,
|
|
181
|
+
method,
|
|
182
|
+
headers,
|
|
183
|
+
body,
|
|
184
|
+
timeoutMs,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: result.summary }],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: `Load test error: ${msg}` }],
|
|
194
|
+
isError: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// -------------------------------------------------------------------
|
|
199
|
+
// Start server
|
|
200
|
+
// -------------------------------------------------------------------
|
|
201
|
+
async function main() {
|
|
202
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
203
|
+
await server.connect(transport);
|
|
204
|
+
console.error("mcp-perf-tools server running on stdio");
|
|
205
|
+
}
|
|
206
|
+
main().catch((err) => {
|
|
207
|
+
console.error("Fatal error:", err);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface BenchmarkResult {
|
|
2
|
+
iterations: number;
|
|
3
|
+
minMs: number;
|
|
4
|
+
maxMs: number;
|
|
5
|
+
avgMs: number;
|
|
6
|
+
medianMs: number;
|
|
7
|
+
p95Ms: number;
|
|
8
|
+
p99Ms: number;
|
|
9
|
+
opsPerSecond: number;
|
|
10
|
+
totalMs: number;
|
|
11
|
+
timings: number[];
|
|
12
|
+
}
|
|
13
|
+
export interface ComparisonResult {
|
|
14
|
+
a: BenchmarkResult & {
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
17
|
+
b: BenchmarkResult & {
|
|
18
|
+
label: string;
|
|
19
|
+
};
|
|
20
|
+
fasterLabel: string;
|
|
21
|
+
speedupFactor: number;
|
|
22
|
+
summary: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function runBenchmark(code: string, iterations: number): BenchmarkResult;
|
|
25
|
+
export declare function compareBenchmarks(codeA: string, labelA: string, codeB: string, labelB: string, iterations: number): ComparisonResult;
|
|
26
|
+
export declare function formatBenchmarkResult(result: BenchmarkResult): string;
|
|
27
|
+
//# sourceMappingURL=benchmark.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runBenchmark = runBenchmark;
|
|
4
|
+
exports.compareBenchmarks = compareBenchmarks;
|
|
5
|
+
exports.formatBenchmarkResult = formatBenchmarkResult;
|
|
6
|
+
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
7
|
+
function percentile(sorted, p) {
|
|
8
|
+
if (sorted.length === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
const idx = (p / 100) * (sorted.length - 1);
|
|
11
|
+
const lower = Math.floor(idx);
|
|
12
|
+
const upper = Math.ceil(idx);
|
|
13
|
+
if (lower === upper)
|
|
14
|
+
return sorted[lower];
|
|
15
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
|
|
16
|
+
}
|
|
17
|
+
function runBenchmark(code, iterations) {
|
|
18
|
+
const timings = [];
|
|
19
|
+
// Wrap the code in a function so it can be executed repeatedly
|
|
20
|
+
const fn = new Function(code);
|
|
21
|
+
// Warmup: 10% of iterations or at least 3
|
|
22
|
+
const warmupCount = Math.max(3, Math.floor(iterations * 0.1));
|
|
23
|
+
for (let i = 0; i < warmupCount; i++) {
|
|
24
|
+
fn();
|
|
25
|
+
}
|
|
26
|
+
for (let i = 0; i < iterations; i++) {
|
|
27
|
+
const start = node_perf_hooks_1.performance.now();
|
|
28
|
+
fn();
|
|
29
|
+
const end = node_perf_hooks_1.performance.now();
|
|
30
|
+
timings.push(end - start);
|
|
31
|
+
}
|
|
32
|
+
const sorted = [...timings].sort((a, b) => a - b);
|
|
33
|
+
const totalMs = timings.reduce((s, t) => s + t, 0);
|
|
34
|
+
const avgMs = totalMs / timings.length;
|
|
35
|
+
return {
|
|
36
|
+
iterations,
|
|
37
|
+
minMs: sorted[0],
|
|
38
|
+
maxMs: sorted[sorted.length - 1],
|
|
39
|
+
avgMs,
|
|
40
|
+
medianMs: percentile(sorted, 50),
|
|
41
|
+
p95Ms: percentile(sorted, 95),
|
|
42
|
+
p99Ms: percentile(sorted, 99),
|
|
43
|
+
opsPerSecond: totalMs > 0 ? (iterations / totalMs) * 1000 : Infinity,
|
|
44
|
+
totalMs,
|
|
45
|
+
timings: sorted,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function compareBenchmarks(codeA, labelA, codeB, labelB, iterations) {
|
|
49
|
+
const resultA = runBenchmark(codeA, iterations);
|
|
50
|
+
const resultB = runBenchmark(codeB, iterations);
|
|
51
|
+
const fasterIsA = resultA.avgMs < resultB.avgMs;
|
|
52
|
+
const fasterLabel = fasterIsA ? labelA : labelB;
|
|
53
|
+
const speedupFactor = fasterIsA
|
|
54
|
+
? resultB.avgMs / resultA.avgMs
|
|
55
|
+
: resultA.avgMs / resultB.avgMs;
|
|
56
|
+
const summary = [
|
|
57
|
+
`"${fasterLabel}" is ${speedupFactor.toFixed(2)}x faster.`,
|
|
58
|
+
` ${labelA}: avg ${resultA.avgMs.toFixed(4)}ms, p95 ${resultA.p95Ms.toFixed(4)}ms, ${resultA.opsPerSecond.toFixed(0)} ops/s`,
|
|
59
|
+
` ${labelB}: avg ${resultB.avgMs.toFixed(4)}ms, p95 ${resultB.p95Ms.toFixed(4)}ms, ${resultB.opsPerSecond.toFixed(0)} ops/s`,
|
|
60
|
+
].join("\n");
|
|
61
|
+
return {
|
|
62
|
+
a: { ...resultA, label: labelA },
|
|
63
|
+
b: { ...resultB, label: labelB },
|
|
64
|
+
fasterLabel,
|
|
65
|
+
speedupFactor,
|
|
66
|
+
summary,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function formatBenchmarkResult(result) {
|
|
70
|
+
return [
|
|
71
|
+
`Benchmark Results (${result.iterations} iterations):`,
|
|
72
|
+
` Min: ${result.minMs.toFixed(4)} ms`,
|
|
73
|
+
` Max: ${result.maxMs.toFixed(4)} ms`,
|
|
74
|
+
` Avg: ${result.avgMs.toFixed(4)} ms`,
|
|
75
|
+
` Median: ${result.medianMs.toFixed(4)} ms`,
|
|
76
|
+
` P95: ${result.p95Ms.toFixed(4)} ms`,
|
|
77
|
+
` P99: ${result.p99Ms.toFixed(4)} ms`,
|
|
78
|
+
` Ops/s: ${result.opsPerSecond.toFixed(0)}`,
|
|
79
|
+
` Total: ${result.totalMs.toFixed(2)} ms`,
|
|
80
|
+
].join("\n");
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=benchmark.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ComplexityClass = "O(1)" | "O(log n)" | "O(n)" | "O(n log n)" | "O(n^2)" | "O(n^3)" | "O(2^n)" | "unknown";
|
|
2
|
+
export interface BigOEstimate {
|
|
3
|
+
bestFit: ComplexityClass;
|
|
4
|
+
confidence: number;
|
|
5
|
+
allFits: Array<{
|
|
6
|
+
complexity: ComplexityClass;
|
|
7
|
+
r2: number;
|
|
8
|
+
}>;
|
|
9
|
+
growthCurve: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function estimateBigO(inputSizes: number[], executionTimesMs: number[]): BigOEstimate;
|
|
13
|
+
//# sourceMappingURL=big-o-estimator.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.estimateBigO = estimateBigO;
|
|
4
|
+
function linearRegression(xs, ys) {
|
|
5
|
+
const n = xs.length;
|
|
6
|
+
if (n < 2)
|
|
7
|
+
return { slope: 0, intercept: ys[0] ?? 0, r2: 0 };
|
|
8
|
+
const sumX = xs.reduce((a, b) => a + b, 0);
|
|
9
|
+
const sumY = ys.reduce((a, b) => a + b, 0);
|
|
10
|
+
const sumXY = xs.reduce((a, x, i) => a + x * ys[i], 0);
|
|
11
|
+
const sumX2 = xs.reduce((a, x) => a + x * x, 0);
|
|
12
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
13
|
+
if (denom === 0)
|
|
14
|
+
return { slope: 0, intercept: sumY / n, r2: 0 };
|
|
15
|
+
const slope = (n * sumXY - sumX * sumY) / denom;
|
|
16
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
17
|
+
const meanY = sumY / n;
|
|
18
|
+
const ssTot = ys.reduce((a, y) => a + (y - meanY) ** 2, 0);
|
|
19
|
+
const ssRes = ys.reduce((a, y, i) => a + (y - (slope * xs[i] + intercept)) ** 2, 0);
|
|
20
|
+
const r2 = ssTot === 0 ? 1 : 1 - ssRes / ssTot;
|
|
21
|
+
return { slope, intercept, r2 };
|
|
22
|
+
}
|
|
23
|
+
function tryFit(sizes, times, transform, label, description) {
|
|
24
|
+
const transformed = sizes.map(transform);
|
|
25
|
+
// Check for non-finite values
|
|
26
|
+
if (transformed.some((v) => !isFinite(v))) {
|
|
27
|
+
return { complexity: label, r2: -Infinity, description };
|
|
28
|
+
}
|
|
29
|
+
const { r2 } = linearRegression(transformed, times);
|
|
30
|
+
return { complexity: label, r2, description };
|
|
31
|
+
}
|
|
32
|
+
function estimateBigO(inputSizes, executionTimesMs) {
|
|
33
|
+
if (inputSizes.length !== executionTimesMs.length) {
|
|
34
|
+
throw new Error("inputSizes and executionTimesMs must have the same length");
|
|
35
|
+
}
|
|
36
|
+
if (inputSizes.length < 3) {
|
|
37
|
+
throw new Error("Need at least 3 data points for a meaningful estimate");
|
|
38
|
+
}
|
|
39
|
+
const fits = [
|
|
40
|
+
tryFit(inputSizes, executionTimesMs, (_n) => 1, "O(1)", "Constant time: performance does not change with input size."),
|
|
41
|
+
tryFit(inputSizes, executionTimesMs, (n) => Math.log2(n), "O(log n)", "Logarithmic: performance grows slowly, typical of binary search or balanced trees."),
|
|
42
|
+
tryFit(inputSizes, executionTimesMs, (n) => n, "O(n)", "Linear: performance scales proportionally with input size."),
|
|
43
|
+
tryFit(inputSizes, executionTimesMs, (n) => n * Math.log2(n), "O(n log n)", "Linearithmic: typical of efficient sorting algorithms (mergesort, heapsort)."),
|
|
44
|
+
tryFit(inputSizes, executionTimesMs, (n) => n * n, "O(n^2)", "Quadratic: nested iteration over input, common in naive sorting or pairwise comparisons."),
|
|
45
|
+
tryFit(inputSizes, executionTimesMs, (n) => n * n * n, "O(n^3)", "Cubic: triple-nested loops, e.g., naive matrix multiplication."),
|
|
46
|
+
tryFit(inputSizes, executionTimesMs, (n) => Math.pow(2, n), "O(2^n)", "Exponential: brute-force recursive algorithms, rapidly becomes infeasible."),
|
|
47
|
+
];
|
|
48
|
+
// Sort by R^2 descending (best fit first)
|
|
49
|
+
fits.sort((a, b) => b.r2 - a.r2);
|
|
50
|
+
const best = fits[0];
|
|
51
|
+
const confidence = Math.max(0, Math.min(1, best.r2));
|
|
52
|
+
// Build a simple ASCII growth curve description
|
|
53
|
+
const growthCurve = buildGrowthCurve(inputSizes, executionTimesMs);
|
|
54
|
+
const allFits = fits.map((f) => ({ complexity: f.complexity, r2: f.r2 }));
|
|
55
|
+
const summary = [
|
|
56
|
+
`Big O Estimate: ${best.complexity} (confidence: ${(confidence * 100).toFixed(1)}%)`,
|
|
57
|
+
`${best.description}`,
|
|
58
|
+
"",
|
|
59
|
+
"All fits (by R² descending):",
|
|
60
|
+
...allFits.map((f) => ` ${f.complexity.padEnd(10)} R² = ${f.r2.toFixed(4)}`),
|
|
61
|
+
"",
|
|
62
|
+
"Growth Curve:",
|
|
63
|
+
growthCurve,
|
|
64
|
+
].join("\n");
|
|
65
|
+
return { bestFit: best.complexity, confidence, allFits, growthCurve, summary };
|
|
66
|
+
}
|
|
67
|
+
function buildGrowthCurve(sizes, times) {
|
|
68
|
+
const maxTime = Math.max(...times);
|
|
69
|
+
const maxBarWidth = 40;
|
|
70
|
+
const lines = [];
|
|
71
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
72
|
+
const barLen = maxTime > 0 ? Math.round((times[i] / maxTime) * maxBarWidth) : 0;
|
|
73
|
+
const bar = "\u2588".repeat(barLen);
|
|
74
|
+
const sizeStr = String(sizes[i]).padStart(10);
|
|
75
|
+
const timeStr = times[i].toFixed(2).padStart(10);
|
|
76
|
+
lines.push(` n=${sizeStr} ${timeStr}ms ${bar}`);
|
|
77
|
+
}
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=big-o-estimator.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ModuleInfo {
|
|
2
|
+
filePath: string;
|
|
3
|
+
sizeBytes: number;
|
|
4
|
+
sizeKB: number;
|
|
5
|
+
/** Percentage of total bundle size */
|
|
6
|
+
percentage: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TreeShakingOpportunity {
|
|
9
|
+
filePath: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BundleAnalysis {
|
|
13
|
+
filePath: string;
|
|
14
|
+
rawSizeBytes: number;
|
|
15
|
+
rawSizeKB: number;
|
|
16
|
+
gzipEstimateBytes: number;
|
|
17
|
+
gzipEstimateKB: number;
|
|
18
|
+
compressionRatio: number;
|
|
19
|
+
moduleCount: number;
|
|
20
|
+
largestModules: ModuleInfo[];
|
|
21
|
+
treeShakingOpportunities: TreeShakingOpportunity[];
|
|
22
|
+
summary: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function analyzeBundle(filePath: string): BundleAnalysis;
|
|
25
|
+
//# sourceMappingURL=bundle-analyzer.d.ts.map
|