|
1 | 1 | using Elements.Core; |
2 | 2 | using FrooxEngine; |
3 | | -using Newtonsoft.Json; |
| 3 | +using FrooxEngine.ProtoFlux; |
| 4 | +using ResoniteMetricsCounter.Serialization; |
| 5 | +using ResoniteMetricsCounter.Utils; |
| 6 | +using ResoniteModLoader; |
4 | 7 | using System; |
5 | 8 | using System.Collections.Generic; |
6 | 9 | using System.IO; |
7 | 10 | using System.Linq; |
8 | 11 | using System.Runtime.CompilerServices; |
| 12 | +using System.Text.Json; |
| 13 | +using System.Text.Json.Serialization; |
9 | 14 |
|
10 | 15 | namespace ResoniteMetricsCounter.Metrics; |
11 | 16 |
|
12 | | -internal sealed class MetricsCounter : IDisposable |
| 17 | + |
| 18 | +public sealed class MetricsCounter : IDisposable |
13 | 19 | { |
14 | | - internal Dictionary<int, Metric> Metrics = new(); |
15 | | - private readonly string filename; |
16 | | - private HashSet<string> blackList; |
17 | | - private Slot? ignoredHierarchy; |
18 | | - internal long TotalTicks |
| 20 | + private readonly CachedElementValue<IWorldElement, bool> shouldSkip; |
| 21 | + |
| 22 | + [JsonInclude] public Slot? IgnoredHierarchy { get; private set; } |
| 23 | + internal bool IsDisposed { get; private set; } |
| 24 | + |
| 25 | + [JsonInclude] public string Filename { get; private set; } |
| 26 | + [JsonInclude] public VersionNumber EngineVersion { get; private set; } |
| 27 | + [JsonInclude] public HashSet<string> BlackList { get; private set; } |
| 28 | + |
| 29 | + [JsonInclude] public MetricsByStageStorage<IWorldElement> ByElement { get; private set; } = new(); |
| 30 | + [JsonInclude] public MetricsStorage<Slot> ByObjectRoot { get; private set; } = new(); |
| 31 | + |
| 32 | + public MetricsCounter(IEnumerable<string> blackList) |
19 | 33 | { |
20 | | - get; |
21 | | - private set; |
| 34 | + shouldSkip = new(ShouldSkipImpl); |
| 35 | + |
| 36 | + EngineVersion = Engine.Version; |
| 37 | + Filename = UniLog.GenerateLogName(EngineVersion.ToString(), "-trace").Replace(".log", ".json"); |
| 38 | + BlackList = blackList.ToHashSet(); |
22 | 39 | } |
23 | | - internal long MaxTicks |
| 40 | + |
| 41 | + private bool ShouldSkipImpl(IWorldElement element) |
24 | 42 | { |
25 | | - get; |
26 | | - private set; |
| 43 | + if (element.World.Focus != World.WorldFocus.Focused) return true; |
| 44 | + if (element.IsLocalElement || element.IsRemoved || BlackList.Contains(element.GetNameFast())) return true; |
| 45 | + |
| 46 | + var slot = element.GetSlotFast(); |
| 47 | + if (slot is null || IgnoredHierarchy is null) return false; |
| 48 | + return IgnoredHierarchy.IsChildOf(slot, includeSelf: true); |
27 | 49 | } |
28 | 50 |
|
29 | | - public MetricsCounter(IEnumerable<string> blackList) |
| 51 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 52 | + public void AddForCurrentStage(object? obj, long ticks) |
30 | 53 | { |
31 | | - filename = UniLog.GenerateLogName(Engine.VersionNumber, "-trace").Replace(".log", ".json"); |
32 | | - this.blackList = blackList.ToHashSet(); |
| 54 | + if (obj is IWorldElement element) AddForCurrentStage(element, ticks); |
| 55 | + else if (obj is ProtoFluxNodeGroup group) AddForCurrentStage(group, ticks); |
| 56 | + else if (ResoniteMod.IsDebugEnabled()) ResoniteMod.Debug($"Unknown object type: {obj?.GetType()}"); |
33 | 57 | } |
34 | 58 |
|
35 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
36 | | - public void Add(string name, Slot slot, long ticks, MetricType type) |
| 60 | + private void AddForCurrentStage(ProtoFluxNodeGroup group, long ticks) |
37 | 61 | { |
38 | | - Add(new Metric() |
39 | | - { |
40 | | - Slot = slot, |
41 | | - Name = name, |
42 | | - Ticks = ticks, |
43 | | - Type = type |
44 | | - }); |
| 62 | + var world = group.World; |
| 63 | + if (world.Focus != World.WorldFocus.Focused) return; |
| 64 | + |
| 65 | + var node = group.Nodes.FirstOrDefault(); |
| 66 | + if (node is null) return; |
| 67 | + |
| 68 | + AddForCurrentStage(node, ticks); |
45 | 69 | } |
46 | 70 |
|
47 | | - public void Add(in Metric metric) |
| 71 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 72 | + private void AddForCurrentStage(IWorldElement element, long ticks) |
48 | 73 | { |
49 | | - if (metric.Ticks == 0 || blackList.Contains(metric.Name) || (ignoredHierarchy is not null && metric.Slot.IsChildOf(ignoredHierarchy, includeSelf: true))) return; |
| 74 | + if (shouldSkip.GetOrCache(element)) return; |
50 | 75 |
|
51 | | - TotalTicks += metric.Ticks; |
| 76 | + ByElement.Add(element, ticks); |
52 | 77 |
|
53 | | - var id = metric.GetHashCode(); |
54 | | - if (Metrics.TryGetValue(id, out var prevValue)) |
55 | | - { |
56 | | - Metrics[id] = prevValue + metric; |
57 | | - } |
58 | | - else |
59 | | - { |
60 | | - Metrics[id] = metric; |
61 | | - } |
| 78 | + var objectRoot = element.GetExactObjectRootOrWorldRootFast(); |
| 79 | + if (objectRoot is null) return; |
62 | 80 |
|
63 | | - var ticks = Metrics[id].Ticks; |
64 | | - if (ticks > MaxTicks) |
65 | | - { |
66 | | - MaxTicks = ticks; |
67 | | - } |
| 81 | + ByObjectRoot.Add(objectRoot, ticks); |
68 | 82 | } |
69 | 83 |
|
| 84 | + private static readonly JsonSerializerOptions jsonSerializerOptions = new() |
| 85 | + { |
| 86 | + WriteIndented = true, |
| 87 | + IgnoreReadOnlyFields = false, |
| 88 | + IgnoreReadOnlyProperties = false, |
| 89 | + Converters = { new IWorldElementConverter(), new JsonStringEnumConverter<World.RefreshStage>() }, |
| 90 | + }; |
| 91 | + |
70 | 92 | public void Flush() |
71 | 93 | { |
72 | | - var serializer = new JsonSerializer(); |
73 | | - var streamWriter = new StreamWriter(filename, append: true); |
74 | | - serializer.Serialize(streamWriter, Metrics); |
75 | | - streamWriter.Close(); |
| 94 | + ResoniteMod.DebugFunc(() => $"Writing metrics to {Filename}"); |
| 95 | + using (var writer = new FileStream(Filename, FileMode.Create)) |
| 96 | + { |
| 97 | + JsonSerializer.Serialize(writer, this, jsonSerializerOptions); |
| 98 | + } |
76 | 99 | } |
77 | 100 |
|
78 | 101 | public void Dispose() |
79 | 102 | { |
| 103 | + IsDisposed = true; |
80 | 104 | Flush(); |
81 | 105 | } |
82 | 106 |
|
83 | 107 | internal void UpdateBlacklist(IEnumerable<string> blackList) |
84 | 108 | { |
85 | | - this.blackList = blackList.ToHashSet(); |
86 | | - foreach (var metric in Metrics.Values) |
87 | | - { |
88 | | - if (this.blackList.Contains(metric.Name)) |
89 | | - { |
90 | | - Metrics.Remove(metric.GetHashCode()); |
91 | | - } |
92 | | - } |
| 109 | + shouldSkip.Clear(); |
| 110 | + BlackList = blackList.ToHashSet(); |
| 111 | + ByElement.RemoveWhere(m => BlackList.Contains(m.Target.GetNameFast())); |
| 112 | + ByObjectRoot.RemoveWhere(m => BlackList.Contains(m.Target.GetNameFast())); |
93 | 113 | } |
94 | 114 |
|
95 | | - internal void Remove(in Metric metric) |
| 115 | + internal void Remove(Slot slot) |
96 | 116 | { |
97 | | - Metrics.Remove(metric.GetHashCode()); |
| 117 | + ByObjectRoot.Remove(slot); |
98 | 118 | } |
99 | 119 |
|
100 | 120 | internal void IgnoreHierarchy(Slot slot) |
101 | 121 | { |
102 | | - ignoredHierarchy = slot; |
| 122 | + IgnoredHierarchy = slot; |
103 | 123 | } |
104 | 124 | } |
0 commit comments