Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sparse arrays stats #14

Merged
merged 14 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,35 +1,36 @@
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);

var state = timerObjectType.GetFieldByName("m_state").ReadObject(address, false);

Check warning on line 30 in src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 30 in src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
var dueTime = timerObjectType.GetFieldByName("m_dueTime").Read<uint>(address, true);

Check warning on line 31 in src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
var period = timerObjectType.GetFieldByName("m_period").Read<uint>(address, true);

Check warning on line 32 in src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
var canceled = timerObjectType.GetFieldByName("m_canceled").Read<bool>(address, true);

Check warning on line 33 in src/Heartbeat.Runtime/Analyzers/TimerQueueTimerAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

// var timerCallback = timerObjectType.GetFieldByName("m_timerCallback")
// .GetValue(address);
Expand All @@ -56,9 +57,9 @@
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
Loading