feat(profiler): 实现高级性能分析器 (#248)

* feat(profiler): 实现高级性能分析器

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖

* test(core): 添加 ProfilerSDK 和 AdvancedProfilerCollector 测试覆盖
This commit is contained in:
YHH
2025-11-30 00:22:47 +08:00
committed by GitHub
parent 359886c72f
commit 374e08a79e
35 changed files with 4168 additions and 9096 deletions

View File

@@ -90,33 +90,10 @@ export class Core {
*/
private _serviceContainer: ServiceContainer;
/**
* 定时器管理器
*
* 负责管理所有的游戏定时器。
*/
public _timerManager: TimerManager;
/**
* 性能监控器
*
* 监控游戏性能并提供优化建议。
*/
public _performanceMonitor: PerformanceMonitor;
/**
* 对象池管理器
*
* 管理所有对象池的生命周期。
*/
public _poolManager: PoolManager;
/**
* 调试管理器
*
* 负责收集和发送调试数据。
*/
public _debugManager?: DebugManager;
private _timerManager: TimerManager;
private _performanceMonitor: PerformanceMonitor;
private _poolManager: PoolManager;
private _debugManager?: DebugManager;
/**
* 场景管理器

View File

@@ -923,10 +923,6 @@ export class QuerySystem {
return this._archetypeSystem.getEntityArchetype(entity);
}
// ============================================================
// 响应式查询支持(内部智能缓存)
// ============================================================
/**
* 响应式查询集合(内部使用,作为智能缓存)
* 传统查询API(queryAll/queryAny/queryNone)内部自动使用响应式查询优化性能

View File

@@ -915,8 +915,6 @@ export class Scene implements IScene {
SceneSerializer.deserialize(this, saveData, options);
}
// ==================== 增量序列化 API ====================
/** 增量序列化的基础快照 */
private _incrementalBaseSnapshot?: unknown;

View File

@@ -891,10 +891,6 @@ export abstract class EntitySystem implements ISystemBase, IService {
// 子类可以重写此方法进行清理操作
}
// ============================================================
// 类型安全的辅助方法
// ============================================================
/**
* 类型安全地获取单个组件
*

View File

@@ -121,8 +121,6 @@ export class World {
this._services = new ServiceContainer();
}
// ===== 服务容器 =====
/**
* World级别的服务容器
* 用于管理World范围内的全局服务
@@ -131,8 +129,6 @@ export class World {
return this._services;
}
// ===== Scene管理 =====
/**
* 创建并添加Scene到World
*/
@@ -267,8 +263,6 @@ export class World {
return this._activeScenes.size;
}
// ===== 全局System管理 =====
/**
* 添加全局System
* 全局System会在所有激活Scene之前更新
@@ -317,8 +311,6 @@ export class World {
return null;
}
// ===== World生命周期 =====
/**
* 启动World
*/
@@ -435,8 +427,6 @@ export class World {
this._activeScenes.clear();
}
// ===== 状态信息 =====
/**
* 获取World状态
*/
@@ -484,8 +474,6 @@ export class World {
return stats;
}
// ===== 私有方法 =====
/**
* 检查是否应该执行自动清理
*/
@@ -528,8 +516,6 @@ export class World {
}
}
// ===== 访问器 =====
/**
* 检查World是否激活
*/

View File

@@ -87,8 +87,6 @@ export class WorldManager implements IService {
});
}
// ===== World管理 =====
/**
* 创建新World
*/
@@ -184,8 +182,6 @@ export class WorldManager implements IService {
return world?.isActive ?? false;
}
// ===== 批量操作 =====
/**
* 更新所有活跃的World
*
@@ -292,8 +288,6 @@ export class WorldManager implements IService {
return null;
}
// ===== 统计和监控 =====
/**
* 获取WorldManager统计信息
*/
@@ -342,8 +336,6 @@ export class WorldManager implements IService {
};
}
// ===== 生命周期管理 =====
/**
* 清理空World
*/
@@ -396,8 +388,6 @@ export class WorldManager implements IService {
this.destroy();
}
// ===== 私有方法 =====
/**
* 判断World是否应该被清理
* 清理策略:
@@ -423,8 +413,6 @@ export class WorldManager implements IService {
return !hasEntities && isOldEnough;
}
// ===== 访问器 =====
/**
* 获取World总数
*/

View File

