Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DiagnoseServerLag v0.9.1
BepInEx/plugins/DiagnoseServerLag.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("DiagnoseServerLag")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.9.1.0")] [assembly: AssemblyInformationalVersion("0.9.1+c050a26e2d64f185f8b74d284a2ec92a42eb79f5")] [assembly: AssemblyProduct("DiagnoseServerLag")] [assembly: AssemblyTitle("DiagnoseServerLag")] [assembly: AssemblyVersion("0.9.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DiagnoseServerLag { internal sealed class ClientSeries { internal struct Second { internal long UtcTicks; internal float FrameMaxMs; internal int Stalls; internal float CpuMsPerSec; internal int Collections; } private const byte Layout = 3; internal long Uid; internal string Name = ""; internal string CpuName = ""; internal int Cores; internal bool HasCpu; internal readonly List<Second> Seconds = new List<Second>(); internal readonly List<Sample> Samples = new List<Sample>(); internal bool Full; internal float FrameMedianMs; internal int TotalStalls; internal float CpuMedianMsPerSec; internal int RoundTripMs; internal int OwnedAI; internal int NearbyAI; internal ZPackage Pack() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)3); val.Write(Uid); val.Write(Name ?? ""); val.Write(CpuName ?? ""); val.Write(Cores); val.Write(HasCpu); val.Write(FrameMedianMs); val.Write(TotalStalls); val.Write(CpuMedianMsPerSec); val.Write(RoundTripMs); val.Write(OwnedAI); val.Write(NearbyAI); ZPackage val2 = new ZPackage(); val2.Write(Samples.Count); foreach (Sample sample in Samples) { SampleWire.Write(val2, sample); } val.WriteCompressed(val2); return val; } internal static ClientSeries Unpack(ZPackage pkg) { if (pkg == null) { return null; } try { ClientSeries clientSeries = new ClientSeries(); byte b = pkg.ReadByte(); if (b < 1) { return null; } clientSeries.Uid = pkg.ReadLong(); clientSeries.Name = pkg.ReadString(); clientSeries.CpuName = pkg.ReadString(); clientSeries.Cores = pkg.ReadInt(); clientSeries.HasCpu = pkg.ReadBool(); clientSeries.FrameMedianMs = pkg.ReadSingle(); clientSeries.TotalStalls = pkg.ReadInt(); clientSeries.CpuMedianMsPerSec = pkg.ReadSingle(); clientSeries.RoundTripMs = pkg.ReadInt(); if (b >= 3) { clientSeries.OwnedAI = pkg.ReadInt(); clientSeries.NearbyAI = pkg.ReadInt(); } if (b >= 2) { ZPackage val = pkg.ReadCompressedPackage(); int num = val.ReadInt(); if (num < 0 || num > 20000) { return null; } for (int i = 0; i < num; i++) { clientSeries.Samples.Add(SampleWire.Read(val, b)); } clientSeries.Full = true; clientSeries.FillSecondsFromSamples(); } else { int num2 = pkg.ReadInt(); if (num2 < 0 || num2 > 20000) { return null; } for (int j = 0; j < num2; j++) { clientSeries.Seconds.Add(new Second { UtcTicks = pkg.ReadLong(), FrameMaxMs = pkg.ReadSingle(), Stalls = pkg.ReadInt(), CpuMsPerSec = pkg.ReadSingle(), Collections = pkg.ReadInt() }); } } return clientSeries; } catch (Exception ex) { DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] Could not read a client series: " + ex.Message)); return null; } } private void FillSecondsFromSamples() { Seconds.Clear(); foreach (Sample sample in Samples) { Seconds.Add(new Second { UtcTicks = sample.UtcTicks, FrameMaxMs = sample.FrameMsMax, Stalls = sample.Stalls, CpuMsPerSec = sample.CpuMsPerSec, Collections = Machine.Collections(sample) }); } } internal static ClientSeries FromLocal(int seconds) { List<Sample> list = Sampler.History.Recent(seconds); ClientSeries clientSeries = new ClientSeries { Uid = ZDOMan.GetSessionID(), Name = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "(no character)"), CpuName = SystemInfo.processorType, Cores = Machine.ProcessorCount, RoundTripMs = Mathf.RoundToInt(LagNetwork.RoundTripMs) }; foreach (Sample item in list) { if (item.HasCpu) { clientSeries.HasCpu = true; } clientSeries.TotalStalls += item.Stalls; clientSeries.Samples.Add(item); } clientSeries.Full = true; clientSeries.FillSecondsFromSamples(); if (list.Count > 0) { clientSeries.OwnedAI = list[list.Count - 1].OwnedAI; clientSeries.NearbyAI = list[list.Count - 1].NearbyAI; } clientSeries.FrameMedianMs = Stats.Median(list, (Sample x) => x.FrameMsAvg); clientSeries.CpuMedianMsPerSec = Stats.Median(list, (Sample x) => x.CpuMsPerSec); return clientSeries; } } internal static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; public static ConsoleEvent <>9__0_3; public static ConsoleEvent <>9__0_4; public static ConsoleEvent <>9__0_5; public static ConsoleEvent <>9__0_6; public static ConsoleEvent <>9__0_7; public static ConsoleEvent <>9__0_8; public static Func<Sample, float> <>9__1_0; public static Func<Sample, float> <>9__1_1; public static Func<Sample, float> <>9__1_5; public static Func<Sample, float> <>9__1_6; public static Func<Sample, float> <>9__1_7; public static Func<Sample, float> <>9__1_8; public static Func<Sample, float> <>9__1_9; public static Func<Sample, float> <>9__1_10; public static Func<Sample, float> <>9__1_11; public static Func<Sample, float> <>9__1_12; public static Func<Sample, float> <>9__1_13; public static Func<Sample, float> <>9__1_2; public static Func<Sample, float> <>9__1_3; public static Func<Sample, float> <>9__1_4; internal void <Register>b__0_0(ConsoleEventArgs args) { if ((Object)(object)Player.m_localPlayer == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("No character loaded."); } } else { LagPanel.Toggle(); } } internal void <Register>b__0_1(ConsoleEventArgs args) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Verdict.CoverageNote()); foreach (Finding item in Verdict.Diagnose()) { stringBuilder.AppendLine($"[{item.Confidence}%] {item.Headline}"); foreach (string item2 in item.Evidence) { stringBuilder.AppendLine(" - " + item2); } if (!string.IsNullOrEmpty(item.Advice)) { stringBuilder.AppendLine(" => " + item.Advice); } } Terminal context = args.Context; if (context != null) { context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } } internal void <Register>b__0_2(ConsoleEventArgs args) { if (!Sampler.TryNewest(out var s)) { Terminal context = args.Context; if (context != null) { context.AddString("Nothing measured yet."); } return; } Terminal context2 = args.Context; if (context2 != null) { context2.AddString($"frames {s.FrameMsAvg:0.0} ms avg / {s.FrameMsMax:0} ms worst over {s.Frames} frames, {s.Stalls} stalls\n" + string.Format("ping {0}, quality {1:0.0}%/{2:0.0}%\n", s.HasPing ? (s.Ping + " ms") : "not measurable", s.LocalQuality * 100f, s.RemoteQuality * 100f) + "queue " + Stats.Bytes(s.SendQueue) + ", rate " + Stats.Bytes(s.SendRate) + "/s, in " + Stats.Bytes(s.InByteSec) + "/s, out " + Stats.Bytes(s.OutByteSec) + "/s\n" + $"objects {s.Zdos} known / {s.Instances} built, {s.ZdosSent}/s sent, {s.ZdosRecv}/s received, {s.ChangeQueue} unacknowledged\n" + $"peers {s.Peers}, history {Sampler.History.Count}s"); } } internal void <Register>b__0_3(ConsoleEventArgs args) { ServerReport latest = LagNetwork.Latest; if (latest == null) { Terminal context = args.Context; if (context != null) { context.AddString(Verdict.CoverageNote()); } return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Verdict.CoverageNote()); stringBuilder.AppendLine($"tick {latest.TickMsAvg:0.0} ms now / {latest.BaselineTickMs:0.0} ms median over {latest.WindowSeconds}s, worst {latest.WorstTickMs:0} ms, {latest.StallsInWindow} stalls"); stringBuilder.AppendLine(string.Format("world {0} objects, {1}/s sent, {2}/s received, {3} players, {4}", latest.Zdos, latest.ZdosSent, latest.ZdosRecv, latest.PeerCount, latest.Dedicated ? "dedicated" : "player-hosted")); stringBuilder.AppendLine("worst player queue " + Stats.Bytes(latest.WorstSendQueue) + ", total send rate " + Stats.Bytes(latest.TotalSendRate) + "/s"); if (latest.PeerDetailWithheld) { stringBuilder.AppendLine("the per-player table was not shared with you"); } foreach (PeerSample peer in latest.Peers) { string name = peer.Name; object arg; if (!peer.HasPing) { arg = "?"; } else { int ping = peer.Ping; arg = ping + " ms"; } stringBuilder.AppendLine($" {name}: ping {arg}, quality {peer.Quality * 100f:0.0}%, " + $"queued {Stats.Bytes(peer.SendQueue)}, rate {Stats.Bytes(peer.SendRate)}/s, {peer.DistanceFromCenter:0} m from center"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } } internal void <Register>b__0_4(ConsoleEventArgs args) { string text = Dump(); Terminal context = args.Context; if (context != null) { context.AddString((text == null) ? "Could not write the dump; see the log." : ("Wrote " + text)); } } internal void <Register>b__0_5(ConsoleEventArgs args) { int seconds = 120; if (args.Length > 1 && int.TryParse(args[1], out var result)) { seconds = Mathf.Clamp(result, 5, Sampler.History.Capacity); } string csvPath; string text = Bench(seconds, out csvPath); DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] bench\n" + text)); Terminal context = args.Context; if (context != null) { context.AddString(text + ((csvPath == null) ? "" : ("\nwrote " + csvPath))); } } internal void <Register>b__0_6(ConsoleEventArgs args) { int seconds = 120; if (args.Length > 1 && int.TryParse(args[1], out var result)) { seconds = result; } LagNetwork.AskCapture(seconds); Terminal context = args.Context; if (context != null) { context.AddString("Asked the server for a capture; its reply prints here when it arrives."); } } internal void <Register>b__0_7(ConsoleEventArgs args) { if (!Machine.Readable) { Terminal context = args.Context; if (context != null) { context.AddString("Process counters unavailable: " + Machine.UnreadableReason); } return; } if (!Sampler.TryNewest(out var s) || !s.HasCpu) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Nothing measured yet."); } return; } Terminal context3 = args.Context; if (context3 != null) { context3.AddString($"CPU {s.CpuMsPerSec:0} ms/s = {Machine.CoreShare(s.CpuMsPerSec) * 100f:0.0}% of one core, " + $"{Machine.MachineShare(s.CpuMsPerSec) * 100f:0.0}% of {Machine.ProcessorCount} cores\n" + $"headroom about {Machine.Headroom(s.CpuMsPerSec):0.0}x the current load before one core is full\n" + $"collections {Machine.Collections(s)} this second" + (Machine.GenerationsDistinct ? $" ({s.Gc0}/{s.Gc1}/{s.Gc2} by generation)" : " (generations not separated)") + ", heap " + Stats.Bytes(s.HeapBytes) + (Machine.HasWorkingSet ? (", working set " + Stats.Bytes(s.WorkingSetBytes)) : ", working set not measurable")); } } internal void <Register>b__0_8(ConsoleEventArgs args) { Sampler.Reset(); Terminal context = args.Context; if (context != null) { context.AddString("History cleared. The baseline rebuilds over the next minute."); } } internal float <Bench>b__1_0(Sample x) { return x.FrameMsAvg; } internal float <Bench>b__1_1(Sample x) { return x.FrameMsMax; } internal float <Bench>b__1_5(Sample x) { return x.CpuMsPerSec; } internal float <Bench>b__1_6(Sample x) { return x.CpuMsPerSec; } internal float <Bench>b__1_7(Sample x) { return x.Gc0; } internal float <Bench>b__1_8(Sample x) { return x.Gc1; } internal float <Bench>b__1_9(Sample x) { return x.Gc2; } internal float <Bench>b__1_10(Sample x) { return Machine.Collections(x); } internal float <Bench>b__1_11(Sample x) { return x.HeapBytes; } internal float <Bench>b__1_12(Sample x) { return x.WorkingSetBytes; } internal float <Bench>b__1_13(Sample x) { return x.HeapBytes; } internal float <Bench>b__1_2(Sample x) { return x.ZdosSent; } internal float <Bench>b__1_3(Sample x) { return x.ZdosRecv; } internal float <Bench>b__1_4(Sample x) { return x.SendQueue; } } internal static void Register() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if ((Object)(object)Player.m_localPlayer == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("No character loaded."); } } else { LagPanel.Toggle(); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("dsl", "Diagnose Server Lag: open or close the lag report", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Verdict.CoverageNote()); foreach (Finding item in Verdict.Diagnose()) { stringBuilder.AppendLine($"[{item.Confidence}%] {item.Headline}"); foreach (string item2 in item.Evidence) { stringBuilder.AppendLine(" - " + item2); } if (!string.IsNullOrEmpty(item.Advice)) { stringBuilder.AppendLine(" => " + item.Advice); } } Terminal context = args.Context; if (context != null) { context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("dsl_why", "Diagnose Server Lag: print the verdict and its evidence", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { if (!Sampler.TryNewest(out var s)) { Terminal context = args.Context; if (context != null) { context.AddString("Nothing measured yet."); } } else { Terminal context2 = args.Context; if (context2 != null) { context2.AddString($"frames {s.FrameMsAvg:0.0} ms avg / {s.FrameMsMax:0} ms worst over {s.Frames} frames, {s.Stalls} stalls\n" + string.Format("ping {0}, quality {1:0.0}%/{2:0.0}%\n", s.HasPing ? (s.Ping + " ms") : "not measurable", s.LocalQuality * 100f, s.RemoteQuality * 100f) + "queue " + Stats.Bytes(s.SendQueue) + ", rate " + Stats.Bytes(s.SendRate) + "/s, in " + Stats.Bytes(s.InByteSec) + "/s, out " + Stats.Bytes(s.OutByteSec) + "/s\n" + $"objects {s.Zdos} known / {s.Instances} built, {s.ZdosSent}/s sent, {s.ZdosRecv}/s received, {s.ChangeQueue} unacknowledged\n" + $"peers {s.Peers}, history {Sampler.History.Count}s"); } } }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("dsl_now", "Diagnose Server Lag: the last second measured on this machine", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj4 = <>c.<>9__0_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { ServerReport latest = LagNetwork.Latest; if (latest == null) { Terminal context = args.Context; if (context != null) { context.AddString(Verdict.CoverageNote()); } } else { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Verdict.CoverageNote()); stringBuilder.AppendLine($"tick {latest.TickMsAvg:0.0} ms now / {latest.BaselineTickMs:0.0} ms median over {latest.WindowSeconds}s, worst {latest.WorstTickMs:0} ms, {latest.StallsInWindow} stalls"); stringBuilder.AppendLine(string.Format("world {0} objects, {1}/s sent, {2}/s received, {3} players, {4}", latest.Zdos, latest.ZdosSent, latest.ZdosRecv, latest.PeerCount, latest.Dedicated ? "dedicated" : "player-hosted")); stringBuilder.AppendLine("worst player queue " + Stats.Bytes(latest.WorstSendQueue) + ", total send rate " + Stats.Bytes(latest.TotalSendRate) + "/s"); if (latest.PeerDetailWithheld) { stringBuilder.AppendLine("the per-player table was not shared with you"); } foreach (PeerSample peer in latest.Peers) { string name = peer.Name; object arg; if (!peer.HasPing) { arg = "?"; } else { int ping = peer.Ping; arg = ping + " ms"; } stringBuilder.AppendLine($" {name}: ping {arg}, quality {peer.Quality * 100f:0.0}%, " + $"queued {Stats.Bytes(peer.SendQueue)}, rate {Stats.Bytes(peer.SendRate)}/s, {peer.DistanceFromCenter:0} m from center"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } } }; <>c.<>9__0_3 = val4; obj4 = (object)val4; } new ConsoleCommand("dsl_server", "Diagnose Server Lag: what the server last reported about itself", (ConsoleEvent)obj4, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj5 = <>c.<>9__0_4; if (obj5 == null) { ConsoleEvent val5 = delegate(ConsoleEventArgs args) { string text = Dump(); Terminal context = args.Context; if (context != null) { context.AddString((text == null) ? "Could not write the dump; see the log." : ("Wrote " + text)); } }; <>c.<>9__0_4 = val5; obj5 = (object)val5; } new ConsoleCommand("dsl_dump", "Diagnose Server Lag: write the measured seconds to a CSV", (ConsoleEvent)obj5, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj6 = <>c.<>9__0_5; if (obj6 == null) { ConsoleEvent val6 = delegate(ConsoleEventArgs args) { int seconds = 120; if (args.Length > 1 && int.TryParse(args[1], out var result)) { seconds = Mathf.Clamp(result, 5, Sampler.History.Capacity); } string csvPath; string text = Bench(seconds, out csvPath); DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] bench\n" + text)); Terminal context = args.Context; if (context != null) { context.AddString(text + ((csvPath == null) ? "" : ("\nwrote " + csvPath))); } }; <>c.<>9__0_5 = val6; obj6 = (object)val6; } new ConsoleCommand("dsl_bench", "Diagnose Server Lag: summarize the last N seconds (default 120) and write them to a CSV", (ConsoleEvent)obj6, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj7 = <>c.<>9__0_6; if (obj7 == null) { ConsoleEvent val7 = delegate(ConsoleEventArgs args) { int seconds = 120; if (args.Length > 1 && int.TryParse(args[1], out var result)) { seconds = result; } LagNetwork.AskCapture(seconds); Terminal context = args.Context; if (context != null) { context.AddString("Asked the server for a capture; its reply prints here when it arrives."); } }; <>c.<>9__0_6 = val7; obj7 = (object)val7; } new ConsoleCommand("dsl_bench_server", "Diagnose Server Lag: ask the server to capture itself (admin only)", (ConsoleEvent)obj7, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj8 = <>c.<>9__0_7; if (obj8 == null) { ConsoleEvent val8 = delegate(ConsoleEventArgs args) { Sample s; if (!Machine.Readable) { Terminal context = args.Context; if (context != null) { context.AddString("Process counters unavailable: " + Machine.UnreadableReason); } } else if (!Sampler.TryNewest(out s) || !s.HasCpu) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Nothing measured yet."); } } else { Terminal context3 = args.Context; if (context3 != null) { context3.AddString($"CPU {s.CpuMsPerSec:0} ms/s = {Machine.CoreShare(s.CpuMsPerSec) * 100f:0.0}% of one core, " + $"{Machine.MachineShare(s.CpuMsPerSec) * 100f:0.0}% of {Machine.ProcessorCount} cores\n" + $"headroom about {Machine.Headroom(s.CpuMsPerSec):0.0}x the current load before one core is full\n" + $"collections {Machine.Collections(s)} this second" + (Machine.GenerationsDistinct ? $" ({s.Gc0}/{s.Gc1}/{s.Gc2} by generation)" : " (generations not separated)") + ", heap " + Stats.Bytes(s.HeapBytes) + (Machine.HasWorkingSet ? (", working set " + Stats.Bytes(s.WorkingSetBytes)) : ", working set not measurable")); } } }; <>c.<>9__0_7 = val8; obj8 = (object)val8; } new ConsoleCommand("dsl_cpu", "Diagnose Server Lag: what this process is costing the machine", (ConsoleEvent)obj8, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj9 = <>c.<>9__0_8; if (obj9 == null) { ConsoleEvent val9 = delegate(ConsoleEventArgs args) { Sampler.Reset(); Terminal context = args.Context; if (context != null) { context.AddString("History cleared. The baseline rebuilds over the next minute."); } }; <>c.<>9__0_8 = val9; obj9 = (object)val9; } new ConsoleCommand("dsl_reset", "Diagnose Server Lag: forget the measurements and start again", (ConsoleEvent)obj9, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } internal static string Bench(int seconds, out string csvPath) { csvPath = null; List<Sample> list = Sampler.History.Recent(seconds); if (list.Count == 0) { return "Nothing measured yet."; } StringBuilder stringBuilder = new StringBuilder(); bool isDedicatedHere = Sampler.IsDedicatedHere; bool isServerHere = Sampler.IsServerHere; string arg = (isDedicatedHere ? "dedicated server" : (isServerHere ? "host (also a client)" : "client")); float num = Stats.Median(list, (Sample x) => x.FrameMsAvg); float num2 = Stats.Max(list, (Sample x) => x.FrameMsMax); int num3 = 0; foreach (Sample item in list) { num3 += item.Stalls; } bool flag = num3 == 0 && num2 <= num * DslConfig.SteadyTickRatio.Value + 2f; Sampler.TryNewest(out var s); stringBuilder.AppendLine(string.Format("DiagnoseServerLag {0} bench - last {1}s", "0.9.1", list.Count)); stringBuilder.AppendLine($" role {arg}, {Machine.ProcessorCount} cores"); stringBuilder.AppendLine($" world {s.Zdos} objects, {s.Peers} players connected"); stringBuilder.AppendLine($" tick {num:0.0} ms median, {num2:0} ms worst, {num3} stalls" + (flag ? " (capped - not a load measurement)" : "")); bool flag2 = false; foreach (Sample item2 in list) { if (item2.HasCpu) { flag2 = true; break; } } if (Machine.Readable && flag2) { float num4 = Stats.Median(list, (Sample x) => x.CpuMsPerSec); float num5 = Stats.Max(list, (Sample x) => x.CpuMsPerSec); stringBuilder.AppendLine($" CPU {num4:0} ms/s = {Machine.CoreShare(num4) * 100f:0.0}% of one core, {Machine.MachineShare(num4) * 100f:0.0}% of the machine"); stringBuilder.AppendLine($" CPU peak {num5:0} ms/s = {Machine.CoreShare(num5) * 100f:0.0}% of one core"); stringBuilder.AppendLine($" headroom about {Machine.Headroom(num4):0.0}x the current load before one core is full"); stringBuilder.AppendLine(Machine.GenerationsDistinct ? $" GC per minute {Stats.Mean(list, (Sample x) => x.Gc0) * 60f:0} gen0, {Stats.Mean(list, (Sample x) => x.Gc1) * 60f:0} gen1, {Stats.Mean(list, (Sample x) => x.Gc2) * 60f:0.0} gen2" : $" GC per minute {Stats.Mean(list, (Sample x) => Machine.Collections(x)) * 60f:0.0} (generations not separated by this runtime)"); stringBuilder.AppendLine(Machine.HasWorkingSet ? (" memory heap " + Stats.Bytes(Stats.Median(list, (Sample x) => x.HeapBytes)) + ", working set " + Stats.Bytes(Stats.Median(list, (Sample x) => x.WorkingSetBytes))) : (" memory heap " + Stats.Bytes(Stats.Median(list, (Sample x) => x.HeapBytes)) + ", working set not measurable")); } else { string text = (string.IsNullOrEmpty(Machine.UnreadableReason) ? "" : (" (" + Machine.UnreadableReason + ")")); stringBuilder.AppendLine(" CPU not measurable" + text); } Sampler.TryNewest(out var s2); if (s2.NearbyAI > 0) { stringBuilder.AppendLine($" simulating {s2.OwnedAI} of {s2.NearbyAI} creatures loaded nearby"); } stringBuilder.AppendLine($" object traffic {Stats.Median(list, (Sample x) => x.ZdosSent):0}/s out, {Stats.Median(list, (Sample x) => x.ZdosRecv):0}/s in"); if (isServerHere) { stringBuilder.AppendLine(" worst queue " + Stats.Bytes(Stats.Max(list, (Sample x) => x.SendQueue))); } csvPath = Dump(seconds); return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } internal static string Dump(int seconds = 0) { try { string text = Path.Combine(Paths.ConfigPath, "DiagnoseServerLag"); Directory.CreateDirectory(text); string text2 = (Sampler.IsDedicatedHere ? "dedicated" : (Sampler.IsServerHere ? "host" : "client")); string text3 = Path.Combine(text, $"lag-{text2}-{DateTime.Now:yyyyMMdd-HHmmss}.csv"); List<Sample> list = ((seconds > 0) ? Sampler.History.Recent(seconds) : Sampler.History.Recent(Sampler.History.Capacity)); CultureInfo invariantCulture = CultureInfo.InvariantCulture; StringBuilder stringBuilder = new StringBuilder(); Sampler.TryNewest(out var s); stringBuilder.AppendLine("# mod,0.9.1"); stringBuilder.AppendLine($"# captured,{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); stringBuilder.AppendLine("# role," + text2); stringBuilder.AppendLine($"# cores,{Machine.ProcessorCount}"); stringBuilder.AppendLine("# os," + SystemInfo.operatingSystem); stringBuilder.AppendLine("# cpu_name," + SystemInfo.processorType); stringBuilder.AppendLine($"# seconds,{list.Count}"); stringBuilder.AppendLine($"# zdos,{s.Zdos}"); stringBuilder.AppendLine($"# peers,{s.Peers}"); stringBuilder.AppendLine($"# cpu_readable,{(Machine.Readable ? 1 : 0)}"); stringBuilder.AppendLine($"# gc_generations_distinct,{(Machine.GenerationsDistinct ? 1 : 0)}"); stringBuilder.AppendLine($"# working_set_readable,{(Machine.HasWorkingSet ? 1 : 0)}"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("second,frames,frame_avg_ms,frame_max_ms,stalls,ping_ms,ping_measured,ping_round_trip,quality_local,quality_remote,in_bytes_sec,out_bytes_sec,send_queue_bytes,send_rate_bytes_sec,zdos,instances,zdos_sent_sec,zdos_recv_sec,change_queue,peers,owned_ai,nearby_ai,cpu_ms_per_sec,cpu_measured,gc0,gc1,gc2,heap_bytes,working_set_bytes"); foreach (Sample item in list) { string[] array = new string[29]; float at = item.At; array[0] = at.ToString("0.0", invariantCulture); int frames = item.Frames; array[1] = frames.ToString(invariantCulture); at = item.FrameMsAvg; array[2] = at.ToString("0.00", invariantCulture); at = item.FrameMsMax; array[3] = at.ToString("0.00", invariantCulture); frames = item.Stalls; array[4] = frames.ToString(invariantCulture); frames = item.Ping; array[5] = frames.ToString(invariantCulture); array[6] = (item.HasPing ? "1" : "0"); array[7] = (item.PingFromRoundTrip ? "1" : "0"); at = item.LocalQuality; array[8] = at.ToString("0.0000", invariantCulture); at = item.RemoteQuality; array[9] = at.ToString("0.0000", invariantCulture); at = item.InByteSec; array[10] = at.ToString("0", invariantCulture); at = item.OutByteSec; array[11] = at.ToString("0", invariantCulture); frames = item.SendQueue; array[12] = frames.ToString(invariantCulture); frames = item.SendRate; array[13] = frames.ToString(invariantCulture); frames = item.Zdos; array[14] = frames.ToString(invariantCulture); frames = item.Instances; array[15] = frames.ToString(invariantCulture); frames = item.ZdosSent; array[16] = frames.ToString(invariantCulture); frames = item.ZdosRecv; array[17] = frames.ToString(invariantCulture); frames = item.ChangeQueue; array[18] = frames.ToString(invariantCulture); frames = item.Peers; array[19] = frames.ToString(invariantCulture); frames = item.OwnedAI; array[20] = frames.ToString(invariantCulture); frames = item.NearbyAI; array[21] = frames.ToString(invariantCulture); at = item.CpuMsPerSec; array[22] = at.ToString("0.0", invariantCulture); array[23] = (item.HasCpu ? "1" : "0"); frames = item.Gc0; array[24] = frames.ToString(invariantCulture); frames = item.Gc1; array[25] = frames.ToString(invariantCulture); frames = item.Gc2; array[26] = frames.ToString(invariantCulture); long heapBytes = item.HeapBytes; array[27] = heapBytes.ToString(invariantCulture); heapBytes = item.WorkingSetBytes; array[28] = heapBytes.ToString(invariantCulture); stringBuilder.AppendLine(string.Join(",", array)); } File.WriteAllText(text3, stringBuilder.ToString()); DiagnoseServerLagMod.Log.LogInfo((object)$"[DiagnoseServerLag] Wrote {list.Count} seconds to {text3}"); return text3; } catch (Exception arg) { DiagnoseServerLagMod.Log.LogError((object)$"[DiagnoseServerLag] Could not write the dump: {arg}"); return null; } } } [BepInPlugin("DeathMonger.DiagnoseServerLag", "Diagnose Server Lag", "0.9.1")] public class DiagnoseServerLagMod : BaseUnityPlugin { public const string ModGuid = "DeathMonger.DiagnoseServerLag"; public const string ModName = "Diagnose Server Lag"; public const string ModVersion = "0.9.1"; internal static ConfigEntry<bool> ModEnabled; private readonly Harmony _harmony = new Harmony("DeathMonger.DiagnoseServerLag"); private bool _wasInWorld; internal static DiagnoseServerLagMod Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Mod Enabled", true, "Master toggle for the entire mod. Set to false to disable the measurements, the report and the console commands without removing the DLL. Requires a game restart to take effect."); if (!ModEnabled.Value) { Log.LogInfo((object)"[DiagnoseServerLag] Mod Enabled = false in config; measuring nothing."); return; } DslConfig.Bind(this); Sampler.ApplyCapacity(DslConfig.HistoryMinutes.Value * 60); Commands.Register(); _harmony.PatchAll(); Log.LogInfo((object)"[DiagnoseServerLag] 0.9.1 loaded."); } private void Update() { if (ModEnabled.Value) { bool flag = (Object)(object)ZNet.instance != (Object)null; if (_wasInWorld && !flag) { Sampler.Reset(); LagNetwork.Reset(); LagPanel.Close(); LagPause.Release(); } _wasInWorld = flag; Sampler.Tick(); LagNetwork.Update(); if ((Object)(object)Player.m_localPlayer == (Object)null) { LagPanel.Close(); LagPause.Release(); } else { LagPanel.Update(); LagHud.Update(); LagPause.Refresh(); } } } private void OnDestroy() { LagPause.Release(); _harmony.UnpatchSelf(); } internal ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } internal ConfigEntry<KeyboardShortcut> BindKey(string section, string key, KeyboardShortcut defaultValue, string description, params KeyboardShortcut[] supersededDefaults) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<KeyboardShortcut> val = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); foreach (KeyboardShortcut val2 in supersededDefaults) { if (((object)val.Value/*cast due to .constrained prefix*/).Equals((object?)val2)) { val.Value = defaultValue; Log.LogInfo((object)$"[DiagnoseServerLag] Config '{key}' still held the old default {val2}; moved it to {defaultValue}."); break; } } return val; } internal ConfigEntry<float> BindRange(string section, string key, float defaultValue, float min, float max, string description) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())); } internal ConfigEntry<int> BindRangeInt(string section, string key, int defaultValue, int min, int max, string description) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>())); } internal static void Message(string text) { if (!((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } internal static class DslConfig { internal static ConfigEntry<KeyboardShortcut> OpenKey; internal static ConfigEntry<bool> ShowHud; internal static ConfigEntry<KeyboardShortcut> HudKey; internal static ConfigEntry<LagHud.Corner> HudPosition; internal static ConfigEntry<float> HudX; internal static ConfigEntry<float> HudY; internal static ConfigEntry<string> PanelSize; internal static ConfigEntry<string> PanelPosition; internal static ConfigEntry<int> ListScrollRows; internal static ConfigEntry<bool> PauseWhileOpen; internal static ConfigEntry<bool> ShowPauseButton; internal static ConfigEntry<float> StallMs; internal static ConfigEntry<int> WindowSeconds; internal static ConfigEntry<int> BaselineSeconds; internal static ConfigEntry<int> HistoryMinutes; internal static ConfigEntry<float> WatchSeconds; internal static ConfigEntry<float> BackgroundSeconds; internal static ConfigEntry<bool> AnswerClients; internal static ConfigEntry<bool> SharePeerDetail; internal static ConfigEntry<bool> ShareMyPerformance; internal static ConfigEntry<float> ServerTickWarnMs; internal static ConfigEntry<float> ServerTickSevereMs; internal static ConfigEntry<float> SteadyTickRatio; internal static ConfigEntry<float> ClientFrameWarnMs; internal static ConfigEntry<float> QueueWarnBytes; internal static ConfigEntry<float> QueueSevereBytes; internal static ConfigEntry<float> QualityWarn; internal static ConfigEntry<float> QualitySevere; internal static ConfigEntry<float> PingJitterWarnMs; internal static ConfigEntry<float> ChurnFloor; internal static ConfigEntry<float> ChurnFactor; internal static ConfigEntry<float> InstanceRiseWarn; internal static ConfigEntry<float> GcCoincidence; internal static void Bind(DiagnoseServerLagMod mod) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) OpenKey = mod.BindKey("Keys", "Open Report", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Opens and closes the lag report. Escape closes it too. Ignored while typing in chat, the console or a text box.", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>())); HudKey = mod.BindKey("Keys", "Toggle Readout", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Turns the corner readout on and off without opening the full report.", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), new KeyboardShortcut((KeyCode)291, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 })); ShowHud = mod.Bind("Display", "Show Readout", defaultValue: true, "The corner readout: frame time, stalls, CPU share, round trip to the server, creatures this machine is simulating, and the server's tick. Shift+F8 toggles it. The creature line is the one worth watching - Valheim runs a creature's AI only on the machine that owns it, and ownership falls to whoever was in range first, so it is the only warning you get that you are carrying a zone for everyone else. A config that already exists keeps whatever it was set to."); HudPosition = mod.Bind("Display", "Readout Position", LagHud.Corner.BottomRight, "Which corner the readout sits in. Choose Custom to place it anywhere with the two settings below. Changes apply immediately, so you can drag the values around while looking at it."); HudX = mod.BindRange("Display", "Readout Custom X", 98f, 0f, 100f, "Custom position only: distance across the screen, as a percentage. 0 is the left edge, 100 the right. Ignored unless Readout Position is Custom."); HudY = mod.BindRange("Display", "Readout Custom Y", 4f, 0f, 100f, "Custom position only: distance up the screen, as a percentage. 0 is the bottom edge, 100 the top. Ignored unless Readout Position is Custom."); PanelSize = mod.Bind("Display", "Panel Size", "860,640", "Width and height of the report, remembered when you drag its bottom-right corner."); PanelPosition = mod.Bind("Display", "Panel Position", "0,0", "Where the report sits, as an offset from the screen center, remembered when you drag it by its title. Set to 0,0 to put it back in the middle."); ListScrollRows = mod.BindRangeInt("Display", "List Scroll Rows", 4, 1, 20, "How many rows a list moves per notch of the mouse wheel."); PauseWhileOpen = mod.Bind("Display", "Pause While Open", defaultValue: false, "Pause the game while the report is open, the way the ESC menu does. Toggle it from the button in the report's top-right corner. This works by itself when playing solo or hosting alone; on a dedicated server it takes the Pause My Server mod, and the button shows whether the pause actually took effect. While the game is paused the mod stops recording, so the verdict you are reading stays the verdict for the seconds that were actually played."); ShowPauseButton = mod.Bind("Display", "Show Pause Button", defaultValue: true, "Show the pause toggle in the report's top-right corner. Turning it off leaves the setting above reachable only from this file."); StallMs = mod.BindRange("Measurement", "Stall Milliseconds", 100f, 20f, 1000f, "A frame longer than this counts as a stall. 100 ms is a single frame at 10 per second, which is comfortably past the point where a person notices. Lower it to catch smaller hitches; raise it if a machine that plays fine is reporting stalls constantly."); WindowSeconds = mod.BindRangeInt("Measurement", "Window Seconds", 10, 3, 120, "How many recent seconds the verdict is made from. Short enough that a bad patch is not diluted by the good minute after it."); BaselineSeconds = mod.BindRangeInt("Measurement", "Baseline Seconds", 60, 10, 600, "How much history counts as 'normal for this server' when judging whether something has got worse. Rules with no universal right answer - object churn especially - are judged against this rather than against a fixed number."); HistoryMinutes = mod.BindRangeInt("Measurement", "History Minutes", 60, 5, 180, "How many minutes of per-second samples to keep, and therefore the longest capture dsl_bench can summarize. A sample is about 110 bytes, so an hour costs roughly 400 KB and three hours about 1.2 MB - the ceiling is set by what is useful to capture, not by what it costs. Longer is not automatically better: the summary reports medians over whatever is in the window, so a window covering ten minutes of work and ten of standing still describes neither. Match the capture to the activity. Takes effect on restart."); WatchSeconds = mod.BindRange("Measurement", "Watch Seconds", 1f, 0.5f, 30f, "How often to ask the server for its numbers while the report is open. Once a second keeps the verdict live; the request and its answer are a few hundred bytes."); BackgroundSeconds = mod.BindRange("Measurement", "Background Seconds", 5f, 0f, 120f, "How often to ask while the report is closed, so history exists before you go looking for it. 0 asks only while the report is open, which means a client that never opens it sends nothing at all."); AnswerClients = mod.Bind("Server", "Answer Clients", defaultValue: true, "SERVER SIDE. Answer clients that ask for the server's own measurements. Turning this off makes the server indistinguishable from one without the mod, and every client falls back to diagnosing its own end alone."); SharePeerDetail = mod.Bind("Server", "Share Peer Detail", defaultValue: true, "SERVER SIDE. Include the per-player table - each player's ping, connection quality, queued bytes and distance from the world center - in the answer to everyone, not only admins. The aggregate numbers that diagnose the server always go to everyone; this is the part that names who is on a bad line. Admins receive it either way."); ShareMyPerformance = mod.Bind("Display", "Share My Performance", defaultValue: true, "Answer an admin's group capture with this machine's frame times, stalls, CPU share and collection counts for the window they asked about. It is what lets a capture tell 'everyone hitched at once', which is the server or the network, from 'one machine hitched', which is that machine. Performance numbers only - nothing about what you were doing. Turn it off and you are simply absent from the group view; everything else still works."); ServerTickWarnMs = mod.BindRange("Thresholds", "Server Tick Warn Ms", 50f, 5f, 500f, "Server milliseconds per tick above which the server is called slow - but only when the tick time is also uneven; see Steady Tick Ratio. This was 33 ms until a real server turned out to run a rock-steady 33.3 ms frame cap, which is exactly 30 ticks a second, so the threshold sat on top of a perfectly healthy server and accused it permanently. 50 ms is 20 ticks a second."); ServerTickSevereMs = mod.BindRange("Thresholds", "Server Tick Severe Ms", 100f, 10f, 1000f, "Server milliseconds per tick above which the server is called badly starved whatever the shape of the measurement: 10 ticks a second, where position updates arrive too late to hide and everyone online rubber-bands. A steady tick is forgiven below this and not above it, because a server holding a metronomic 200 ms is still far too slow to run the game."); SteadyTickRatio = mod.BindRange("Thresholds", "Steady Tick Ratio", 1.5f, 1f, 10f, "How close the server's worst tick has to be to its median before the tick rate is read as a deliberate frame cap rather than a struggle. A frame limiter holds every tick to nearly the same length; a machine that genuinely cannot keep up produces variance, because the work that overruns is not the same work every tick. Below this ratio, with no stalls, the server is left alone however slow the number looks. Raise it to forgive more, lower it to accuse more."); ClientFrameWarnMs = mod.BindRange("Thresholds", "Client Frame Warn Ms", 33f, 8f, 500f, "Your own milliseconds per frame above which your machine is called the bottleneck. 33 ms is 30 frames a second."); QueueWarnBytes = mod.BindRange("Thresholds", "Queue Warn Bytes", 16384f, 1024f, 1048576f, "Bytes handed to the socket and not yet sent, above which the link is called full. Some queue is normal and harmless; what matters is whether it drains, which is why the rule also looks at whether the number is climbing."); QueueSevereBytes = mod.BindRange("Thresholds", "Queue Severe Bytes", 65536f, 4096f, 4194304f, "Queued bytes above which the link is called saturated regardless of direction of travel."); QualityWarn = mod.BindRange("Thresholds", "Quality Warn", 0.98f, 0.5f, 1f, "Connection quality below which packet loss is reported. This is Steam's own figure: a fraction of packets arriving, where 1.0 is no measured loss. 0.98 is about two percent lost, which Valheim already feels."); QualitySevere = mod.BindRange("Thresholds", "Quality Severe", 0.9f, 0.1f, 1f, "Connection quality below which loss is reported as the headline cause."); PingJitterWarnMs = mod.BindRange("Thresholds", "Ping Jitter Warn Ms", 40f, 5f, 500f, "Average change in ping from one second to the next, above which the link is called unstable. Jitter is judged separately from latency because they feel nothing alike: a steady 140 ms is playable, while a ping swinging between 20 and 220 averages better and plays far worse."); ChurnFloor = mod.BindRange("Thresholds", "Churn Floor", 150f, 10f, 5000f, "Object updates a second below which churn is never reported, however much it has risen. Tripling a very small number is not news."); ChurnFactor = mod.BindRange("Thresholds", "Churn Factor", 3f, 1.2f, 20f, "How many times the baseline rate of object updates counts as churn. Judged against this server's own recent normal, because a quiet forest and a working base legitimately differ by an order of magnitude and no fixed number is right for both."); GcCoincidence = mod.BindRange("Thresholds", "GC Coincidence", 0.5f, 0.1f, 1f, "What fraction of stalled seconds must contain a gen1 or gen2 garbage collection before the hitches are blamed on the collector. Also required to be at least twice the rate seen in seconds that did not stall, because a machine collecting constantly would otherwise have every stall blamed on it. Gen0 is ignored entirely: it is cheap and continuous."); InstanceRiseWarn = mod.BindRange("Thresholds", "Instance Rise Warn", 40f, 5f, 2000f, "New objects a second being built into the scene which, happening at the same time as a stall, attributes that stall to loading the world rather than to the machine being too slow."); } } internal static class GroupReport { private const int Together = 2; internal static string Build(int seconds, List<ClientSeries> clients, int expected, out string csvPath) { csvPath = null; StringBuilder stringBuilder = new StringBuilder(); List<Sample> list = Sampler.History.Recent(seconds); stringBuilder.AppendLine(string.Format("DiagnoseServerLag {0} group capture - {1}s, ", "0.9.1", list.Count) + string.Format("{0} of {1} client{2} answered", clients.Count, expected, (expected == 1) ? "" : "s")); stringBuilder.AppendLine(); float num = Stats.Median(list, (Sample x) => x.FrameMsAvg); int num2 = 0; foreach (Sample item in list) { num2 += item.Stalls; } float num3 = Stats.Median(list, (Sample x) => x.CpuMsPerSec); stringBuilder.AppendLine(string.Format(" {0,-16} tick {1,6:0.0} ms stalls {2,3} ", "server", num, num2) + ((num3 > 0f) ? $"CPU {Machine.CoreShare(num3) * 100f,5:0.0}% of a core" : "CPU n/a") + $" {Stats.Median(list, (Sample x) => x.Zdos):0} objects"); foreach (ClientSeries client in clients) { string text = ((client.HasCpu && client.CpuMedianMsPerSec > 0f) ? $"CPU {client.CpuMedianMsPerSec / 10f,5:0.0}% of a core" : "CPU n/a"); string text2 = ((client.NearbyAI > 0) ? $" simulating {client.OwnedAI}/{client.NearbyAI} creatures" : ""); stringBuilder.AppendLine($" {Trim(client.Name, 16),-16} frame {client.FrameMedianMs,5:0.0} ms stalls {client.TotalStalls,3} {text}" + ((client.RoundTripMs > 0) ? $" rt {client.RoundTripMs} ms" : "") + text2); } int num4 = 0; foreach (ClientSeries client2 in clients) { if (!client2.Full) { num4++; } } if (num4 > 0) { stringBuilder.AppendLine($" ({num4} client(s) sent the reduced 0.5.x format; their extra columns are empty, not zero)"); } if (clients.Count < expected) { stringBuilder.AppendLine($" ({expected - clients.Count} client(s) did not answer: no mod, an older version, or Share My Performance off)"); } stringBuilder.AppendLine(); stringBuilder.AppendLine(Correlate(list, clients)); string text3 = Ownership(clients); if (text3 != null) { stringBuilder.AppendLine(); stringBuilder.AppendLine(text3); } csvPath = WriteCsv(list, clients); return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static string Correlate(List<Sample> window, List<ClientSeries> clients) { Dictionary<long, List<string>> stalledBy = new Dictionary<long, List<string>>(); foreach (Sample item in window) { if (item.Stalls > 0 && item.UtcTicks > 0) { Mark(ToSecond(item.UtcTicks), "server"); } } foreach (ClientSeries client in clients) { foreach (ClientSeries.Second second in client.Seconds) { if (second.Stalls > 0 && second.UtcTicks > 0) { Mark(ToSecond(second.UtcTicks), Trim(client.Name, 16)); } } } int num = clients.Count + 1; List<KeyValuePair<long, List<string>>> list = new List<KeyValuePair<long, List<string>>>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<long, List<string>> item2 in stalledBy) { if (item2.Value.Count >= 2) { list.Add(item2); continue; } string key = item2.Value[0]; dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1)); } if (stalledBy.Count == 0) { return "Nobody stalled. Nothing to attribute."; } StringBuilder stringBuilder = new StringBuilder(); if (list.Count > 0) { list.Sort((KeyValuePair<long, List<string>> a, KeyValuePair<long, List<string>> b) => a.Key.CompareTo(b.Key)); stringBuilder.AppendLine($"SHARED: {list.Count} second(s) where two or more machines stalled together."); int num2 = 0; foreach (KeyValuePair<long, List<string>> item3 in list) { if (num2++ >= 5) { stringBuilder.AppendLine($" ... and {list.Count - 5} more"); break; } stringBuilder.AppendLine($" {new DateTime(item3.Key * 10000000, DateTimeKind.Utc).ToLocalTime():HH:mm:ss} " + string.Join(", ", item3.Value.ToArray())); } stringBuilder.AppendLine(" Machines do not hitch in the same second by chance. Look at the server and at the"); stringBuilder.AppendLine(" network path everyone shares, not at any one computer."); } if (dictionary.Count > 0) { if (list.Count > 0) { stringBuilder.AppendLine(); } stringBuilder.AppendLine("ALONE: stalls nobody else had, which are local to that machine."); foreach (KeyValuePair<string, int> item4 in dictionary) { stringBuilder.AppendLine($" {item4.Key,-16} {item4.Value} second(s) stalling by itself"); } if (list.Count == 0 && num > 1) { stringBuilder.AppendLine(" No second had two machines stalling together, so nothing here points at the server."); } } if (num == 1) { stringBuilder.AppendLine("Only this machine reported, so nothing can be told apart. Ask again with players connected."); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); void Mark(long second, string who) { if (!stalledBy.TryGetValue(second, out var value2)) { value2 = (stalledBy[second] = new List<string>()); } if (!value2.Contains(who)) { value2.Add(who); } } } private static string Ownership(List<ClientSeries> clients) { int num = 0; int num2 = 0; ClientSeries clientSeries = null; foreach (ClientSeries client in clients) { num += client.OwnedAI; if (client.OwnedAI > 0) { num2++; } if (clientSeries == null || client.OwnedAI > clientSeries.OwnedAI) { clientSeries = client; } } if (clientSeries == null || num < 5 || clients.Count < 2) { return null; } float num3 = (float)clientSeries.OwnedAI / (float)num; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"CREATURE SIMULATION: {num} creature(s) across {clients.Count} clients, " + $"{num2} of them carrying any."); foreach (ClientSeries client2 in clients) { if (client2.OwnedAI > 0) { stringBuilder.AppendLine($" {Trim(client2.Name, 16),-16} {client2.OwnedAI,4} owned of {client2.NearbyAI} loaded nearby"); } } if (num3 >= 0.7f && clients.Count > 1) { stringBuilder.AppendLine($" {Trim(clientSeries.Name, 16)} is running {num3 * 100f:0}% of it. Valheim simulates a creature only on"); stringBuilder.AppendLine(" the machine that owns it, so everyone else's fights are being computed there, and"); stringBuilder.AppendLine(" their hits are routed through that connection. Ownership goes to whoever was in"); stringBuilder.AppendLine(" range first and is never rebalanced - spreading out, or letting the strongest"); stringBuilder.AppendLine(" machine enter a zone first, is the only lever there is."); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static long ToSecond(long utcTicks) { return utcTicks / 10000000; } private static string Trim(string s, int max) { if (string.IsNullOrEmpty(s)) { return "(unnamed)"; } if (s.Length > max) { return s.Substring(0, max); } return s; } private static string WriteCsv(List<Sample> window, List<ClientSeries> clients) { try { string text = Path.Combine(Paths.ConfigPath, "DiagnoseServerLag"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, $"group-{DateTime.Now:yyyyMMdd-HHmmss}.csv"); CultureInfo invariantCulture = CultureInfo.InvariantCulture; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# mod,0.9.1"); stringBuilder.AppendLine($"# captured,{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); stringBuilder.AppendLine($"# machines,{clients.Count + 1}"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("utc,machine,frame_avg_ms,frame_max_ms,stalls,frames,ping_ms,ping_measured,ping_round_trip,quality_local,quality_remote,in_bytes_sec,out_bytes_sec,send_queue_bytes,send_rate_bytes_sec,zdos,instances,zdos_sent_sec,zdos_recv_sec,change_queue,peers,cpu_ms_per_sec,cpu_measured,gc0,gc1,gc2,collections,heap_bytes,working_set_bytes,owned_ai,nearby_ai"); foreach (Sample item in window) { stringBuilder.AppendLine(Row("server", item, invariantCulture)); } foreach (ClientSeries client in clients) { if (client.Full) { foreach (Sample sample in client.Samples) { stringBuilder.AppendLine(Row(Csv(client.Name), sample, invariantCulture)); } continue; } foreach (ClientSeries.Second second in client.Seconds) { string[] array = new string[31]; array[0] = Iso(second.UtcTicks); array[1] = Csv(client.Name); array[2] = ""; float frameMaxMs = second.FrameMaxMs; array[3] = frameMaxMs.ToString("0.00", invariantCulture); int stalls = second.Stalls; array[4] = stalls.ToString(invariantCulture); array[5] = ""; array[6] = ""; array[7] = ""; array[8] = ""; array[9] = ""; array[10] = ""; array[11] = ""; array[12] = ""; array[13] = ""; array[14] = ""; array[15] = ""; array[16] = ""; array[17] = ""; array[18] = ""; array[19] = ""; array[20] = ""; frameMaxMs = second.CpuMsPerSec; array[21] = frameMaxMs.ToString("0.0", invariantCulture); array[22] = ""; array[23] = ""; array[24] = ""; array[25] = ""; stalls = second.Collections; array[26] = stalls.ToString(invariantCulture); array[27] = ""; array[28] = ""; array[29] = ""; array[30] = ""; stringBuilder.AppendLine(string.Join(",", array)); } } File.WriteAllText(text2, stringBuilder.ToString()); return text2; } catch (Exception arg) { DiagnoseServerLagMod.Log.LogError((object)$"[DiagnoseServerLag] Could not write the group CSV: {arg}"); return null; } } private static string Row(string machine, Sample s, CultureInfo c) { return string.Join(",", Iso(s.UtcTicks), machine, s.FrameMsAvg.ToString("0.00", c), s.FrameMsMax.ToString("0.00", c), s.Stalls.ToString(c), s.Frames.ToString(c), s.Ping.ToString(c), s.HasPing ? "1" : "0", s.PingFromRoundTrip ? "1" : "0", s.LocalQuality.ToString("0.0000", c), s.RemoteQuality.ToString("0.0000", c), s.InByteSec.ToString("0", c), s.OutByteSec.ToString("0", c), s.SendQueue.ToString(c), s.SendRate.ToString(c), s.Zdos.ToString(c), s.Instances.ToString(c), s.ZdosSent.ToString(c), s.ZdosRecv.ToString(c), s.ChangeQueue.ToString(c), s.Peers.ToString(c), s.CpuMsPerSec.ToString("0.0", c), s.HasCpu ? "1" : "0", s.Gc0.ToString(c), s.Gc1.ToString(c), s.Gc2.ToString(c), Machine.Collections(s).ToString(c), s.HeapBytes.ToString(c), s.WorkingSetBytes.ToString(c), s.OwnedAI.ToString(c), s.NearbyAI.ToString(c)); } private static string Iso(long ticks) { if (ticks > 0) { return new DateTime(ticks, DateTimeKind.Utc).ToString("yyyy-MM-ddTHH:mm:ssZ"); } return ""; } private static string Csv(string s) { if (!string.IsNullOrEmpty(s)) { if (s.IndexOf(',') < 0) { return s; } return "\"" + s.Replace("\"", "\"\"") + "\""; } return ""; } } internal static class LagHud { internal enum Corner { TopLeft, TopRight, BottomLeft, BottomRight, Custom } private const float Margin = 18f; private const float HintGap = 12f; private const float HintFallback = 110f; private static readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4]; private static GameObject _root; private static TMP_Text _label; private static float _nextRefresh; private static readonly Color Good = new Color(0.78f, 0.75f, 0.7f); private static readonly Color Warn = new Color(1f, 0.72f, 0.32f); private static readonly Color Bad = new Color(1f, 0.42f, 0.35f); internal static void Update() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (DslConfig.HudKey != null && Keys.CanTakeInput() && Keys.IsDown(DslConfig.HudKey.Value)) { DslConfig.ShowHud.Value = !DslConfig.ShowHud.Value; DiagnoseServerLagMod.Message(DslConfig.ShowHud.Value ? "Lag readout on" : "Lag readout off"); } if (!DslConfig.ShowHud.Value || Sampler.History.Count <= 0) { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } } else if (!((Object)(object)_root == (Object)null) || Create()) { _root.SetActive(true); if (!(Time.unscaledTime < _nextRefresh)) { _nextRefresh = Time.unscaledTime + 1f; Refresh(); } } } internal static void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } } private static void Refresh() { if (!Sampler.TryNewest(out var s)) { return; } ServerReport latest = LagNetwork.Latest; List<Sample> list = Sampler.History.Recent(DslConfig.WindowSeconds.Value); int num = 0; foreach (Sample item in list) { num += item.Stalls; } List<string> list2 = new List<string>(); list2.Add(Line("frames", $"{s.FrameMsAvg:0} ms {1000f / Mathf.Max(0.01f, s.FrameMsAvg):0}/s", Rank(s.FrameMsAvg, DslConfig.ClientFrameWarnMs.Value, DslConfig.ClientFrameWarnMs.Value * 2f))); list2.Add(Line("stalls", $"{num} in {list.Count}s", (num > 0) ? ((num <= 2) ? 1 : 2) : 0)); if (Machine.Readable && s.HasCpu) { float num2 = Machine.MachineShare(s.CpuMsPerSec); list2.Add(Line("cpu", $"{Machine.CoreShare(s.CpuMsPerSec) * 100f:0}% of a core" + $" ({num2 * 100f:0}% of {Machine.ProcessorCount})", Rank(num2, 0.5f, 0.8f))); } if (s.HasPing && s.Ping > 0) { list2.Add(Line(s.PingFromRoundTrip ? "round trip" : "ping", $"{s.Ping} ms", Rank(s.Ping, 120f, 250f))); } else if (LagNetwork.RoundTripMs > 0f) { list2.Add(Line("round trip", $"{LagNetwork.RoundTripMs:0} ms", Rank(LagNetwork.RoundTripMs, 120f, 250f))); } else { list2.Add(Line("link", s.HasPing ? "under 1 ms" : "not measurable", 0)); } if (s.NearbyAI > 0) { bool flag = Sampler.PlayersOnline > 1 && s.NearbyAI >= 5 && (float)s.OwnedAI >= (float)s.NearbyAI * 0.8f; string arg = (string.IsNullOrEmpty(Sampler.OtherOwners) ? "" : (" (" + Sampler.OtherOwners + ")")); list2.Add(Line("simulating", $"{s.OwnedAI}/{s.NearbyAI}{arg}", flag ? 1 : 0)); List<LagNetwork.OwnerLatency> list3 = LagNetwork.OwnerLatencies(); LagNetwork.OwnerLatency ownerLatency = null; foreach (LagNetwork.OwnerLatency item2 in list3) { if (item2.Answered && (ownerLatency == null || item2.Ms > ownerLatency.Ms)) { ownerLatency = item2; } } if (ownerLatency != null) { list2.Add(Line("their stuff", string.Format("{0} {1:0} ms", string.IsNullOrEmpty(ownerLatency.Name) ? "another player" : ownerLatency.Name, ownerLatency.Ms) + ((list3.Count > 1) ? $" (+{list3.Count - 1} more)" : ""), Rank(ownerLatency.Ms, 200f, 400f))); } } if (latest == null) { list2.Add(Line("server", (LagNetwork.Module == ServerModule.Absent) ? "no mod" : "asking...", 0)); } else { float num3 = Mathf.Max(latest.TickMsAvg, latest.BaselineTickMs); list2.Add(Line("server", $"{num3:0.0} ms {latest.Zdos} obj", (!Verdict.ServerPaced(latest)) ? Rank(num3, DslConfig.ServerTickWarnMs.Value, DslConfig.ServerTickSevereMs.Value) : 0)); } ApplyPosition(); _label.text = string.Join("\n", list2.ToArray()); } private static int Rank(float value, float warn, float severe) { if (!(value >= severe)) { return (value >= warn) ? 1 : 0; } return 2; } private static string Line(string label, string value, int rank) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) string arg = ColorUtility.ToHtmlStringRGB((rank >= 2) ? Bad : ((rank == 1) ? Warn : Good)); return $"<color=#{arg}><mspace=0.55em>{label,-11}</mspace>{value}</color>"; } private static void ApplyPosition() { //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_label == (Object)null)) { RectTransform rectTransform = _label.rectTransform; Corner corner = ((DslConfig.HudPosition != null) ? DslConfig.HudPosition.Value : Corner.BottomRight); float num = 18f + BottomClearance(); Vector2 val = default(Vector2); Vector2 zero = default(Vector2); switch (corner) { case Corner.TopLeft: ((Vector2)(ref val))..ctor(0f, 1f); ((Vector2)(ref zero))..ctor(18f, -18f); break; case Corner.TopRight: ((Vector2)(ref val))..ctor(1f, 1f); ((Vector2)(ref zero))..ctor(-18f, -18f); break; case Corner.BottomLeft: ((Vector2)(ref val))..ctor(0f, 0f); ((Vector2)(ref zero))..ctor(18f, num); break; case Corner.Custom: { float num2 = ((DslConfig.HudX != null) ? DslConfig.HudX.Value : 98f) / 100f; float num3 = ((DslConfig.HudY != null) ? DslConfig.HudY.Value : 4f) / 100f; ((Vector2)(ref val))..ctor(Mathf.Clamp01(num2), Mathf.Clamp01(num3)); zero = Vector2.zero; break; } default: ((Vector2)(ref val))..ctor(1f, 0f); ((Vector2)(ref zero))..ctor(-18f, num); break; } Vector2 val2 = (rectTransform.pivot = val); Vector2 anchorMin = (rectTransform.anchorMax = val2); rectTransform.anchorMin = anchorMin; rectTransform.anchoredPosition = zero; _label.alignment = (TextAlignmentOptions)((!(val.x > 0.5f)) ? ((val.y > 0.5f) ? 257 : 1025) : ((val.y > 0.5f) ? 260 : 1028)); } } private static float BottomClearance() { try { KeyHints instance = KeyHints.instance; if ((Object)(object)instance == (Object)null || !((Component)instance).gameObject.activeInHierarchy) { return 0f; } RectTransform component = ((Component)instance).GetComponent<RectTransform>(); if ((Object)(object)component == (Object)null) { return 110f; } Canvas val = (((Object)(object)_root != (Object)null) ? _root.GetComponent<Canvas>() : null); float num = (((Object)(object)val != (Object)null && val.scaleFactor > 0f) ? val.scaleFactor : 1f); float num2 = (float)Screen.height / num * 0.4f; float num3 = 0f; for (int i = 0; i < ((Transform)component).childCount; i++) { Transform child = ((Transform)component).GetChild(i); RectTransform val2 = (RectTransform)(object)((child is RectTransform) ? child : null); if (!((Object)(object)val2 == (Object)null) && ((Component)val2).gameObject.activeInHierarchy) { float num4 = TopOf(val2, num); if (num4 > num3 && num4 <= num2) { num3 = num4; } } } if (num3 <= 0f) { float num5 = TopOf(component, num); if (num5 > 0f && num5 <= num2) { num3 = num5; } } return (num3 > 0f) ? (num3 + 12f) : 110f; } catch { return 110f; } } private static float TopOf(RectTransform rt, float scale) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) rt.GetWorldCorners(_corners); return RectTransformUtility.WorldToScreenPoint((Camera)null, _corners[1]).y / scale; } private static bool Create() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) MessageHud instance = MessageHud.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_messageCenterText == (Object)null) { return false; } TMP_Text messageCenterText = instance.m_messageCenterText; _root = new GameObject("DiagnoseServerLag_Hud"); Canvas obj = _root.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 900; CanvasScaler obj2 = _root.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; GameObject val = new GameObject("DiagnoseServerLag_HudLabel", new Type[2] { typeof(RectTransform), typeof(CanvasRenderer) }); val.SetActive(false); val.transform.SetParent(_root.transform, false); TextMeshProUGUI obj3 = val.AddComponent<TextMeshProUGUI>(); ((TMP_Text)obj3).font = messageCenterText.font; ((TMP_Text)obj3).fontSharedMaterial = messageCenterText.fontSharedMaterial; ((TMP_Text)obj3).fontSize = 17f; ((TMP_Text)obj3).alignment = (TextAlignmentOptions)257; ((TMP_Text)obj3).richText = true; ((Graphic)obj3).raycastTarget = false; ((TMP_Text)obj3).rectTransform.sizeDelta = new Vector2(420f, 150f); val.SetActive(true); _label = (TMP_Text)(object)obj3; ApplyPosition(); return true; } } internal enum ServerModule { Unknown, Present, Absent, Local } internal static class LagNetwork { internal sealed class OwnerLatency { internal long Uid; internal string Name = ""; internal int Objects; internal float Ms; internal bool Answered; internal float LastReplyAt; } private const string RpcRequest = "DSL_Request"; private const string RpcReport = "DSL_Report"; private const string RpcCapture = "DSL_Capture"; private const string RpcCaptureResult = "DSL_CaptureResult"; private const string RpcCaptureAll = "DSL_CaptureAll"; private const string RpcClientSeries = "DSL_ClientSeries"; private const string RpcEcho = "DSL_Echo"; private const string RpcEchoReply = "DSL_EchoReply"; private const int SilenceBeforeAbsent = 3; private static ZRoutedRpc _registeredOn; private static float _lastAskedAt = -999f; private static int _unanswered; private static int _seq; private static int _awaitingSeq = -1; private static float _awaitingSince; private static readonly Dictionary<long, float> _peerRtt = new Dictionary<long, float>(); private static readonly Dictionary<long, OwnerLatency> _owners = new Dictionary<long, OwnerLatency>(); private static readonly Dictionary<int, float> _echoSentAt = new Dictionary<int, float>(); private static readonly Dictionary<int, long> _echoTarget = new Dictionary<int, long>(); private static int _echoSeq; private static float _nextEchoAt; private const float EchoEverySeconds = 4f; private const int MaxEchoTargets = 4; private const float EchoTimeout = 10f; private const float GatherSeconds = 8f; private static readonly List<ClientSeries> _gathered = new List<ClientSeries>(); private static long _gatherFor; private static int _gatherSeconds; private static float _gatherDeadline; private static int _gatherExpected; private static bool _gathering; internal static float RoundTripMs { get; private set; } internal static ServerReport Latest { get; private set; } internal static ServerModule Module { get; private set; } = ServerModule.Unknown; internal static float ReportAge { get { if (Latest != null) { return Time.unscaledTime - Latest.ReceivedAt; } return -1f; } } internal static float PeerRoundTripMs(long uid) { if (!_peerRtt.TryGetValue(uid, out var value)) { return 0f; } return value; } private static void RecordRoundTrip(float ms) { if (!(ms <= 0f) && !(ms > 10000f)) { RoundTripMs = ((RoundTripMs <= 0f) ? ms : ((RoundTripMs * 2f + ms) / 3f)); } } internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || instance == _registeredOn) { return; } _registeredOn = instance; try { instance.Register<ZPackage>("DSL_Request", (Action<long, ZPackage>)RPC_Request); instance.Register<ZPackage>("DSL_Report", (Action<long, ZPackage>)RPC_Report); instance.Register<ZPackage>("DSL_Capture", (Action<long, ZPackage>)RPC_Capture); instance.Register<ZPackage>("DSL_CaptureResult", (Action<long, ZPackage>)RPC_CaptureResult); instance.Register<ZPackage>("DSL_CaptureAll", (Action<long, ZPackage>)RPC_CaptureAll); instance.Register<ZPackage>("DSL_ClientSeries", (Action<long, ZPackage>)RPC_ClientSeries); instance.Register<ZPackage>("DSL_Echo", (Action<long, ZPackage>)RPC_Echo); instance.Register<ZPackage>("DSL_EchoReply", (Action<long, ZPackage>)RPC_EchoReply); DiagnoseServerLagMod.Log.LogInfo((object)"[DiagnoseServerLag] Diagnostic RPCs registered."); } catch (Exception arg) { DiagnoseServerLagMod.Log.LogError((object)$"[DiagnoseServerLag] Could not register the diagnostic RPCs: {arg}"); } } internal static void Reset() { Latest = null; Module = ServerModule.Unknown; _lastAskedAt = -999f; _unanswered = 0; RoundTripMs = 0f; _awaitingSeq = -1; _peerRtt.Clear(); _owners.Clear(); _echoSentAt.Clear(); _echoTarget.Clear(); } internal static void Update() { Register(); if ((Object)(object)ZNet.instance == (Object)null) { return; } UpdateGather(); if (Sampler.IsServerHere) { Module = ServerModule.Local; if (Latest == null || !(Time.unscaledTime - Latest.ReceivedAt < 1f)) { Latest = ServerReport.FromLocal(includePeerDetail: true); Latest.ReceivedAt = Time.unscaledTime; } } else { float num = (LagPanel.IsOpen ? DslConfig.WatchSeconds.Value : DslConfig.BackgroundSeconds.Value); if (!(num <= 0f) && !(Time.unscaledTime - _lastAskedAt < num)) { Ask(); } } } internal static void Ask() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown ZRoutedRpc instance = ZRoutedRpc.instance; ZNet instance2 = ZNet.instance; if (instance != null && !((Object)(object)instance2 == (Object)null) && !instance2.IsServer()) { _lastAskedAt = Time.unscaledTime; if (Module != ServerModule.Present && ++_unanswered >= 3) { Module = ServerModule.Absent; } ZPackage val = new ZPackage(); val.Write(++_seq); val.Write(RoundTripMs); _awaitingSeq = _seq; _awaitingSince = Time.unscaledTime; instance.InvokeRoutedRPC("DSL_Request", new object[1] { val }); } } private static void RPC_Request(long sender, ZPackage pkg) { try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || !DslConfig.AnswerClients.Value) { return; } int echo = 0; try { echo = pkg.ReadInt(); float num = pkg.ReadSingle(); if (num > 0f && num < 10000f) { _peerRtt[sender] = num; } } catch { } ServerReport serverReport = ServerReport.FromLocal(MaySeePeerDetail(instance, sender)); serverReport.Echo = echo; ZRoutedRpc instance2 = ZRoutedRpc.instance; if (instance2 != null) { instance2.InvokeRoutedRPC(sender, "DSL_Report", new object[1] { serverReport.Pack() }); } } catch (Exception ex) { DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] Could not answer a diagnostic request: " + ex.Message)); } } private static void RPC_Report(long sender, ZPackage pkg) { ServerReport serverReport = ServerReport.Unpack(pkg); if (serverReport != null) { serverReport.ReceivedAt = Time.unscaledTime; if (serverReport.Echo != 0 && serverReport.Echo == _awaitingSeq) { RecordRoundTrip((Time.unscaledTime - _awaitingSince) * 1000f); _awaitingSeq = -1; } Latest = serverReport; Module = ServerModule.Present; _unanswered = 0; } } internal static void AskCapture(int seconds) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown ZRoutedRpc instance = ZRoutedRpc.instance; ZNet instance2 = ZNet.instance; if (instance != null && !((Object)(object)instance2 == (Object)null)) { if (instance2.IsServer()) { Commands.Bench(seconds, out var _); BeginGather(ZDOMan.GetSessionID(), seconds); return; } ZPackage val = new ZPackage(); val.Write(seconds); instance.InvokeRoutedRPC("DSL_Capture", new object[1] { val }); } } private static void RPC_Capture(long sender, ZPackage pkg) { try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } int num = 120; try { num = pkg.ReadInt(); } catch { } num = Mathf.Clamp(num, 5, Sampler.History.Capacity); if (!IsAdmin(instance, sender)) { ZRoutedRpc instance2 = ZRoutedRpc.instance; if (instance2 != null) { instance2.InvokeRoutedRPC(sender, "DSL_CaptureResult", new object[1] { Wrap("Capturing the server needs admin rights.") }); } } else { Commands.Bench(num, out var _); BeginGather(sender, num); } } catch (Exception ex) { DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] Could not take a capture: " + ex.Message)); } } private static void RPC_CaptureResult(long sender, ZPackage pkg) { string text; try { text = pkg.ReadString(); } catch { return; } if (!string.IsNullOrEmpty(text)) { Print(text); } } private static ZPackage Wrap(string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(text ?? ""); return val; } private static void Print(string text) { DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] " + text)); try { Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(text); } } catch { } DiagnoseServerLagMod.Message("Server capture ready - see the console (F5)"); } private static bool IsAdmin(ZNet znet, long sender) { if (sender == ZDOMan.GetSessionID()) { return true; } ZNetPeer peer = znet.GetPeer(sender); if (peer?.m_socket == null) { return false; } string hostName = peer.m_socket.GetHostName(); if (!string.IsNullOrEmpty(hostName)) { return znet.IsAdmin(hostName); } return false; } internal static List<OwnerLatency> OwnerLatencies() { List<OwnerLatency> list = new List<OwnerLatency>(_owners.Values); list.Sort((OwnerLatency a, OwnerLatency b) => b.Objects.CompareTo(a.Objects)); return list; } internal static void TrackOwners(Dictionary<long, int> owners) { List<long> list = new List<long>(); foreach (KeyValuePair<long, OwnerLatency> owner in _owners) { if (!owners.ContainsKey(owner.Key)) { list.Add(owner.Key); } } foreach (long item in list) { _owners.Remove(item); } foreach (KeyValuePair<long, int> owner2 in owners) { if (!_owners.TryGetValue(owner2.Key, out var value)) { Dictionary<long, OwnerLatency> owners2 = _owners; long key = owner2.Key; OwnerLatency obj = new OwnerLatency { Uid = owner2.Key }; value = obj; owners2[key] = obj; } value.Objects = owner2.Value; if (string.IsNullOrEmpty(value.Name)) { value.Name = NameFor(owner2.Key); } if (value.Answered && Time.unscaledTime - value.LastReplyAt > 30f) { value.Answered = false; } } if (!(Time.unscaledTime < _nextEchoAt)) { _nextEchoAt = Time.unscaledTime + 4f; SendEchoes(); } } private static void SendEchoes() { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || (Object)(object)ZNet.instance == (Object)null) { return; } List<int> list = new List<int>(); foreach (KeyValuePair<int, float> item in _echoSentAt) { if (Time.unscaledTime - item.Value > 10f) { list.Add(item.Key); } } foreach (int item2 in list) { _echoSentAt.Remove(item2); _echoTarget.Remove(item2); } List<OwnerLatency> list2 = OwnerLatencies(); int num = 0; foreach (OwnerLatency item3 in list2) { if (num >= 4) { break; } num++; int num2 = ++_echoSeq; _echoSentAt[num2] = Time.unscaledTime; _echoTarget[num2] = item3.Uid; ZPackage val = new ZPackage(); val.Write(num2); instance.InvokeRoutedRPC(item3.Uid, "DSL_Echo", new object[1] { val }); } } private static void RPC_Echo(long sender, ZPackage pkg) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown try { if (DslConfig.ShareMyPerformance.Value) { int num = pkg.ReadInt(); ZPackage val = new ZPackage(); val.Write(num); val.Write(((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : ""); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "DSL_EchoReply", new object[1] { val }); } } } catch { } } private static void RPC_EchoReply(long sender, ZPackage pkg) { try { int key = pkg.ReadInt(); string text = pkg.ReadString(); if (!_echoSentAt.TryGetValue(key, out var value)) { return; } _echoSentAt.Remove(key); _echoTarget.Remove(key); if (!_owners.TryGetValue(sender, out var value2)) { return; } float num = (Time.unscaledTime - value) * 1000f; if (!(num <= 0f) && !(num > 20000f)) { value2.Ms = ((value2.Ms <= 0f) ? num : ((value2.Ms * 2f + num) / 3f)); value2.Answered = true; value2.LastReplyAt = Time.unscaledTime; if (!string.IsNullOrEmpty(text)) { value2.Name = text; } } } catch { } } private static string NameFor(long uid) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return ""; } foreach (PlayerInfo player in instance.GetPlayerList()) { ZDOID characterID = player.m_characterID; if (((ZDOID)(ref characterID)).UserID == uid) { return player.m_name; } } } catch { } return ""; } private static void BeginGather(long asker, int seconds) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown ZNet instance = ZNet.instance; ZRoutedRpc instance2 = ZRoutedRpc.instance; if ((Object)(object)instance == (Object)null || instance2 == null) { return; } _gathered.Clear(); _gatherFor = asker; _gatherSeconds = seconds; _gatherDeadline = Time.unscaledTime + 8f; _gathering = true; _gatherExpected = 0; ZPackage val = new ZPackage(); val.Write(seconds); foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { if (connectedPeer != null && connectedPeer.IsReady()) { _gatherExpected++; instance2.InvokeRoutedRPC(connectedPeer.m_uid, "DSL_CaptureAll", new object[1] { val }); } } if (_gatherExpected == 0) { FinishGather(); } } private static void UpdateGather() { if (_gathering && (_gathered.Count >= _gatherExpected || !(Time.unscaledTime < _gatherDeadline))) { FinishGather(); } } private static void FinishGather() { _gathering = false; string text; try { text = GroupReport.Build(_gatherSeconds, _gathered, _gatherExpected, out var csvPath); if (csvPath != null) { text = text + "\nwrote " + csvPath + " on the server"; } DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] group capture\n" + text)); } catch (Exception ex) { text = "The group capture failed: " + ex.Message; DiagnoseServerLagMod.Log.LogError((object)$"[DiagnoseServerLag] Group capture failed: {ex}"); } if (Sampler.IsServerHere && (Object)(object)Player.m_localPlayer != (Object)null && _gatherFor == ZDOMan.GetSessionID()) { Print(text); } else { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(_gatherFor, "DSL_CaptureResult", new object[1] { Wrap(text) }); } } _gathered.Clear(); } private static void RPC_CaptureAll(long sender, ZPackage pkg) { try { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsDedicated() && DslConfig.ShareMyPerformance.Value) { int num = 120; try { num = pkg.ReadInt(); } catch { } num = Mathf.Clamp(num, 5, Sampler.History.Capacity); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "DSL_ClientSeries", new object[1] { ClientSeries.FromLocal(num).Pack() }); } } } catch (Exception ex) { DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] Could not answer a group capture: " + ex.Message)); } } private static void RPC_ClientSeries(long sender, ZPackage pkg) { if (_gathering) { ClientSeries clientSeries = ClientSeries.Unpack(pkg); if (clientSeries != null) { clientSeries.Uid = sender; _gathered.Add(clientSeries); } } } private static bool MaySeePeerDetail(ZNet znet, long sender) { if (DslConfig.SharePeerDetail.Value) { return true; } if (sender == ZDOMan.GetSessionID()) { return true; } ZNetPeer peer = znet.GetPeer(sender); if (peer?.m_socket == null) { return false; } string hostName = peer.m_socket.GetHostName(); if (!string.IsNullOrEmpty(hostName)) { return znet.IsAdmin(hostName); } return false; } } internal static class LagPanel { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Func<Sample, float> <>9__48_0; public static Func<Sample, float> <>9__48_1; public static Func<Sample, float> <>9__48_2; public static Func<Sample, float> <>9__48_3; public static Func<Sample, float> <>9__48_4; public static Func<Sample, float> <>9__48_5; public static Func<Sample, float> <>9__48_6; public static Func<Sample, float> <>9__48_7; public static Action <>9__58_0; public static Action <>9__58_1; public static UnityAction <>9__59_0; internal float <AddThisMachine>b__48_0(Sample x) { return x.FrameMsMax; } internal float <AddThisMachine>b__48_1(Sample x) { return x.Ping; } internal float <AddThisMachine>b__48_2(Sample x) { return x.Ping; } internal float <AddThisMachine>b__48_3(Sample x) { return x.SendQueue; } internal float <AddThisMachine>b__48_4(Sample x) { return Machine.Collections(x); } internal float <AddThisMachine>b__48_5(Sample x) { return x.Gc0; } internal float <AddThisMachine>b__48_6(Sample x) { return x.Gc1; } internal float <AddThisMachine>b__48_7(Sample x) { return x.Gc2; } internal void <BuildInner>b__58_0() { LagNetwork.Ask(); Populate(); } internal void <BuildInner>b__58_1() { string text = Commands.Dump(); DiagnoseServerLagMod.Message((text == null) ? "Could not write the CSV" : ("Wrote " + Path.GetFileName(text))); } internal void <BuildPauseToggle>b__59_0() { if (DslConfig.PauseWhileOpen != null) { DslConfig.PauseWhileOpen.Value = !DslConfig.PauseWhileOpen.Value; LagPause.Refresh(); PaintPauseToggle(); } } } private const float W = 860f; private const float H = 640f; private const float MinW = 560f; private const float MinH = 400f; private const float MaxW = 1800f; private const float MaxH = 1400f; private const float TitleY = 14f; private const float SubtitleY = 48f; private const float VerdictTop = 78f; private const float SidePad = 30f; private const float ButtonH = 34f; private static readonly Color Bad = new Color(1f, 0.42f, 0.35f); private static readonly Color Good = new Color(0.55f, 0.85f, 0.5f); private static readonly Color PauseOff = new Color(0.6f, 0.6f, 0.6f, 0.85f); private static readonly Color PausePaused = new Color(1f, 0.63f, 0.24f, 1f); private static readonly Color PauseRefused = new Color(1f, 0.45f, 0.4f, 1f); private static GameObject _root; private static RectTransform _list; private static ScrollRect _scroll; private static TextMeshProUGUI _title; private static TextMeshProUGUI _subtitle; private static TextMeshProUGUI _verdict; private static TextMeshProUGUI _advice; private static Button _refreshButton; private static Button _dumpButton; private static Button _closeButton; private static RectTransform _grip; private static RectTransform _mover; private static GameObject _pauseToggle; private static Image _pauseLeft; private static Image _pauseRight; private static Image _pauseSlash; private static float _w = 860f; private static float _h = 640f; private static bool _openedThisFrame; private static bool _buildFailed; private static int _closedFrame; private static float _nextRefresh; internal static bool IsOpen { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } internal static bool JustClosed => Time.frameCount - _closedFrame <= 1; internal static void Toggle() { if (IsOpen) { Close(); } else { Open(); } } internal static void Open() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { if ((Object)(object)_root == (Object)null && !Build()) { DiagnoseServerLagMod.Message("Report unavailable; try dsl_why in the console."); return; } LoadPlacement(); _root.SetActive(true); _root.transform.SetAsLastSibling(); _openedThisFrame = true; Layout(); LagNetwork.Ask(); Populate(); LagPause.Refresh(); _nextRefresh = Time.unscaledTime + 1f; } } internal static void Close() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { SavePlacement(); _root.SetActive(false); _closedFrame = Time.frameCount; } LagPause.Refresh(); } internal static void Update() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (!IsOpen) { if (DslConfig.OpenKey != null && Keys.IsDown(DslConfig.OpenKey.Value) && Keys.CanTakeInput()) { Open(); } } else if (_openedThisFrame) { _openedThisFrame = false; } else if (!Console.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus())) { if (ZInput.GetKeyDown((KeyCode)27, true) || (DslConfig.OpenKey != null && Keys.IsDown(DslConfig.OpenKey.Value))) { Close(); } else if (!(Time.unscaledTime < _nextRefresh)) { _nextRefresh = Time.unscaledTime + 1f; Populate(); } } } private static void Populate() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_list == (Object)null)) { List<Finding> list = Verdict.Diagnose(); Finding finding = ((list.Count > 0) ? list[0] : null); ((TMP_Text)_subtitle).text = Verdict.CoverageNote(); ((Graphic)_subtitle).color = ((LagNetwork.Module == ServerModule.Absent) ? UiKit.Header : UiKit.Dim); if (Sampler.Frozen) { TextMeshProUGUI subtitle = _subtitle; ((TMP_Text)subtitle).text = ((TMP_Text)subtitle).text + " - paused, so nothing is being recorded"; ((Graphic)_subtitle).color = PausePaused; } if (finding != null) { ((TMP_Text)_verdict).text = finding.Headline; ((Graphic)_verdict).color = SeverityColor(finding); ((TMP_Text)_advice).text = finding.Advice; } float verticalNormalizedPosition = (((Object)(object)_scroll != (Object)null) ? _scroll.verticalNormalizedPosition : 1f); UiKit.ClearChildren((Transform)(object)_list); AddOtherFindings(list); AddThisMachine(); AddOwnerLatency(); AddServerSection(); AddPeers(); if ((Object)(object)_scroll != (Object)null) { _scroll.verticalNormalizedPosition = verticalNormalizedPosition; } PaintPauseToggle(); Layout(); } } private static void AddOtherFindings(List<Finding> findings) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) UiKit.SectionHeader((Transform)(object)_list, "Why"); if (findings.Count == 0) { return; } for (int i = 0; i < findings.Count; i++) { Finding finding = findings[i]; string text = ((i == 0) ? "verdict" : "also"); UiKit.Row((Transform)(object)_list, text + " " + finding.Headline, (finding.Cause == Cause.Measuring || finding.Cause == Cause.Healthy) ? "" : $"{finding.Confidence}%", null, null, 17f, SeverityColor(finding)); foreach (string item in finding.Evidence) { UiKit.Row((Transform)(object)_list, " " + item, "", null, null, 15f, UiKit.Dim); } if (i > 0 && !string.IsNullOrEmpty(finding.Advice)) { Paragraph((Transform)(object)_list, finding.Advice, 14f, UiKit.Body, 24f); } } } private static void AddThisMachine() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) UiKit.SectionHeader((Transform)(object)_list, "This machine"); if (!Sampler.TryNewest(out var s)) { UiKit.Row((Transform)(object)_list, "nothing measured yet", "", null, null, 15f, UiKit.Dim); return; } List<Sample> list = Sampler.History.Recent(DslConfig.WindowSeconds.Value); int num = 0; foreach (Sample item in list) { num += item.Stalls; } StatRow("frame time", $"{s.FrameMsAvg:0.0} ms ({Verdict.Fps(s.FrameMs