This commit is contained in:
手瓜一十雪 2025-08-06 18:43:44 +08:00
parent 9f0ba6d385
commit 5abdc8c538
10 changed files with 1400 additions and 95 deletions

145
package-lock.json generated
View File

@ -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",

View File

@ -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",

197
src/common/perf-cli.ts Normal file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env node
/**
*
*/
import { performanceMonitor } from '../common/performance-monitor';
function printColoredText(text: string, color: string): void {
const colors: Record<string, string> = {
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 <command> [options]', 'cyan');
console.log();
printColoredText('命令:', 'yellow');
console.log(' report, r 显示完整性能报告 (默认)');
console.log(' top <type> [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 };

View File

@ -0,0 +1,87 @@
/**
*
*/
import { performanceMonitor } from './performance-monitor';
// 模拟一些函数调用来测试性能监控
class ExampleService {
async fetchData(id: string): Promise<string> {
// 模拟网络请求延迟
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<void> {
// 模拟保存操作
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<void> {
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);
}

View File

@ -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<string, FunctionStats>();
private startTimes = new Map<string, number>();
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);
});
}

View File

@ -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(

View File

@ -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);
}
};

View File

@ -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 `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NapCat </title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 2.5em;
font-weight: 300;
}
.header p {
margin: 10px 0 0;
opacity: 0.9;
}
.content {
padding: 30px;
}
.section {
margin-bottom: 40px;
}
.section h2 {
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
}
.stats-card {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
border-left: 4px solid #667eea;
}
.stats-card h3 {
margin: 0 0 15px;
color: #667eea;
display: flex;
align-items: center;
}
.stats-card h3::before {
content: '';
width: 20px;
height: 20px;
margin-right: 10px;
border-radius: 50%;
background: #667eea;
}
.stats-list {
list-style: none;
padding: 0;
margin: 0;
}
.stats-item {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.stats-item:last-child {
border-bottom: none;
}
.function-name {
font-weight: 600;
color: #495057;
flex: 1;
margin-right: 15px;
word-break: break-all;
}
.stats-values {
display: flex;
gap: 15px;
font-size: 0.9em;
}
.stat-value {
text-align: center;
}
.stat-label {
font-size: 0.8em;
color: #6c757d;
display: block;
}
.stat-number {
font-weight: bold;
color: #28a745;
}
.rank {
background: #667eea;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9em;
font-weight: bold;
margin-right: 15px;
}
.refresh-btn {
position: fixed;
bottom: 30px;
right: 30px;
background: #667eea;
color: white;
border: none;
border-radius: 50px;
padding: 15px 20px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
}
.refresh-btn:hover {
background: #5a6fd8;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6);
}
.clear-btn {
background: #dc3545;
margin-left: 10px;
}
.clear-btn:hover {
background: #c82333;
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
}
.stats-values {
flex-direction: column;
gap: 5px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 NapCat </h1>
<p> - ${new Date().toLocaleString()}</p>
</div>
<div class="content">
<div class="stats-grid">
<div class="stats-card">
<h3>🔥 (Top 10)</h3>
<ul class="stats-list">
${totalTimeStats.map((stat, index) => `
<li class="stats-item">
<span class="rank">${index + 1}</span>
<span class="function-name">${stat.name}</span>
<div class="stats-values">
<div class="stat-value">
<span class="stat-number">${stat.totalTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.callCount}</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.averageTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
</div>
</li>
`).join('')}
</ul>
</div>
<div class="stats-card">
<h3>📈 (Top 10)</h3>
<ul class="stats-list">
${callCountStats.map((stat, index) => `
<li class="stats-item">
<span class="rank">${index + 1}</span>
<span class="function-name">${stat.name}</span>
<div class="stats-values">
<div class="stat-value">
<span class="stat-number">${stat.callCount}</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.totalTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.averageTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
</div>
</li>
`).join('')}
</ul>
</div>
<div class="stats-card">
<h3> (Top 10)</h3>
<ul class="stats-list">
${averageTimeStats.map((stat, index) => `
<li class="stats-item">
<span class="rank">${index + 1}</span>
<span class="function-name">${stat.name}</span>
<div class="stats-values">
<div class="stat-value">
<span class="stat-number">${stat.averageTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.callCount}</span>
<span class="stat-label"></span>
</div>
<div class="stat-value">
<span class="stat-number">${stat.totalTime.toFixed(2)}ms</span>
<span class="stat-label"></span>
</div>
</div>
</li>
`).join('')}
</ul>
</div>
</div>
</div>
</div>
<button class="refresh-btn" onclick="window.location.reload()">
🔄
</button>
<button class="refresh-btn clear-btn" onclick="clearStats()">
🗑
</button>
<script>
function clearStats() {
if (confirm('确定要清空所有性能统计数据吗?')) {
fetch('/performance/clear', {
method: 'POST'
}).then(response => {
if (response.ok) {
alert('统计数据已清空');
window.location.reload();
} else {
alert('清空失败');
}
}).catch(error => {
alert('清空失败: ' + error.message);
});
}
}
// 自动刷新
setInterval(() => {
window.location.reload();
}, 30000); // 30秒自动刷新
</script>
</body>
</html>
`;
}

View File

@ -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;

View File

@ -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 },