@@ -0,0 +1,509 @@
/**
* 高级性能分析数据收集器
*
* 整合 ProfilerSDK 和现有 PerformanceMonitor 的数据,
* 提供统一的高级性能分析数据接口
*/
import { ProfilerSDK } from '../Profiler/ProfilerSDK';
import {
ProfileCategory,
ProfileFrame,
ProfileReport,
MemorySnapshot
} from '../Profiler/ProfilerTypes';
import { Time } from '../Time';
/**
* 旧版 PerformanceMonitor 接口 (用于兼容)
*/
export interface ILegacyPerformanceMonitor {
getAllSystemStats?: () => Map<string, {
averageTime: number;
minTime?: number;
maxTime?: number;
executionCount?: number;
}>;
getAllSystemData?: () => Map<string, {
executionTime: number;
entityCount?: number;
}>;
}
/**
* 高级性能数据接口
*/
export interface IAdvancedProfilerData {
/** 当前帧信息 */
currentFrame: {
frameNumber: number;
frameTime: number;
fps: number;
memory: MemorySnapshot;
};
/** 帧时间历史 (用于绘制图表) */
frameTimeHistory: Array<{
frameNumber: number;
time: number;
duration: number;
}>;
/** 按类别分组的统计 */
categoryStats: Array<{
category: string;
totalTime: number;
percentOfFrame: number;
sampleCount: number;
expanded?: boolean;
items: Array<{
name: string;
inclusiveTime: number;
exclusiveTime: number;
callCount: number;
percentOfCategory: number;
percentOfFrame: number;
}>;
}>;
/** 热点函数列表 */
hotspots: Array<{
name: string;
category: string;
inclusiveTime: number;
inclusiveTimePercent: number;
exclusiveTime: number;
exclusiveTimePercent: number;
callCount: number;
avgCallTime: number;
}>;
/** 调用关系数据 */
callGraph: {
/** 当前选中的函数 */
currentFunction: string | null;
/** 调用当前函数的函数列表 */
callers: Array<{
name: string;
callCount: number;
totalTime: number;
percentOfCurrent: number;
}>;
/** 当前函数调用的函数列表 */
callees: Array<{
name: string;
callCount: number;
totalTime: number;
percentOfCurrent: number;
}>;
};
/** 长任务列表 */
longTasks: Array<{
startTime: number;
duration: number;
attribution: string[];
}>;
/** 内存趋势 */
memoryTrend: Array<{
time: number;
usedMB: number;
totalMB: number;
gcCount: number;
}>;
/** 统计摘要 */
summary: {
totalFrames: number;
averageFrameTime: number;
minFrameTime: number;
maxFrameTime: number;
p95FrameTime: number;
p99FrameTime: number;
currentMemoryMB: number;
peakMemoryMB: number;
gcCount: number;
longTaskCount: number;
};
}
/**
* 高级性能分析数据收集器
*/
export class AdvancedProfilerCollector {
private selectedFunction: string | null = null;
private peakMemory = 0;
constructor() {
// ProfilerSDK 通过静态方法访问
}
/**
* 设置选中的函数(用于调用关系视图)
*/
public setSelectedFunction(name: string | null): void {
this.selectedFunction = name;
}
/**
* 收集高级性能数据
*/
public collectAdvancedData(performanceMonitor?: ILegacyPerformanceMonitor): IAdvancedProfilerData {
const frameHistory = ProfilerSDK.getFrameHistory();
const currentFrame = ProfilerSDK.getCurrentFrame();
const report = ProfilerSDK.getReport(300);
const currentMemory = currentFrame?.memory || this.getDefaultMemory();
if (currentMemory.usedHeapSize > this.peakMemory) {
this.peakMemory = currentMemory.usedHeapSize;
}
return {
currentFrame: this.buildCurrentFrameData(currentFrame),
frameTimeHistory: this.buildFrameTimeHistory(frameHistory),
categoryStats: this.buildCategoryStats(currentFrame, performanceMonitor),
hotspots: this.buildHotspots(report),
callGraph: this.buildCallGraph(report),
longTasks: report.longTasks,
memoryTrend: this.buildMemoryTrend(report.memoryTrend),
summary: this.buildSummary(report, currentMemory)
};
}
/**
* 从现有 PerformanceMonitor 数据构建兼容格式
*/
public collectFromLegacyMonitor(performanceMonitor: ILegacyPerformanceMonitor | null): IAdvancedProfilerData {
if (!performanceMonitor) {
return this.createEmptyData();
}
const systemStats = performanceMonitor.getAllSystemStats?.() || new Map();
const systemData = performanceMonitor.getAllSystemData?.() || new Map();
const frameTime = Time.deltaTime * 1000;
const fps = frameTime > 0 ? Math.round(1000 / frameTime) : 0;
const categoryStats = this.buildCategoryStatsFromLegacy(systemStats, systemData, frameTime);
const hotspots = this.buildHotspotsFromLegacy(systemStats, systemData, frameTime);
return {
currentFrame: {
frameNumber: 0,
frameTime,
fps,
memory: this.getCurrentMemory()
},
frameTimeHistory: [],
categoryStats,
hotspots,
callGraph: {
currentFunction: this.selectedFunction,
callers: [],
callees: []
},
longTasks: [],
memoryTrend: [],
summary: {
totalFrames: 0,
averageFrameTime: frameTime,
minFrameTime: frameTime,
maxFrameTime: frameTime,
p95FrameTime: frameTime,
p99FrameTime: frameTime,
currentMemoryMB: this.getCurrentMemory().usedHeapSize / (1024 * 1024),
peakMemoryMB: this.peakMemory / (1024 * 1024),
gcCount: 0,
longTaskCount: 0
}
};
}
private buildCurrentFrameData(frame: ProfileFrame | null): IAdvancedProfilerData['currentFrame'] {
if (!frame) {
const frameTime = Time.deltaTime * 1000;
return {
frameNumber: 0,
frameTime,
fps: frameTime > 0 ? Math.round(1000 / frameTime) : 0,
memory: this.getCurrentMemory()
};
}
return {
frameNumber: frame.frameNumber,
frameTime: frame.duration,
fps: frame.duration > 0 ? Math.round(1000 / frame.duration) : 0,
memory: frame.memory
};
}
private buildFrameTimeHistory(frames: ProfileFrame[]): IAdvancedProfilerData['frameTimeHistory'] {
return frames.map(f => ({
frameNumber: f.frameNumber,
time: f.startTime,
duration: f.duration
}));
}
private buildCategoryStats(
frame: ProfileFrame | null,
performanceMonitor?: any
): IAdvancedProfilerData['categoryStats'] {
const result: IAdvancedProfilerData['categoryStats'] = [];
if (frame && frame.categoryStats.size > 0) {
const frameDuration = frame.duration || 1;
for (const [category, stats] of frame.categoryStats) {
const categoryItems = frame.sampleStats
.filter(s => s.category === category)
.map(s => ({
name: s.name,
inclusiveTime: s.inclusiveTime,
exclusiveTime: s.exclusiveTime,
callCount: s.callCount,
percentOfCategory: stats.totalTime > 0
? (s.inclusiveTime / stats.totalTime) * 100
: 0,
percentOfFrame: (s.inclusiveTime / frameDuration) * 100
}))
.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
result.push({
category,
totalTime: stats.totalTime,
percentOfFrame: stats.percentOfFrame,
sampleCount: stats.sampleCount,
items: categoryItems
});
}
}
if (performanceMonitor && result.length === 0) {
const systemStats = performanceMonitor.getAllSystemStats?.() || new Map();
const systemData = performanceMonitor.getAllSystemData?.() || new Map();
const frameTime = Time.deltaTime * 1000 || 1;
return this.buildCategoryStatsFromLegacy(systemStats, systemData, frameTime);
}
return result.sort((a, b) => b.totalTime - a.totalTime);
}
private buildCategoryStatsFromLegacy(
systemStats: Map<string, any>,
systemData: Map<string, any>,
frameTime: number
): IAdvancedProfilerData['categoryStats'] {
const ecsItems: IAdvancedProfilerData['categoryStats'][0]['items'] = [];
let totalECSTime = 0;
for (const [name, stats] of systemStats.entries()) {
const data = systemData.get(name);
const execTime = data?.executionTime || stats?.averageTime || 0;
totalECSTime += execTime;
ecsItems.push({
name,
inclusiveTime: execTime,
exclusiveTime: execTime,
callCount: 1,
percentOfCategory: 0,
percentOfFrame: frameTime > 0 ? (execTime / frameTime) * 100 : 0
});
}
for (const item of ecsItems) {
item.percentOfCategory = totalECSTime > 0
? (item.inclusiveTime / totalECSTime) * 100
: 0;
}
ecsItems.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
if (ecsItems.length === 0) {
return [];
}
return [{
category: ProfileCategory.ECS,
totalTime: totalECSTime,
percentOfFrame: frameTime > 0 ? (totalECSTime / frameTime) * 100 : 0,
sampleCount: ecsItems.length,
items: ecsItems
}];
}
private buildHotspots(report: ProfileReport): IAdvancedProfilerData['hotspots'] {
const totalTime = report.hotspots.reduce((sum, h) => sum + h.inclusiveTime, 0) || 1;
return report.hotspots.slice(0, 50).map(h => ({
name: h.name,
category: h.category,
inclusiveTime: h.inclusiveTime,
inclusiveTimePercent: (h.inclusiveTime / totalTime) * 100,
exclusiveTime: h.exclusiveTime,
exclusiveTimePercent: (h.exclusiveTime / totalTime) * 100,
callCount: h.callCount,
avgCallTime: h.averageTime
}));
}
private buildHotspotsFromLegacy(
systemStats: Map<string, any>,
systemData: Map<string, any>,
frameTime: number
): IAdvancedProfilerData['hotspots'] {
const hotspots: IAdvancedProfilerData['hotspots'] = [];
for (const [name, stats] of systemStats.entries()) {
const data = systemData.get(name);
const execTime = data?.executionTime || stats?.averageTime || 0;
hotspots.push({
name,
category: ProfileCategory.ECS,
inclusiveTime: execTime,
inclusiveTimePercent: frameTime > 0 ? (execTime / frameTime) * 100 : 0,
exclusiveTime: execTime,
exclusiveTimePercent: frameTime > 0 ? (execTime / frameTime) * 100 : 0,
callCount: stats?.executionCount || 1,
avgCallTime: stats?.averageTime || execTime
});
}
return hotspots.sort((a, b) => b.inclusiveTime - a.inclusiveTime).slice(0, 50);
}
private buildCallGraph(report: ProfileReport): IAdvancedProfilerData['callGraph'] {
if (!this.selectedFunction) {
return {
currentFunction: null,
callers: [],
callees: []
};
}
const node = report.callGraph.get(this.selectedFunction);
if (!node) {
return {
currentFunction: this.selectedFunction,
callers: [],
callees: []
};
}
const callers = Array.from(node.callers.entries())
.map(([name, data]) => ({
name,
callCount: data.count,
totalTime: data.totalTime,
percentOfCurrent: node.totalTime > 0 ? (data.totalTime / node.totalTime) * 100 : 0
}))
.sort((a, b) => b.totalTime - a.totalTime);
const callees = Array.from(node.callees.entries())
.map(([name, data]) => ({
name,
callCount: data.count,
totalTime: data.totalTime,
percentOfCurrent: node.totalTime > 0 ? (data.totalTime / node.totalTime) * 100 : 0
}))
.sort((a, b) => b.totalTime - a.totalTime);
return {
currentFunction: this.selectedFunction,
callers,
callees
};
}
private buildMemoryTrend(snapshots: MemorySnapshot[]): IAdvancedProfilerData['memoryTrend'] {
return snapshots.map(s => ({
time: s.timestamp,
usedMB: s.usedHeapSize / (1024 * 1024),
totalMB: s.totalHeapSize / (1024 * 1024),
gcCount: s.gcCount
}));
}
private buildSummary(
report: ProfileReport,
currentMemory: MemorySnapshot
): IAdvancedProfilerData['summary'] {
return {
totalFrames: report.totalFrames,
averageFrameTime: report.averageFrameTime,
minFrameTime: report.minFrameTime,
maxFrameTime: report.maxFrameTime,
p95FrameTime: report.p95FrameTime,
p99FrameTime: report.p99FrameTime,
currentMemoryMB: currentMemory.usedHeapSize / (1024 * 1024),
peakMemoryMB: this.peakMemory / (1024 * 1024),
gcCount: currentMemory.gcCount,
longTaskCount: report.longTasks.length
};
}
private getCurrentMemory(): MemorySnapshot {
const perfWithMemory = performance as Performance & {
memory?: {
usedJSHeapSize?: number;
totalJSHeapSize?: number;
jsHeapSizeLimit?: number;
};
};
const usedHeapSize = perfWithMemory.memory?.usedJSHeapSize || 0;
const totalHeapSize = perfWithMemory.memory?.totalJSHeapSize || 0;
const heapSizeLimit = perfWithMemory.memory?.jsHeapSizeLimit || 0;
return {
timestamp: performance.now(),
usedHeapSize,
totalHeapSize,
heapSizeLimit,
utilizationPercent: heapSizeLimit > 0 ? (usedHeapSize / heapSizeLimit) * 100 : 0,
gcCount: 0
};
}
private getDefaultMemory(): MemorySnapshot {
return {
timestamp: performance.now(),
usedHeapSize: 0,
totalHeapSize: 0,
heapSizeLimit: 0,
utilizationPercent: 0,
gcCount: 0
};
}
private createEmptyData(): IAdvancedProfilerData {
return {
currentFrame: {
frameNumber: 0,
frameTime: 0,
fps: 0,
memory: this.getDefaultMemory()
},
frameTimeHistory: [],
categoryStats: [],
hotspots: [],
callGraph: {
currentFunction: null,
callers: [],
callees: []
},
longTasks: [],
memoryTrend: [],
summary: {
totalFrames: 0,
averageFrameTime: 0,
minFrameTime: 0,
maxFrameTime: 0,
p95FrameTime: 0,
p99FrameTime: 0,
currentMemoryMB: 0,
peakMemoryMB: 0,
gcCount: 0,
longTaskCount: 0
}
};
}
}

