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;
}
}
}