performance-reporter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /**
  2. * Performance Reporter
  3. * Collects and analyzes performance metrics, generates reports
  4. */
  5. class PerformanceReporter {
  6. constructor() {
  7. this.benchmarks = []
  8. this.systemInfo = {
  9. platform: process.platform,
  10. arch: process.arch,
  11. cpuCores: require('os').cpus().length,
  12. totalMemory: require('os').totalmem()
  13. }
  14. }
  15. /**
  16. * Add a benchmark result
  17. * @param {Object} benchmark - Benchmark data
  18. */
  19. addBenchmark(benchmark) {
  20. this.benchmarks.push({
  21. ...benchmark,
  22. timestamp: new Date().toISOString()
  23. })
  24. }
  25. /**
  26. * Generate comprehensive performance report
  27. * @returns {Object} Performance report with summary and recommendations
  28. */
  29. generateReport() {
  30. return {
  31. systemInfo: this.systemInfo,
  32. summary: this.getSummary(),
  33. detailed: this.benchmarks,
  34. recommendations: this.getRecommendations(),
  35. generatedAt: new Date().toISOString()
  36. }
  37. }
  38. /**
  39. * Calculate summary statistics
  40. * @returns {Object} Summary statistics
  41. */
  42. getSummary() {
  43. if (this.benchmarks.length === 0) {
  44. return { message: 'No benchmarks recorded' }
  45. }
  46. const grouped = this.groupBenchmarksByType()
  47. const summary = {}
  48. for (const [type, benchmarks] of Object.entries(grouped)) {
  49. const durations = benchmarks.map(b => b.duration)
  50. const cpuUsages = benchmarks.map(b => b.cpuAvg).filter(v => v != null)
  51. const memoryPeaks = benchmarks.map(b => b.memoryPeak).filter(v => v != null)
  52. summary[type] = {
  53. count: benchmarks.length,
  54. avgDuration: this.average(durations),
  55. minDuration: Math.min(...durations),
  56. maxDuration: Math.max(...durations),
  57. avgCPU: cpuUsages.length > 0 ? this.average(cpuUsages) : null,
  58. avgMemoryPeak: memoryPeaks.length > 0 ? this.average(memoryPeaks) : null,
  59. gpuUsed: benchmarks.some(b => b.gpuUsed)
  60. }
  61. }
  62. return summary
  63. }
  64. /**
  65. * Group benchmarks by type (sequential, parallel-2, parallel-4, etc.)
  66. * @returns {Object} Grouped benchmarks
  67. */
  68. groupBenchmarksByType() {
  69. const grouped = {}
  70. for (const benchmark of this.benchmarks) {
  71. const type = benchmark.name || benchmark.type || 'unknown'
  72. if (!grouped[type]) {
  73. grouped[type] = []
  74. }
  75. grouped[type].push(benchmark)
  76. }
  77. return grouped
  78. }
  79. /**
  80. * Generate optimization recommendations based on benchmark results
  81. * @returns {Array} Array of recommendation objects
  82. */
  83. getRecommendations() {
  84. const recommendations = []
  85. const summary = this.getSummary()
  86. // Check if we have comparison data
  87. const types = Object.keys(summary)
  88. if (types.length < 2) {
  89. recommendations.push({
  90. level: 'info',
  91. category: 'data',
  92. message: 'More benchmark data needed for detailed recommendations. Run benchmarks with different concurrency levels.'
  93. })
  94. return recommendations
  95. }
  96. // Compare sequential vs parallel performance
  97. const sequential = summary['sequential']
  98. const parallel2 = summary['parallel-2']
  99. const parallel4 = summary['parallel-4']
  100. const parallel8 = summary['parallel-8']
  101. if (sequential && parallel2) {
  102. const improvement = ((sequential.avgDuration - parallel2.avgDuration) / sequential.avgDuration * 100)
  103. if (improvement > 30) {
  104. recommendations.push({
  105. level: 'success',
  106. category: 'concurrency',
  107. message: `Parallel downloads (2 concurrent) are ${improvement.toFixed(1)}% faster than sequential. Consider increasing default concurrency.`,
  108. value: { improvement, optimalConcurrent: 2 }
  109. })
  110. }
  111. }
  112. if (parallel4 && parallel2) {
  113. const improvement = ((parallel2.avgDuration - parallel4.avgDuration) / parallel2.avgDuration * 100)
  114. if (improvement > 20) {
  115. recommendations.push({
  116. level: 'success',
  117. category: 'concurrency',
  118. message: `4 concurrent downloads are ${improvement.toFixed(1)}% faster than 2. Recommend maxConcurrent >= 4.`,
  119. value: { improvement, optimalConcurrent: 4 }
  120. })
  121. } else if (improvement < -10) {
  122. recommendations.push({
  123. level: 'warning',
  124. category: 'concurrency',
  125. message: `4 concurrent downloads are slower than 2. System may be bottlenecked. Recommend maxConcurrent = 2.`,
  126. value: { improvement, optimalConcurrent: 2 }
  127. })
  128. }
  129. }
  130. if (parallel8 && parallel4) {
  131. const improvement = ((parallel4.avgDuration - parallel8.avgDuration) / parallel4.avgDuration * 100)
  132. if (improvement < 10) {
  133. recommendations.push({
  134. level: 'info',
  135. category: 'concurrency',
  136. message: `8 concurrent downloads show diminishing returns (${improvement.toFixed(1)}% improvement). Recommend maxConcurrent = 4 for balanced performance.`,
  137. value: { improvement, optimalConcurrent: 4 }
  138. })
  139. }
  140. }
  141. // CPU usage recommendations
  142. const allCpuUsages = Object.values(summary).map(s => s.avgCPU).filter(v => v != null)
  143. if (allCpuUsages.length > 0) {
  144. const avgCpu = this.average(allCpuUsages)
  145. if (avgCpu > 80) {
  146. recommendations.push({
  147. level: 'warning',
  148. category: 'cpu',
  149. message: `High CPU usage detected (${avgCpu.toFixed(1)}%). Consider reducing maxConcurrent to prevent system slowdown.`
  150. })
  151. } else if (avgCpu < 40) {
  152. recommendations.push({
  153. level: 'info',
  154. category: 'cpu',
  155. message: `CPU usage is low (${avgCpu.toFixed(1)}%). System can handle higher concurrency.`
  156. })
  157. }
  158. }
  159. // Memory recommendations
  160. const allMemoryPeaks = Object.values(summary).map(s => s.avgMemoryPeak).filter(v => v != null)
  161. if (allMemoryPeaks.length > 0) {
  162. const avgMemory = this.average(allMemoryPeaks)
  163. const memoryUsagePercent = (avgMemory / this.systemInfo.totalMemory) * 100
  164. if (memoryUsagePercent > 70) {
  165. recommendations.push({
  166. level: 'warning',
  167. category: 'memory',
  168. message: `High memory usage detected (${memoryUsagePercent.toFixed(1)}% of total). Monitor for memory leaks.`
  169. })
  170. }
  171. }
  172. // GPU recommendations
  173. const gpuBenchmarks = this.benchmarks.filter(b => b.gpuUsed)
  174. const nonGpuBenchmarks = this.benchmarks.filter(b => !b.gpuUsed && b.hasConversion)
  175. if (gpuBenchmarks.length > 0 && nonGpuBenchmarks.length > 0) {
  176. const gpuAvgDuration = this.average(gpuBenchmarks.map(b => b.duration))
  177. const cpuAvgDuration = this.average(nonGpuBenchmarks.map(b => b.duration))
  178. const improvement = ((cpuAvgDuration - gpuAvgDuration) / cpuAvgDuration * 100)
  179. if (improvement > 30) {
  180. recommendations.push({
  181. level: 'success',
  182. category: 'gpu',
  183. message: `GPU acceleration provides ${improvement.toFixed(1)}% performance improvement. Keep GPU acceleration enabled.`,
  184. value: { improvement }
  185. })
  186. }
  187. }
  188. // If no specific recommendations, add a general one
  189. if (recommendations.length === 0) {
  190. recommendations.push({
  191. level: 'info',
  192. category: 'general',
  193. message: 'Performance appears optimal for current system configuration.'
  194. })
  195. }
  196. return recommendations
  197. }
  198. /**
  199. * Export report to JSON file
  200. * @param {string} filepath - Path to save report
  201. */
  202. async exportToJSON(filepath) {
  203. const report = this.generateReport()
  204. const fs = require('fs').promises
  205. await fs.writeFile(filepath, JSON.stringify(report, null, 2))
  206. return filepath
  207. }
  208. /**
  209. * Export report to Markdown file
  210. * @param {string} filepath - Path to save report
  211. */
  212. async exportToMarkdown(filepath) {
  213. const report = this.generateReport()
  214. const fs = require('fs').promises
  215. let markdown = '# GrabZilla Performance Report\n\n'
  216. markdown += `**Generated:** ${report.generatedAt}\n\n`
  217. // System info
  218. markdown += '## System Information\n\n'
  219. markdown += `- **Platform:** ${report.systemInfo.platform} (${report.systemInfo.arch})\n`
  220. markdown += `- **CPU Cores:** ${report.systemInfo.cpuCores}\n`
  221. markdown += `- **Total Memory:** ${(report.systemInfo.totalMemory / (1024 ** 3)).toFixed(2)} GB\n\n`
  222. // Summary
  223. markdown += '## Performance Summary\n\n'
  224. for (const [type, stats] of Object.entries(report.summary)) {
  225. if (typeof stats === 'object' && stats.avgDuration) {
  226. markdown += `### ${type}\n\n`
  227. markdown += `- **Count:** ${stats.count}\n`
  228. markdown += `- **Average Duration:** ${(stats.avgDuration / 1000).toFixed(2)}s\n`
  229. markdown += `- **Min Duration:** ${(stats.minDuration / 1000).toFixed(2)}s\n`
  230. markdown += `- **Max Duration:** ${(stats.maxDuration / 1000).toFixed(2)}s\n`
  231. if (stats.avgCPU) markdown += `- **Average CPU:** ${stats.avgCPU.toFixed(1)}%\n`
  232. if (stats.avgMemoryPeak) markdown += `- **Average Memory Peak:** ${(stats.avgMemoryPeak / (1024 ** 2)).toFixed(0)} MB\n`
  233. markdown += `- **GPU Used:** ${stats.gpuUsed ? 'Yes' : 'No'}\n\n`
  234. }
  235. }
  236. // Recommendations
  237. markdown += '## Recommendations\n\n'
  238. for (const rec of report.recommendations) {
  239. const emoji = rec.level === 'success' ? '✅' : rec.level === 'warning' ? '⚠️' : 'ℹ️'
  240. markdown += `${emoji} **${rec.category.toUpperCase()}:** ${rec.message}\n\n`
  241. }
  242. // Detailed results
  243. markdown += '## Detailed Results\n\n'
  244. markdown += '| Benchmark | Duration | CPU Avg | Memory Peak | GPU |\n'
  245. markdown += '|-----------|----------|---------|-------------|-----|\n'
  246. for (const benchmark of report.detailed) {
  247. const duration = (benchmark.duration / 1000).toFixed(2) + 's'
  248. const cpu = benchmark.cpuAvg ? benchmark.cpuAvg.toFixed(1) + '%' : 'N/A'
  249. const memory = benchmark.memoryPeak ? (benchmark.memoryPeak / (1024 ** 2)).toFixed(0) + ' MB' : 'N/A'
  250. const gpu = benchmark.gpuUsed ? 'Yes' : 'No'
  251. markdown += `| ${benchmark.name} | ${duration} | ${cpu} | ${memory} | ${gpu} |\n`
  252. }
  253. await fs.writeFile(filepath, markdown)
  254. return filepath
  255. }
  256. /**
  257. * Calculate average of array
  258. * @param {Array} arr - Array of numbers
  259. * @returns {number} Average value
  260. */
  261. average(arr) {
  262. if (arr.length === 0) return 0
  263. return arr.reduce((sum, val) => sum + val, 0) / arr.length
  264. }
  265. /**
  266. * Clear all benchmarks
  267. */
  268. clear() {
  269. this.benchmarks = []
  270. }
  271. }
  272. module.exports = PerformanceReporter