View File

@@ -4,6 +4,7 @@ import { SystemDataCollector } from './SystemDataCollector';
import { PerformanceDataCollector } from './PerformanceDataCollector';
import { ComponentDataCollector } from './ComponentDataCollector';
import { SceneDataCollector } from './SceneDataCollector';
import { AdvancedProfilerCollector } from './AdvancedProfilerCollector';
import { WebSocketManager } from './WebSocketManager';
import { Component } from '../../ECS/Component';
import { ComponentPoolManager } from '../../ECS/Core/ComponentPool';
@@ -15,6 +16,7 @@ import { SceneManager } from '../../ECS/SceneManager';
import { PerformanceMonitor } from '../PerformanceMonitor';
import { Injectable, InjectProperty, Updatable } from '../../Core/DI/Decorators';
import { DebugConfigService } from './DebugConfigService';
import { ProfilerSDK } from '../Profiler/ProfilerSDK';
/**
* 调试管理器
@@ -31,6 +33,7 @@ export class DebugManager implements IService, IUpdatable {
private performanceCollector!: PerformanceDataCollector;
private componentCollector!: ComponentDataCollector;
private sceneCollector!: SceneDataCollector;
private advancedProfilerCollector!: AdvancedProfilerCollector;
@InjectProperty(SceneManager)
private sceneManager!: SceneManager;
@@ -62,6 +65,10 @@ export class DebugManager implements IService, IUpdatable {
this.performanceCollector = new PerformanceDataCollector();
this.componentCollector = new ComponentDataCollector();
this.sceneCollector = new SceneDataCollector();
this.advancedProfilerCollector = new AdvancedProfilerCollector();
// 启用高级性能分析器
ProfilerSDK.setEnabled(true);
// 初始化WebSocket管理器
this.webSocketManager = new WebSocketManager(
@@ -290,6 +297,14 @@ export class DebugManager implements IService, IUpdatable {
this.handleGetEntityDetailsRequest(message);
break;
case 'get_advanced_profiler_data':
this.handleGetAdvancedProfilerDataRequest(message);
break;
case 'set_profiler_selected_function':
this.handleSetProfilerSelectedFunction(message);
break;
case 'ping':
this.webSocketManager.send({
type: 'pong',
@@ -437,6 +452,54 @@ export class DebugManager implements IService, IUpdatable {
}
/**
* 处理获取高级性能分析数据请求
*/
private handleGetAdvancedProfilerDataRequest(message: any): void {
try {
const { requestId } = message;
// 收集高级性能数据
const advancedData = ProfilerSDK.isEnabled()
? this.advancedProfilerCollector.collectAdvancedData(this.performanceMonitor)
: this.advancedProfilerCollector.collectFromLegacyMonitor(this.performanceMonitor);
this.webSocketManager.send({
type: 'get_advanced_profiler_data_response',
requestId,
data: advancedData
});
} catch (error) {
this.webSocketManager.send({
type: 'get_advanced_profiler_data_response',
requestId: message.requestId,
error: error instanceof Error ? error.message : String(error)
});
}
}
/**
* 处理设置性能分析器选中函数请求
*/
private handleSetProfilerSelectedFunction(message: any): void {
try {
const { functionName, requestId } = message;
this.advancedProfilerCollector.setSelectedFunction(functionName || null);
this.webSocketManager.send({
type: 'set_profiler_selected_function_response',
requestId,
success: true
});
} catch (error) {
this.webSocketManager.send({
type: 'set_profiler_selected_function_response',
requestId: message.requestId,
error: error instanceof Error ? error.message : String(error)
});
}
}
/**
* 处理内存快照请求
*/

