Skip to content

Commit

Permalink
1) Added full assembly profiling by dllname
Browse files Browse the repository at this point in the history
2) Added feature for harmony patches dumper. Dump possible harmony patches conflicts, where Prefix patch can disable another prefixes or transpilers
3) some fixes
  • Loading branch information
bananasss00 committed Mar 18, 2020
1 parent ff4f894 commit 5ad6713
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 32 deletions.
Binary file modified Assemblies/HarmonyProfiler.dll
Binary file not shown.
95 changes: 90 additions & 5 deletions Source/HarmonyMain.cs
Expand Up @@ -7,11 +7,14 @@

namespace HarmonyProfiler
{
/// <summary>
/// index ordered by execution priority
/// </summary>
public enum PatchType
{
Prefix,
Postfix,
Transpiler
Transpiler,
Postfix
}

public class PatchInfo
Expand Down Expand Up @@ -49,6 +52,12 @@ public static HashSet<string> GetAllHarmonyInstances()
return owners;
}

/// <summary>
/// Get collection [method, patches]
/// </summary>
/// <param name="owners">filter patches by owners or null for get all patches</param>
/// <param name="skipGenericMethods">include generic methods</param>
/// <returns></returns>
public static Dictionary<MethodBase, Patches> GetPatches(string[] owners, bool skipGenericMethods)
{
var patches = Instance.GetPatchedMethods()
Expand All @@ -61,24 +70,30 @@ public static HashSet<string> GetAllHarmonyInstances()
.ToDictionary(x => x.method, y => y.patches);
}

/// <summary>
/// Get collection [method, patch, patchType]
/// </summary>
/// <param name="owners">filter patches by owners or null for get all patches</param>
/// <param name="skipGenericMethods">include generic methods</param>
/// <returns></returns>
public static IEnumerable<PatchInfo> GetPatchedMethods(string[] owners, bool skipGenericMethods)
{
var patches = GetPatches(owners, skipGenericMethods);
foreach (var p in patches)
{
foreach (var valuePrefix in p.Value.Prefixes)
{
if (owners.Any(x => x == valuePrefix.owner))
if (owners?.Any(x => x == valuePrefix.owner) ?? true)
yield return new PatchInfo { originalMethod = p.Key, harmonyPatch = valuePrefix, patchType = PatchType.Prefix };
}
foreach (var valuePostfix in p.Value.Postfixes)
{
if (owners.Any(x => x == valuePostfix.owner))
if (owners?.Any(x => x == valuePostfix.owner) ?? true)
yield return new PatchInfo { originalMethod = p.Key, harmonyPatch = valuePostfix, patchType = PatchType.Postfix };
}
foreach (var valueTranspiler in p.Value.Transpilers)
{
if (owners.Any(x => x == valueTranspiler.owner))
if (owners?.Any(x => x == valueTranspiler.owner) ?? true)
yield return new PatchInfo { originalMethod = p.Key, harmonyPatch = valueTranspiler, patchType = PatchType.Transpiler };
}
}
Expand Down Expand Up @@ -123,5 +138,75 @@ void dumpPatchesInfo(string type, ReadOnlyCollection<Patch> patches)

return sb.ToString();
}

public static string CanConflictHarmonyPatchesDump()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("===[Harmony Patches Can Conflict]===");

var allPatches = HarmonyMain.GetPatchedMethods(null, false)
.GroupBy(x => x.originalMethod)
.Select(x => new
{
method = x.Key,
patches = x // sort patches in execution priority. prefixes->transpilers->postfixes
.Where(y => y.patchType != PatchType.Postfix) // can be blocked only transpilers or low priority prefixes
.OrderBy(y => y.patchType)
.ThenByDescending(y => y.harmonyPatch.priority)
.ThenBy(y => y.harmonyPatch.index)
.ToList()
});

