From 5abdc8c5385263133638b59b41094890cb17aaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Wed, 6 Aug 2025 18:43:44 +0800 Subject: [PATCH] fix --- package-lock.json | 145 +++++------ package.json | 11 +- src/common/perf-cli.ts | 197 ++++++++++++++ src/common/performance-demo.ts | 87 +++++++ src/common/performance-monitor.ts | 316 ++++++++++++++++++++++ src/onebot/index.ts | 7 +- src/plugin/index.ts | 5 +- src/webui/src/performance-api.ts | 405 +++++++++++++++++++++++++++++ vite-plugin-performance-monitor.ts | 306 ++++++++++++++++++++++ vite.config.ts | 16 ++ 10 files changed, 1400 insertions(+), 95 deletions(-) create mode 100644 src/common/perf-cli.ts create mode 100644 src/common/performance-demo.ts create mode 100644 src/common/performance-monitor.ts create mode 100644 src/webui/src/performance-api.ts create mode 100644 vite-plugin-performance-monitor.ts diff --git a/package-lock.json b/package-lock.json index c6dbee5c..da300e1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,24 @@ { "name": "napcat", - "version": "4.8.93", + "version": "4.8.95", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "napcat", - "version": "4.8.93", + "version": "4.8.95", "dependencies": { "express": "^5.0.0", "silk-wasm": "^3.6.1", "ws": "^8.18.0" }, "devDependencies": { + "@babel/core": "^7.28.0", + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/preset-typescript": "^7.24.7", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", "@eslint/compat": "^1.3.1", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.31.0", @@ -41,9 +46,8 @@ "cors": "^2.8.5", "esbuild": "0.25.5", "eslint": "^9.14.0", - "eslint-import-resolver-typescript": "^4.0.0", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.32.0", "express-rate-limit": "^7.5.0", "fast-xml-parser": "^4.3.6", "file-type": "^21.0.0", @@ -65,7 +69,6 @@ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -95,29 +98,27 @@ "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -133,16 +134,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -168,7 +169,6 @@ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -202,6 +202,16 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", @@ -339,7 +349,6 @@ "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" @@ -349,13 +358,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -469,38 +478,28 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1277,18 +1276,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1301,16 +1296,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1319,9 +1304,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3223,7 +3208,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -3402,8 +3386,7 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0", - "peer": true + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", @@ -3579,8 +3562,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", @@ -3858,8 +3840,7 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/enabled": { "version": "2.0.0", @@ -4080,7 +4061,6 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4945,7 +4925,6 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -5958,7 +5937,6 @@ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -6281,8 +6259,7 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", @@ -8115,7 +8092,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -8479,8 +8455,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/yazl": { "version": "2.5.1", diff --git a/package.json b/package.json index 45809070..105a82e0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,12 @@ "dev:depend": "npm i && cd napcat.webui && npm i" }, "devDependencies": { + "@babel/core": "^7.28.0", + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/preset-typescript": "^7.24.7", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", "@eslint/compat": "^1.3.1", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.31.0", @@ -41,6 +46,7 @@ "ajv": "^8.13.0", "async-mutex": "^0.5.0", "commander": "^13.0.0", + "compressing": "^1.10.1", "cors": "^2.8.5", "esbuild": "0.25.5", "eslint": "^9.14.0", @@ -52,14 +58,13 @@ "globals": "^16.0.0", "json5": "^2.2.3", "multer": "^2.0.1", + "napcat.protobuf": "^1.1.4", "typescript": "^5.3.3", "typescript-eslint": "^8.35.1", "vite": "^6.0.1", "vite-plugin-cp": "^6.0.0", "vite-tsconfig-paths": "^5.1.0", - "napcat.protobuf": "^1.1.4", - "winston": "^3.17.0", - "compressing": "^1.10.1" + "winston": "^3.17.0" }, "dependencies": { "express": "^5.0.0", diff --git a/src/common/perf-cli.ts b/src/common/perf-cli.ts new file mode 100644 index 00000000..dbab70f9 --- /dev/null +++ b/src/common/perf-cli.ts @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +/** + * 性能监控命令行工具 + */ + +import { performanceMonitor } from '../common/performance-monitor'; + +function printColoredText(text: string, color: string): void { + const colors: Record = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + reset: '\x1b[0m', + bright: '\x1b[1m' + }; + + console.log(`${colors[color] || colors['white']}${text}${colors['reset']}`); +} + +function printHeader(title: string): void { + const line = '='.repeat(50); + printColoredText(line, 'cyan'); + printColoredText(title.toUpperCase().padStart((50 + title.length) / 2), 'bright'); + printColoredText(line, 'cyan'); +} + +function printSubHeader(title: string): void { + const line = '-'.repeat(30); + printColoredText(line, 'yellow'); + printColoredText(title, 'yellow'); + printColoredText(line, 'yellow'); +} + +function formatTime(ms: number): string { + if (ms < 1) { + return `${(ms * 1000).toFixed(2)}μs`; + } else if (ms < 1000) { + return `${ms.toFixed(2)}ms`; + } else { + return `${(ms / 1000).toFixed(2)}s`; + } +} + +function printStatsTable(stats: any[], title: string): void { + printSubHeader(`${title} (Top 10)`); + + if (stats.length === 0) { + printColoredText(' 暂无数据', 'yellow'); + return; + } + + console.log(); + console.log(' 排名 | 函数名称 | 调用次数 | 总耗时 | 平均耗时 | 最小耗时 | 最大耗时'); + console.log(' ' + '-'.repeat(100)); + + stats.slice(0, 10).forEach((stat, index) => { + const rank = (index + 1).toString().padEnd(4); + const name = stat.name.length > 30 ? stat.name.substring(0, 27) + '...' : stat.name.padEnd(30); + const callCount = stat.callCount.toString().padEnd(8); + const totalTime = formatTime(stat.totalTime).padEnd(10); + const avgTime = formatTime(stat.averageTime).padEnd(10); + const minTime = formatTime(stat.minTime).padEnd(10); + const maxTime = formatTime(stat.maxTime).padEnd(10); + + const color = index < 3 ? 'green' : 'white'; + printColoredText( + ` ${rank} | ${name} | ${callCount} | ${totalTime} | ${avgTime} | ${minTime} | ${maxTime}`, + color + ); + }); + + console.log(); +} + +function printSummary(): void { + const stats = performanceMonitor.getStats(); + const totalFunctions = stats.length; + const totalCalls = stats.reduce((sum, stat) => sum + stat.callCount, 0); + const totalTime = stats.reduce((sum, stat) => sum + stat.totalTime, 0); + const avgTimePerCall = totalCalls > 0 ? totalTime / totalCalls : 0; + + printSubHeader('📊 统计摘要'); + console.log(); + printColoredText(` 监控函数数量: ${totalFunctions}`, 'cyan'); + printColoredText(` 总调用次数: ${totalCalls}`, 'cyan'); + printColoredText(` 总耗时: ${formatTime(totalTime)}`, 'cyan'); + printColoredText(` 平均每次调用耗时: ${formatTime(avgTimePerCall)}`, 'cyan'); + console.log(); +} + +function main(): void { + const args = process.argv.slice(2); + const command = args[0] || 'report'; + + switch (command) { + case 'report': + case 'r': + printHeader('🚀 NapCat 性能监控报告'); + console.log(); + + printSummary(); + + const totalTimeStats = performanceMonitor.getTopByTotalTime(10); + const callCountStats = performanceMonitor.getTopByCallCount(10); + const avgTimeStats = performanceMonitor.getTopByAverageTime(10); + + printStatsTable(totalTimeStats, '🔥 总耗时排行榜'); + printStatsTable(callCountStats, '📈 调用次数排行榜'); + printStatsTable(avgTimeStats, '⏱️ 平均耗时排行榜'); + + break; + + case 'top': + case 't': + const type = args[1] || 'total'; + const limit = parseInt(args[2] || '10') || 10; + + switch (type) { + case 'total': + case 'time': + printHeader('🔥 总耗时排行榜'); + printStatsTable(performanceMonitor.getTopByTotalTime(limit), ''); + break; + case 'count': + case 'calls': + printHeader('📈 调用次数排行榜'); + printStatsTable(performanceMonitor.getTopByCallCount(limit), ''); + break; + case 'avg': + case 'average': + printHeader('⏱️ 平均耗时排行榜'); + printStatsTable(performanceMonitor.getTopByAverageTime(limit), ''); + break; + default: + printColoredText('未知的排行榜类型。可用类型: total, count, avg', 'red'); + } + break; + + case 'clear': + case 'c': + performanceMonitor.clear(); + printColoredText('✅ 性能统计数据已清空', 'green'); + break; + + case 'json': + case 'j': + const jsonStats = performanceMonitor.toJSON(); + console.log(JSON.stringify(jsonStats, null, 2)); + break; + + case 'help': + case 'h': + case '--help': + printHelp(); + break; + + default: + printColoredText(`未知命令: ${command}`, 'red'); + printHelp(); + process.exit(1); + } +} + +function printHelp(): void { + printHeader('📖 帮助信息'); + console.log(); + printColoredText('用法: napcat-perf [options]', 'cyan'); + console.log(); + printColoredText('命令:', 'yellow'); + console.log(' report, r 显示完整性能报告 (默认)'); + console.log(' top [limit] 显示指定类型的排行榜'); + console.log(' - total, time 按总耗时排序'); + console.log(' - count, calls 按调用次数排序'); + console.log(' - avg, average 按平均耗时排序'); + console.log(' clear, c 清空所有统计数据'); + console.log(' json, j 以JSON格式输出数据'); + console.log(' help, h 显示此帮助信息'); + console.log(); + printColoredText('示例:', 'yellow'); + console.log(' napcat-perf report'); + console.log(' napcat-perf top total 20'); + console.log(' napcat-perf top count'); + console.log(' napcat-perf clear'); + console.log(); +} + +// 如果直接运行此文件 +if (require.main === module) { + main(); +} + +export { main as runPerfMonitor }; diff --git a/src/common/performance-demo.ts b/src/common/performance-demo.ts new file mode 100644 index 00000000..20e73320 --- /dev/null +++ b/src/common/performance-demo.ts @@ -0,0 +1,87 @@ +/** + * 性能监控演示示例 + */ + +import { performanceMonitor } from './performance-monitor'; + +// 模拟一些函数调用来测试性能监控 +class ExampleService { + async fetchData(id: string): Promise { + // 模拟网络请求延迟 + await new Promise(resolve => setTimeout(resolve, Math.random() * 100)); + return `Data for ${id}`; + } + + processData(data: string): string { + // 模拟CPU密集型操作 + let result = data; + for (let i = 0; i < 1000; i++) { + result = result.split('').reverse().join(''); + } + return result; + } + + async saveData(data: string): Promise { + // 模拟保存操作 + console.log(`保存数据: ${data.length} 字符`); + await new Promise(resolve => setTimeout(resolve, Math.random() * 50)); + } +} + +// 工具函数 +function calculateHash(input: string): string { + let hash = 0; + for (let i = 0; i < input.length; i++) { + const char = input.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // 转换为32位整数 + } + return hash.toString(16); +} + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +// 演示函数 +export async function runPerformanceDemo(): Promise { + console.log('🚀 开始性能监控演示...\n'); + + const service = new ExampleService(); + + // 执行一些操作来生成性能数据 + for (let i = 0; i < 10; i++) { + try { + // 获取数据 + const data = await service.fetchData(`item-${i}`); + + // 处理数据 + const processed = service.processData(data); + + // 计算哈希 + const hash = calculateHash(processed); + + // 保存数据 + await service.saveData(`${processed}-${hash}`); + + console.log(`✅ 处理完成第 ${i + 1} 项`); + + // 随机延迟 + await delay(Math.random() * 20); + } catch (error) { + console.error(`❌ 处理第 ${i + 1} 项时出错:`, error); + } + } + + // 等待一小段时间确保所有异步操作完成 + await delay(100); + + console.log('\n📊 性能监控演示完成!'); + console.log('性能统计数据:'); + + // 显示性能统计 + performanceMonitor.printReport(); +} + +// 如果直接运行此文件 +if (require.main === module) { + runPerformanceDemo().catch(console.error); +} diff --git a/src/common/performance-monitor.ts b/src/common/performance-monitor.ts new file mode 100644 index 00000000..7d818033 --- /dev/null +++ b/src/common/performance-monitor.ts @@ -0,0 +1,316 @@ +/** + * 性能监控器 - 用于统计函数调用次数、耗时等信息 + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +export interface FunctionStats { + name: string; + callCount: number; + totalTime: number; + averageTime: number; + minTime: number; + maxTime: number; + fileName?: string; + lineNumber?: number; +} + +export class PerformanceMonitor { + private static instance: PerformanceMonitor; + private stats = new Map(); + private startTimes = new Map(); + private reportInterval: NodeJS.Timeout | null = null; + + static getInstance(): PerformanceMonitor { + if (!PerformanceMonitor.instance) { + PerformanceMonitor.instance = new PerformanceMonitor(); + // 启动定时统计报告 + PerformanceMonitor.instance.startPeriodicReport(); + } + return PerformanceMonitor.instance; + } + + /** + * 开始定时统计报告 (每60秒) + */ + private startPeriodicReport(): void { + if (this.reportInterval) { + clearInterval(this.reportInterval); + } + + this.reportInterval = setInterval(() => { + if (this.stats.size > 0) { + this.printPeriodicReport(); + this.writeDetailedLogToFile(); + } + }, 60000); // 60秒 + } + + /** + * 停止定时统计报告 + */ + stopPeriodicReport(): void { + if (this.reportInterval) { + clearInterval(this.reportInterval); + this.reportInterval = null; + } + } + + /** + * 打印定时统计报告 (简化版本) + */ + private printPeriodicReport(): void { + const now = new Date().toLocaleString(); + console.log(`\n=== 性能监控定时报告 [${now}] ===`); + + const totalFunctions = this.stats.size; + const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0); + const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0); + + console.log(`📊 总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms`); + + // 显示Top 5最活跃的函数 + console.log('\n🔥 最活跃函数 (Top 5):'); + this.getTopByCallCount(5).forEach((stat, index) => { + console.log(`${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms`); + }); + + // 显示Top 5最耗时的函数 + console.log('\n⏱️ 最耗时函数 (Top 5):'); + this.getTopByTotalTime(5).forEach((stat, index) => { + console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms`); + }); + + console.log('===============================\n'); + } + + /** + * 将详细统计数据写入日志文件 + */ + private writeDetailedLogToFile(): void { + try { + const now = new Date(); + const dateStr = now.toISOString().replace(/[:.]/g, '-').split('T')[0]; + const timeStr = now.toTimeString().split(' ')[0]?.replace(/:/g, '-') || 'unknown-time'; + const timestamp = `${dateStr}_${timeStr}`; + const fileName = `${timestamp}.log.txt`; + const logPath = path.join(process.cwd(), 'logs', fileName); + + // 确保logs目录存在 + const logsDir = path.dirname(logPath); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + + const totalFunctions = this.stats.size; + const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0); + const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0); + + let logContent = ''; + logContent += `=== 性能监控详细报告 ===\n`; + logContent += `生成时间: ${now.toLocaleString()}\n`; + logContent += `统计周期: 60秒\n`; + logContent += `总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms\n\n`; + + // 详细函数统计 + logContent += `=== 所有函数详细统计 ===\n`; + const allStats = this.getStats().sort((a, b) => b.totalTime - a.totalTime); + + allStats.forEach((stat, index) => { + logContent += `${index + 1}. 函数: ${stat.name}\n`; + logContent += ` 文件: ${stat.fileName || 'N/A'}\n`; + logContent += ` 行号: ${stat.lineNumber || 'N/A'}\n`; + logContent += ` 调用次数: ${stat.callCount}\n`; + logContent += ` 总耗时: ${stat.totalTime.toFixed(4)}ms\n`; + logContent += ` 平均耗时: ${stat.averageTime.toFixed(4)}ms\n`; + logContent += ` 最小耗时: ${stat.minTime === Infinity ? 'N/A' : stat.minTime.toFixed(4)}ms\n`; + logContent += ` 最大耗时: ${stat.maxTime.toFixed(4)}ms\n`; + logContent += ` 性能占比: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`; + logContent += `\n`; + }); + + // 排行榜统计 + logContent += `=== 总耗时排行榜 (Top 20) ===\n`; + this.getTopByTotalTime(20).forEach((stat, index) => { + logContent += `${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 平均: ${stat.averageTime.toFixed(2)}ms\n`; + }); + + logContent += `\n=== 调用次数排行榜 (Top 20) ===\n`; + this.getTopByCallCount(20).forEach((stat, index) => { + logContent += `${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms\n`; + }); + + logContent += `\n=== 平均耗时排行榜 (Top 20) ===\n`; + this.getTopByAverageTime(20).forEach((stat, index) => { + logContent += `${index + 1}. ${stat.name} - 平均: ${stat.averageTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms\n`; + }); + + logContent += `\n=== 性能热点分析 ===\n`; + // 找出最耗时的前10个函数 + const hotSpots = this.getTopByTotalTime(10); + hotSpots.forEach((stat, index) => { + const efficiency = stat.callCount / stat.totalTime; // 每毫秒的调用次数 + logContent += `${index + 1}. ${stat.name}\n`; + logContent += ` 性能影响: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`; + logContent += ` 调用效率: ${efficiency.toFixed(4)} 调用/ms\n`; + logContent += ` 优化建议: ${stat.averageTime > 10 ? '考虑优化此函数的执行效率' : + stat.callCount > 1000 ? '考虑减少此函数的调用频率' : + '性能表现良好'}\n\n`; + }); + + logContent += `=== 报告结束 ===\n`; + + // 写入文件 + fs.writeFileSync(logPath, logContent, 'utf8'); + console.log(`📄 详细性能报告已保存到: ${logPath}`); + + } catch (error) { + console.error('写入性能日志文件时出错:', error); + } + } + + /** + * 开始记录函数调用 + */ + startFunction(functionName: string, fileName?: string, lineNumber?: number): string { + const callId = `${functionName}_${Date.now()}_${Math.random()}`; + this.startTimes.set(callId, performance.now()); + + // 初始化或更新统计信息 + if (!this.stats.has(functionName)) { + this.stats.set(functionName, { + name: functionName, + callCount: 0, + totalTime: 0, + averageTime: 0, + minTime: Infinity, + maxTime: 0, + fileName, + lineNumber + }); + } + + const stat = this.stats.get(functionName)!; + stat.callCount++; + + return callId; + } + + /** + * 结束记录函数调用 + */ + endFunction(callId: string, functionName: string): void { + const startTime = this.startTimes.get(callId); + if (!startTime) return; + + const endTime = performance.now(); + const duration = endTime - startTime; + + this.startTimes.delete(callId); + + const stat = this.stats.get(functionName); + if (!stat) return; + + stat.totalTime += duration; + stat.averageTime = stat.totalTime / stat.callCount; + stat.minTime = Math.min(stat.minTime, duration); + stat.maxTime = Math.max(stat.maxTime, duration); + } + + /** + * 获取所有统计信息 + */ + getStats(): FunctionStats[] { + return Array.from(this.stats.values()); + } + + /** + * 获取排行榜 - 按总耗时排序 + */ + getTopByTotalTime(limit = 20): FunctionStats[] { + return this.getStats() + .sort((a, b) => b.totalTime - a.totalTime) + .slice(0, limit); + } + + /** + * 获取排行榜 - 按调用次数排序 + */ + getTopByCallCount(limit = 20): FunctionStats[] { + return this.getStats() + .sort((a, b) => b.callCount - a.callCount) + .slice(0, limit); + } + + /** + * 获取排行榜 - 按平均耗时排序 + */ + getTopByAverageTime(limit = 20): FunctionStats[] { + return this.getStats() + .sort((a, b) => b.averageTime - a.averageTime) + .slice(0, limit); + } + + /** + * 清空统计数据 + */ + clear(): void { + this.stats.clear(); + this.startTimes.clear(); + } + + /** + * 打印统计报告 + */ + printReport(): void { + console.log('\n=== 函数性能监控报告 ==='); + + console.log('\n🔥 总耗时排行榜 (Top 10):'); + this.getTopByTotalTime(10).forEach((stat, index) => { + console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 平均耗时: ${stat.averageTime.toFixed(2)}ms`); + }); + + console.log('\n📈 调用次数排行榜 (Top 10):'); + this.getTopByCallCount(10).forEach((stat, index) => { + console.log(`${index + 1}. ${stat.name} - 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均耗时: ${stat.averageTime.toFixed(2)}ms`); + }); + + console.log('\n⏱️ 平均耗时排行榜 (Top 10):'); + this.getTopByAverageTime(10).forEach((stat, index) => { + console.log(`${index + 1}. ${stat.name} - 平均耗时: ${stat.averageTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms`); + }); + + console.log('\n========================\n'); + } + + /** + * 获取JSON格式的统计数据 + */ + toJSON(): FunctionStats[] { + return this.getStats(); + } +} + +// 全局性能监控器实例 +export const performanceMonitor = PerformanceMonitor.getInstance(); + +// 在进程退出时打印报告并停止定时器 +if (typeof process !== 'undefined') { + process.on('exit', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + }); + + process.on('SIGINT', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + process.exit(0); + }); + + process.on('SIGTERM', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + process.exit(0); + }); +} diff --git a/src/onebot/index.ts b/src/onebot/index.ts index 1cacec7c..4d6eb932 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -50,6 +50,7 @@ import { import { OB11Message } from './types'; import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; import { OB11HttpSSEServerAdapter } from './network/http-server-sse'; +import { OB11PluginAdapter } from './network/plugin'; //OneBot实现类 export class NapCatOneBot11Adapter { @@ -113,9 +114,9 @@ export class NapCatOneBot11Adapter { //创建NetWork服务 // 注册Plugin 如果需要基于NapCat进行快速开发 - // this.networkManager.registerAdapter( - // new OB11PluginAdapter('myPlugin', this.core, this,this.actions) - // ); + this.networkManager.registerAdapter( + new OB11PluginAdapter('myPlugin', this.core, this,this.actions) + ); for (const key of ob11Config.network.httpServers) { if (key.enable) { this.networkManager.registerAdapter( diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 484a58b5..c0c67d3e 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -4,8 +4,5 @@ import { ActionMap } from '@/onebot/action'; import { OB11PluginAdapter } from '@/onebot/network/plugin'; export const plugin_onmessage = async (adapter: string, _core: NapCatCore, _obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginAdapter) => { - if (message.raw_message === 'ping') { - const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config); - console.log(ret); - } + }; diff --git a/src/webui/src/performance-api.ts b/src/webui/src/performance-api.ts new file mode 100644 index 00000000..9b024219 --- /dev/null +++ b/src/webui/src/performance-api.ts @@ -0,0 +1,405 @@ +/** + * 性能监控API - 提供HTTP接口查看性能统计 + */ +import { Router, Request, Response } from 'express'; +import { performanceMonitor } from '@/common/performance-monitor'; + +export function createPerformanceRouter(): Router { + const router = Router(); + + // 获取所有统计数据 + router.get('/stats', (_req: Request, res: Response) => { + try { + const stats = performanceMonitor.getStats(); + res.json({ + success: true, + data: stats, + count: stats.length + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + // 获取总耗时排行榜 + router.get('/stats/total-time', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByTotalTime(limit); + res.json({ + success: true, + data: stats, + count: stats.length + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + // 获取调用次数排行榜 + router.get('/stats/call-count', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByCallCount(limit); + res.json({ + success: true, + data: stats, + count: stats.length + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + // 获取平均耗时排行榜 + router.get('/stats/average-time', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByAverageTime(limit); + res.json({ + success: true, + data: stats, + count: stats.length + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + // 清空统计数据 + router.post('/clear', (_req: Request, res: Response) => { + try { + performanceMonitor.clear(); + res.json({ + success: true, + message: 'Performance statistics cleared' + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + // 获取性能报告页面 + router.get('/report', (_req: Request, res: Response) => { + try { + const totalTimeStats = performanceMonitor.getTopByTotalTime(10); + const callCountStats = performanceMonitor.getTopByCallCount(10); + const averageTimeStats = performanceMonitor.getTopByAverageTime(10); + + const html = generateReportHTML(totalTimeStats, callCountStats, averageTimeStats); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(html); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + }); + + return router; +} + +function generateReportHTML(totalTimeStats: any[], callCountStats: any[], averageTimeStats: any[]): string { + return ` + + + + + + NapCat 性能监控报告 + + + +
+
+

🚀 NapCat 性能监控报告

+

实时函数调用性能统计 - ${new Date().toLocaleString()}

+
+ +
+
+
+

🔥 总耗时排行榜 (Top 10)

+
    + ${totalTimeStats.map((stat, index) => ` +
  • + ${index + 1} + ${stat.name} +
    +
    + ${stat.totalTime.toFixed(2)}ms + 总耗时 +
    +
    + ${stat.callCount} + 调用次数 +
    +
    + ${stat.averageTime.toFixed(2)}ms + 平均耗时 +
    +
    +
  • + `).join('')} +
+
+ +
+

📈 调用次数排行榜 (Top 10)

+
    + ${callCountStats.map((stat, index) => ` +
  • + ${index + 1} + ${stat.name} +
    +
    + ${stat.callCount} + 调用次数 +
    +
    + ${stat.totalTime.toFixed(2)}ms + 总耗时 +
    +
    + ${stat.averageTime.toFixed(2)}ms + 平均耗时 +
    +
    +
  • + `).join('')} +
+
+ +
+

⏱️ 平均耗时排行榜 (Top 10)

+
    + ${averageTimeStats.map((stat, index) => ` +
  • + ${index + 1} + ${stat.name} +
    +
    + ${stat.averageTime.toFixed(2)}ms + 平均耗时 +
    +
    + ${stat.callCount} + 调用次数 +
    +
    + ${stat.totalTime.toFixed(2)}ms + 总耗时 +
    +
    +
  • + `).join('')} +
+
+
+
+
+ + + + + + + + `; +} diff --git a/vite-plugin-performance-monitor.ts b/vite-plugin-performance-monitor.ts new file mode 100644 index 00000000..c52090b3 --- /dev/null +++ b/vite-plugin-performance-monitor.ts @@ -0,0 +1,306 @@ +import { Plugin } from 'vite'; +import { parse } from '@babel/parser'; +import traverseDefault from '@babel/traverse'; +import generateDefault from '@babel/generator'; +import * as t from '@babel/types'; +import { resolve } from 'path'; + +// @ts-ignore +const traverse = traverseDefault.default || traverseDefault; +// @ts-ignore +const generate = generateDefault.default || generateDefault; + +interface PerformancePluginOptions { +} + +/** + * Vite插件:自动在函数中插入性能监控代码 + */ +export function performanceMonitorPlugin(options: PerformancePluginOptions): Plugin { + const exclude = [/node_modules/, /\.min\./, /performance-monitor\.ts$/]; + + return { + name: 'performance-monitor', + transform(code: string, id: string) { + const fileName = id.replace(process.cwd(), '').replace(/\\/g, '/'); + + // 排除规则检查 + if (exclude.some(pattern => pattern.test(id))) { + return null; + } + + try { + // 解析AST + const ast = parse(code, { + sourceType: 'module', + plugins: [ + 'typescript', + 'decorators-legacy', + 'classProperties', + 'asyncGenerators', + 'bigInt', + 'dynamicImport', + 'exportDefaultFrom', + 'exportNamespaceFrom', + 'nullishCoalescingOperator', + 'numericSeparator', + 'optionalCatchBinding', + 'optionalChaining', + 'topLevelAwait' + ] + }); + + let hasMonitorImport = false; + let hasMonitorExport = false; + let needsMonitor = false; + // 遍历AST + traverse(ast, { + // 检查是否已经导入了性能监控器 + ImportDeclaration(path) { + if (path.node.source.value.includes('performance-monitor')) { + hasMonitorImport = true; + } + }, + + // 检查是否已经导出了性能监控器 + ExportNamedDeclaration(path) { + if (path.node.declaration && t.isVariableDeclaration(path.node.declaration)) { + path.node.declaration.declarations.forEach(declarator => { + if (t.isIdentifier(declarator.id) && declarator.id.name === 'performanceMonitor') { + hasMonitorExport = true; + } + }); + } + }, + + // 检查变量声明 + VariableDeclaration(path) { + path.node.declarations.forEach(declarator => { + if (t.isIdentifier(declarator.id) && declarator.id.name === 'performanceMonitor') { + hasMonitorExport = true; + } + }); + }, + + // 处理函数声明 + FunctionDeclaration(path) { + const functionName = path.node.id?.name || 'anonymous'; + const lineNumber = path.node.loc?.start.line || 0; + + instrumentFunction(path, functionName, fileName, lineNumber); + needsMonitor = true; + }, + + // 处理箭头函数 + ArrowFunctionExpression(path) { + const parent = path.parent; + let functionName = 'anonymous'; + + if (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)) { + functionName = parent.id.name; + } else if (t.isProperty(parent) && t.isIdentifier(parent.key)) { + functionName = parent.key.name; + } else if (t.isAssignmentExpression(parent) && t.isMemberExpression(parent.left)) { + const property = parent.left.property; + if (t.isIdentifier(property)) { + functionName = property.name; + } + } + + const lineNumber = path.node.loc?.start.line || 0; + instrumentFunction(path, functionName, fileName, lineNumber); + needsMonitor = true; + }, + + // 处理函数表达式 + FunctionExpression(path) { + const functionName = path.node.id?.name || 'anonymous'; + const lineNumber = path.node.loc?.start.line || 0; + + instrumentFunction(path, functionName, fileName, lineNumber); + needsMonitor = true; + }, + + // 处理类方法 + ClassMethod(path) { + const methodName = t.isIdentifier(path.node.key) ? path.node.key.name : 'anonymous'; + const className = getClassName(path); + const functionName = `${className}.${methodName}`; + const lineNumber = path.node.loc?.start.line || 0; + + instrumentFunction(path, functionName, fileName, lineNumber); + needsMonitor = true; + }, + + // 处理对象方法 + ObjectMethod(path) { + const methodName = t.isIdentifier(path.node.key) ? path.node.key.name : 'anonymous'; + const lineNumber = path.node.loc?.start.line || 0; + + instrumentFunction(path, methodName, fileName, lineNumber); + needsMonitor = true; + } + }); + + if (!needsMonitor) { + return null; + } + + // 如果需要监控但还没有导入且没有导出,则添加导入语句 + if (!hasMonitorImport && !hasMonitorExport) { + const importDeclaration = t.importDeclaration( + [t.importSpecifier(t.identifier('performanceMonitor'), t.identifier('performanceMonitor'))], + t.stringLiteral('@/common/performance-monitor') + ); + ast.program.body.unshift(importDeclaration); + } + + // 生成新代码 + const result = generate(ast, { + retainLines: true, + compact: false + }); + + return { + code: result.code, + map: result.map + }; + + } catch (error) { + console.warn(`性能监控插件处理文件 ${id} 时出错:`, error); + return null; + } + } + }; +} + +/** + * 为函数添加性能监控代码 + */ +function instrumentFunction( + path: any, + functionName: string, + fileName: string, + lineNumber: number +) { + // 跳过已经被监控的函数 + if (functionName.includes('__perf_monitor__')) { + return; + } + + const isAsync = path.node.async; + const body = path.node.body; + + // 确保函数体是块语句 + if (!t.isBlockStatement(body)) { + // 对于箭头函数的表达式体,转换为块语句 + const returnStatement = t.returnStatement(body); + path.node.body = t.blockStatement([returnStatement]); + } + + const blockBody = path.node.body as t.BlockStatement; + + // 生成唯一的调用ID变量名 + const callIdVar = `__perf_monitor_${functionName.replace(/[^a-zA-Z0-9]/g, '_')}_${lineNumber}__`; + + // 创建开始监控的语句 + const startMonitoring = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(callIdVar), + t.callExpression( + t.memberExpression( + t.identifier('performanceMonitor'), + t.identifier('startFunction') + ), + [ + t.stringLiteral(functionName), + t.stringLiteral(fileName), + t.numericLiteral(lineNumber) + ] + ) + ) + ]); + + // 创建结束监控的语句 + const endMonitoring = t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('performanceMonitor'), + t.identifier('endFunction') + ), + [ + t.identifier(callIdVar), + t.stringLiteral(functionName) + ] + ) + ); + + if (isAsync) { + // 对于异步函数,需要在所有可能的返回点添加监控结束 + instrumentAsyncFunction(blockBody, startMonitoring, endMonitoring, callIdVar, functionName); + } else { + // 对于同步函数,使用try-finally确保监控结束 + instrumentSyncFunction(blockBody, startMonitoring, endMonitoring); + } +} + +/** + * 为同步函数添加监控 + */ +function instrumentSyncFunction( + blockBody: t.BlockStatement, + startMonitoring: t.VariableDeclaration, + endMonitoring: t.ExpressionStatement +) { + const originalStatements = [...blockBody.body]; + + const tryStatement = t.tryStatement( + t.blockStatement(originalStatements), + null, + t.blockStatement([endMonitoring]) + ); + + blockBody.body = [startMonitoring, tryStatement]; +} + +/** + * 为异步函数添加监控 + */ +function instrumentAsyncFunction( + blockBody: t.BlockStatement, + startMonitoring: t.VariableDeclaration, + endMonitoring: t.ExpressionStatement, + callIdVar: string, + functionName: string +) { + const originalStatements = [...blockBody.body]; + + // 创建包装的异步执行体 + const asyncTryStatement = t.tryStatement( + t.blockStatement(originalStatements), + null, + t.blockStatement([endMonitoring]) + ); + + blockBody.body = [startMonitoring, asyncTryStatement]; +} + +/** + * 获取类名 + */ +function getClassName(path: any): string { + let current = path; + while (current) { + if (current.isClassDeclaration && current.isClassDeclaration()) { + return current.node.id?.name || 'AnonymousClass'; + } else if (current.isClassExpression && current.isClassExpression()) { + return current.node.id?.name || 'AnonymousClass'; + } else if (current.node && (t.isClassDeclaration(current.node) || t.isClassExpression(current.node))) { + return current.node.id?.name || 'AnonymousClass'; + } + current = current.parent; + } + return 'UnknownClass'; +} + +export default performanceMonitorPlugin; diff --git a/vite.config.ts b/vite.config.ts index 92ac954c..78e65f26 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig, PluginOption, UserConfig } from 'vite'; import { resolve } from 'path'; import nodeResolve from '@rollup/plugin-node-resolve'; import { builtinModules } from 'module'; +import { performanceMonitorPlugin } from './vite-plugin-performance-monitor'; //依赖排除 const external = [ 'silk-wasm', @@ -21,6 +22,11 @@ if (process.env.NAPCAT_BUILDSYS == 'linux') { } const UniversalBaseConfigPlugin: PluginOption[] = [ + performanceMonitorPlugin({ + enabled: process.env.NODE_ENV !== 'production', + exclude: [/node_modules/, /\.min\./, /performance-monitor/], + include: [/\.ts$/, /\.js$/] + }), cp({ targets: [ { src: './manifest.json', dest: 'dist' }, @@ -44,6 +50,11 @@ const UniversalBaseConfigPlugin: PluginOption[] = [ ]; const FrameworkBaseConfigPlugin: PluginOption[] = [ + performanceMonitorPlugin({ + enabled: process.env.NODE_ENV !== 'production', + exclude: [/node_modules/, /\.min\./, /performance-monitor/], + include: [/\.ts$/, /\.js$/] + }), cp({ targets: [ { src: './manifest.json', dest: 'dist' }, @@ -64,6 +75,11 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [ ]; const ShellBaseConfigPlugin: PluginOption[] = [ + performanceMonitorPlugin({ + enabled: process.env.NODE_ENV !== 'production', + exclude: [/node_modules/, /\.min\./, /performance-monitor/], + include: [/\.ts$/, /\.js$/] + }), cp({ targets: [ { src: './src/native/packet', dest: 'dist/moehoo', flatten: false },