Files

207 lines
6.8 KiB
C#

// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#if notWorking
using System.Linq;
using System.Reflection.Emit;
using JetBrains.Annotations;
namespace PerformanceFish;
[UsedImplicitly]
public sealed class AllocationProfiling : ClassWithFishPatches
{
public override bool RequiresLoadedGameForPatching => false;
public static bool Active
{
get => _active;
set
{
return;
_active = value;
FishPatch.Get<ObjectPatch>().Enabled = value;
}
}
public static FishTable<Type, TypeData> Data = new();
private static bool _active;
/// <summary>
/// The idea here was to patch object..ctor, fetch the type, count calls and get the allocation count for every
/// type by doing so, while ignoring structs which are expected to not construct as full object with type.
/// This however doesn't work. The IL method bodies of class constructors do contain calls to object..ctor, but
/// they appear to only go through for static constructor invocations and literal object construction. Everything
/// else somehow skips the call.
/// </summary>
public sealed class ObjectPatch : FishPatch
{
public override bool DefaultState => true;
public override MethodBase? TargetMethodInfo { get; }
= MainTabWindow._objectConstructor;
// = AccessTools.Constructor(typeof(NotAClass));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void P0stf1x(object __instance)
{
CallCount++;
ref var typeData = ref Data.GetOrAddReference(__instance.GetType());
typeData.Count++;
// if ((typeData.Count & 1023) != 0)
// return;
typeData.StackTraces.GetOrAddReference(StackTraceUtility.ExtractStackTrace())++;
}
public static CodeInstructions Transpiler(CodeInstructions instructions)
{
var codes = instructions.AsOrToList();
Log.Message(string.Join("\n", codes));
var retIndex = codes.FindIndex(static code => code.opcode == OpCodes.Ret);
if (retIndex == -1)
retIndex = codes.Count - 1;
codes.Insert(retIndex, new(OpCodes.Ldarg_0));
codes.Insert(retIndex + 1, new(OpCodes.Call, methodof(P0stf1x)));
return codes;
}
// ReSharper disable once VirtualMemberCallInConstructor
//private ObjectPatch() => TryPatch();
public static class NotAClass
{
}
}
public static ulong CallCount;
public record struct TypeData
{
public int Count;
public FishTable<string, int> StackTraces = new();
[UsedImplicitly]
public TypeData()
{
}
}
[UsedImplicitly]
public sealed class MainTabWindow : RimWorld.MainTabWindow
{
public MainTabWindow()
{
draggable = true;
resizeable = true;
absorbInputAroundWindow = false;
closeOnClickedOutside = false;
}
private GUIScope.ScrollViewStatus _scrollViewStatus = new();
private Listing_Standard _listing = new();
public override void PreOpen()
{
Active = true;
base.PreOpen();
}
public override void PostClose()
{
base.PostClose();
Active = false;
}
internal static MethodBase _objectConstructor
= (MethodBase)MethodBodyReader.GetInstructions(null, AccessTools.DeclaredConstructor(typeof(PreceptComp)))
.First(static code => code.opcode == OpCodes.Call
&& code.operand is MethodBase method
&& method.Name.Contains("ctor"))
.operand;
// = typeof(object).GetMembers(AccessTools.all).OfType<MethodBase>()
// .First(static member => member.Name.Contains("ctor"));
// = AccessTools.Constructor(typeof(ObjectPatch.NotAClass));
public override void DoWindowContents(Rect inRect)
{
absorbInputAroundWindow = false;
closeOnClickedOutside = false;
if (GUIHelper.CanSkip)
return;
Widgets.Label(inRect with { height = Text.LineHeight }, $"Data: {Data}, count: {
Data.Count.ToStringCached()}, callCount: {CallCount.ToString()}");
inRect.yMin += Text.LineHeight;
var objectPatches = Harmony.GetPatchInfo(_objectConstructor);
var patchWidgetLabel = $"Patches on {_objectConstructor.FullDescription()}: pre {
objectPatches?.Prefixes?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, post: {
objectPatches?.Postfixes?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, trans: {
objectPatches?.Transpilers?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, final: {
objectPatches?.Finalizers?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, Active? {
Active.ToString()}, Enabled? {FishPatch.Get<ObjectPatch>().Enabled.ToString()}";
var patchWidgetHeight = Text.CalcHeight(patchWidgetLabel, inRect.width);
Widgets.Label(inRect with { height = patchWidgetHeight }, patchWidgetLabel);
inRect.yMin += patchWidgetHeight;
var objectMembers = typeof(object).GetMembers(AccessTools.all).OfType<MethodBase>()
.Where(static member => member.Name.Contains("ctor"));
var objectMembersLabel = $"object members:\n{string.Join("\n --- ", objectMembers.Select(static member
=> member.FullDescription()))}";
var objectMembersHeight = Text.CalcHeight(objectMembersLabel, inRect.width);
Widgets.Label(inRect with { height = objectMembersHeight }, objectMembersLabel);
inRect.yMin += objectMembersHeight;
using var scrollView = new GUIScope.ScrollableListingStandard(inRect, _scrollViewStatus, _listing);
var listing = _listing;
using var keyValuePairsPooled = new PooledArray<KeyValuePair<Type, TypeData>>(Data);
keyValuePairsPooled.Sort(_typeDataComparer);
var keyValuePairs = keyValuePairsPooled.Array;
var typeCount = keyValuePairsPooled.Length;
for (var i = 0; i < typeCount; i++)
{
if (scrollView.TryCull(Text.LineHeight))
continue;
var kvp = keyValuePairs[i];
var labelRect = listing.GetRect(Text.LineHeight);
Widgets.Label(labelRect,
string.Concat(kvp.Key, " ", kvp.Value.Count.ToStringCached()));
if (Mouse.IsOver(labelRect))
{
var traces = kvp.Value.StackTraces;
using var traceArray = new PooledArray<KeyValuePair<string, int>>(traces);
traceArray.Sort(_traceComparer);
// listing.Label(traceArray.Array[0].Key);
listing.Label(traceArray.FirstOrDefault(static kvp => !kvp.Key.Contains("cctor")).Key ?? "NULL");
// TooltipHandler.TipRegion(labelRect,
// string.Join("\n", traceArray.Select(static kvp => kvp.Key)));
}
}
}
private static IComparer<KeyValuePair<Type, TypeData>> _typeDataComparer
= Comparer<KeyValuePair<Type, TypeData>>.Create(static (x, y) => x.Value.Count.CompareTo(y.Value.Count));
private static IComparer<KeyValuePair<string, int>> _traceComparer
= Comparer<KeyValuePair<string, int>>.Create(static (x, y) => x.Value.CompareTo(y.Value));
}
}
#endif