View File

@@ -6,3 +6,5 @@ export { SceneDataCollector } from './SceneDataCollector';
export { WebSocketManager } from './WebSocketManager';
export { DebugManager } from './DebugManager';
export { DebugConfigService } from './DebugConfigService';
export { AdvancedProfilerCollector } from './AdvancedProfilerCollector';
export type { IAdvancedProfilerData } from './AdvancedProfilerCollector';

View File

@@ -2,10 +2,7 @@
* 全局管理器的基类。所有全局管理器都应该从此类继承。
*/
export class GlobalManager {
/**
* 表示管理器是否启用
*/
public _enabled: boolean = false;
private _enabled: boolean = false;
/**
* 获取或设置管理器是否启用

View File

@@ -0,0 +1,778 @@
/**
* 性能分析器 SDK
*
* 提供统一的性能分析接口,支持:
* - 手动采样标记
* - 自动作用域测量
* - 调用层级追踪
* - 计数器和仪表
*/
import {
ProfileCategory,
ProfileSample,
ProfileSampleStats,
ProfileFrame,
ProfileCounter,
MemorySnapshot,
SampleHandle,
ProfilerConfig,
CallGraphNode,
ProfileReport,
LongTaskInfo,
DEFAULT_PROFILER_CONFIG
} from './ProfilerTypes';
let idCounter = 0;
function generateId(): string {
return `sample_${++idCounter}_${Date.now()}`;
}
/**
* 性能分析器 SDK
*/
export class ProfilerSDK {
private static instance: ProfilerSDK | null = null;
private config: ProfilerConfig;
private currentFrame: ProfileFrame | null = null;
private frameHistory: ProfileFrame[] = [];
private frameNumber = 0;
private activeSamples: Map<string, SampleHandle> = new Map();
private sampleStack: SampleHandle[] = [];
private counters: Map<string, ProfileCounter> = new Map();
private callGraph: Map<string, CallGraphNode> = new Map();
private gcCount = 0;
private previousHeapSize = 0;
private longTasks: LongTaskInfo[] = [];
private performanceObserver: PerformanceObserver | null = null;
private constructor(config?: Partial<ProfilerConfig>) {
this.config = { ...DEFAULT_PROFILER_CONFIG, ...config };
if (this.config.detectLongTasks) {
this.setupLongTaskObserver();
}
}
/**
* 获取单例实例
*/
public static getInstance(config?: Partial<ProfilerConfig>): ProfilerSDK {
if (!ProfilerSDK.instance) {
ProfilerSDK.instance = new ProfilerSDK(config);
}
return ProfilerSDK.instance;
}
/**
* 重置实例(测试用)
*/
public static resetInstance(): void {
if (ProfilerSDK.instance) {
ProfilerSDK.instance.dispose();
ProfilerSDK.instance = null;
}
}
/**
* 开始采样
*/
public static beginSample(name: string, category: ProfileCategory = ProfileCategory.Custom): SampleHandle | null {
return ProfilerSDK.getInstance().beginSample(name, category);
}
/**
* 结束采样
*/
public static endSample(handle: SampleHandle | null): void {
if (handle) {
ProfilerSDK.getInstance().endSample(handle);
}
}
/**
* 测量同步函数执行时间
*/
public static measure<T>(name: string, fn: () => T, category: ProfileCategory = ProfileCategory.Custom): T {
return ProfilerSDK.getInstance().measure(name, fn, category);
}
/**
* 测量异步函数执行时间
*/
public static async measureAsync<T>(
name: string,
fn: () => Promise<T>,
category: ProfileCategory = ProfileCategory.Custom
): Promise<T> {
return ProfilerSDK.getInstance().measureAsync(name, fn, category);
}
/**
* 开始帧
*/
public static beginFrame(): void {
ProfilerSDK.getInstance().beginFrame();
}
/**
* 结束帧
*/
public static endFrame(): void {
ProfilerSDK.getInstance().endFrame();
}
/**
* 递增计数器
*/
public static incrementCounter(
name: string,
value: number = 1,
category: ProfileCategory = ProfileCategory.Custom
): void {
ProfilerSDK.getInstance().incrementCounter(name, value, category);
}
/**
* 设置仪表值
*/
public static setGauge(
name: string,
value: number,
category: ProfileCategory = ProfileCategory.Custom
): void {
ProfilerSDK.getInstance().setGauge(name, value, category);
}
/**
* 启用/禁用分析器
*/
public static setEnabled(enabled: boolean): void {
ProfilerSDK.getInstance().setEnabled(enabled);
}
/**
* 检查是否启用
*/
public static isEnabled(): boolean {
return ProfilerSDK.getInstance().config.enabled;
}
/**
* 获取当前帧数据
*/
public static getCurrentFrame(): ProfileFrame | null {
return ProfilerSDK.getInstance().currentFrame;
}
/**
* 获取帧历史
*/
public static getFrameHistory(): ProfileFrame[] {
return ProfilerSDK.getInstance().frameHistory;
}
/**
* 获取分析报告
*/
public static getReport(frameCount?: number): ProfileReport {
return ProfilerSDK.getInstance().generateReport(frameCount);
}
/**
* 重置数据
*/
public static reset(): void {
ProfilerSDK.getInstance().reset();
}
/**
* 开始采样
*/
public beginSample(name: string, category: ProfileCategory = ProfileCategory.Custom): SampleHandle | null {
if (!this.config.enabled || !this.config.enabledCategories.has(category)) {
return null;
}
const parentHandle = this.sampleStack.length > 0
? this.sampleStack[this.sampleStack.length - 1]
: undefined;
if (parentHandle && this.sampleStack.length >= this.config.maxSampleDepth) {
return null;
}
const handle: SampleHandle = {
id: generateId(),
name,
category,
startTime: performance.now(),
depth: this.sampleStack.length,
parentId: parentHandle?.id
};
this.activeSamples.set(handle.id, handle);
this.sampleStack.push(handle);
return handle;
}
/**
* 结束采样
*/
public endSample(handle: SampleHandle): void {
if (!this.config.enabled || !this.activeSamples.has(handle.id)) {
return;
}
const endTime = performance.now();
const duration = endTime - handle.startTime;
const sample: ProfileSample = {
id: handle.id,
name: handle.name,
category: handle.category,
startTime: handle.startTime,
endTime,
duration,
selfTime: duration,
parentId: handle.parentId,
depth: handle.depth,
callCount: 1
};
if (this.currentFrame) {
this.currentFrame.samples.push(sample);
}
this.updateCallGraph(handle.name, handle.category, duration, handle.parentId);
this.activeSamples.delete(handle.id);
const stackIndex = this.sampleStack.indexOf(handle);
if (stackIndex !== -1) {
this.sampleStack.splice(stackIndex, 1);
}
}
/**
* 测量同步函数
*/
public measure<T>(name: string, fn: () => T, category: ProfileCategory = ProfileCategory.Custom): T {
const handle = this.beginSample(name, category);
try {
return fn();
} finally {
if (handle) {
this.endSample(handle);
}
}
}
/**
* 测量异步函数
*/
public async measureAsync<T>(
name: string,
fn: () => Promise<T>,
category: ProfileCategory = ProfileCategory.Custom
): Promise<T> {
const handle = this.beginSample(name, category);
try {
return await fn();
} finally {
if (handle) {
this.endSample(handle);
}
}
}
/**
* 开始帧
*/
public beginFrame(): void {
if (!this.config.enabled) return;
this.frameNumber++;
this.currentFrame = {
frameNumber: this.frameNumber,
startTime: performance.now(),
endTime: 0,
duration: 0,
samples: [],
sampleStats: [],
counters: new Map(this.counters),
memory: this.captureMemory(),
categoryStats: new Map()
};
this.resetFrameCounters();
}
/**
* 结束帧
*/
public endFrame(): void {
if (!this.config.enabled || !this.currentFrame) return;
this.currentFrame.endTime = performance.now();
this.currentFrame.duration = this.currentFrame.endTime - this.currentFrame.startTime;
this.calculateSampleStats();
this.calculateCategoryStats();
this.frameHistory.push(this.currentFrame);
while (this.frameHistory.length > this.config.maxFrameHistory) {
this.frameHistory.shift();
}
this.sampleStack = [];
this.activeSamples.clear();
}
/**
* 递增计数器
*/
public incrementCounter(
name: string,
value: number = 1,
category: ProfileCategory = ProfileCategory.Custom
): void {
if (!this.config.enabled) return;
let counter = this.counters.get(name);
if (!counter) {
counter = {
name,
category,
value: 0,
type: 'counter',
history: []
};
this.counters.set(name, counter);
}
counter.value += value;
counter.history.push({ time: performance.now(), value: counter.value });
if (counter.history.length > 100) {
counter.history.shift();
}
}
/**
* 设置仪表值
*/
public setGauge(
name: string,
value: number,
category: ProfileCategory = ProfileCategory.Custom
): void {
if (!this.config.enabled) return;
let counter = this.counters.get(name);
if (!counter) {
counter = {
name,
category,
value: 0,
type: 'gauge',
history: []
};
this.counters.set(name, counter);
}
counter.value = value;
counter.history.push({ time: performance.now(), value });
if (counter.history.length > 100) {
counter.history.shift();
}
}
/**
* 设置启用状态
*/
public setEnabled(enabled: boolean): void {
this.config.enabled = enabled;
if (enabled && this.config.detectLongTasks && !this.performanceObserver) {
this.setupLongTaskObserver();
}
}
/**
* 重置数据
*/
public reset(): void {
this.frameHistory = [];
this.currentFrame = null;
this.frameNumber = 0;
this.activeSamples.clear();
this.sampleStack = [];
this.counters.clear();
this.callGraph.clear();
this.gcCount = 0;
this.longTasks = [];
}
/**
* 生成分析报告
*/
public generateReport(frameCount?: number): ProfileReport {
const frames = frameCount
? this.frameHistory.slice(-frameCount)
: this.frameHistory;
if (frames.length === 0) {
return this.createEmptyReport();
}
const frameTimes = frames.map((f) => f.duration);
const sortedTimes = [...frameTimes].sort((a, b) => a - b);
const aggregatedStats = this.aggregateSampleStats(frames);
const hotspots = aggregatedStats
.sort((a, b) => b.inclusiveTime - a.inclusiveTime)
.slice(0, 20);
const categoryBreakdown = this.aggregateCategoryStats(frames);
const firstFrame = frames[0];
const lastFrame = frames[frames.length - 1];
return {
startTime: firstFrame?.startTime ?? 0,
endTime: lastFrame?.endTime ?? 0,
totalFrames: frames.length,
averageFrameTime: frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length,
minFrameTime: Math.min(...frameTimes),
maxFrameTime: Math.max(...frameTimes),
p95FrameTime: sortedTimes[Math.floor(sortedTimes.length * 0.95)] || 0,
p99FrameTime: sortedTimes[Math.floor(sortedTimes.length * 0.99)] || 0,
hotspots,
callGraph: new Map(this.callGraph),
categoryBreakdown,
memoryTrend: frames.map((f) => f.memory),
longTasks: [...this.longTasks]
};
}
/**
* 获取调用图数据
*/
public getCallGraph(): Map<string, CallGraphNode> {
return new Map(this.callGraph);
}
/**
* 获取特定函数的调用关系
*/
public getFunctionCallInfo(name: string): {
callers: Array<{ name: string; count: number; totalTime: number }>;
callees: Array<{ name: string; count: number; totalTime: number }>;
} | null {
const node = this.callGraph.get(name);
if (!node) return null;
return {
callers: Array.from(node.callers.entries()).map(([name, data]) => ({
name,
...data
})),
callees: Array.from(node.callees.entries()).map(([name, data]) => ({
name,
...data
}))
};
}
/**
* 释放资源
*/
public dispose(): void {
if (this.performanceObserver) {
this.performanceObserver.disconnect();
this.performanceObserver = null;
}
this.reset();
}
private captureMemory(): MemorySnapshot {
const now = performance.now();
let usedHeapSize = 0;
let totalHeapSize = 0;
let heapSizeLimit = 0;
const perfWithMemory = performance as Performance & {
memory?: {
usedJSHeapSize?: number;
totalJSHeapSize?: number;
jsHeapSizeLimit?: number;
};
};
if (perfWithMemory.memory) {
usedHeapSize = perfWithMemory.memory.usedJSHeapSize || 0;
totalHeapSize = perfWithMemory.memory.totalJSHeapSize || 0;
heapSizeLimit = perfWithMemory.memory.jsHeapSizeLimit || 0;
if (this.previousHeapSize > 0 && usedHeapSize < this.previousHeapSize - 1024 * 1024) {
this.gcCount++;
}
this.previousHeapSize = usedHeapSize;
}
return {
timestamp: now,
usedHeapSize,
totalHeapSize,
heapSizeLimit,
utilizationPercent: heapSizeLimit > 0 ? (usedHeapSize / heapSizeLimit) * 100 : 0,
gcCount: this.gcCount
};
}
private resetFrameCounters(): void {
for (const counter of this.counters.values()) {
if (counter.type === 'counter') {
counter.value = 0;
}
}
}
private calculateSampleStats(): void {
if (!this.currentFrame) return;
const sampleMap = new Map<string, ProfileSampleStats>();
for (const sample of this.currentFrame.samples) {
let stats = sampleMap.get(sample.name);
if (!stats) {
stats = {
name: sample.name,
category: sample.category,
inclusiveTime: 0,
exclusiveTime: 0,
callCount: 0,
averageTime: 0,
minTime: Number.MAX_VALUE,
maxTime: 0,
percentOfFrame: 0,
percentOfParent: 0,
children: [],
depth: sample.depth
};
sampleMap.set(sample.name, stats);
}
stats.inclusiveTime += sample.duration;
stats.callCount += 1;
stats.minTime = Math.min(stats.minTime, sample.duration);
stats.maxTime = Math.max(stats.maxTime, sample.duration);
}
for (const sample of this.currentFrame.samples) {
if (sample.parentId) {
const parentSample = this.currentFrame.samples.find((s) => s.id === sample.parentId);
if (parentSample) {
const parentStats = sampleMap.get(parentSample.name);
if (parentStats) {
parentStats.exclusiveTime = parentStats.inclusiveTime;
for (const childSample of this.currentFrame.samples) {
if (childSample.parentId === parentSample.id) {
parentStats.exclusiveTime -= childSample.duration;
}
}
}
}
}
}
const frameDuration = this.currentFrame.duration || 1;
for (const stats of sampleMap.values()) {
stats.averageTime = stats.inclusiveTime / stats.callCount;
stats.percentOfFrame = (stats.inclusiveTime / frameDuration) * 100;
if (stats.exclusiveTime === 0) {
stats.exclusiveTime = stats.inclusiveTime;
}
}
this.currentFrame.sampleStats = Array.from(sampleMap.values())
.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
}
private calculateCategoryStats(): void {
if (!this.currentFrame) return;
const categoryMap = new Map<ProfileCategory, { totalTime: number; sampleCount: number }>();
for (const sample of this.currentFrame.samples) {
if (sample.depth === 0) {
let stats = categoryMap.get(sample.category);
if (!stats) {
stats = { totalTime: 0, sampleCount: 0 };
categoryMap.set(sample.category, stats);
}
stats.totalTime += sample.duration;
stats.sampleCount += 1;
}
}
const frameDuration = this.currentFrame.duration || 1;
for (const [category, stats] of categoryMap) {
this.currentFrame.categoryStats.set(category, {
...stats,
percentOfFrame: (stats.totalTime / frameDuration) * 100
});
}
}
private updateCallGraph(
name: string,
category: ProfileCategory,
duration: number,
parentId?: string
): void {
let node = this.callGraph.get(name);
if (!node) {
node = {
name,
category,
callCount: 0,
totalTime: 0,
callers: new Map(),
callees: new Map()
};
this.callGraph.set(name, node);
}
node.callCount++;
node.totalTime += duration;
if (parentId) {
const parentHandle = this.activeSamples.get(parentId);
if (parentHandle) {
const callerData = node.callers.get(parentHandle.name) || { count: 0, totalTime: 0 };
callerData.count++;
callerData.totalTime += duration;
node.callers.set(parentHandle.name, callerData);
const parentNode = this.callGraph.get(parentHandle.name);
if (parentNode) {
const calleeData = parentNode.callees.get(name) || { count: 0, totalTime: 0 };
calleeData.count++;
calleeData.totalTime += duration;
parentNode.callees.set(name, calleeData);
}
}
}
}
private aggregateSampleStats(frames: ProfileFrame[]): ProfileSampleStats[] {
const aggregated = new Map<string, ProfileSampleStats>();
for (const frame of frames) {
for (const stats of frame.sampleStats) {
let agg = aggregated.get(stats.name);
if (!agg) {
agg = {
...stats,
minTime: Number.MAX_VALUE
};
aggregated.set(stats.name, agg);
} else {
agg.inclusiveTime += stats.inclusiveTime;
agg.exclusiveTime += stats.exclusiveTime;
agg.callCount += stats.callCount;
agg.minTime = Math.min(agg.minTime, stats.minTime);
agg.maxTime = Math.max(agg.maxTime, stats.maxTime);
}
}
}
const totalTime = frames.reduce((sum, f) => sum + f.duration, 0);
for (const stats of aggregated.values()) {
stats.averageTime = stats.inclusiveTime / stats.callCount;
stats.percentOfFrame = (stats.inclusiveTime / totalTime) * 100;
}
return Array.from(aggregated.values());
}
private aggregateCategoryStats(frames: ProfileFrame[]): Map<ProfileCategory, {
totalTime: number;
averageTime: number;
percentOfTotal: number;
}> {
const aggregated = new Map<ProfileCategory, { totalTime: number; frameCount: number }>();
for (const frame of frames) {
for (const [category, stats] of frame.categoryStats) {
let agg = aggregated.get(category);
if (!agg) {
agg = { totalTime: 0, frameCount: 0 };
aggregated.set(category, agg);
}
agg.totalTime += stats.totalTime;
agg.frameCount++;
}
}
const totalTime = frames.reduce((sum, f) => sum + f.duration, 0);
const result = new Map<ProfileCategory, { totalTime: number; averageTime: number; percentOfTotal: number }>();
for (const [category, agg] of aggregated) {
result.set(category, {
totalTime: agg.totalTime,
averageTime: agg.frameCount > 0 ? agg.totalTime / agg.frameCount : 0,
percentOfTotal: totalTime > 0 ? (agg.totalTime / totalTime) * 100 : 0
});
}
return result;
}
private setupLongTaskObserver(): void {
if (typeof PerformanceObserver === 'undefined') return;
try {
this.performanceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > this.config.longTaskThreshold) {
this.longTasks.push({
startTime: entry.startTime,
duration: entry.duration,
attribution: (entry as any).attribution?.map((a: any) => a.name) || []
});
if (this.longTasks.length > 100) {
this.longTasks.shift();
}
}
}
});
this.performanceObserver.observe({ entryTypes: ['longtask'] });
} catch {
// Long Task API not supported
}
}
private createEmptyReport(): ProfileReport {
return {
startTime: 0,
endTime: 0,
totalFrames: 0,
averageFrameTime: 0,
minFrameTime: 0,
maxFrameTime: 0,
p95FrameTime: 0,
p99FrameTime: 0,
hotspots: [],
callGraph: new Map(),
categoryBreakdown: new Map(),
memoryTrend: [],
longTasks: []
};
}
}

