Skip to content

Commit 12b2308

Browse files
authored
feat: add arrays grid (#13)
1 parent f868a93 commit 12b2308

File tree

30 files changed

+512
-67
lines changed

30 files changed

+512
-67
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.2.0</VersionPrefix>
10+
<VersionPrefix>0.3.0</VersionPrefix>
1111
<RepositoryUrl>https://github.com/Ne4to/Heartbeat</RepositoryUrl>
1212
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1313
<PackageLicenseExpression>MIT</PackageLicenseExpression>

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
# Heartbeat
2-
Diagnostics utility to analyze memory dumps of a .NET application
32
[![NuGet Badge](https://buildstats.info/nuget/heartbeat?includePreReleases=true&dWidth=0)](https://www.nuget.org/packages/Heartbeat/)
43

4+
Diagnostics utility with web UI to analyze memory dumps of a .NET application
5+
56
## Getting started
67

78
```shell
89
dotnet tool install --global Heartbeat
10+
# optional
11+
export PATH=$PATH:$HOME/.dotnet/tools
912
heartbeat --dump <path-to-dump-file>
1013
```
1114
Open `http://localhost:5000/` in web browser.
12-
See [UI screen]([https://](https://github.com/Ne4to/Heartbeat/tree/master/assets)) for examples
15+
See [UI screen](https://github.com/Ne4to/Heartbeat/tree/master/assets) for examples
1316

1417
<!---
1518
TODO: update description

assets/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<a href="01-dashboard.jpeg"><img src="01-dashboard.jpeg" alt="Dashboard" width="30%"/><a>
2+
<a href="02-heap-dump.jpeg"><img src="02-heap-dump.jpeg" alt="Heap dump" width="30%"/><a>
3+
<a href="03-segments.jpeg"><img src="03-segments.jpeg" alt="Segments" width="30%"/><a>
4+
<a href="04-roots.jpeg"><img src="04-roots.jpeg" alt="Roots" width="30%"/><a>
5+
<a href="05-modules.jpeg"><img src="05-modules.jpeg" alt="Modules" width="30%"/><a>
6+
<a href="06-strings.jpeg"><img src="06-strings.jpeg" alt="Strings" width="30%"/><a>
7+
<a href="07-string-duplicates.jpeg"><img src="07-string-duplicates.jpeg" alt="String duplicates" width="30%"/><a>
8+
<a href="08-single-objest.jpeg"><img src="08-single-objest.jpeg" alt="Single object" width="30%"/><a>
9+
<a href="09-object-list.jpeg"><img src="09-object-list.jpeg" alt="Object list" width="30%"/><a>

scripts/reinstall-dev-tool.ps1

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@ try
77
{
88
Set-Location $RepositoryRoot
99

10+
[xml]$XmlConfig = Get-Content 'Directory.Build.props'
11+
12+
$XmlElement = Select-Xml '/Project/PropertyGroup/VersionPrefix' $XmlConfig |
13+
Select-Object -ExpandProperty Node
14+
15+
$VersionPrefix = $XmlElement.InnerText
16+
1017
dotnet tool uninstall -g Heartbeat
1118
dotnet clean --configuration Release
1219
Get-Date -Format ''
1320
$VersionSuffix = "rc.$(Get-Date -Format 'yyyy-MM-dd-HHmm')"
1421
dotnet pack --version-suffix $VersionSuffix
15-
# TODO get VersionPrefix from Directory.Build.props
16-
$PackageVersion = "0.2.0-$VersionSuffix"
22+
$PackageVersion = "$VersionPrefix-$VersionSuffix"
1723
dotnet tool install --global --add-source ./src/Heartbeat/nupkg Heartbeat --version $PackageVersion
1824
}
1925
catch {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using Microsoft.Diagnostics.Runtime;
2+
3+
namespace Heartbeat.Runtime.Extensions
4+
{
5+
public static class ClrValueTypeExtensions
6+
{
7+
public static bool IsDefaultValue(this ClrValueType valueType)
8+
{
9+
if (valueType.Type == null)
10+
{
11+
return true;
12+
}
13+
14+
foreach (var field in valueType.Type.Fields)
15+
{
16+
if (field.IsObjectReference)
17+
{
18+
if (!field.ReadObject(valueType.Address, true).IsNull)
19+
{
20+
return false;
21+
}
22+
}
23+
else if (field.IsPrimitive)
24+
{
25+
if (!IsValueDefault(valueType.Address, field))
26+
{
27+
return false;
28+
}
29+
}
30+
else if (field.ElementType == ClrElementType.Struct)
31+
{
32+
var fieldValue = field.ReadStruct(valueType.Address, true);
33+
if (!fieldValue.IsDefaultValue())
34+
{
35+
return false;
36+
}
37+
}
38+
else if (field.ElementType == ClrElementType.Pointer)
39+
{
40+
if (!IsZeroPtr(valueType.Address, field))
41+
{
42+
return false;
43+
}
44+
}
45+
else
46+
{
47+
throw new InvalidOperationException(
48+
"Unexpected field, it non of IsObjectReference | IsValueType | IsPrimitive");
49+
}
50+
}
51+
52+
return true;
53+
}
54+
55+
private static bool IsValueDefault(ulong objRef, ClrInstanceField field)
56+
{
57+
return field.ElementType switch
58+
{
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,
69+
ClrElementType.Float => field.Read<float>(objRef, true) == 0f,
70+
ClrElementType.Double => field.Read<double>(objRef, true) == 0d,
71+
ClrElementType.NativeInt => field.Read<nint>(objRef, true) == nint.Zero,
72+
ClrElementType.NativeUInt => field.Read<nuint>(objRef, true) == nuint.Zero,
73+
_ => throw new ArgumentOutOfRangeException()
74+
};
75+
}
76+
77+
private static bool IsZeroPtr(ulong objRef, ClrInstanceField field)
78+
{
79+
return field.Type.Name switch
80+
{
81+
"System.UIntPtr" => field.Read<UIntPtr>(objRef, true) == UIntPtr.Zero,
82+
"System.IntPtr" => field.Read<IntPtr>(objRef, true) == IntPtr.Zero,
83+
_ => throw new ArgumentException($"Unknown Pointer type: {field.Type.Name}")
84+
};
85+
}
86+
}
87+
}

src/Heartbeat.Runtime/Proxies/ArrayProxy.cs

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1+
using Heartbeat.Runtime.Extensions;
2+
13
using Microsoft.Diagnostics.Runtime;
24

35
namespace Heartbeat.Runtime.Proxies;
46

57
public sealed class ArrayProxy : ProxyBase
68
{
79
private ClrArray _clrArray;
10+
private readonly Lazy<int> _unusedItemsCount;
11+
812
public int Length => _clrArray.Length;
913

14+
public int UnusedItemsCount => _unusedItemsCount.Value;
15+
public double UnusedItemsPercent => (double)UnusedItemsCount / Length;
16+
public Size Wasted => new Size((ulong)(_clrArray.Type.ComponentSize * UnusedItemsCount));
17+
1018
public ArrayProxy(RuntimeContext context, ClrObject targetObject)
1119
: base(context, targetObject)
1220
{
1321
_clrArray = TargetObject.AsArray();
22+
_unusedItemsCount = new Lazy<int>(GetUnusedItemsCount);
1423
}
1524

1625
public ArrayProxy(RuntimeContext context, ulong address)
1726
: base(context, address)
1827
{
1928
_clrArray = TargetObject.AsArray();
29+
_unusedItemsCount = new Lazy<int>(GetUnusedItemsCount);
2030
}
2131

2232
public string?[] GetStringArray()
@@ -105,7 +115,7 @@ public static IEnumerable<ClrObject> EnumerateObjectItems(ClrArray array)
105115
}
106116
}
107117
}
108-
118+
109119
public static IEnumerable<ClrValueType> EnumerateValueTypes(ClrArray array)
110120
{
111121
var length = array.Length;
@@ -143,4 +153,16 @@ public static IEnumerable<ClrValueType> EnumerateValueTypes(ClrArray array)
143153
}
144154
}
145155
}
156+
157+
private int GetUnusedItemsCount()
158+
{
159+
if (_clrArray.Type.ComponentType?.IsValueType ?? false)
160+
{
161+
return EnumerateValueTypes(_clrArray)
162+
.Count(t => t.IsDefaultValue());
163+
}
164+
165+
return EnumerateObjectItems(_clrArray)
166+
.Count(t => t.IsNull);
167+
}
146168
}

src/Heartbeat/ClientApp/api.yml

+65
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,39 @@ paths:
237237
application/json:
238238
schema:
239239
$ref: '#/components/schemas/GetObjectInstancesResult'
240+
/api/dump/arrays:
241+
get:
242+
tags:
243+
- Dump
244+
summary: Get arrays
245+
description: Get arrays
246+
operationId: GetArrays
247+
parameters:
248+
- name: traversingMode
249+
in: query
250+
style: form
251+
schema:
252+
$ref: '#/components/schemas/TraversingHeapModes'
253+
- name: generation
254+
in: query
255+
style: form
256+
schema:
257+
$ref: '#/components/schemas/Generation'
258+
responses:
259+
'500':
260+
description: Server Error
261+
content:
262+
application/json:
263+
schema:
264+
$ref: '#/components/schemas/ProblemDetails'
265+
'200':
266+
description: Success
267+
content:
268+
application/json:
269+
schema:
270+
type: array
271+
items:
272+
$ref: '#/components/schemas/ArrayInfo'
240273
'/api/dump/object/{address}':
241274
get:
242275
tags:
@@ -321,6 +354,38 @@ components:
321354
- Armv6
322355
- Ppc64le
323356
type: string
357+
ArrayInfo:
358+
required:
359+
- address
360+
- length
361+
- methodTable
362+
- unusedItemsCount
363+
- unusedPercent
364+
- wasted
365+
type: object
366+
properties:
367+
address:
368+
type: integer
369+
format: int64
370+
methodTable:
371+
type: integer
372+
format: int64
373+
typeName:
374+
type: string
375+
nullable: true
376+
length:
377+
type: integer
378+
format: int32
379+
unusedItemsCount:
380+
type: integer
381+
format: int32
382+
unusedPercent:
383+
type: number
384+
format: double
385+
wasted:
386+
type: integer
387+
format: int64
388+
additionalProperties: false
324389
ClrObjectField:
325390
required:
326391
- isValueType

src/Heartbeat/ClientApp/src/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import objectInstances from './objectInstances'
77
import clrObject from './clrObject'
88
import roots from './roots'
99
import modules from './modules'
10+
import arraysGrid from './arraysGrid'
1011
import stringsGrid from './stringsGrid'
1112
import stringDuplicates from './stringDuplicates'
1213
import {AlertContext} from './contexts/alertContext';
@@ -58,6 +59,7 @@ const App = () => {
5859
<Resource name='roots' {...roots} />
5960
<Resource name='modules' {...modules} />
6061
<Resource name='clr-object' {...clrObject} />
62+
<Resource name='arrays' {...arraysGrid} />
6163
<Resource name='strings' {...stringsGrid} />
6264
<Resource name='string-duplicates' {...stringDuplicates} />
6365
</Admin>

0 commit comments

Comments
 (0)