Skip to content

Commit d84d61a

Browse files
authored
feat: add sparse arrays stats (#14)
* 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
1 parent 12b2308 commit d84d61a

File tree

87 files changed

+2169
-1566
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+2169
-1566
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<PropertyGroup>
10-
<VersionPrefix>0.3.0</VersionPrefix>
10+
<VersionPrefix>0.4.0</VersionPrefix>
1111
<RepositoryUrl>https://github.com/Ne4to/Heartbeat</RepositoryUrl>
1212
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1313
<PackageLicenseExpression>MIT</PackageLicenseExpression>

scripts/reinstall-release-tool.ps1

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
$ErrorActionPreference = "Stop"
2+
3+
try
4+
{
5+
dotnet tool uninstall -g Heartbeat
6+
dotnet tool install --global Heartbeat
7+
}
8+
catch {
9+
Write-Host 'Install global tool - FAILED!' -ForegroundColor Red
10+
throw
11+
}

scripts/update-ts-client.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ try
1616
Set-Location $FrontendRoot
1717
$env:HEARTBEAT_GENERATE_CONTRACTS = 'true'
1818
dotnet swagger tofile --yaml --output $ContractPath $DllPath Heartbeat
19-
dotnet kiota generate -l typescript --openapi $ContractPath -c HeartbeatClient -o ./src/client
19+
dotnet kiota generate -l typescript --openapi $ContractPath -c HeartbeatClient -o ./src/client --clean-output
2020

2121
# TODO try --serializer Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory --deserializer Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory
2222
}

src/DebugHost/Program.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Heartbeat.Domain;
22
using Heartbeat.Runtime;
3+
using Heartbeat.Runtime.Domain;
34
using Heartbeat.Runtime.Proxies;
45

56
using Microsoft.Diagnostics.Runtime;
@@ -18,7 +19,7 @@ static void ProcessFile(string filePath)
1819