View File

@@ -0,0 +1,227 @@
/**
* 性能分析器类型定义
*/
/**
* 性能分析类别
*/
export enum ProfileCategory {
/** ECS 系统 */
ECS = 'ECS',
/** 渲染相关 */
Rendering = 'Rendering',
/** 物理系统 */
Physics = 'Physics',
/** 音频系统 */
Audio = 'Audio',
/** 网络相关 */
Network = 'Network',
/** 用户脚本 */
Script = 'Script',
/** 内存相关 */
Memory = 'Memory',
/** 动画系统 */
Animation = 'Animation',
/** AI/行为树 */
AI = 'AI',
/** 输入处理 */
Input = 'Input',
/** 资源加载 */
Loading = 'Loading',
/** 自定义 */
Custom = 'Custom'
}
/**
* 采样句柄
*/
export interface SampleHandle {
id: string;
name: string;
category: ProfileCategory;
startTime: number;
depth: number;
parentId?: string | undefined;
}
/**
* 性能采样数据
*/
export interface ProfileSample {
id: string;
name: string;
category: ProfileCategory;
startTime: number;
endTime: number;
duration: number;
selfTime: number;
parentId?: string | undefined;
depth: number;
callCount: number;
metadata?: Record<string, unknown>;
}
/**
* 聚合后的采样统计
*/
export interface ProfileSampleStats {
name: string;
category: ProfileCategory;
/** 包含时间(包含子调用) */
inclusiveTime: number;
/** 独占时间(不包含子调用) */
exclusiveTime: number;
/** 调用次数 */
callCount: number;
/** 平均时间 */
averageTime: number;
/** 最小时间 */
minTime: number;
/** 最大时间 */
maxTime: number;
/** 占总帧时间百分比 */
percentOfFrame: number;
/** 占父级时间百分比 */
percentOfParent: number;
/** 子采样 */
children: ProfileSampleStats[];
/** 深度 */
depth: number;
}
/**
* 内存快照
*/
export interface MemorySnapshot {
timestamp: number;
/** 已使用堆内存 (bytes) */
usedHeapSize: number;
/** 总堆内存 (bytes) */
totalHeapSize: number;
/** 堆内存限制 (bytes) */
heapSizeLimit: number;
/** 使用率 (0-100) */
utilizationPercent: number;
/** 检测到的 GC 次数 */
gcCount: number;
}
/**
* 计数器数据
*/
export interface ProfileCounter {
name: string;
category: ProfileCategory;
value: number;
type: 'counter' | 'gauge';
history: Array<{ time: number; value: number }>;
}
/**
* 单帧性能数据
*/
export interface ProfileFrame {
frameNumber: number;
startTime: number;
endTime: number;
duration: number;
samples: ProfileSample[];
sampleStats: ProfileSampleStats[];
counters: Map<string, ProfileCounter>;
memory: MemorySnapshot;
/** 按类别分组的统计 */
categoryStats: Map<ProfileCategory, {
totalTime: number;
sampleCount: number;
percentOfFrame: number;
}>;
}
/**
* 分析器配置
*/
export interface ProfilerConfig {
/** 是否启用 */
enabled: boolean;
/** 最大历史帧数 */
maxFrameHistory: number;
/** 采样深度限制 */
maxSampleDepth: number;
/** 是否收集内存数据 */
collectMemory: boolean;
/** 内存采样间隔 (ms) */
memorySampleInterval: number;
/** 是否检测长任务 */
detectLongTasks: boolean;
/** 长任务阈值 (ms) */
longTaskThreshold: number;
/** 启用的类别 */
enabledCategories: Set<ProfileCategory>;
}
/**
* 长任务信息
*/
export interface LongTaskInfo {
startTime: number;
duration: number;
attribution: string[];
}
/**
* 调用关系节点
*/
export interface CallGraphNode {
name: string;
category: ProfileCategory;
/** 被调用次数 */
callCount: number;
/** 总耗时 */
totalTime: number;
/** 调用者列表 */
callers: Map<string, { count: number; totalTime: number }>;
/** 被调用者列表 */
callees: Map<string, { count: number; totalTime: number }>;
}
/**
* 性能分析报告
*/
export interface ProfileReport {
startTime: number;
endTime: number;
totalFrames: number;
averageFrameTime: number;
minFrameTime: number;
maxFrameTime: number;
p95FrameTime: number;
p99FrameTime: number;
/** 热点函数 (按耗时排序) */
hotspots: ProfileSampleStats[];
/** 调用图 */
callGraph: Map<string, CallGraphNode>;
/** 类别统计 */
categoryBreakdown: Map<ProfileCategory, {
totalTime: number;
averageTime: number;
percentOfTotal: number;
}>;
/** 内存趋势 */
memoryTrend: MemorySnapshot[];
/** 长任务列表 */
longTasks: LongTaskInfo[];
}
/**
* 默认配置
*/
export const DEFAULT_PROFILER_CONFIG: ProfilerConfig = {
enabled: false,
maxFrameHistory: 300,
maxSampleDepth: 32,
collectMemory: true,
memorySampleInterval: 100,
detectLongTasks: true,
longTaskThreshold: 50,
enabledCategories: new Set(Object.values(ProfileCategory))
};

