Skip to content

Commit

Permalink
feat: add sparse arrays stats (#14)
Browse files Browse the repository at this point in the history
* feat: add sparse array stats grid

fix: live object check in heap index

* fix: navigation on single page with auto data refresh

* feat: add ProgressContainer

- change defaults theme

* refactor: use ProgressContainer on home page

* refactor: use ProgressContainer on segments page

* refactor: use ProgressContainer on modules page

* refactor: use ProgressContainer on roots page

* refactor: use ProgressContainer on arrays page

* refactor: rename TraversingHeapModes to ObjectGCStatus

* refactor: use ProgressContainer on string page

* refactor: use ProgressContainer on object instances page

* refactor: use ProgressContainer on string duplicates page

* refactor: use ProgressContainer on sparse arrays stat page

* fix: ProgressContainer infinitive data refresh
  • Loading branch information
Ne4to authored Jan 27, 2024
1 parent 12b2308 commit d84d61a
Show file tree
Hide file tree
Showing 87 changed files with 2,169 additions and 1,566 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<PropertyGroup>
<VersionPrefix>0.3.0</VersionPrefix>
<VersionPrefix>0.4.0</VersionPrefix>
<RepositoryUrl>https://github.com/Ne4to/Heartbeat</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
11 changes: 11 additions & 0 deletions scripts/reinstall-release-tool.ps1
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion scripts/update-ts-client.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
3 changes: 2 additions & 1 deletion src/DebugHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Heartbeat.Domain;
using Heartbeat.Runtime;
using Heartbeat.Runtime.Domain;
using Heartbeat.Runtime.Proxies;

using Microsoft.Diagnostics.Runtime;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

using Heartbeat.Runtime.Analyzers.Interfaces;
using Heartbeat.Runtime.Domain;
using Heartbeat.Runtime.Proxies;

using Microsoft.Diagnostics.Runtime;
Expand All @@ -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)
{
}
Expand All @@ -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")
Expand Down Expand Up @@ -146,7 +147,7 @@ private IEnumerable<ClrObject> 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
Expand Down
11 changes: 5 additions & 6 deletions src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@

using Heartbeat.Runtime.Analyzers.Interfaces;
using Heartbeat.Runtime.Domain;
using Heartbeat.Runtime.Extensions;

using Microsoft.Diagnostics.Runtime;
using Microsoft.Extensions.Logging;

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)
Expand All @@ -36,10 +37,8 @@ private void WriteLog(ILogger logger, int topTypeCount)
public IReadOnlyCollection<ObjectTypeStatistics> 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
Expand Down
7 changes: 4 additions & 3 deletions src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -17,7 +18,7 @@ public IReadOnlyCollection<HttpClientInfo> GetClientsInfo()
{
var result = new List<HttpClientInfo>();

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");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Heartbeat.Runtime.Domain;

namespace Heartbeat.Runtime.Analyzers.Interfaces;

public interface IWithObjectGCStatus
{
ObjectGCStatus? ObjectGcStatus { get; set; }
}

This file was deleted.

23 changes: 12 additions & 11 deletions src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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<ClrObject> GetLongestStrings(int count, TraversingHeapModes traversingMode)
private IEnumerable<ClrObject> 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;

Expand All @@ -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<LongStringInfo> GetStrings(int count, int? truncateLength)
public IReadOnlyCollection<LongStringInfo> GetStrings(ObjectGCStatus? status, int count, int? truncateLength)
{
var result = new List<LongStringInfo>(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<int>("_stringLength");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Heartbeat.Runtime.Analyzers.Interfaces;
using Heartbeat.Runtime.Domain;
using Heartbeat.Runtime.Extensions;

using Microsoft.Diagnostics.Runtime;
using Microsoft.Extensions.Logging;

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)
Expand Down Expand Up @@ -50,7 +51,7 @@ public IReadOnlyList<StringDuplicate> GetStringDuplicates()
var stringCount = new Dictionary<string, StringDuplicateInfo>(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)
Expand Down
15 changes: 8 additions & 7 deletions src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
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)
{
}

public void Dump(ILogger logger)
{
WriteLog(logger, TraversingHeapMode);
WriteLog(logger, ObjectGcStatus);
}

public IReadOnlyCollection<TimerQueueTimerInfo> GetTimers(TraversingHeapModes traversingMode)
public IReadOnlyCollection<TimerQueueTimerInfo> GetTimers(ObjectGCStatus? status)
{
var result = new List<TimerQueueTimerInfo>();

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

Expand Down Expand Up @@ -56,9 +57,9 @@ public IReadOnlyCollection<TimerQueueTimerInfo> 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}");

Expand Down
8 changes: 8 additions & 0 deletions src/Heartbeat.Runtime/Domain/ObjectGCStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Heartbeat.Runtime.Domain;

[Flags]
public enum ObjectGCStatus
{
Live,
Dead
}
9 changes: 0 additions & 9 deletions src/Heartbeat.Runtime/Domain/TraversingHeapModes.cs

This file was deleted.

20 changes: 10 additions & 10 deletions src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ private static bool IsValueDefault(ulong objRef, ClrInstanceField field)
{
return field.ElementType switch
{
ClrElementType.Boolean => field.Read<bool>(objRef, true) == false,
ClrElementType.Char => field.Read<char>(objRef, true) == (char)0,
ClrElementType.Int8 => field.Read<sbyte>(objRef, true) == (sbyte)0,
ClrElementType.UInt8 => field.Read<byte>(objRef, true) == (byte)0,
ClrElementType.Int16 => field.Read<short>(objRef, true) == (short)0,
ClrElementType.UInt16 => field.Read<ushort>(objRef, true) == (ushort)0,
ClrElementType.Int32 => field.Read<int>(objRef, true) == 0,
ClrElementType.UInt32 => field.Read<int>(objRef, true) == (uint)0,
ClrElementType.Int64 => field.Read<long>(objRef, true) == 0L,
ClrElementType.UInt64 => field.Read<ulong>(objRef, true) == 0UL,
ClrElementType.Boolean => field.Read<bool>(objRef, true) == default,
ClrElementType.Char => field.Read<char>(objRef, true) == default,
ClrElementType.Int8 => field.Read<sbyte>(objRef, true) == default,
ClrElementType.UInt8 => field.Read<byte>(objRef, true) == default,
ClrElementType.Int16 => field.Read<short>(objRef, true) == default,
ClrElementType.UInt16 => field.Read<ushort>(objRef, true) == default,
ClrElementType.Int32 => field.Read<int>(objRef, true) == default,
ClrElementType.UInt32 => field.Read<int>(objRef, true) == default,
ClrElementType.Int64 => field.Read<long>(objRef, true) == default,
ClrElementType.UInt64 => field.Read<ulong>(objRef, true) == default,
ClrElementType.Float => field.Read<float>(objRef, true) == 0f,
ClrElementType.Double => field.Read<double>(objRef, true) == 0d,
ClrElementType.NativeInt => field.Read<nint>(objRef, true) == nint.Zero,
Expand Down
Loading

0 comments on commit d84d61a

Please sign in to comment.