1920
static void WriteWebRequests(RuntimeContext runtimeContext)
2021
{
21-
var q = from clrObject in runtimeContext.EnumerateObjectsByTypeName("System.Net.HttpWebRequest", TraversingHeapModes.All)
22+
var q = from clrObject in runtimeContext.EnumerateObjectsByTypeName("System.Net.HttpWebRequest", null)
2223
let webRequestProxy = new HttpWebRequestProxy(runtimeContext, clrObject)
2324
let requestContentLength = webRequestProxy.ContentLength
2425
let responseContentLength = webRequestProxy.Response?.ContentLength

src/Heartbeat.Runtime/Analyzers/AsyncStateMachineAnalyzer.cs src/Heartbeat.Runtime/Analyzers/AsyncStatusMachineAnalyzer.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
using Heartbeat.Runtime.Analyzers.Interfaces;
3+
using Heartbeat.Runtime.Domain;
34
using Heartbeat.Runtime.Proxies;
45

56
using Microsoft.Diagnostics.Runtime;
@@ -8,11 +9,11 @@
89

910
namespace Heartbeat.Runtime.Analyzers;
1011

11-
public class AsyncStateMachineAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
12+
public class AsyncStatusMachineAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
1213
{
13-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
14+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1415

15-
public AsyncStateMachineAnalyzer(RuntimeContext context)
16+
public AsyncStatusMachineAnalyzer(RuntimeContext context)
1617
: base(context)
1718
{
1819
}
@@ -38,7 +39,7 @@ private void ProcessAsyncStateMachine(ILogger logger)
3839
{
3940

4041
var stateMachineQuery =
41-
from clrObject in Context.EnumerateObjects(TraversingHeapMode)
42+
from clrObject in Context.EnumerateObjects(ObjectGcStatus)
4243
where clrObject.Type
4344
!.EnumerateInterfaces()
4445
.Any(clrInterface => clrInterface.Name == "System.Runtime.CompilerServices.IAsyncStateMachine")
@@ -146,7 +147,7 @@ private IEnumerable<ClrObject> EnumerateAsyncStateMachineObjects()
146147
var (asyncStateMachineBoxType, debugFinalizableAsyncStateMachineBoxType, taskType) = FindStateMachineTypes();
147148

148149
return
149-
from clrObject in Context.EnumerateObjects(TraversingHeapMode)
150+
from clrObject in Context.EnumerateObjects(ObjectGcStatus)
150151
where
151152
// Skip objects too small to be state machines or tasks, avoiding some compiler-generated caching data structures.
152153
// https://github.com/dotnet/diagnostics/blob/dc9d61a876d6153306b2d59c769d9581e3d5ab2d/src/SOS/Strike/strike.cpp#L4749

src/Heartbeat.Runtime/Analyzers/HeapDumpStatisticsAnalyzer.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11

22
using Heartbeat.Runtime.Analyzers.Interfaces;
3+
using Heartbeat.Runtime.Domain;
34
using Heartbeat.Runtime.Extensions;
45

56
using Microsoft.Diagnostics.Runtime;
67
using Microsoft.Extensions.Logging;
78

89
namespace Heartbeat.Runtime.Analyzers;
910

10-
public sealed class HeapDumpStatisticsAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
11+
public sealed class HeapDumpStatisticsAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
1112
{
12-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
13+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1314
public Generation? Generation { get; set; }
1415

1516
public HeapDumpStatisticsAnalyzer(RuntimeContext context) : base(context)
@@ -36,10 +37,8 @@ private void WriteLog(ILogger logger, int topTypeCount)
3637
public IReadOnlyCollection<ObjectTypeStatistics> GetObjectTypeStatistics()
3738
{
3839
return (
39-
from obj in Context.EnumerateObjects(TraversingHeapMode, Generation)
40-
let objSize = obj.Size
41-
//group new { size = objSize } by type.Name into g
42-
group objSize by obj.Type
40+
from obj in Context.EnumerateObjects(ObjectGcStatus, Generation)
41+
group obj.Size by obj.Type
4342
into g
4443
let totalSize = (ulong)g.Sum(t => (long)t)
4544
let clrType = g.Key

src/Heartbeat.Runtime/Analyzers/HttpClientAnalyzer.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using Heartbeat.Runtime.Analyzers.Interfaces;
2+
using Heartbeat.Runtime.Domain;
23

34
using Microsoft.Extensions.Logging;
45

56
namespace Heartbeat.Runtime.Analyzers;
67

7-
public sealed class HttpClientAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
8+
public sealed class HttpClientAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
89
{
9-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
10+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1011

1112
public HttpClientAnalyzer(RuntimeContext context)
1213
: base(context)
@@ -17,7 +18,7 @@ public IReadOnlyCollection<HttpClientInfo> GetClientsInfo()
1718
{
1819
var result = new List<HttpClientInfo>();
1920

20-
foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Net.Http.HttpClient", TraversingHeapMode))
21+
foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Net.Http.HttpClient", ObjectGcStatus))
2122
{
2223
var httpClientObjectType = Context.Heap.GetObjectType(address);
2324
var timeoutField = httpClientObjectType.GetFieldByName("_timeout");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Heartbeat.Runtime.Domain;
2+
3+
namespace Heartbeat.Runtime.Analyzers.Interfaces;
4+
5+
public interface IWithObjectGCStatus
6+
{
7+
ObjectGCStatus? ObjectGcStatus { get; set; }
8+
}

src/Heartbeat.Runtime/Analyzers/Interfaces/IWithTraversingHeapMode.cs

-6
This file was deleted.

src/Heartbeat.Runtime/Analyzers/LongStringAnalyzer.cs

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
using Heartbeat.Runtime.Analyzers.Interfaces;
2+
using Heartbeat.Runtime.Domain;
23

34
using Microsoft.Diagnostics.Runtime;
45
using Microsoft.Extensions.Logging;
56

67
namespace Heartbeat.Runtime.Analyzers;
78

8-
public sealed class LongStringAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
9+
public sealed class LongStringAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
910
{
10-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
11+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1112

1213
public LongStringAnalyzer(RuntimeContext context)
1314
: base(context)
@@ -16,18 +17,18 @@ public LongStringAnalyzer(RuntimeContext context)
1617

1718
public void Dump(ILogger logger)
1819
{
19-
WriteLog(logger, TraversingHeapMode);
20+
WriteLog(logger, ObjectGcStatus);
2021
}
2122

22-
private void WriteLog(ILogger logger, TraversingHeapModes traversingMode)
23+
private void WriteLog(ILogger logger, ObjectGCStatus? status)
2324
{
24-
LogLongestStrings(logger, traversingMode, 10);
25+
LogLongestStrings(logger, status, 10);
2526
}
2627

27-
private IEnumerable<ClrObject> GetLongestStrings(int count, TraversingHeapModes traversingMode)
28+
private IEnumerable<ClrObject> GetLongestStrings(int count, ObjectGCStatus? status)
2829
{
2930
var query =
30-
from clrObject in Context.EnumerateStrings(traversingMode)
31+
from clrObject in Context.EnumerateStrings(status)
3132
orderby clrObject.Size descending
3233
select clrObject;
3334

@@ -46,19 +47,19 @@ orderby clrObject.Size descending
4647
// Console.WriteLine($"Value: {System.Text.Encoding.Unicode.GetString(buffer)}...");
4748
}
4849

49-
private void LogLongestStrings(ILogger logger, TraversingHeapModes traversingMode, int count, int maxLength = 200)
50+
private void LogLongestStrings(ILogger logger, ObjectGCStatus? status, int count, int maxLength = 200)
5051
{
51-
foreach (var s in GetStrings(count, maxLength))
52+
foreach (var s in GetStrings(status, count, maxLength))
5253
{
5354
logger.LogInformation($"Length = {s.Length} symbols, Value = {s.Value}");
5455
}
5556
}
5657

57-
public IReadOnlyCollection<LongStringInfo> GetStrings(int count, int? truncateLength)
58+
public IReadOnlyCollection<LongStringInfo> GetStrings(ObjectGCStatus? status, int count, int? truncateLength)
5859
{
5960
var result = new List<LongStringInfo>(count);
6061

61-
foreach (var stringClrObject in GetLongestStrings(count, TraversingHeapMode))
62+
foreach (var stringClrObject in GetLongestStrings(count, status))
6263
{
6364
string value = stringClrObject.AsString(truncateLength ?? 4096)!;
6465
var length = stringClrObject.ReadField<int>("_stringLength");

src/Heartbeat.Runtime/Analyzers/ServicePointManagerAnalyzer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Heartbeat.Runtime.Analyzers.Interfaces;
2+
using Heartbeat.Runtime.Domain;
23
using Heartbeat.Runtime.Exceptions;
34
using Heartbeat.Runtime.Extensions;
45
using Heartbeat.Runtime.Proxies;
@@ -66,7 +67,7 @@ public void Dump(ILogger logger)
6667
}
6768
}
6869

69-
foreach (var spObject in Context.EnumerateObjectsByTypeName("System.Net.ServicePoint", TraversingHeapModes.All))
70+
foreach (var spObject in Context.EnumerateObjectsByTypeName("System.Net.ServicePoint", null))
7071
{
7172
var servicePointProxy = new ServicePointProxy(Context, spObject);
7273
var servicePointAnalyzer = new ServicePointAnalyzer(Context, servicePointProxy)

src/Heartbeat.Runtime/Analyzers/StringDuplicateAnalyzer.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using Heartbeat.Runtime.Analyzers.Interfaces;
2+
using Heartbeat.Runtime.Domain;
23
using Heartbeat.Runtime.Extensions;
34

45
using Microsoft.Diagnostics.Runtime;
56
using Microsoft.Extensions.Logging;
67

78
namespace Heartbeat.Runtime.Analyzers;
89

9-
public sealed class StringDuplicateAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
10+
public sealed class StringDuplicateAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
1011
{
11-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
12+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1213
public Generation? Generation { get; set; }
1314

1415
public StringDuplicateAnalyzer(RuntimeContext context) : base(context)
@@ -50,7 +51,7 @@ public IReadOnlyList<StringDuplicate> GetStringDuplicates()
5051
var stringCount = new Dictionary<string, StringDuplicateInfo>(StringComparer.OrdinalIgnoreCase);
5152

5253
var query =
53-
from clrObject in Context.EnumerateStrings(TraversingHeapMode, Generation)
54+
from clrObject in Context.EnumerateStrings(ObjectGcStatus, Generation)
5455
select clrObject;
5556

5657
foreach (var stringInstance in query)

src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
using Heartbeat.Runtime.Analyzers.Interfaces;
2+
using Heartbeat.Runtime.Domain;
23
using Heartbeat.Runtime.Proxies;
34

45
using Microsoft.Extensions.Logging;
56

67
namespace Heartbeat.Runtime.Analyzers;
78

8-
public sealed class TimerQueueTimerAnalyzer : AnalyzerBase, ILoggerDump, IWithTraversingHeapMode
9+
public sealed class TimerQueueTimerAnalyzer : AnalyzerBase, ILoggerDump, IWithObjectGCStatus
910
{
10-
public TraversingHeapModes TraversingHeapMode { get; set; } = TraversingHeapModes.All;
11+
public ObjectGCStatus? ObjectGcStatus { get; set; }
1112

1213
public TimerQueueTimerAnalyzer(RuntimeContext context) : base(context)
1314
{
1415
}
1516

1617
public void Dump(ILogger logger)
1718
{
18-
WriteLog(logger, TraversingHeapMode);
19+
WriteLog(logger, ObjectGcStatus);
1920
}
2021

21-
public IReadOnlyCollection<TimerQueueTimerInfo> GetTimers(TraversingHeapModes traversingMode)
22+
public IReadOnlyCollection<TimerQueueTimerInfo> GetTimers(ObjectGCStatus? status)
2223
{
2324
var result = new List<TimerQueueTimerInfo>();
2425

25-
foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Threading.TimerQueueTimer", traversingMode))
26+
foreach (var address in Context.EnumerateObjectAddressesByTypeName("System.Threading.TimerQueueTimer", status))
2627
{
2728
var timerObjectType = Context.Heap.GetObjectType(address);
2829

@@ -56,9 +57,9 @@ public IReadOnlyCollection<TimerQueueTimerInfo> GetTimers(TraversingHeapModes tr
5657
return result;
5758
}
5859

59-
private void WriteLog(ILogger logger, TraversingHeapModes traversingMode)
60+
private void WriteLog(ILogger logger, ObjectGCStatus? status)
6061
{
61-
foreach (var timer in GetTimers(traversingMode))
62+
foreach (var timer in GetTimers(status))
6263
{
6364
logger.LogInformation($"{timer.Address} m_dueTime = {timer.DueTime}, m_period = {timer.Period}, m_canceled = {timer.Cancelled}");
6465

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Heartbeat.Runtime.Domain;
2+
3+
[Flags]
4+
public enum ObjectGCStatus
5+
{
6+
Live,
7+
Dead
8+
}

src/Heartbeat.Runtime/Domain/TraversingHeapModes.cs

-9
This file was deleted.

src/Heartbeat.Runtime/Extensions/ClrValueTypeExtensions.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,16 @@ private static bool IsValueDefault(ulong objRef, ClrInstanceField field)
5656
{
5757
return field.ElementType switch
5858
{
59-
ClrElementType.Boolean => field.Read<bool>(objRef, true) == false,
60-
ClrElementType.Char => field.Read<char>(objRef, true) == (char)0,
61-
ClrElementType.Int8 => field.Read<sbyte>(objRef, true) == (sbyte)0,
62-
ClrElementType.UInt8 => field.Read<byte>(objRef, true) == (byte)0,
63-
ClrElementType.Int16 => field.Read<short>(objRef, true) == (short)0,
64-
ClrElementType.UInt16 => field.Read<ushort>(objRef, true) == (ushort)0,
65-
ClrElementType.Int32 => field.Read<int>(objRef, true) == 0,
66-
ClrElementType.UInt32 => field.Read<int>(objRef, true) == (uint)0,
67-
ClrElementType.Int64 => field.Read<long>(objRef, true) == 0L,
68-
ClrElementType.UInt64 => field.Read<ulong>(objRef, true) == 0UL,
59+
ClrElementType.Boolean => field.Read<bool>(objRef, true) == default,
60+
ClrElementType.Char => field.Read<char>(objRef, true) == default,
61+
ClrElementType.Int8 => field.Read<sbyte>(objRef, true) == default,
62+
ClrElementType.UInt8 => field.Read<byte>(objRef, true) == default,
63+
ClrElementType.Int16 => field.Read<short>(objRef, true) == default,
64+
ClrElementType.UInt16 => field.Read<ushort>(objRef, true) == default,
65+
ClrElementType.Int32 => field.Read<int>(objRef, true) == default,
66+
ClrElementType.UInt32 => field.Read<int>(objRef, true) == default,
67+
ClrElementType.Int64 => field.Read<long>(objRef, true) == default,
68+
ClrElementType.UInt64 => field.Read<ulong>(objRef, true) == default,
6969
ClrElementType.Float => field.Read<float>(objRef, true) == 0f,
7070
ClrElementType.Double => field.Read<double>(objRef, true) == 0d,
7171
ClrElementType.NativeInt => field.Read<nint>(objRef, true) == nint.Zero,

0 commit comments

Comments
 (0)