Files
bd2/plugins/LocalIdentity/ClientDiagnostics.cs
T

131 lines
5.0 KiB
C#

using System;
using BD2.GameNames;
using static BD2.GameNames.Game;
using System.Reflection;
using BepInEx.Logging;
using HarmonyLib;
using System.Diagnostics;
using System.Threading;
namespace Bd2LocalIdentity;
internal static class ClientDiagnostics
{
private static ManualLogSource Log;
internal static void Initialize(ManualLogSource logger) => Log = logger;
internal static void InstallMaintenanceTimeoutGuard(Harmony harmony, Type introUI)
{
MethodInfo finishMaintenance = introUI?.GetGameMethod(
nameof(IntroUI.OnFinishMaintenanceRequest),
BindingFlags.Instance | BindingFlags.Public,
null,
Type.EmptyTypes,
null);
MethodInfo falseTimeoutTelemetry = introUI?.GetGameMethod(
"CheckMaintenanceTimeout",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
if (finishMaintenance == null || falseTimeoutTelemetry == null)
{
throw new MissingMethodException("IntroUI maintenance timeout methods were not found");
}
harmony.Patch(
finishMaintenance,
postfix: new HarmonyMethod(typeof(ClientDiagnostics), nameof(OnFinishMaintenancePostfix)));
harmony.Patch(
falseTimeoutTelemetry,
prefix: new HarmonyMethod(typeof(ClientDiagnostics), nameof(SkipLocalTimeoutTelemetry)));
}
private static void OnFinishMaintenancePostfix(object __instance)
{
// CancelMaintenanceTimeout cancels the active CTS and then
// immediately stores a fresh CTS. Depending on async scheduling, the
// timeout task can capture that fresh token after the successful
// response and emit a false 10-second timeout. Cancel the replacement
// token only after OnFinishMaintenanceRequest has completed.
FieldInfo timeout = __instance.GetType().GetGameField(
"_maintenanceTimeoutCts",
BindingFlags.Instance | BindingFlags.NonPublic);
var source = timeout?.GetValue(__instance) as CancellationTokenSource;
source?.Cancel();
Log?.LogInfo("Maintenance timeout guard cancelled after successful response");
}
private static bool SkipLocalTimeoutTelemetry()
{
// This method only emits intro_server_info_timeout telemetry after ten
// seconds. Network request failures have their own callbacks. In
// In the configured client it can outlive a successful local maintenance response.
return false;
}
internal static void InstallDatabaseDiagnostics(Harmony harmony)
{
Type rawDataManager = typeof(RawDataManager);
MethodInfo dbLoad = (rawDataManager?.GetGameMethod(
nameof(RawDataManager.DBLoad),
BindingFlags.Instance | BindingFlags.Public)) ?? throw new MissingMethodException("RawDataManager.DBLoad was not found");
harmony.Patch(
dbLoad,
prefix: new HarmonyMethod(typeof(ClientDiagnostics), nameof(DBLoadPrefix)));
Type clientLocalInfo = typeof(Proto.Local.ClientLocalInfo);
MethodInfo loadDB = (clientLocalInfo?.GetGameMethod(
nameof(Proto.Local.ClientLocalInfo.LoadDB),
BindingFlags.Static | BindingFlags.Public)) ?? throw new MissingMethodException("ClientLocalInfo.LoadDB was not found");
harmony.Patch(
loadDB,
prefix: new HarmonyMethod(typeof(ClientDiagnostics), nameof(ClientLocalLoadPrefix)),
postfix: new HarmonyMethod(typeof(ClientDiagnostics), nameof(ClientLocalLoadPostfix)),
finalizer: new HarmonyMethod(typeof(ClientDiagnostics), nameof(ClientLocalLoadFinalizer)));
Log?.LogInfo("Database diagnostics active");
}
private static void DBLoadPrefix(RawDataManager.DBOption __0, ref Action __1)
{
string dbName = __0.dbName ?? "<unknown>";
Action original = __1;
var elapsed = Stopwatch.StartNew();
Log?.LogInfo("DBLoad start: " + dbName);
__1 = delegate
{
Log?.LogInfo("DBLoad callback enter: " + dbName + " (" + elapsed.ElapsedMilliseconds + " ms)");
try
{
original?.Invoke();
}
finally
{
Log?.LogInfo("DBLoad callback exit: " + dbName + " (" + elapsed.ElapsedMilliseconds + " ms)");
}
};
}
private static void ClientLocalLoadPrefix(out Stopwatch __state)
{
__state = Stopwatch.StartNew();
Log?.LogInfo("ClientLocalInfo.LoadDB enter");
}
private static void ClientLocalLoadPostfix(Stopwatch __state)
{
Log?.LogInfo("ClientLocalInfo.LoadDB exit (" + __state.ElapsedMilliseconds + " ms)");
}
private static Exception ClientLocalLoadFinalizer(Exception __exception, Stopwatch __state)
{
if (__exception != null)
{
Log?.LogError(
"ClientLocalInfo.LoadDB threw after " + __state.ElapsedMilliseconds + " ms: " + __exception);
}
return __exception;
}
}