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

Metadata statistics #190

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
34 changes: 34 additions & 0 deletions src/Microsoft.Metadata.Visualizer/BlobKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the License.txt file in the project root for more information.

namespace Microsoft.Metadata.Tools;

public enum BlobKind
{
None,
Key,
FileHash,

MethodSignature,
FieldSignature,
MemberRefSignature,
StandAloneSignature,

TypeSpec,
MethodSpec,

ConstantValue,
Marshalling,
PermissionSet,
CustomAttribute,

DocumentName,
DocumentHash,
SequencePoints,
Imports,
ImportAlias,
ImportNamespace,
LocalConstantSignature,
CustomDebugInformation
}
14 changes: 14 additions & 0 deletions src/Microsoft.Metadata.Visualizer/MetadataReaderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the.NET Foundation under one or more agreements.
// The.NET Foundation licenses this file to you under the MIT license.
// See the License.txt file in the project root for more information.

using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace Microsoft.Metadata.Tools;

internal static class MetadataReaderExtensions
{
public static int GetTableSize(this MetadataReader reader, TableIndex table)
=> reader.GetTableRowCount(table) * reader.GetTableRowSize(table);
}
98 changes: 98 additions & 0 deletions src/Microsoft.Metadata.Visualizer/MetadataStatistics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the.NET Foundation under one or more agreements.
// The.NET Foundation licenses this file to you under the MIT license.
// See the License.txt file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace Microsoft.Metadata.Tools;

