mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-11 23:40:24 +00:00
fix
This commit is contained in:
197
src/common/perf-cli.ts
Normal file
197
src/common/perf-cli.ts
Normal 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 };
|
||||
87
src/common/performance-demo.ts
Normal file
87
src/common/performance-demo.ts
Normal 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);
|
||||
}
|
||||
316
src/common/performance-monitor.ts
Normal file
316
src/common/performance-monitor.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
405
src/webui/src/performance-api.ts
Normal file
405
src/webui/src/performance-api.ts
Normal 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>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user