压测工具优化

This commit is contained in:
wuwenbo 2026-05-15 17:43:59 +08:00
parent 94b3b21924
commit e2d4231bd3
2 changed files with 133 additions and 31 deletions

View File

@ -21,14 +21,18 @@ namespace Logic.Editor
{
public class NetworkStressEditorWindow : EditorWindow
{
private const float TestSeconds = 60f;
private const float MessagesPerSecond = 30f;
private const float TotalSeconds = 60f;
private const float SendSeconds = 50f;
private const float ReportWaitSeconds = TotalSeconds - SendSeconds;
private const float ClientReportRetrySeconds = 10f;
private const float ClientReportRetryInterval = 1f;
private const float MessagesPerSecond = 10f;
private const int SmallPayloadBytes = 512;
private const int LargePayloadBytes = 256 * 1024;
private const int LargeEveryMessages = 60;
private const int LargePayloadBytes = 128 * 1024;
private const int LargeEveryMessages = 120;
private const int JitterMaxMs = 80;
private const float DropPercent = 2f;
private const float UnreliablePercent = 5f;
private const float DropPercent = 1f;
private const float UnreliablePercent = 2f;
private const int RandomSeed = 1214;
private const string ScenarioName = "OneClick-60s";
@ -36,6 +40,7 @@ namespace Logic.Editor
{
Idle,
Running,
WaitingForReports,
}
private class PeerStats
@ -96,6 +101,9 @@ namespace Logic.Editor
private string _lastExportPath = string.Empty;
private string _eventLog = string.Empty;
private double _startTime;
private double _reportWaitUntil;
private double _reportRetryUntil;
private double _nextReportRetryTime;
private double _lastUpdateTime;
private double _nextRepaintTime;
private double _sendAccumulator;
@ -164,7 +172,7 @@ namespace Logic.Editor
private void DrawStatus(ILobby lobby)
{
EditorGUILayout.LabelField("Steam P2P 一键压测", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("房主点一次开始。测试固定 60 秒覆盖小包、周期性大包、ACK、轻量抖动和轻量丢包到时自动停止并导出报告。", MessageType.Info);
EditorGUILayout.HelpBox("房主点一次开始。总流程固定 60 秒:前 50 秒发包,后 10 秒等待从端报告;房主收齐报告会立刻自动导出。", MessageType.Info);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
@ -194,11 +202,11 @@ namespace Logic.Editor
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
GUI.enabled = lobby != null && lobby.IsInitialized() && lobby.IsInLobby();
var buttonText = _state == RunState.Running ? "停止并导出报告" : "开始 60 秒一键压测";
var buttonText = _state == RunState.Idle ? "开始 60 秒一键压测" : "停止并导出报告";
if (GUILayout.Button(buttonText, GUILayout.Height(52)))
{
if (_state == RunState.Running)
FinishAndExport(true, true);
if (_state != RunState.Idle)
FinishAndExportNow(true, true);
else
StartOneClickTest();
}
@ -216,8 +224,9 @@ namespace Logic.Editor
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField("实时结果", EditorStyles.boldLabel);
var elapsed = _state == RunState.Running ? EditorApplication.timeSinceStartup - _startTime : 0d;
EditorGUILayout.LabelField($"进度: {Math.Min(elapsed, TestSeconds):0.0}/{TestSeconds:0}s Pending ACK: {_pendingAcks.Count} 延迟队列: {_scheduledProbes.Count}");
var elapsed = _state == RunState.Idle ? 0d : EditorApplication.timeSinceStartup - _startTime;
var waitLeft = _state == RunState.WaitingForReports ? Math.Max(0d, _reportWaitUntil - EditorApplication.timeSinceStartup) : 0d;
EditorGUILayout.LabelField($"进度: {Math.Min(elapsed, TotalSeconds):0.0}/{TotalSeconds:0}s 等待报告: {waitLeft:0.0}s Pending ACK: {_pendingAcks.Count} 延迟队列: {_scheduledProbes.Count}");
DrawStatsLine(GetSelfStats(), "本机");
if (lobby != null && lobby.IsInLobby())
@ -268,9 +277,18 @@ namespace Logic.Editor
GenerateProbes(now, delta);
DrainScheduledProbes(now);
if (now - _startTime >= TestSeconds)
FinishAndExport(true, true);
if (now - _startTime >= SendSeconds)
FinishSendingAndWaitForReports(true);
}
else if (_state == RunState.WaitingForReports)
{
if (LobbyManager.Instance.Lobby?.IsLobbyOwner() == true && HasAllReports())
ExportHostReportAndStop("房主已收齐报告,自动导出");
else if (now >= _reportWaitUntil)
ExportHostReportAndStop("等待从端报告超时,导出现有报告");
}
RetryReportToHost(now);
if (now >= _nextRepaintTime)
{
@ -307,6 +325,9 @@ namespace Logic.Editor
_state = RunState.Running;
_statusLine = "运行中";
_startTime = EditorApplication.timeSinceStartup;
_reportWaitUntil = 0d;
_reportRetryUntil = 0d;
_nextReportRetryTime = 0d;
_lastUpdateTime = _startTime;
_sendAccumulator = 0d;
_nextSequence = 0;
@ -319,7 +340,36 @@ namespace Logic.Editor
AppendEvent($"开始 60 秒压测: session={_sessionId}, run={_runId}");
}
private void FinishAndExport(bool broadcastStop, bool sendReportToHost)
private void FinishSendingAndWaitForReports(bool broadcastStop)
{
if (_state != RunState.Running) return;
foreach (var pending in _pendingAcks.Values.ToList())
GetStats(pending.TargetMemberId).AckTimeouts++;
GetSelfStats().AckTimeouts += _pendingAcks.Count;
_scheduledProbes.Clear();
_pendingAcks.Clear();
_state = RunState.WaitingForReports;
_statusLine = "等待从端报告";
_reportWaitUntil = EditorApplication.timeSinceStartup + ReportWaitSeconds;
_lastReport = BuildReport();
_reports[$"{ScenarioName}/{_runId}/{GetSelfMemberId()}"] = _lastReport;
if (broadcastStop && LobbyManager.Instance.Lobby != null && LobbyManager.Instance.Lobby.IsLobbyOwner())
BroadcastControl(NetworkStressMessageKind.ControlStop);
if (LobbyManager.Instance.Lobby == null || !LobbyManager.Instance.Lobby.IsLobbyOwner())
StartReportRetryToHost();
if (LobbyManager.Instance.Lobby?.IsLobbyOwner() != true)
ExportHostReportAndStop("从端压测结束,本地报告已导出并发送给房主");
else if (HasAllReports())
ExportHostReportAndStop("房主已收齐报告,自动导出");
else
AppendEvent("房主等待从端报告");
}
private void FinishAndExportNow(bool broadcastStop, bool sendReportToHost)
{
if (_state == RunState.Running)
{
@ -328,8 +378,6 @@ namespace Logic.Editor
GetSelfStats().AckTimeouts += _pendingAcks.Count;
}
_state = RunState.Idle;
_statusLine = "已完成";
_scheduledProbes.Clear();
_pendingAcks.Clear();
_lastReport = BuildReport();
@ -339,10 +387,17 @@ namespace Logic.Editor
BroadcastControl(NetworkStressMessageKind.ControlStop);
if (sendReportToHost)
SendReportToHost();
StartReportRetryToHost();
ExportHostReportAndStop("手动停止,报告已导出");
}
private void ExportHostReportAndStop(string reason)
{
_state = RunState.Idle;
_statusLine = "已完成";
ExportReport();
AppendEvent("压测结束,报告已自动导出");
AppendEvent(reason);
}
private void GenerateProbes(double now, double delta)
@ -386,7 +441,7 @@ namespace Logic.Editor
PayloadHash = ComputeHash(payload),
Reliable = reliable,
RequiresAck = true,
DurationSeconds = TestSeconds,
DurationSeconds = SendSeconds,
MessagesPerSecond = MessagesPerSecond,
SmallPayloadBytes = SmallPayloadBytes,
LargePayloadBytes = LargePayloadBytes,
@ -518,13 +573,14 @@ namespace Logic.Editor
private void HandleControlStop(NetworkStressMessage message)
{
if (message.RunId != _runId) return;
FinishAndExport(false, true);
FinishAndExportNow(false, true);
AppendEvent("收到房主停止");
}
private void HandleProbe(NetworkStressMessage message)
{
if (message.RunId != _runId) return;
if (_state != RunState.Running) return;
var stats = GetStats(message.SenderMemberId);
stats.ReceivedProbes++;
@ -582,6 +638,7 @@ namespace Logic.Editor
private void HandleAck(NetworkStressMessage message)
{
if (message.RunId != _runId) return;
if (_state != RunState.Running) return;
var key = (message.SenderMemberId, message.AckSequence);
if (!_pendingAcks.TryGetValue(key, out var sentProbe))
{
@ -600,9 +657,15 @@ namespace Logic.Editor
private void HandleReport(NetworkStressMessage message)
{
_reports[$"{message.RunId}/{message.SenderMemberId}"] = message.Text ?? string.Empty;
if (_state == RunState.Idle && !string.IsNullOrEmpty(_lastExportPath))
_reports[$"{ScenarioName}/{message.RunId}/{message.SenderMemberId}"] = message.Text ?? string.Empty;
AppendEvent($"收到从端报告: {message.SenderMemberId}");
if (LobbyManager.Instance.Lobby?.IsLobbyOwner() == true && _state == RunState.WaitingForReports && HasAllReports())
ExportHostReportAndStop("房主已收齐报告,自动导出");
else if (LobbyManager.Instance.Lobby?.IsLobbyOwner() == true && _state == RunState.Idle && !string.IsNullOrEmpty(_lastExportPath))
{
ExportReport();
AppendEvent("收到迟到从端报告,已更新导出文件");
}
}
private void BroadcastControl(NetworkStressMessageKind kind)
@ -614,7 +677,7 @@ namespace Logic.Editor
RunId = _runId,
ScenarioName = ScenarioName,
SenderMemberId = GetSelfMemberId(),
DurationSeconds = TestSeconds,
DurationSeconds = SendSeconds,
MessagesPerSecond = MessagesPerSecond,
SmallPayloadBytes = SmallPayloadBytes,
LargePayloadBytes = LargePayloadBytes,
@ -629,12 +692,28 @@ namespace Logic.Editor
AppendEvent($"{kind} 广播失败");
}
private void SendReportToHost()
private void StartReportRetryToHost()
{
if (LobbyManager.Instance.Lobby?.IsLobbyOwner() == true) return;
_reportRetryUntil = EditorApplication.timeSinceStartup + ClientReportRetrySeconds;
_nextReportRetryTime = 0d;
RetryReportToHost(EditorApplication.timeSinceStartup);
}
private void RetryReportToHost(double now)
{
if (string.IsNullOrEmpty(_lastReport)) return;
if (now > _reportRetryUntil || now < _nextReportRetryTime) return;
_nextReportRetryTime = now + ClientReportRetryInterval;
SendReportToHost();
}
private bool SendReportToHost()
{
var lobby = LobbyManager.Instance.Lobby;
if (lobby == null || !lobby.IsInLobby() || lobby.IsLobbyOwner()) return;
if (lobby == null || !lobby.IsInLobby() || lobby.IsLobbyOwner()) return false;
var host = lobby.GetLobbyOwnerId();
if (host == 0) return;
if (host == 0) return false;
var report = new NetworkStressMessage
{
@ -646,7 +725,7 @@ namespace Logic.Editor
TargetMemberId = host,
Text = _lastReport,
};
TrySendMessage(report, false, host, true, out _);
return TrySendMessage(report, false, host, true, out _);
}
private bool TrySendMessage(NetworkStressMessage message, bool broadcast, ulong target, bool reliable,
@ -740,6 +819,20 @@ namespace Logic.Editor
return memberId.ToString();
}
private bool HasAllReports()
{
var lobby = LobbyManager.Instance.Lobby;
if (lobby == null || !lobby.IsInLobby()) return false;
foreach (var memberId in lobby.GetAllMemberIds())
{
if (!_reports.ContainsKey($"{ScenarioName}/{_runId}/{memberId}"))
return false;
}
return true;
}
private string BuildReport()
{
var sb = new StringBuilder(4096);
@ -750,7 +843,8 @@ namespace Logic.Editor
AppendJson(sb, "memberId", GetSelfMemberId().ToString(), true, false);
AppendJson(sb, "memberName", GetMemberName(GetSelfMemberId()), true);
AppendJson(sb, "state", _statusLine, true);
AppendJson(sb, "durationSeconds", TestSeconds.ToString("0"), true, false);
AppendJson(sb, "sendSeconds", SendSeconds.ToString("0"), true, false);
AppendJson(sb, "totalSeconds", TotalSeconds.ToString("0"), true, false);
AppendJson(sb, "messagesPerSecond", MessagesPerSecond.ToString("0"), true, false);
AppendJson(sb, "smallPayloadBytes", SmallPayloadBytes.ToString(), true, false);
AppendJson(sb, "largePayloadBytes", LargePayloadBytes.ToString(), true, false);

View File

@ -23,8 +23,10 @@ namespace TH1_Logic.Steam
private const int MaxLargeMessageBytes = 64 * 1024 * 1024;
private const int MaxLargeWireMessageBytes = MaxLargeMessageBytes + OrderedMessageHeaderSize;
private const float LargeMessageTimeout = 30f;
private const float OutgoingMessageSendInterval = 0.05f;
private const float OutgoingMessageSendInterval = 0.01f;
private const float OutgoingMessageLimitRetryDelay = 0.25f;
private const int MaxOutgoingMessagesPerUpdate = 8;
private const int MaxOutgoingBytesPerUpdate = 512 * 1024;
private const int OrderedMessageMagic = 0x31514f54; // TOQ1
private const int OrderedMessageVersion = 1;
private const int OrderedMessageHeaderSize = 20;
@ -1168,9 +1170,12 @@ namespace TH1_Logic.Steam
if (_outgoingPeerOrder.Count == 0) return;
var peerCount = _outgoingPeerOrder.Count;
var sentCount = 0;
var sentBytes = 0;
for (int attempt = 0; attempt < peerCount; attempt++)
{
if (_outgoingPeerOrder.Count == 0) return;
if (sentCount >= MaxOutgoingMessagesPerUpdate || sentBytes >= MaxOutgoingBytesPerUpdate) return;
if (_outgoingPeerRoundRobinIndex >= _outgoingPeerOrder.Count) _outgoingPeerRoundRobinIndex = 0;
var target = _outgoingPeerOrder[_outgoingPeerRoundRobinIndex];
_outgoingPeerRoundRobinIndex = (_outgoingPeerRoundRobinIndex + 1) % _outgoingPeerOrder.Count;
@ -1231,11 +1236,14 @@ namespace TH1_Logic.Steam
var result = SendRawToResult(pending.Target, conn, pending.Data, pending.Reliable, pending.UseNoNagle, false);
if (result == EResult.k_EResultOK)
{
var sentLength = pending.Data.Length;
DequeueOutgoingMessage(pending.Target, queue);
if (pending.IsLastLargeChunk)
LogSystem.LogInfo($"Queued large P2P message sent to {pending.Target}, messageId: {pending.MessageId}, chunks: {pending.ChunkCount}");
sentCount++;
sentBytes += sentLength;
queue.NextSendTime = Time.time + OutgoingMessageSendInterval;
return;
continue;
}
pending.Attempts++;