using MessagePipe.Internal; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Security; using System.Threading; namespace MessagePipe { internal interface IHandlerHolderMarker { } public class StackTraceInfo { static bool displayFileNames = true; static int idSeed = 0; public int Id { get; } public DateTimeOffset Timestamp { get; } public StackTrace StackTrace { get; } public string Head { get; } internal string formattedStackTrace = default; // cache field for internal use(Unity Editor, etc...) public StackTraceInfo(StackTrace stackTrace) { Id = Interlocked.Increment(ref idSeed); Timestamp = DateTimeOffset.UtcNow; StackTrace = stackTrace; Head = GetGroupKey(stackTrace); } internal static string GetGroupKey(StackTrace stackTrace) { for (int i = 0; i < stackTrace.FrameCount; i++) { var sf = stackTrace.GetFrame(i); if (sf == null) continue; var m = sf.GetMethod(); if (m == null) continue; if (m.DeclaringType == null) continue; if (m.DeclaringType.Namespace == null || !m.DeclaringType.Namespace.StartsWith("MessagePipe")) { if (displayFileNames && sf.GetILOffset() != -1) { string fileName = null; try { fileName = sf.GetFileName(); } catch (NotSupportedException) { displayFileNames = false; } catch (SecurityException) { displayFileNames = false; } if (fileName != null) { return m.DeclaringType.FullName + "." + m.Name + " (at " + Path.GetFileName(fileName) + ":" + sf.GetFileLineNumber() + ")"; } else { return m.DeclaringType.FullName + "." + m.Name + " (offset: " + sf.GetILOffset() + ")"; } } return m.DeclaringType.FullName + "." + m.Name; } } return ""; } } /// /// Diagnostics info of in-memory(ISubscriber/IAsyncSubscriber) subscriptions. /// [Preserve] public sealed class MessagePipeDiagnosticsInfo { static readonly ILookup EmptyLookup = Array.Empty().ToLookup(_ => "", x => x); int subscribeCount; bool dirty; MessagePipeOptions options; object gate = new object(); Dictionary> capturedStackTraces = new Dictionary>(); /// Get current subscribed count. public int SubscribeCount => subscribeCount; internal bool CheckAndResetDirty() { var d = dirty; dirty = false; return d; } internal MessagePipeOptions MessagePipeOptions => options; /// /// When MessagePipeOptions.EnableCaptureStackTrace is enabled, list all stacktrace on subscribe. /// public StackTraceInfo[] GetCapturedStackTraces(bool ascending = true) { if (!options.EnableCaptureStackTrace) return Array.Empty(); lock (gate) { var iter = capturedStackTraces.SelectMany(x => x.Value.Values); iter = (ascending) ? iter.OrderBy(x => x.Id) : iter.OrderByDescending(x => x.Id); return iter.ToArray(); } } /// /// When MessagePipeOptions.EnableCaptureStackTrace is enabled, groped by caller of subscribe. /// public ILookup GetGroupedByCaller(bool ascending = true) { if (!options.EnableCaptureStackTrace) return EmptyLookup; lock (gate) { var iter = capturedStackTraces.SelectMany(x => x.Value.Values); iter = (ascending) ? iter.OrderBy(x => x.Id) : iter.OrderByDescending(x => x.Id); return iter.ToLookup(x => x.Head); } } [Preserve] public MessagePipeDiagnosticsInfo(MessagePipeOptions options) { this.options = options; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void IncrementSubscribe(IHandlerHolderMarker handlerHolder, IDisposable subscription) { Interlocked.Increment(ref subscribeCount); if (options.EnableCaptureStackTrace) { AddStackTrace(handlerHolder, subscription); } dirty = true; } [MethodImpl(MethodImplOptions.NoInlining)] void AddStackTrace(IHandlerHolderMarker handlerHolder, IDisposable subscription) { lock (gate) { if (!capturedStackTraces.TryGetValue(handlerHolder, out var dict)) { dict = new Dictionary(); capturedStackTraces[handlerHolder] = dict; } dict.Add(subscription, new StackTraceInfo(new StackTrace(true))); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void DecrementSubscribe(IHandlerHolderMarker handlerHolder, IDisposable subscription) { Interlocked.Decrement(ref subscribeCount); if (options.EnableCaptureStackTrace) { RemoveStackTrace(handlerHolder, subscription); } dirty = true; } [MethodImpl(MethodImplOptions.NoInlining)] void RemoveStackTrace(IHandlerHolderMarker handlerHolder, IDisposable subscription) { lock (gate) { if (!capturedStackTraces.TryGetValue(handlerHolder, out var dict)) { return; } dict.Remove(subscription); } } internal void RemoveTargetDiagnostics(IHandlerHolderMarker targetHolder, int removeCount) { Interlocked.Add(ref subscribeCount, -removeCount); if (options.EnableCaptureStackTrace) { lock (gate) { capturedStackTraces.Remove(targetHolder); } } dirty = true; } } }