public sealed class MetadataStatistics
{
private readonly MetadataReader _reader;
private readonly ImmutableDictionary<BlobHandle, BlobKind> _blobKinds;
private readonly TextWriter _writer;

public MetadataStatistics(TextWriter writer, MetadataReader reader, ImmutableDictionary<BlobHandle, BlobKind> blobKinds)
{
_writer = writer;
_reader = reader;
_blobKinds = blobKinds;
}

public void Visualize()
{
WriteTableAndHeapSizes();
WriteBlobSizes();
}

internal void WriteTableAndHeapSizes()
{
var table = new TableBuilder("Table and Heap sizes",
"Table/Heap",
"Size [B]",
"% of metadata");

double totalSize = _reader.MetadataLength;

foreach (TableIndex index in Enum.GetValues(typeof(TableIndex)))
{
var size = _reader.GetTableSize(index);
if (size != 0)
{
table.AddRow(index.ToString(), $"{size,10}", $"{100 * size / totalSize,5:F2}%");
}
}

foreach (HeapIndex index in Enum.GetValues(typeof(HeapIndex)))
{
var size = _reader.GetHeapSize(index);
if (size != 0)
{
table.AddRow($"#{index}", $"{size,10}", $"{100 * size / totalSize,5:F2}%");
}
}

table.WriteTo(_writer);
}

internal void WriteBlobSizes()
{
var table = new TableBuilder("Blob sizes",
"Kind",
"Size [B]",
"% of #Blob",
"% of metadata");

var allKinds = (BlobKind[])Enum.GetValues(typeof(BlobKind));
var sizePerKind = new int[allKinds.Length];
foreach (var entry in _blobKinds)
{
var handle = entry.Key;
var kind = entry.Value;

sizePerKind[(int)kind] += _reader.GetBlobReader(handle).Length;
}

var sum = 0;
double totalMetadataSize = _reader.MetadataLength;
double totalBlobSize = _reader.GetHeapSize(HeapIndex.Blob);
for (int i = 0; i < sizePerKind.Length; i++)
{
var size = sizePerKind[i];
if (size > 0)
{
table.AddRow($"{(BlobKind)i}", $"{size,10}", $"{100 * size / totalBlobSize,5:F2}%", $"{100 * size / totalMetadataSize,5:F2}%");
sum += size;
}
}

var miscSize = totalBlobSize - sum;
table.AddRow("<misc>", $"{miscSize,10}", $"{100 * miscSize / totalBlobSize,5:F2}%", $"{100 * miscSize / totalMetadataSize,5:F2}%");

table.WriteTo(_writer);
}
}
174 changes: 6 additions & 168 deletions src/Microsoft.Metadata.Visualizer/MetadataVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,151 +34,8 @@ public sealed partial class MetadataVisualizer
{
private const string BadMetadataStr = "<bad metadata>";

private enum BlobKind
{
None,
Key,
FileHash,

MethodSignature,
FieldSignature,
MemberRefSignature,
StandAloneSignature,

TypeSpec,
MethodSpec,

ConstantValue,
Marshalling,
PermissionSet,
CustomAttribute,

DocumentName,
DocumentHash,
SequencePoints,
Imports,
ImportAlias,
ImportNamespace,
LocalConstantSignature,
CustomDebugInformation,

Count
}

private delegate TResult FuncRef<TArg, TResult>(ref TArg arg);

private sealed class TableBuilder
{
private readonly string _title;
private readonly string[] _header;
private readonly List<(string[] fields, string details)> _rows;

public char HorizontalSeparatorChar = '=';
public string Indent = "";
public int FirstRowNumber = 1;

public TableBuilder(string title, params string[] header)
{
_rows = new List<(string[] fields, string details)>();
_title = title;
_header = header;
}

public int RowCount
=> _rows.Count;

public void AddRow(params string[] fields)
=> AddRowWithDetails(fields, details: null);

public void AddRowWithDetails(string[] fields, string details)
{
Debug.Assert(_header.Length == fields.Length);
_rows.Add((fields, details));
}

public void WriteTo(TextWriter writer)
{
if (_rows.Count == 0)
{
return;
}

if (_title != null)
{
writer.Write(Indent);
writer.WriteLine(_title);
}

string columnSeparator = " ";
var columnWidths = new int[_rows.First().fields.Length];

void updateColumnWidths( string[] fields)
{
for (int i = 0; i < fields.Length; i++)
{
columnWidths[i] = Math.Max(columnWidths[i], fields[i].Length + columnSeparator.Length);
}
}

updateColumnWidths(_header);

foreach (var (fields, _) in _rows)
{
updateColumnWidths(fields);
}

void writeRow(string[] fields)
{
for (int i = 0; i < fields.Length; i++)
{
var field = fields[i];

writer.Write(field);
writer.Write(new string(' ', columnWidths[i] - field.Length));
}
}

// header:
int rowNumberWidth = (FirstRowNumber + _rows.Count - 1).ToString("x").Length;
int tableWidth = Math.Max(_title?.Length ?? 0, columnWidths.Sum() + columnWidths.Length);
string horizontalSeparator = new string(HorizontalSeparatorChar, tableWidth);

writer.Write(Indent);
writer.WriteLine(horizontalSeparator);

writer.Write(Indent);
writer.Write(new string(' ', rowNumberWidth + 2));

writeRow(_header);

writer.WriteLine();
writer.Write(Indent);
writer.WriteLine(horizontalSeparator);

// rows:
int rowNumber = FirstRowNumber;
foreach (var (fields, details) in _rows)
{
string rowNumberStr = rowNumber.ToString("x");
writer.Write(Indent);
writer.Write(new string(' ', rowNumberWidth - rowNumberStr.Length));
writer.Write(rowNumberStr);
writer.Write(": ");

writeRow(fields);
writer.WriteLine();

if (details != null)
{
writer.Write(Indent);
writer.Write(details);
}

rowNumber++;
}
}
}

private readonly TextWriter _writer;
private readonly IReadOnlyList<MetadataReader> _readers;
private readonly MetadataAggregator _aggregator;
Expand Down Expand Up @@ -225,6 +82,9 @@ public MetadataVisualizer(IReadOnlyList<MetadataReader> readers, TextWriter writ
{
}

public ImmutableDictionary<BlobHandle, BlobKind> GetBlobKinds()
=> _blobKinds.ToImmutableDictionary();

private ImmutableDictionary<EntityHandle, EntityHandle> CalculateEncAddedMemberToParentMap()
{
var builder = ImmutableDictionary.CreateBuilder<EntityHandle, EntityHandle>();
Expand Down Expand Up @@ -325,17 +185,11 @@ public void Visualize(int generation = -1)

private bool IsDelta => _reader.GetTableRowCount(TableIndex.EncLog) > 0;

private string MakeTableName(TableIndex index)
=> $"{index} (index: 0x{(byte)index:X2}, size: {_reader.GetTableRowCount(index) * _reader.GetTableRowSize(index)}): ";
private string MakeTableName(TableIndex table)
=> $"{table} (index: 0x{(byte)table:X2}, size: {_reader.GetTableSize(table)}): ";

private void WriteTable(TableBuilder table)
{
if (table.RowCount > 0)
{
table.WriteTo(_writer);
_writer.WriteLine();
}
}
=> table.WriteTo(_writer);

private EntityHandle GetAggregateHandle(EntityHandle generationHandle, int generation)
{
Expand Down Expand Up @@ -1934,8 +1788,6 @@ private void WriteBlobs()
var heapOffset = _reader.GetHeapMetadataOffset(HeapIndex.Blob);
bool hasBadMetadata = false;

int[] sizePerKind = new int[(int)BlobKind.Count];

_writer.WriteLine($"#Blob (size = {heapSize}):");
var handle = MetadataTokens.BlobHandle(0);
do
Expand Down Expand Up @@ -1976,9 +1828,6 @@ private void WriteBlobs()
if (_blobKinds.TryGetValue(handle, out var kind))
{
kindString = " (" + kind + ")";

// ignoring the compressed blob size:
sizePerKind[(int)kind] += value.Length;
}

if (value.Length > 0)
Expand All @@ -2002,17 +1851,6 @@ private void WriteBlobs()
}
while (!handle.IsNil);

_writer.WriteLine();
_writer.WriteLine("Sizes:");

for (int i = 0; i < sizePerKind.Length; i++)
{
if (sizePerKind[i] > 0)
{
_writer.WriteLine($" {(BlobKind)i}: {(decimal)sizePerKind[i]} bytes");
}
}

// don't calculate statistics for EnC delta, it's not interesting
if (_aggregator == null)
{
Expand Down
Loading