Skip to content

Commit be06f8f

Browse files
authored
Merge pull request #45 from sakamotopaya/feature/story-20-performance-optimization
feat: Implement comprehensive CLI performance optimizations (Story #20)
2 parents 39cc379 + 523ae2b commit be06f8f

File tree

13 files changed

+4599
-0
lines changed

13 files changed

+4599
-0
lines changed

scripts/performance/benchmark.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Performance benchmark script for CLI optimizations
4+
*/
5+
6+
const { performance } = require("perf_hooks")
7+
const { spawn } = require("child_process")
8+
const fs = require("fs")
9+
const path = require("path")
10+
11+
async function runBenchmark() {
12+
console.log("🚀 CLI Performance Benchmark")
13+
console.log("=" * 50)
14+
15+
const results = {
16+
coldStart: [],
17+
warmStart: [],
18+
memory: [],
19+
commands: [],
20+
}
21+
22+
// Test cold start performance
23+
console.log("\n📊 Testing cold start performance...")
24+
for (let i = 0; i < 3; i++) {
25+
const startTime = performance.now()
26+
27+
try {
28+
await runCLICommand(["--help"], { timeout: 10000 })
29+
const duration = performance.now() - startTime
30+
results.coldStart.push(duration)
31+
console.log(` Cold start ${i + 1}: ${Math.round(duration)}ms`)
32+
} catch (error) {
33+
console.log(` Cold start ${i + 1}: FAILED (${error.message})`)
34+
}
35+
}
36+
37+
// Test warm start performance (multiple quick commands)
38+
console.log("\n🔥 Testing warm start performance...")
39+
for (let i = 0; i < 5; i++) {
40+
const startTime = performance.now()
41+
42+
try {
43+
await runCLICommand(["version"], { timeout: 5000 })
44+
const duration = performance.now() - startTime
45+
results.warmStart.push(duration)
46+
console.log(` Warm start ${i + 1}: ${Math.round(duration)}ms`)
47+
} catch (error) {
48+
console.log(` Warm start ${i + 1}: FAILED (${error.message})`)
49+
}
50+
}
51+
52+
// Test memory usage with verbose output
53+
console.log("\n💾 Testing memory usage...")
54+
try {
55+
const memoryResult = await runCLICommand(["--verbose", "config", "--show"], {
56+
timeout: 10000,
57+
captureOutput: true,
58+
})
59+
60+
if (memoryResult.stdout) {
61+
const memoryMatch = memoryResult.stdout.match(/(\d+)ms/)
62+
if (memoryMatch) {
63+
results.memory.push(parseInt(memoryMatch[1]))
64+
console.log(` Memory test: ${memoryMatch[1]}ms`)
65+
}
66+
}
67+
} catch (error) {
68+
console.log(` Memory test: FAILED (${error.message})`)
69+
}
70+
71+
// Test various commands
72+
console.log("\n⚡ Testing command performance...")
73+
const commands = [["help"], ["version", "--json"], ["config", "--show"]]
74+
75+
for (const command of commands) {
76+
const startTime = performance.now()
77+
78+
try {
79+
await runCLICommand(command, { timeout: 5000 })
80+
const duration = performance.now() - startTime
81+
results.commands.push({ command: command.join(" "), duration })
82+
console.log(` ${command.join(" ")}: ${Math.round(duration)}ms`)
83+
} catch (error) {
84+
console.log(` ${command.join(" ")}: FAILED (${error.message})`)
85+
}
86+
}
87+
88+
// Generate report
89+
console.log("\n📈 Performance Report")
90+
console.log("=" * 50)
91+
92+
if (results.coldStart.length > 0) {
93+
const avgColdStart = results.coldStart.reduce((a, b) => a + b, 0) / results.coldStart.length
94+
const target = 2000 // 2 seconds target
95+
const status = avgColdStart < target ? "✅ PASS" : "❌ FAIL"
96+
console.log(`Cold Start Average: ${Math.round(avgColdStart)}ms (target: <${target}ms) ${status}`)
97+
}
98+
99+
if (results.warmStart.length > 0) {
100+
const avgWarmStart = results.warmStart.reduce((a, b) => a + b, 0) / results.warmStart.length
101+
const improvement =
102+
results.coldStart.length > 0 ? ((results.coldStart[0] - avgWarmStart) / results.coldStart[0]) * 100 : 0
103+
console.log(`Warm Start Average: ${Math.round(avgWarmStart)}ms (improvement: ${Math.round(improvement)}%)`)
104+
}
105+
106+
if (results.commands.length > 0) {
107+
const avgCommand = results.commands.reduce((a, b) => a + b.duration, 0) / results.commands.length
108+
const target = 1000 // 1 second target
109+
const status = avgCommand < target ? "✅ PASS" : "❌ FAIL"
110+
console.log(`Command Average: ${Math.round(avgCommand)}ms (target: <${target}ms) ${status}`)
111+
}
112+
113+
// Performance recommendations
114+
console.log("\n💡 Recommendations:")
115+
if (results.coldStart.length > 0 && results.coldStart[0] > 2000) {
116+
console.log(" • Consider enabling aggressive startup optimization")
117+
console.log(" • Check for unnecessary module loading during startup")
118+
}
119+
120+
if (results.warmStart.length > 0 && results.warmStart.some((t) => t > 500)) {
121+
console.log(" • Cache performance could be improved")
122+
console.log(" • Consider increasing cache sizes")
123+
}
124+
125+
console.log("\n✨ Benchmark completed!")
126+
}
127+
128+
function runCLICommand(args, options = {}) {
129+
return new Promise((resolve, reject) => {
130+
const { timeout = 5000, captureOutput = false } = options
131+
132+
// Use the compiled CLI if available, otherwise use npm run
133+
const cliPath = path.join(__dirname, "../../src/dist/cli/index.js")
134+
const useCompiledCLI = fs.existsSync(cliPath)
135+
136+
const command = useCompiledCLI ? "node" : "npm"
137+
const commandArgs = useCompiledCLI ? [cliPath, ...args] : ["run", "cli", "--", ...args]
138+
139+
const child = spawn(command, commandArgs, {
140+
cwd: path.join(__dirname, "../.."),
141+
stdio: captureOutput ? ["pipe", "pipe", "pipe"] : ["pipe", "inherit", "inherit"],
142+
})
143+
144+
let stdout = ""
145+
let stderr = ""
146+
147+
if (captureOutput) {
148+
child.stdout?.on("data", (data) => {
149+
stdout += data.toString()
150+
})
151+
152+
child.stderr?.on("data", (data) => {
153+
stderr += data.toString()
154+
})
155+
}
156+
157+
const timeoutHandle = setTimeout(() => {
158+
child.kill("SIGTERM")
159+
reject(new Error("Command timeout"))
160+
}, timeout)
161+
162+
child.on("close", (code) => {
163+
clearTimeout(timeoutHandle)
164+
165+
if (code === 0) {
166+
resolve({ stdout, stderr, code })
167+
} else {
168+
reject(new Error(`Command failed with code ${code}`))
169+
}
170+
})
171+
172+
child.on("error", (error) => {
173+
clearTimeout(timeoutHandle)
174+
reject(error)
175+
})
176+
})
177+
}
178+
179+
// Run benchmark if this script is executed directly
180+
if (require.main === module) {
181+
runBenchmark().catch((error) => {
182+
console.error("Benchmark failed:", error)
183+
process.exit(1)
184+
})
185+
}
186+
187+
module.exports = { runBenchmark }

0 commit comments

Comments
 (0)