bool HasConflictPatches(List<PatchInfo> sortedByExecuteOrder)
{
for (int i = 0; i < sortedByExecuteOrder.Count; i++)
{
var p = sortedByExecuteOrder[i];
if (p.patchType == PatchType.Prefix
&& p.harmonyPatch.patch.ReturnType == typeof(System.Boolean)
&& i != sortedByExecuteOrder.Count - 1)
{
// can block another prefixes, transpilers or postfixes
return true;
}

if (p.patchType > PatchType.Prefix)
{
// transpilers and postfixes can't be bool patch
return false;
}
}

return false;
}

foreach (var p in allPatches)
{
if (!HasConflictPatches(p.patches))
continue;

int owners = p.patches.Select(x => x.harmonyPatch.owner).Distinct().Count();
if (owners <= 1)
continue;

int prefixes = p.patches.Count(x => x.patchType == PatchType.Prefix);
int transpilers = p.patches.Count(x => x.patchType == PatchType.Transpiler);
int postfixes = p.patches.Count(x => x.patchType == PatchType.Postfix);
sb.AppendLine($"{p.method.GetMethodFullString()}:(Owners: {owners} Prefixes:{prefixes}, Postfixes:{postfixes}, Transpilers:{transpilers})");
foreach (var patchInfo in p.patches)
{
var harmonyPatch = patchInfo.harmonyPatch;
var patchMethod = harmonyPatch.patch;
sb.AppendLine($" {patchInfo.patchType} => {patchMethod.ReturnType.Name} {patchMethod.GetMethodFullString()} [mod:{harmonyPatch.owner}, prior:{harmonyPatch.priority}, idx:{harmonyPatch.index}]");
foreach (var b in harmonyPatch.before)
sb.AppendLine($" before:{b}");
foreach (var a in harmonyPatch.after)
sb.AppendLine($" after:{a}");
}
sb.AppendLine();
}

return sb.ToString();
}
}
}
2 changes: 1 addition & 1 deletion Source/Initializer.cs
Expand Up @@ -27,7 +27,7 @@ internal void Tick(int currentTick)
var settings = Settings.Get();
if (settings.perfomanceMode)
{
Patcher.UnpatchByRule(settings.ruleTiming);
Patcher.UnpatchByRule(settings.ruleTiming, settings.ruleTicks);
}
}