View File

@@ -0,0 +1,6 @@
/**
* 性能分析器模块
*/
export * from './ProfilerTypes';
export { ProfilerSDK } from './ProfilerSDK';

View File

@@ -6,11 +6,11 @@ import { Time } from '../Time';
*/
export class Timer<TContext = unknown> implements ITimer<TContext>{
public context!: TContext;
public _timeInSeconds: number = 0;
public _repeats: boolean = false;
public _onTime!: (timer: ITimer<TContext>) => void;
public _isDone: boolean = false;
public _elapsedTime: number = 0;
private _timeInSeconds: number = 0;
private _repeats: boolean = false;
private _onTime!: (timer: ITimer<TContext>) => void;
private _isDone: boolean = false;
private _elapsedTime: number = 0;
public getContext<T>(): T {
return this.context as unknown as T;

View File

@@ -11,7 +11,7 @@ import { Updatable } from '../../Core/DI';
*/
@Updatable()
export class TimerManager implements IService, IUpdatable {
public _timers: Array<Timer<unknown>> = [];
private _timers: Array<Timer<unknown>> = [];
public update() {
for (let i = this._timers.length - 1; i >= 0; i --){

View File

@@ -7,3 +7,4 @@ export { Time } from './Time';
export * from './Debug';
export * from './Logger';
export * from './BinarySerializer';
export * from './Profiler';