Files
aibis-dream/Assets/Scripts/Utility/PerformanceProfiler.cs
2025-09-01 21:25:54 +08:00

550 lines
19 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using Unity.Profiling;
using System.Diagnostics;
using Debug = UnityEngine.Debug;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace AibisDream.Utility
{
/// <summary>
/// 性能分析工具,用于监控游戏性能
/// </summary>
public class PerformanceProfiler : MonoBehaviour
{
[Header("性能监控设置")]
public bool enableProfiling = true;
public bool showFPS = true;
public bool showMemory = true;
public bool showDrawCalls = true;
public bool showBatches = true;
public bool showTris = true;
public bool showVerts = true;
[Header("数据记录设置")]
public bool enableCsvLogging = false;
public float logInterval = 0.5f; // 秒
public string csvFileName = "performance_log";
[Header("性能警告阈值")]
public float fpsWarningThreshold = 30f;
public float memoryWarningThreshold = 100f; // MB
public int drawCallWarningThreshold = 100;
public int batchWarningThreshold = 50;
[Header("显示设置")]
public Color normalColor = Color.white;
public Color warningColor = Color.yellow;
public Color errorColor = Color.red;
public int fontSize = 14;
[Header("CPU/GPU 时间分析")]
public bool showCPUTime = true;
public bool showGPUTime = true;
public float cpuTimeWarningThreshold = 16.67f; // 60fps对应的帧时间
public float gpuTimeWarningThreshold = 16.67f;
[Header("GC监控")]
public bool showGCStats = true;
public int gcWarningThreshold = 5; // 每秒GC次数警告阈值
[Header("资源监控")]
public bool showResourceStats = true;
public long textureMemoryWarningThreshold = 1024; // MB
public long meshMemoryWarningThreshold = 512; // MB
[Header("系统性能分析")]
public bool enableSystemProfiling = true;
public float systemTimeWarningThreshold = 8f; // ms
private float deltaTime = 0.0f;
private float cpuFrameTime;
private float gpuFrameTime;
private long lastGCCount;
private float gcTimeAccumulator;
private int gcCountInLastSecond;
private int loadedTextureCount;
private int loadedMeshCount;
private long totalTextureMemory;
private long totalMeshMemory;
private Dictionary<string, CustomProfiler> systemProfilers = new Dictionary<string, CustomProfiler>();
public class CustomProfiler
{
public string name;
public float totalTime;
public float maxTime;
public int sampleCount;
public Stopwatch stopwatch;
public float AverageTime => sampleCount > 0 ? totalTime / sampleCount : 0;
public void Reset()
{
totalTime = 0;
maxTime = 0;
sampleCount = 0;
stopwatch = new Stopwatch();
}
}
private float fps = 0.0f;
private float memoryUsage = 0.0f;
private int drawCalls = 0;
private int batches = 0;
private int triangles = 0;
private int vertices = 0;
private GUIStyle style;
private List<string> performanceWarnings = new List<string>();
// CSV 日志
private StreamWriter csvWriter;
private float nextLogTime = 0f;
private string logFilePath;
private void Start()
{
if (!enableProfiling) return;
// 创建GUI样式
style = new GUIStyle();
style.fontSize = fontSize;
style.normal.textColor = normalColor;
// 启动性能监控协程
StartCoroutine(PerformanceMonitor());
// 初始化CSV日志
if (enableCsvLogging)
{
InitCsvLogging();
}
}
private void Update()
{
if (!enableProfiling) return;
// 计算FPS
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
fps = 1.0f / deltaTime;
// 获取渲染统计信息
#if UNITY_EDITOR
drawCalls = UnityEditor.UnityStats.drawCalls;
batches = UnityEditor.UnityStats.batches;
triangles = UnityEditor.UnityStats.triangles;
vertices = UnityEditor.UnityStats.vertices;
#else
drawCalls = 0;
batches = 0;
triangles = 0;
vertices = 0;
#endif
// 获取内存使用情况
memoryUsage = Profiler.GetTotalAllocatedMemoryLong() / (1024f * 1024f); // 转换为MB
// CPU和GPU时间
cpuFrameTime = Time.unscaledDeltaTime * 1000f; // 转换为毫秒
if (FrameTimingManager.IsFeatureEnabled())
{
var frameData = new FrameTiming[1];
FrameTimingManager.GetLatestTimings(1, frameData);
if (frameData.Length > 0)
{
gpuFrameTime = (float)frameData[0].gpuFrameTime;
}
}
// GC监控
if (System.GC.CollectionCount(0) > lastGCCount)
{
gcCountInLastSecond++;
lastGCCount = System.GC.CollectionCount(0);
}
// 资源监控
if (showResourceStats)
{
var textures = Resources.FindObjectsOfTypeAll<Texture>();
var meshes = Resources.FindObjectsOfTypeAll<Mesh>();
loadedTextureCount = textures.Length;
loadedMeshCount = meshes.Length;
totalTextureMemory = 0;
foreach (var tex in textures)
{
if (tex != null)
totalTextureMemory += Profiler.GetRuntimeMemorySizeLong(tex);
}
totalMeshMemory = 0;
foreach (var mesh in meshes)
{
if (mesh != null)
totalMeshMemory += Profiler.GetRuntimeMemorySizeLong(mesh);
}
}
// 按间隔写入CSV
if (enableCsvLogging && csvWriter != null && Time.unscaledTime >= nextLogTime)
{
WriteCsvRow();
nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval);
gcCountInLastSecond = 0; // 重置GC计数器
}
}
private System.Collections.IEnumerator PerformanceMonitor()
{
while (enableProfiling)
{
performanceWarnings.Clear();
// 检查FPS
if (fps < fpsWarningThreshold)
{
performanceWarnings.Add($"FPS过低: {fps:F1}");
}
// 检查内存使用
if (memoryUsage > memoryWarningThreshold)
{
performanceWarnings.Add($"内存使用过高: {memoryUsage:F1}MB");
}
// 检查绘制调用
if (drawCalls > drawCallWarningThreshold)
{
performanceWarnings.Add($"绘制调用过多: {drawCalls}");
}
// 检查批处理
if (batches > batchWarningThreshold)
{
performanceWarnings.Add($"批处理过多: {batches}");
}
yield return new WaitForSeconds(1f);
}
}
private void OnGUI()
{
if (!enableProfiling) return;
float yPos = 10f;
float lineHeight = fontSize + 5f;
// 显示FPS
if (showFPS)
{
Color fpsColor = fps < fpsWarningThreshold ? errorColor : normalColor;
style.normal.textColor = fpsColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"FPS: {fps:F1}", style);
yPos += lineHeight;
}
// 显示内存使用
if (showMemory)
{
Color memColor = memoryUsage > memoryWarningThreshold ? warningColor : normalColor;
style.normal.textColor = memColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"内存: {memoryUsage:F1}MB", style);
yPos += lineHeight;
}
// 显示绘制调用
if (showDrawCalls)
{
Color drawColor = drawCalls > drawCallWarningThreshold ? warningColor : normalColor;
style.normal.textColor = drawColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"绘制调用: {drawCalls}", style);
yPos += lineHeight;
}
// 显示批处理
if (showBatches)
{
Color batchColor = batches > batchWarningThreshold ? warningColor : normalColor;
style.normal.textColor = batchColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"批处理: {batches}", style);
yPos += lineHeight;
}
// 显示三角形数量
if (showTris)
{
style.normal.textColor = normalColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"三角形: {triangles:N0}", style);
yPos += lineHeight;
}
// 显示顶点数量
if (showVerts)
{
style.normal.textColor = normalColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"顶点: {vertices:N0}", style);
yPos += lineHeight;
}
// 显示CPU时间
if (showCPUTime)
{
Color timeColor = cpuFrameTime > cpuTimeWarningThreshold ? warningColor : normalColor;
style.normal.textColor = timeColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"CPU时间: {cpuFrameTime:F2}ms", style);
yPos += lineHeight;
}
// 显示GPU时间
if (showGPUTime && FrameTimingManager.IsFeatureEnabled())
{
Color timeColor = gpuFrameTime > gpuTimeWarningThreshold ? warningColor : normalColor;
style.normal.textColor = timeColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GPU时间: {gpuFrameTime:F2}ms", style);
yPos += lineHeight;
}
// 显示GC信息
if (showGCStats)
{
Color gcColor = gcCountInLastSecond > gcWarningThreshold ? warningColor : normalColor;
style.normal.textColor = gcColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GC次数/秒: {gcCountInLastSecond}", style);
yPos += lineHeight;
}
// 显示资源信息
if (showResourceStats)
{
yPos += 10f;
style.normal.textColor = normalColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), "资源统计:", style);
yPos += lineHeight;
Color texMemColor = (totalTextureMemory / (1024f * 1024f)) > textureMemoryWarningThreshold ? warningColor : normalColor;
style.normal.textColor = texMemColor;
GUI.Label(new Rect(20, yPos, 300, lineHeight),
$"贴图: {loadedTextureCount}个 ({totalTextureMemory / (1024f * 1024f):F1}MB)", style);
yPos += lineHeight;
Color meshMemColor = (totalMeshMemory / (1024f * 1024f)) > meshMemoryWarningThreshold ? warningColor : normalColor;
style.normal.textColor = meshMemColor;
GUI.Label(new Rect(20, yPos, 300, lineHeight),
$"网格: {loadedMeshCount}个 ({totalMeshMemory / (1024f * 1024f):F1}MB)", style);
yPos += lineHeight;
}
// 显示系统性能分析
if (enableSystemProfiling && systemProfilers.Count > 0)
{
yPos += 10f;
style.normal.textColor = normalColor;
GUI.Label(new Rect(10, yPos, 200, lineHeight), "系统性能:", style);
yPos += lineHeight;
foreach (var profiler in systemProfilers.Values)
{
if (profiler.sampleCount > 0)
{
Color timeColor = profiler.AverageTime > systemTimeWarningThreshold ? warningColor : normalColor;
style.normal.textColor = timeColor;
GUI.Label(new Rect(20, yPos, 400, lineHeight),
$"{profiler.name}: {profiler.AverageTime:F2}ms (最大: {profiler.maxTime:F2}ms)", style);
yPos += lineHeight;
}
}
}
// 显示性能警告
if (performanceWarnings.Count > 0)
{
yPos += 10f;
style.normal.textColor = errorColor;
GUI.Label(new Rect(10, yPos, 400, lineHeight), "性能警告:", style);
yPos += lineHeight;
foreach (string warning in performanceWarnings)
{
GUI.Label(new Rect(20, yPos, 400, lineHeight), $"• {warning}", style);
yPos += lineHeight;
}
}
}
/// <summary>
/// 获取当前FPS
/// </summary>
public float GetFPS()
{
return fps;
}
/// <summary>
/// 获取内存使用量(MB
/// </summary>
public float GetMemoryUsage()
{
return memoryUsage;
}
/// <summary>
/// 获取绘制调用数量
/// </summary>
public int GetDrawCalls()
{
return drawCalls;
}
/// <summary>
/// 获取批处理数量
/// </summary>
public int GetBatches()
{
return batches;
}
/// <summary>
/// 记录性能数据到日志
/// </summary>
public void LogPerformanceData()
{
Debug.Log($"[性能数据] FPS: {fps:F1}, 内存: {memoryUsage:F1}MB, 绘制调用: {drawCalls}, 批处理: {batches}");
}
private void OnDisable()
{
CloseCsv();
}
private void OnDestroy()
{
CloseCsv();
}
private void InitCsvLogging()
{
try
{
var directory = Application.persistentDataPath;
logFilePath = Path.Combine(directory, $"perf_{System.DateTime.Now:yyyyMMdd_HHmmss}.csv");
csvWriter = new StreamWriter(new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
csvWriter.WriteLine("time,fps,memoryMB,drawCalls,batches,triangles,vertices,cpuTime,gpuTime,gcCount," +
"textureCount,meshCount,textureMemoryMB,meshMemoryMB");
csvWriter.Flush();
nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval);
Debug.Log($"性能CSV日志: {logFilePath}");
}
catch (System.Exception e)
{
Debug.LogWarning($"初始化CSV日志失败: {e.Message}");
enableCsvLogging = false;
}
}
private void WriteCsvRow()
{
try
{
if (csvWriter == null) return;
csvWriter.WriteLine($"{Time.unscaledTime:F3},{fps:F2},{memoryUsage:F1},{drawCalls},{batches},{triangles},{vertices}," +
$"{cpuFrameTime:F2},{gpuFrameTime:F2},{gcCountInLastSecond}," +
$"{loadedTextureCount},{loadedMeshCount},{totalTextureMemory/1024/1024:F1},{totalMeshMemory/1024/1024:F1}");
csvWriter.Flush();
}
catch (System.Exception e)
{
Debug.LogWarning($"写入CSV失败: {e.Message}");
}
}
private void CloseCsv()
{
try
{
if (csvWriter != null)
{
csvWriter.Flush();
csvWriter.Close();
csvWriter.Dispose();
csvWriter = null;
}
}
catch {}
}
public string GetLogFilePath()
{
return logFilePath;
}
/// <summary>
/// 开始系统性能采样
/// </summary>
public void BeginSample(string systemName)
{
if (!enableSystemProfiling) return;
if (!systemProfilers.ContainsKey(systemName))
{
systemProfilers[systemName] = new CustomProfiler
{
name = systemName,
stopwatch = new Stopwatch()
};
}
systemProfilers[systemName].stopwatch.Start();
}
/// <summary>
/// 结束系统性能采样
/// </summary>
public void EndSample(string systemName)
{
if (!enableSystemProfiling) return;
if (systemProfilers.TryGetValue(systemName, out var profiler))
{
profiler.stopwatch.Stop();
float time = profiler.stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond;
profiler.totalTime += time;
profiler.maxTime = Mathf.Max(profiler.maxTime, time);
profiler.sampleCount++;
profiler.stopwatch.Reset();
}
}
/// <summary>
/// 重置所有系统性能分析器
/// </summary>
public void ResetAllProfilers()
{
foreach (var profiler in systemProfilers.Values)
{
profiler.Reset();
}
}
/// <summary>
/// 获取系统性能数据
/// </summary>
public Dictionary<string, (float averageTime, float maxTime, int sampleCount)> GetSystemProfilingData()
{
var result = new Dictionary<string, (float, float, int)>();
foreach (var kvp in systemProfilers)
{
var profiler = kvp.Value;
result[kvp.Key] = (profiler.AverageTime, profiler.maxTime, profiler.sampleCount);
}
return result;
}
}
}