diff --git a/Directory.Build.props b/Directory.Build.props index 036f119..2ac07e6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ - 0.3.0 + 0.4.0 https://github.com/Ne4to/Heartbeat true MIT diff --git a/scripts/reinstall-release-tool.ps1 b/scripts/reinstall-release-tool.ps1 new file mode 100644 index 0000000..453c379 --- /dev/null +++ b/scripts/reinstall-release-tool.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = "Stop" + +try +{ + dotnet tool uninstall -g Heartbeat + dotnet tool install --global Heartbeat +} +catch { + Write-Host 'Install global tool - FAILED!' -ForegroundColor Red + throw +} \ No newline at end of file diff --git a/scripts/update-ts-client.ps1 b/scripts/update-ts-client.ps1 index 4fbdfee..718eaab 100644 --- a/scripts/update-ts-client.ps1 +++ b/scripts/update-ts-client.ps1 @@ -16,7 +16,7 @@ try Set-Location $FrontendRoot $env:HEARTBEAT_GENERATE_CONTRACTS = 'true' dotnet swagger tofile --yaml --output $ContractPath $DllPath Heartbeat - dotnet kiota generate -l typescript --openapi $ContractPath -c HeartbeatClient -o ./src/client + dotnet kiota generate -l typescript --openapi $ContractPath -c HeartbeatClient -o ./src/client --clean-output # TODO try --serializer Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory --deserializer Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory } diff --git a/src/DebugHost/Program.cs b/src/DebugHost/Program.cs index 8b99cf4..f33a0f7 100644 --- a/src/DebugHost/Program.cs +++ b/src/DebugHost/Program.cs @@ -1,5 +1,6 @@ using Heartbeat.Domain; using Heartbeat.Runtime; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Proxies; using Microsoft.Diagnostics.Runtime; @@ -18,7 +19,7 @@ static void ProcessFile(string filePath) static void WriteWebRequests(RuntimeContext runtimeContext) { - var q = from clrObject in runtimeContext.EnumerateObjectsByTypeName("System.Net.HttpWebRequest", TraversingHeapModes.All) + var q = from clrObject in runtimeContext.EnumerateObjectsByTypeName("System.Net.HttpWebRequest", null) let webRequestProxy = new HttpWebRequestProxy(runtimeContext, clrObject) let requestContentLength = webRequestProxy.ContentLength let responseContentLength = webRequestProxy.Response?.ContentLength diff --git a/src/Heartbeat.Runtime/Analyzers/AsyncStateMachineAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/AsyncStatusMachineAnalyzer.cs similarity index 95% rename from src/Heartbeat.Runtime/Analyzers/AsyncStateMachineAnalyzer.cs rename to src/Heartbeat.Runtime/Analyzers/AsyncStatusMachineAnalyzer.cs index f870238..6c14271 100644 --- a/src/Heartbeat.Runtime/Analyzers/AsyncStateMachineAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/AsyncStatusMachineAnalyzer.cs @@ -1,5 +1,6 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Proxies; using Microsoft.Diagnostics.Runtime; @@ -8,11 +9,11 @@ namespace Heartbeat.Runtime.Analyzers; -public class AsyncStateMachineAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public class AsyncStatusMachineAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } - public AsyncStateMachineAnalyzer(RuntimeContext context) + public AsyncStatusMachineAnalyzer(RuntimeContext context) : base(context) { } @@ -38,7 +39,7 @@ private void ProcessAsyncStateMachine(ILogger logger) { var stateMachineQuery = - from clrObject in Context.EnumerateObjects(TraversingHeapMode) + from clrObject in Context.EnumerateObjects(ObjectGcStatus) where clrObject.Type !.EnumerateInterfaces() .Any(clrInterface => clrInterface.Name == "System.Runtime.CompilerServices.IAsyncStateMachine") @@ -146,7 +147,7 @@ private IEnumerable EnumerateAsyncStateMachineObjects() var (asyncStateMachineBoxType, debugFinalizableAsyncStateMachineBoxType, taskType) = FindStateMachineTypes(); return - from clrObject in Context.EnumerateObjects(TraversingHeapMode) + from clrObject in Context.EnumerateObjects(ObjectGcStatus) where // Skip objects too small to be state machines or tasks, avoiding some compiler-generated caching data structures. // https://github.com/dotnet/diagnostics/blob/dc9d61a876d6153306b2d59c769d9581e3d5ab2d/src/SOS/Strike/strike.cpp#L4749 diff --git a/src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs index fb7235e..d4cee18 100644 --- a/src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs @@ -1,5 +1,6 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Extensions; using Microsoft.Diagnostics.Runtime; @@ -7,9 +8,9 @@ namespace Heartbeat.Runtime.Analyzers; -public sealed class HeapDumpStatisticsAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public sealed class HeapDumpStatisticsAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } public Generation? Generation { get; set; } public HeapDumpStatisticsAnalyzer(RuntimeContext context) : base(context) @@ -36,10 +37,8 @@ private void WriteLog(ILogger logger, int topTypeCount) public IReadOnlyCollection GetObjectTypeStatistics() { return ( - from obj in Context.EnumerateObjects(TraversingHeapMode, Generation) - let objSize = obj.Size - //group new { size = objSize } by type.Name into g - group objSize by obj.Type + from obj in Context.EnumerateObjects(ObjectGcStatus, Generation) + group obj.Size by obj.Type into g let totalSize = (ulong)g.Sum(t => (long)t) let clrType = g.Key diff --git a/src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs index 12b63b3..133235d 100644 --- a/src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs @@ -1,12 +1,13 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Microsoft.Extensions.Logging; namespace Heartbeat.Runtime.Analyzers; -public sealed class HttpClientAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public sealed class HttpClientAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } public HttpClientAnalyzer(RuntimeContext context) : base(context) @@ -17,7 +18,7 @@ public IReadOnlyCollection GetClientsInfo() { var result = new List(); - foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Net.Http.HttpClient", TraversingHeapMode)) + foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Net.Http.HttpClient", ObjectGcStatus)) { var httpClientObjectType = Context.Heap.GetObjectType(address); var timeoutField = httpClientObjectType.GetFieldByName("_timeout"); diff --git a/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithObjectGCStatus.cs b/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithObjectGCStatus.cs new file mode 100644 index 0000000..9f27432 --- /dev/null +++ b/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithObjectGCStatus.cs @@ -0,0 +1,8 @@ +using Heartbeat.Runtime.Domain; + +namespace Heartbeat.Runtime.Analyzers.Interfaces; + +public interface IWithObjectGCStatus +{ + ObjectGCStatus? ObjectGcStatus { get; set; } +} \ No newline at end of file diff --git a/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithTraversingHeapMode.cs b/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithTraversingHeapMode.cs deleted file mode 100644 index 30d4534..0000000 --- a/src/Heartbeat.Runtime/Analyzers/Interfaces/IWithTraversingHeapMode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Heartbeat.Runtime.Analyzers.Interfaces; - -public interface IWithTraversingHeapMode -{ - TraversingHeapModes TraversingHeapMode { get; set; } -} \ No newline at end of file diff --git a/src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs index 124dd95..9643e7c 100644 --- a/src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs @@ -1,13 +1,14 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Microsoft.Diagnostics.Runtime; using Microsoft.Extensions.Logging; namespace Heartbeat.Runtime.Analyzers; -public sealed class LongStringAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public sealed class LongStringAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } public LongStringAnalyzer(RuntimeContext context) : base(context) @@ -16,18 +17,18 @@ public LongStringAnalyzer(RuntimeContext context) public void Dump(ILogger logger) { - WriteLog(logger, TraversingHeapMode); + WriteLog(logger, ObjectGcStatus); } - private void WriteLog(ILogger logger, TraversingHeapModes traversingMode) + private void WriteLog(ILogger logger, ObjectGCStatus? status) { - LogLongestStrings(logger, traversingMode, 10); + LogLongestStrings(logger, status, 10); } - private IEnumerable GetLongestStrings(int count, TraversingHeapModes traversingMode) + private IEnumerable GetLongestStrings(int count, ObjectGCStatus? status) { var query = - from clrObject in Context.EnumerateStrings(traversingMode) + from clrObject in Context.EnumerateStrings(status) orderby clrObject.Size descending select clrObject; @@ -46,19 +47,19 @@ orderby clrObject.Size descending // Console.WriteLine($"Value: {System.Text.Encoding.Unicode.GetString(buffer)}..."); } - private void LogLongestStrings(ILogger logger, TraversingHeapModes traversingMode, int count, int maxLength = 200) + private void LogLongestStrings(ILogger logger, ObjectGCStatus? status, int count, int maxLength = 200) { - foreach (var s in GetStrings(count, maxLength)) + foreach (var s in GetStrings(status, count, maxLength)) { logger.LogInformation($"Length = {s.Length} symbols, Value = {s.Value}"); } } - public IReadOnlyCollection GetStrings(int count, int? truncateLength) + public IReadOnlyCollection GetStrings(ObjectGCStatus? status, int count, int? truncateLength) { var result = new List(count); - foreach (var stringClrObject in GetLongestStrings(count, TraversingHeapMode)) + foreach (var stringClrObject in GetLongestStrings(count, status)) { string value = stringClrObject.AsString(truncateLength ?? 4096)!; var length = stringClrObject.ReadField("_stringLength"); diff --git a/src/Heartbeat.Runtime/Analyzers/ServicePointManagerAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/ServicePointManagerAnalyzer.cs index 1216f4b..d475c13 100644 --- a/src/Heartbeat.Runtime/Analyzers/ServicePointManagerAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/ServicePointManagerAnalyzer.cs @@ -1,4 +1,5 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Exceptions; using Heartbeat.Runtime.Extensions; using Heartbeat.Runtime.Proxies; @@ -66,7 +67,7 @@ public void Dump(ILogger logger) } } - foreach (var spObject in Context.EnumerateObjectsByTypeName("System.Net.ServicePoint", TraversingHeapModes.All)) + foreach (var spObject in Context.EnumerateObjectsByTypeName("System.Net.ServicePoint", null)) { var servicePointProxy = new ServicePointProxy(Context, spObject); var servicePointAnalyzer = new ServicePointAnalyzer(Context, servicePointProxy) diff --git a/src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs index 4921dd9..1082eec 100644 --- a/src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs @@ -1,4 +1,5 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Extensions; using Microsoft.Diagnostics.Runtime; @@ -6,9 +7,9 @@ namespace Heartbeat.Runtime.Analyzers; -public sealed class StringDuplicateAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public sealed class StringDuplicateAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } public Generation? Generation { get; set; } public StringDuplicateAnalyzer(RuntimeContext context) : base(context) @@ -50,7 +51,7 @@ public IReadOnlyList GetStringDuplicates() var stringCount = new Dictionary(StringComparer.OrdinalIgnoreCase); var query = - from clrObject in Context.EnumerateStrings(TraversingHeapMode, Generation) + from clrObject in Context.EnumerateStrings(ObjectGcStatus, Generation) select clrObject; foreach (var stringInstance in query) diff --git a/src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs b/src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs index 127d4d7..85971b3 100644 --- a/src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs +++ b/src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs @@ -1,13 +1,14 @@ using Heartbeat.Runtime.Analyzers.Interfaces; +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Proxies; using Microsoft.Extensions.Logging; namespace Heartbeat.Runtime.Analyzers; -public sealed class TimerQueueTimerAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode +public sealed class TimerQueueTimerAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus { - public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All; + public ObjectGCStatus? ObjectGcStatus { get; set; } public TimerQueueTimerAnalyzer(RuntimeContext context) : base(context) { @@ -15,14 +16,14 @@ public TimerQueueTimerAnalyzer(RuntimeContext context) : base(context) public void Dump(ILogger logger) { - WriteLog(logger, TraversingHeapMode); + WriteLog(logger, ObjectGcStatus); } - public IReadOnlyCollection GetTimers(TraversingHeapModes traversingMode) + public IReadOnlyCollection GetTimers(ObjectGCStatus? status) { var result = new List(); - foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Threading.TimerQueueTimer", traversingMode)) + foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Threading.TimerQueueTimer", status)) { var timerObjectType = Context.Heap.GetObjectType(address); @@ -56,9 +57,9 @@ public IReadOnlyCollection GetTimers(TraversingHeapModes tr return result; } - private void WriteLog(ILogger logger, TraversingHeapModes traversingMode) + private void WriteLog(ILogger logger, ObjectGCStatus? status) { - foreach (var timer in GetTimers(traversingMode)) + foreach (var timer in GetTimers(status)) { logger.LogInformation($"{timer.Address} m_dueTime = {timer.DueTime}, m_period = {timer.Period}, m_canceled = {timer.Cancelled}"); diff --git a/src/Heartbeat.Runtime/Domain/ObjectGCStatus.cs b/src/Heartbeat.Runtime/Domain/ObjectGCStatus.cs new file mode 100644 index 0000000..e1e3f99 --- /dev/null +++ b/src/Heartbeat.Runtime/Domain/ObjectGCStatus.cs @@ -0,0 +1,8 @@ +namespace Heartbeat.Runtime.Domain; + +[Flags] +public enum ObjectGCStatus +{ + Live, + Dead +} \ No newline at end of file diff --git a/src/Heartbeat.Runtime/Domain/TraversingHeapModes.cs b/src/Heartbeat.Runtime/Domain/TraversingHeapModes.cs deleted file mode 100644 index f0b057c..0000000 --- a/src/Heartbeat.Runtime/Domain/TraversingHeapModes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Heartbeat.Domain; - -[Flags] -public enum TraversingHeapModes -{ - Live = 1, - Dead = 1 << 1, - All = Live | Dead -} diff --git a/src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs b/src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs index d0ad47d..0ee6aba 100644 --- a/src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs +++ b/src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs @@ -56,16 +56,16 @@ private static bool IsValueDefault(ulong objRef, ClrInstanceField field) { return field.ElementType switch { - ClrElementType.Boolean => field.Read(objRef, true) == false, - ClrElementType.Char => field.Read(objRef, true) == (char)0, - ClrElementType.Int8 => field.Read(objRef, true) == (sbyte)0, - ClrElementType.UInt8 => field.Read(objRef, true) == (byte)0, - ClrElementType.Int16 => field.Read(objRef, true) == (short)0, - ClrElementType.UInt16 => field.Read(objRef, true) == (ushort)0, - ClrElementType.Int32 => field.Read(objRef, true) == 0, - ClrElementType.UInt32 => field.Read(objRef, true) == (uint)0, - ClrElementType.Int64 => field.Read(objRef, true) == 0L, - ClrElementType.UInt64 => field.Read(objRef, true) == 0UL, + ClrElementType.Boolean => field.Read(objRef, true) == default, + ClrElementType.Char => field.Read(objRef, true) == default, + ClrElementType.Int8 => field.Read(objRef, true) == default, + ClrElementType.UInt8 => field.Read(objRef, true) == default, + ClrElementType.Int16 => field.Read(objRef, true) == default, + ClrElementType.UInt16 => field.Read(objRef, true) == default, + ClrElementType.Int32 => field.Read(objRef, true) == default, + ClrElementType.UInt32 => field.Read(objRef, true) == default, + ClrElementType.Int64 => field.Read(objRef, true) == default, + ClrElementType.UInt64 => field.Read(objRef, true) == default, ClrElementType.Float => field.Read(objRef, true) == 0f, ClrElementType.Double => field.Read(objRef, true) == 0d, ClrElementType.NativeInt => field.Read(objRef, true) == nint.Zero, diff --git a/src/Heartbeat.Runtime/HeapIndex.cs b/src/Heartbeat.Runtime/HeapIndex.cs index 23e5ee7..5f4cdfa 100644 --- a/src/Heartbeat.Runtime/HeapIndex.cs +++ b/src/Heartbeat.Runtime/HeapIndex.cs @@ -1,7 +1,6 @@ using Heartbeat.Runtime.Proxies; using Microsoft.Diagnostics.Runtime; - namespace Heartbeat.Runtime; public sealed class HeapIndex @@ -40,16 +39,23 @@ public HeapIndex(ClrHeap heap) continue; } - // Now enumerate all objects that this object points to, add them to the - // evaluation stack if we haven't seen them before. - if (type.IsArray) - { - EnumerateArrayElements(address); - } - else + var obj = heap.GetObject(address); + foreach (var reference in obj.EnumerateReferenceAddresses()) { - EnumerateFields(type, address); + eval.Push(reference); + AddReference(address, reference); } + + // // Now enumerate all objects that this object points to, add them to the + // // evaluation stack if we haven't seen them before. + // if (type.IsArray) + // { + // EnumerateArrayElements(address); + // } + // else + // { + // EnumerateFields(type, address); + // } } void EnumerateArrayElements(ulong address) @@ -80,7 +86,8 @@ void EnumerateArrayElements(ulong address) } else { - throw new NotSupportedException($"Enumerating array of {array.Type.ComponentType} type is not supported"); + throw new NotSupportedException( + $"Enumerating array of {array.Type.ComponentType} type is not supported"); } } @@ -90,7 +97,7 @@ void EnumerateFields(ClrType type, ulong objectAddress, ulong? parentAddress = n { if (instanceField.IsObjectReference) { - var fieldObject = instanceField.ReadObject(objectAddress, false); + var fieldObject = instanceField.ReadObject(objectAddress, !type.IsObjectReference); if (!fieldObject.IsNull) { AddReference(objectAddress, fieldObject.Address); @@ -98,6 +105,7 @@ void EnumerateFields(ClrType type, ulong objectAddress, ulong? parentAddress = n { AddReference(parentAddress.Value, fieldObject.Address); } + eval.Push(fieldObject.Address); } } diff --git a/src/Heartbeat.Runtime/Proxies/ArrayProxy.cs b/src/Heartbeat.Runtime/Proxies/ArrayProxy.cs index 24751d3..9087733 100644 --- a/src/Heartbeat.Runtime/Proxies/ArrayProxy.cs +++ b/src/Heartbeat.Runtime/Proxies/ArrayProxy.cs @@ -91,7 +91,7 @@ public static IEnumerable EnumerateObjectItems(ClrArray array) var lowerBound0 = array.GetLowerBound(0); var upperBound0 = array.GetUpperBound(0); - for (int index0 = lowerBound0; index0 < upperBound0; index0++) + for (int index0 = lowerBound0; index0 <= upperBound0; index0++) { if (array.Rank == 1) { @@ -101,7 +101,7 @@ public static IEnumerable EnumerateObjectItems(ClrArray array) { var lowerBound1 = array.GetLowerBound(1); var upperBound1 = array.GetUpperBound(1); - for (int index1 = lowerBound1; index1 < upperBound1; index1++) + for (int index1 = lowerBound1; index1 <= upperBound1; index1++) { if (array.Rank == 2) { @@ -129,7 +129,7 @@ public static IEnumerable EnumerateValueTypes(ClrArray array) var lowerBound0 = array.GetLowerBound(0); var upperBound0 = array.GetUpperBound(0); - for (int index0 = lowerBound0; index0 < upperBound0; index0++) + for (int index0 = lowerBound0; index0 <= upperBound0; index0++) { if (array.Rank == 1) { @@ -139,7 +139,7 @@ public static IEnumerable EnumerateValueTypes(ClrArray array) { var lowerBound1 = array.GetLowerBound(1); var upperBound1 = array.GetUpperBound(1); - for (int index1 = lowerBound1; index1 < upperBound1; index1++) + for (int index1 = lowerBound1; index1 <= upperBound1; index1++) { if (array.Rank == 2) { diff --git a/src/Heartbeat.Runtime/RuntimeContext.cs b/src/Heartbeat.Runtime/RuntimeContext.cs index 4045781..efc28b9 100644 --- a/src/Heartbeat.Runtime/RuntimeContext.cs +++ b/src/Heartbeat.Runtime/RuntimeContext.cs @@ -1,4 +1,5 @@ +using Heartbeat.Runtime.Domain; using Heartbeat.Runtime.Extensions; using Heartbeat.Runtime.Models; @@ -44,7 +45,7 @@ public string GetAutoPropertyFieldName(string propertyName) throw new NotImplementedException(); } - public IEnumerable EnumerateObjectAddressesByTypeName(string typeName, TraversingHeapModes traversingMode) + public IEnumerable EnumerateObjectAddressesByTypeName(string typeName, ObjectGCStatus? status) { var clrType = Heap.GetTypeByName(typeName); @@ -57,32 +58,33 @@ public IEnumerable EnumerateObjectAddressesByTypeName(string typeName, Tr from clrObject in Heap.EnumerateObjects() let type = clrObject.Type where type != null && !type.IsFree && type.MethodTable == clrType.MethodTable - && FilterByWalkMode(traversingMode, clrObject) + && FilterByGCStatus(status, clrObject) select clrObject.Address; } - public IEnumerable EnumerateObjects(TraversingHeapModes traversingMode, Generation? generation = null) + public IEnumerable EnumerateObjects(ObjectGCStatus? status, Generation? generation = null) { return from obj in Heap.EnumerateObjects() where obj.IsValid - && FilterByWalkMode(traversingMode, obj.Address) + && !obj.IsFree + && FilterByGCStatus(status, obj.Address) where generation == null || Heap.GetGeneration(obj.Address) == generation select obj; } public IEnumerable EnumerateObjectsByTypeName( string typeName, - TraversingHeapModes traversingMode, + ObjectGCStatus? status, Generation? generation = null) { var clrType = Heap.GetTypeByName(typeName) ?? throw new InvalidOperationException($"Type {typeName} is not found"); - return EnumerateObjects(traversingMode, generation).Where(obj => obj.Type!.MethodTable == clrType.MethodTable); + return EnumerateObjects(status, generation).Where(obj => obj.Type!.MethodTable == clrType.MethodTable); } - public IEnumerable EnumerateStrings(TraversingHeapModes traversingMode, Generation? generation = null) + public IEnumerable EnumerateStrings(ObjectGCStatus? status, Generation? generation = null) { - return EnumerateObjectsByTypeName("System.String", traversingMode, generation); + return EnumerateObjectsByTypeName("System.String", status, generation); } public IEnumerable GetAllReferencesTo(ulong address) @@ -198,14 +200,14 @@ public ulong GetWeakRefValue(ClrObject weakRefObject) return value; } - private bool FilterByWalkMode(TraversingHeapModes traversingMode, ulong address) + private bool FilterByGCStatus(ObjectGCStatus? status, ulong address) { - return traversingMode switch + return status switch { - TraversingHeapModes.Live => HeapIndex.HasRoot(address), - TraversingHeapModes.Dead => !HeapIndex.HasRoot(address), - TraversingHeapModes.All => true, - _ => throw new ArgumentOutOfRangeException(nameof(traversingMode), traversingMode, null), + ObjectGCStatus.Live => HeapIndex.HasRoot(address), + ObjectGCStatus.Dead => !HeapIndex.HasRoot(address), + null => true, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, null), }; } } \ No newline at end of file diff --git a/src/Heartbeat/AnalyzeCommandHandler.cs b/src/Heartbeat/AnalyzeCommandHandler.cs index b9df701..ba62846 100644 --- a/src/Heartbeat/AnalyzeCommandHandler.cs +++ b/src/Heartbeat/AnalyzeCommandHandler.cs @@ -45,7 +45,7 @@ private async Task ProcessCommand2(ILogger logger) string filePath = @"C:\Users\Ne4to\projects\GitHub\Ne4to\Heartbeat\tests\dumps\AsyncStask.dmp"; var runtimeContext = new RuntimeContext(filePath); - var traversingMode = _options.TraversingHeapMode; + var traversingMode = _options.ObjectGcStatus; ExecuteWhenTrue(PrintHttpClients, _options.HttpClient); ExecuteWhenTrue(PrintStringDuplicates, _options.StringDuplicate); @@ -57,7 +57,7 @@ void PrintHttpClients() { var analyzer = new HttpClientAnalyzer(runtimeContext) { - TraversingHeapMode = traversingMode + ObjectGcStatus = traversingMode }; var httpClients = analyzer.GetClientsInfo(); @@ -71,7 +71,7 @@ void PrintStringDuplicates() { var analyzer = new StringDuplicateAnalyzer(runtimeContext) { - TraversingHeapMode = traversingMode + ObjectGcStatus = traversingMode }; var duplicates = analyzer.GetStringDuplicates(10, 100); @@ -86,7 +86,7 @@ void PrintObjectTypeStatistics() { var analyzer = new HeapDumpStatisticsAnalyzer(runtimeContext) { - TraversingHeapMode = traversingMode + ObjectGcStatus = traversingMode }; var statistics = analyzer.GetObjectTypeStatistics(); @@ -101,7 +101,7 @@ void PrintTimerQueueTimers() { var analyzer = new TimerQueueTimerAnalyzer(runtimeContext) { - TraversingHeapMode = traversingMode + ObjectGcStatus = traversingMode }; var timers = analyzer.GetTimers(traversingMode); @@ -123,10 +123,10 @@ void PrintLongStrings() { var analyzer = new LongStringAnalyzer(runtimeContext) { - TraversingHeapMode = traversingMode + ObjectGcStatus = traversingMode }; - var strings = analyzer.GetStrings(20, null); + var strings = analyzer.GetStrings(null, 20, null); foreach (var s in strings) { logger.LogInformation($"{s.Address} Length = {s.Length} chars, Value = {s.Value}"); @@ -217,16 +217,16 @@ private void ProcessCommand(DataTarget dataTarget, ILogger logger) { LongStringAnalyzer longStringAnalyzer = new(runtimeContext) { - TraversingHeapMode = _options.TraversingHeapMode + ObjectGcStatus = _options.ObjectGcStatus }; longStringAnalyzer.Dump(logger); } if (_options.AsyncStateMachine) { - var asyncStateMachineAnalyzer = new AsyncStateMachineAnalyzer(runtimeContext) + var asyncStateMachineAnalyzer = new AsyncStatusMachineAnalyzer(runtimeContext) { - TraversingHeapMode = _options.TraversingHeapMode + ObjectGcStatus = _options.ObjectGcStatus }; asyncStateMachineAnalyzer.Dump(logger); } diff --git a/src/Heartbeat/AnalyzeCommandOptions.cs b/src/Heartbeat/AnalyzeCommandOptions.cs index ae7e8ae..f21ef84 100644 --- a/src/Heartbeat/AnalyzeCommandOptions.cs +++ b/src/Heartbeat/AnalyzeCommandOptions.cs @@ -1,4 +1,5 @@ using Heartbeat.Domain; +using Heartbeat.Runtime.Domain; using System.CommandLine; using System.CommandLine.Binding; @@ -23,7 +24,7 @@ public class AnalyzeCommandOptions public bool TaskCompletionSource { get; set; } public bool ObjectTypeStatistics { get; set; } public bool HttpClient { get; set; } - public TraversingHeapModes TraversingHeapMode { get; set; } + public ObjectGCStatus ObjectGcStatus { get; set; } // ReSharper restore UnusedAutoPropertyAccessor.Global public static (RootCommand Command, AnalyzeCommandOptionsBinder OptionsBinder) RootCommand() @@ -96,7 +97,7 @@ public class AnalyzeCommandOptionsBinder : BinderBase private readonly Option _dumpOption; private readonly Option _dacPathOption; private readonly Option _ignoreDacMismatchOption; - private readonly Option _traversingHeapModeOption; + private readonly Option _traversingHeapModeOption; private readonly Option _heapOption; private readonly Option _servicePointManagerOption; private readonly Option _asyncStateMachineOption; @@ -113,7 +114,7 @@ public AnalyzeCommandOptionsBinder( Option dumpOption, Option dacPathOption, Option ignoreDacMismatchOption, - Option traversingHeapModeOption, + Option traversingHeapModeOption, Option heapOption, Option servicePointManagerOption, Option asyncStateMachineOption, @@ -150,7 +151,7 @@ protected override AnalyzeCommandOptions GetBoundValue(BindingContext bindingCon Dump = bindingContext.ParseResult.GetValueForOption(_dumpOption), DacPath = bindingContext.ParseResult.GetValueForOption(_dacPathOption), IgnoreDacMismatch = bindingContext.ParseResult.GetValueForOption(_ignoreDacMismatchOption).GetValueOrDefault(), - TraversingHeapMode = bindingContext.ParseResult.GetValueForOption(_traversingHeapModeOption), + ObjectGcStatus = bindingContext.ParseResult.GetValueForOption(_traversingHeapModeOption), Heap = bindingContext.ParseResult.GetValueForOption(_heapOption).GetValueOrDefault(), ServicePointManager = bindingContext.ParseResult.GetValueForOption(_servicePointManagerOption).GetValueOrDefault(), AsyncStateMachine = bindingContext.ParseResult.GetValueForOption(_asyncStateMachineOption).GetValueOrDefault(), @@ -202,11 +203,11 @@ private static IEnumerable