Expand Down
27 changes: 19 additions & 8 deletions Source/Profiler/Patcher.cs
Expand Up @@ -57,8 +57,8 @@ private static IEnumerable<MethodBase> GetAllMethodsWithOverloads(string typeCol
var methods = type.GetMethods(AccessTools.all).Where(x => x.Name.Equals(arr[1]));
foreach (var m in methods)
{
// not generic and inherited methods only from current assembly(was hook Object.GetHashCode, Equals)
if (!m.IsGenericMethod && m.Module == m.ReflectedType?.Module)
// check if method not generic, baseclass not generic and method from current assembly(was hook Object.GetHashCode, Equals)
if (!m.IsGenericMethod && !m.DeclaringType.IsGenericType && m.Module == m.ReflectedType?.Module)
{
yield return m;
}
Expand Down Expand Up @@ -89,6 +89,7 @@ private static bool TryAddProfiler(MethodBase method)
catch (Exception e)
{
Log.Error($"[TryAddProfiler] Exception: {e.Message}; method => {method.GetMethodFullString()}");
PatchedMethods.Remove(method); // harmony exception for indexers and mb other: cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method)
}
}

Expand All @@ -104,6 +105,10 @@ public static void UnpatchAll()
{
try
{
if (Settings.Get().debug)
{
Log.Error($"[UnpatchAll] Try unpatch method => {methodBase.GetMethodFullString()}");
}
HarmonyMain.Instance.Unpatch(methodBase, HarmonyPatchType.All, HarmonyMain.Id);
Logger.Add($"Unpatched: {methodBase.GetMethodFullString()}");
}
Expand All @@ -120,9 +125,9 @@ public static void UnpatchAll()
PatchedMethods.Clear();
}

public static void UnpatchByRule(float timingLess)
public static void UnpatchByRule(float avgTimeLessThan, int ticksMoreThan)
{
var records = PatchHandler.GetProfileRecordsSorted().Where(x => x.IsValid && x.AvgTime <= timingLess).Select(x => x.Method);
var records = PatchHandler.GetProfileRecordsSorted().Where(x => x.IsValid && x.AvgTime <= avgTimeLessThan && x.TicksNum >= ticksMoreThan).Select(x => x.Method);
PatchHandler.StopCollectData();
int unpatched = 0;
foreach (var methodBase in records)
Expand Down Expand Up @@ -269,10 +274,15 @@ public static void AddDefMethodsAdvanced(this HashSet<string> patches, Type defC
var declaringType = methodInfo.DeclaringType;
if (declaringType == null)
continue;
if (methodInfo.IsGenericMethod || methodInfo.IsAbstract)
if (methodInfo.IsGenericMethod || methodInfo.IsAbstract/* || declaringType.IsGenericType*/)
continue;
if (!declaringType.Assembly.Equals(modAssembly)) // !declaringType.AssemblyQualifiedName.Contains("Assembly-CSharp")
continue;
//if (methodInfo.IsIndexerPropertyMethod())
//{
// Log.Error($"[indexer] {declaringType.FullName}:{methodInfo.Name}");
// continue;
//}

if (!skipInherited || methodInfo.DeclaringType == defClass)
{
Expand Down Expand Up @@ -318,6 +328,7 @@ private static IEnumerable<Type> GetWorkerClasses(Def d, List<string> workerFiel
public static void ProfileMods(List<string> modNames)
{
var profilerCfg = Settings.Get().cfgDef;
var settings = Settings.Get();

HashSet<string> patches = new HashSet<string>();

Expand Down Expand Up @@ -346,22 +357,22 @@ public static void ProfileMods(List<string> modNames)
{
foreach (var child in thinkDef.thinkRoot.ChildrenRecursive)
{
patches.AddDefMethodsAdvanced((child as ThinkNode_JobGiver)?.GetType());
patches.AddDefMethodsAdvanced((child as ThinkNode_JobGiver)?.GetType(), settings.allowCoreAsm);
}
}
// DesignationCategory childs
if (d is DesignationCategoryDef designationCategoryDef)
{
foreach (var child in designationCategoryDef.specialDesignatorClasses)
{
patches.AddDefMethodsAdvanced(child);
patches.AddDefMethodsAdvanced(child, settings.allowCoreAsm);
}
}
// Auto class getter
var workers = GetWorkerClasses(d, profilerCfg.workerFields, profilerCfg.workerGetters);
foreach (var worker in workers)
{
patches.AddDefMethodsAdvanced(worker);
patches.AddDefMethodsAdvanced(worker, settings.allowCoreAsm);
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions Source/Settings.cs
Expand Up @@ -20,13 +20,13 @@ public Settings()
cfgDef = DefDatabase<CfgDef>.GetNamed("harmonyProfilerConfig");
}

public const string CustomExampleStr = "Namespace.Class1:Method1\nNamespace.Class1\nNamespace\nNamespace.*";
public const string CustomExampleStr = "Namespace.Class1:Method1\nNamespace.Class1\nNamespace\nNamespace.*\ndllName.dll";

public string profileInstances = "";
public string profileMods = "";
public string profileCustom = CustomExampleStr;
public bool allowTranspiledMethods = false;
public bool allowCoreAsm = true;
public bool allowCoreAsm = false;
public bool allowInheritedMethods = true;
public bool collectMemAlloc = true;
public bool sortByMemAlloc = false;
Expand All @@ -35,6 +35,7 @@ public Settings()

public bool perfomanceMode = false;
public float ruleTiming = 0.01f;
public string ruleTimingBuf;
public int ruleTicks = 100;
public string ruleTimingBuf, ruleTicksBuf;
}
}

0 comments on commit 5ad6713

Please sign in to comment.