├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── diffwindow.png ├── mainwindow.png └── rootswindow.png ├── dotnet-tool ├── Program.cs └── sizoscope.csproj └── sizoscope ├── DiffForm.Designer.cs ├── DiffForm.cs ├── DiffForm.resx ├── ILLink.Descriptors.xml ├── ILLink.SizeHacks.xml ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── MstatData.Cache.cs ├── MstatData.Diff.cs ├── MstatData.Enumeration.cs ├── MstatData.Graph.cs ├── MstatData.ObjectModel.cs ├── MstatData.cs ├── Program.cs ├── Properties ├── Resources.Designer.cs └── Resources.resx ├── RootForm.Designer.cs ├── RootForm.cs ├── RootForm.resx ├── TreeLogic.cs ├── diff.ico ├── icon.ico ├── sizoscope.csproj └── sizoscope.sln /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: 'Release version to create' 11 | required: true 12 | 13 | jobs: 14 | build_and_test: 15 | runs-on: windows-latest 16 | name: Build 17 | steps: 18 | - name: Checkout repo 19 | uses: actions/checkout@v3 20 | - name: Build (CI) 21 | if: ${{ github.event.inputs.version == '' }} 22 | run: | 23 | cd sizoscope 24 | dotnet publish 25 | - name: Build (CD) 26 | if: ${{ github.event.inputs.version != '' }} 27 | run: | 28 | cd sizoscope 29 | dotnet publish -p:Version=${{ github.event.inputs.version }} 30 | - name: Pack (CI) 31 | if: ${{ github.event.inputs.version == '' }} 32 | run: | 33 | cd dotnet-tool 34 | dotnet pack sizoscope.csproj -o .\nupkg 35 | - name: Pack (CD) 36 | if: ${{ github.event.inputs.version != '' }} 37 | run: | 38 | cd dotnet-tool 39 | dotnet pack sizoscope.csproj -o .\nupkg -p:Version=${{ github.event.inputs.version }} 40 | - name: Archive NuGet (CI) 41 | if: ${{ github.event.inputs.version == '' }} 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: sizoscope.42.42.42.42.nupkg 45 | path: dotnet-tool/nupkg/sizoscope.42.42.42.42.nupkg 46 | - name: Archive NuGet (CD) 47 | if: ${{ github.event.inputs.version != '' }} 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: sizoscope.${{ github.event.inputs.version }}.nupkg 51 | path: dotnet-tool/nupkg/sizoscope.${{ github.event.inputs.version }}.nupkg 52 | - name: Create tag 53 | if: ${{ github.event.inputs.version != '' && github.actor == 'MichalStrehovsky' }} 54 | run: | 55 | git tag v${{ github.event.inputs.version }} 56 | git push origin v${{ github.event.inputs.version }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | [Bb]in/ 4 | [Oo]bj/ 5 | launchSettings.json 6 | .vs/ 7 | msbuild.binlog 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sizoscope 2 | 3 | Sizoscope is a binary size investigation tool to help you optimize and reduce the size of your .NET Native AOT binaries. 4 | 5 | It supports visualizing the size contributions of individual methods and types, but also namespaces and assemblies. It also allows diffing before/after snapshots and offers basic root cause analysis. 6 | 7 | ## Installing Sizoscope 8 | 9 | ```shell 10 | $ dotnet tool install sizoscope --global 11 | ``` 12 | 13 | Alternatively, download a release from the [Releases](https://github.com/MichalStrehovsky/sizoscope/releases) tab. The releases on the Releases tab are built with native AOT and are fully standalone/portable ZIPs. They work great under Linux with Wine. 14 | 15 | There's an Avalonia-based fork of Sizoscope maintained by @hez2010 [here](https://github.com/hez2010/sizoscopeX). 16 | 17 | ## Using Sizoscope 18 | 19 | The tool only supports the [Native AOT deployment model](https://learn.microsoft.com/dotnet/core/deploying/native-aot/) in .NET 7 or later. After enabling Native AOT deployment on your project as documented in the linked doc (basically, add `true` to a `PropertyGroup` in your project), add following lines to the project to enable generation of additional compile-time diagnostic files: 20 | 21 | ```xml 22 | 23 | true 24 | true 25 | 26 | ``` 27 | 28 | Once you have that, `dotnet publish` your project as usual. After publishing, you should see a *.mstat and *.dgml.xml file under `obj\Release\net[7|8].0\[linux|win]-[x64|arm64]\native` - these are the files Sizoscope operates on. 29 | 30 | Launch the tool and open the MSTAT file. The associated *.dgml.xml file will be loaded automatically if it's next to the *.mstat. If the *.dgml.xml doesn't exist, root cause analysis will not work. 31 | 32 | Once the file loads, you'll be greeted with the main screen that shows assembly contributions: 33 | 34 | ![Main window screenshot](docs/mainwindow.png) 35 | 36 | You can click around things that look interesting. You can also open the search subwindow where you can sort individual entries by exclusive/inclusive size. 37 | 38 | If you have a different MSTAT file you'd like to compare with, click the Diff button to enter a diff view: 39 | 40 | ![Diff window screenshot](docs/diffwindow.png) 41 | 42 | The diff view will show you things that are unique to the baseline on the left and things that are unique to the compared MSTAT on the right. 43 | 44 | ## Root analysis 45 | 46 | ℹ️ NOTE: Root analysis is only supported starting in .NET 8. The MSTAT files generated with older SDKs don't have the necessary information. 47 | 48 | The main window and the diff window will show you what is in the binary, but not _why_ it's in the binary. Double-clicking a node in the main window or diff window will open a root analysis window. This will try to explain _why_ something was included. 49 | 50 | ![Roots window screenshot](docs/rootswindow.png) 51 | 52 | In the above screenshot, the reason why the top node was included in the executable was all the things nested under it. Some nodes have two reasons. This is typical for e.g. virtual method implementations - the reason why a virtual method implementation is generated is that _both_ the virtual method gets called somewhere _and_ a type implementing the method was allocated. The screenshot has one such situation for `SyncTextWriter.WriteLine`. 53 | 54 | ## Tips and tricks 55 | 56 | * Passing a MSTAT file name on the command line will launch Sizoscope with the file open. You can associated *.mstat files with Sizoscope in shell. 57 | * You can drag and drop .mstat files into the UI to open them. 58 | * Holding Alt when dragging and dropping will open a diff against the open file. 59 | * MSTAT files are loaded to memory and the associated file system files are closed. This is done on purpose so that you can quickly do before/after comparisons: simply open the MSTAT, make your change to the project and re-publish, and do a diff _against the same file_. 60 | -------------------------------------------------------------------------------- /docs/diffwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/sizoscope/44f6ed1318fa0ae004894e6311f856d04c747fa0/docs/diffwindow.png -------------------------------------------------------------------------------- /docs/mainwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/sizoscope/44f6ed1318fa0ae004894e6311f856d04c747fa0/docs/mainwindow.png -------------------------------------------------------------------------------- /docs/rootswindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/sizoscope/44f6ed1318fa0ae004894e6311f856d04c747fa0/docs/rootswindow.png -------------------------------------------------------------------------------- /dotnet-tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | string exe = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "sizoscope.exe"); 8 | 9 | // Avoid accidental forkbomb 10 | if (new FileInfo(exe).Length < 1024 * 1024) 11 | return; 12 | 13 | if (!OperatingSystem.IsWindows()) 14 | { 15 | string[] newArgs = new string[args.Length + 1]; 16 | newArgs[0] = exe; 17 | Array.Copy(args, 0, newArgs, 1, args.Length); 18 | exe = "wine"; 19 | args = newArgs; 20 | } 21 | 22 | try 23 | { 24 | Process.Start(exe, args); 25 | } 26 | catch (Win32Exception) when (!OperatingSystem.IsWindows()) 27 | { 28 | Console.WriteLine("Failed to launch sizoscope under wine. Make sure you have wine installed."); 29 | Console.WriteLine("On Ubuntu, `sudo apt install wine`, or follow https://www.winehq.org/."); 30 | } 31 | -------------------------------------------------------------------------------- /dotnet-tool/sizoscope.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0 6 | Major 7 | 42.42.42.42 8 | 9 | true 10 | Michal Strehovsky 11 | Michal Strehovsky 12 | .NET tool to analyze size of Native AOT binaries. 13 | https://github.com/MichalStrehovsky/sizoscope 14 | AGPL-3.0-only 15 | README.md 16 | 17 | 18 | 19 | 20 | true 21 | / 22 | 23 | 24 | true 25 | /docs/ 26 | 27 | 28 | true 29 | /docs/ 30 | 31 | 32 | true 33 | /docs/ 34 | 35 | 36 | true 37 | /tools/$(TargetFramework)/any/ 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sizoscope/DiffForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace sizoscope 2 | { 3 | partial class DiffForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DiffForm)); 32 | tableLayoutPanel1 = new TableLayoutPanel(); 33 | _leftTree = new TreeView(); 34 | _rightTree = new TreeView(); 35 | label1 = new Label(); 36 | label2 = new Label(); 37 | toolStripContainer1 = new ToolStripContainer(); 38 | statusStrip1 = new StatusStrip(); 39 | _toolStripStatusLabel = new ToolStripStatusLabel(); 40 | tableLayoutPanel1.SuspendLayout(); 41 | toolStripContainer1.BottomToolStripPanel.SuspendLayout(); 42 | toolStripContainer1.ContentPanel.SuspendLayout(); 43 | toolStripContainer1.SuspendLayout(); 44 | statusStrip1.SuspendLayout(); 45 | SuspendLayout(); 46 | // 47 | // tableLayoutPanel1 48 | // 49 | tableLayoutPanel1.ColumnCount = 2; 50 | tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); 51 | tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); 52 | tableLayoutPanel1.Controls.Add(_leftTree, 0, 1); 53 | tableLayoutPanel1.Controls.Add(_rightTree, 1, 1); 54 | tableLayoutPanel1.Controls.Add(label1, 0, 0); 55 | tableLayoutPanel1.Controls.Add(label2, 1, 0); 56 | tableLayoutPanel1.Dock = DockStyle.Fill; 57 | tableLayoutPanel1.Location = new Point(0, 0); 58 | tableLayoutPanel1.Name = "tableLayoutPanel1"; 59 | tableLayoutPanel1.RowCount = 2; 60 | tableLayoutPanel1.RowStyles.Add(new RowStyle()); 61 | tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); 62 | tableLayoutPanel1.Size = new Size(755, 394); 63 | tableLayoutPanel1.TabIndex = 0; 64 | // 65 | // _leftTree 66 | // 67 | _leftTree.Dock = DockStyle.Fill; 68 | _leftTree.Location = new Point(3, 18); 69 | _leftTree.Name = "_leftTree"; 70 | _leftTree.Size = new Size(371, 373); 71 | _leftTree.TabIndex = 0; 72 | _leftTree.BeforeExpand += _leftTree_BeforeExpand; 73 | _leftTree.NodeMouseDoubleClick += _leftTree_NodeMouseDoubleClick; 74 | // 75 | // _rightTree 76 | // 77 | _rightTree.Dock = DockStyle.Fill; 78 | _rightTree.Location = new Point(380, 18); 79 | _rightTree.Name = "_rightTree"; 80 | _rightTree.Size = new Size(372, 373); 81 | _rightTree.TabIndex = 1; 82 | _rightTree.BeforeExpand += _rightTree_BeforeExpand; 83 | _rightTree.NodeMouseDoubleClick += _rightTree_NodeMouseDoubleClick; 84 | // 85 | // label1 86 | // 87 | label1.AutoSize = true; 88 | label1.Location = new Point(3, 0); 89 | label1.Name = "label1"; 90 | label1.Size = new Size(94, 15); 91 | label1.TabIndex = 2; 92 | label1.Text = "Only in baseline:"; 93 | // 94 | // label2 95 | // 96 | label2.AutoSize = true; 97 | label2.Location = new Point(380, 0); 98 | label2.Name = "label2"; 99 | label2.Size = new Size(98, 15); 100 | label2.TabIndex = 3; 101 | label2.Text = "Only in compare:"; 102 | // 103 | // toolStripContainer1 104 | // 105 | // 106 | // toolStripContainer1.BottomToolStripPanel 107 | // 108 | toolStripContainer1.BottomToolStripPanel.Controls.Add(statusStrip1); 109 | // 110 | // toolStripContainer1.ContentPanel 111 | // 112 | toolStripContainer1.ContentPanel.Controls.Add(tableLayoutPanel1); 113 | toolStripContainer1.ContentPanel.Size = new Size(755, 394); 114 | toolStripContainer1.Dock = DockStyle.Fill; 115 | toolStripContainer1.Location = new Point(0, 0); 116 | toolStripContainer1.Name = "toolStripContainer1"; 117 | toolStripContainer1.Size = new Size(755, 416); 118 | toolStripContainer1.TabIndex = 1; 119 | toolStripContainer1.Text = "toolStripContainer1"; 120 | // 121 | // statusStrip1 122 | // 123 | statusStrip1.Dock = DockStyle.None; 124 | statusStrip1.Items.AddRange(new ToolStripItem[] { _toolStripStatusLabel }); 125 | statusStrip1.Location = new Point(0, 0); 126 | statusStrip1.Name = "statusStrip1"; 127 | statusStrip1.Size = new Size(755, 22); 128 | statusStrip1.TabIndex = 0; 129 | // 130 | // _toolStripStatusLabel 131 | // 132 | _toolStripStatusLabel.Name = "_toolStripStatusLabel"; 133 | _toolStripStatusLabel.Size = new Size(118, 17); 134 | _toolStripStatusLabel.Text = "toolStripStatusLabel1"; 135 | // 136 | // DiffForm 137 | // 138 | AutoScaleDimensions = new SizeF(7F, 15F); 139 | AutoScaleMode = AutoScaleMode.Font; 140 | ClientSize = new Size(755, 416); 141 | Controls.Add(toolStripContainer1); 142 | Icon = (Icon)resources.GetObject("$this.Icon"); 143 | Name = "DiffForm"; 144 | Text = "Sizoscope"; 145 | tableLayoutPanel1.ResumeLayout(false); 146 | tableLayoutPanel1.PerformLayout(); 147 | toolStripContainer1.BottomToolStripPanel.ResumeLayout(false); 148 | toolStripContainer1.BottomToolStripPanel.PerformLayout(); 149 | toolStripContainer1.ContentPanel.ResumeLayout(false); 150 | toolStripContainer1.ResumeLayout(false); 151 | toolStripContainer1.PerformLayout(); 152 | statusStrip1.ResumeLayout(false); 153 | statusStrip1.PerformLayout(); 154 | ResumeLayout(false); 155 | } 156 | 157 | #endregion 158 | 159 | private TableLayoutPanel tableLayoutPanel1; 160 | private TreeView _leftTree; 161 | private TreeView _rightTree; 162 | private Label label1; 163 | private Label label2; 164 | private ToolStripContainer toolStripContainer1; 165 | private StatusStrip statusStrip1; 166 | private ToolStripStatusLabel _toolStripStatusLabel; 167 | } 168 | } -------------------------------------------------------------------------------- /sizoscope/DiffForm.cs: -------------------------------------------------------------------------------- 1 | using static MstatData; 2 | 3 | namespace sizoscope 4 | { 5 | public partial class DiffForm : Form 6 | { 7 | private MstatData _leftDiff, _rightDiff; 8 | private TreeLogic.Sorter _sorter; 9 | 10 | public DiffForm(MstatData leftDiff, MstatData rightDiff, int diffSize) 11 | { 12 | InitializeComponent(); 13 | 14 | _leftDiff = leftDiff; 15 | _rightDiff = rightDiff; 16 | _sorter = TreeLogic.Sorter.BySize(); 17 | 18 | ImageList imageList = new MainForm()._imageList; 19 | _leftTree.ImageList = imageList; 20 | _rightTree.ImageList = imageList; 21 | 22 | TreeLogic.RefreshTree(_leftTree, _leftDiff, _sorter); 23 | TreeLogic.RefreshTree(_rightTree, _rightDiff, _sorter); 24 | 25 | _toolStripStatusLabel.Text = $"Total accounted difference: {TreeLogic.AsFileSize(diffSize)}"; 26 | } 27 | 28 | private void _leftTree_BeforeExpand(object sender, TreeViewCancelEventArgs e) 29 | { 30 | TreeLogic.BeforeExpand(e.Node, _sorter); 31 | } 32 | 33 | private void _rightTree_BeforeExpand(object sender, TreeViewCancelEventArgs e) 34 | { 35 | TreeLogic.BeforeExpand(e.Node, _sorter); 36 | } 37 | 38 | private void _leftTree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) 39 | { 40 | NodeMouseDoubleClickCommon(e.Node, _leftDiff, _rightDiff); 41 | } 42 | 43 | private void NodeMouseDoubleClickCommon(TreeNode treeNode, MstatData data, MstatData compare) 44 | { 45 | if (!data.DgmlSupported) 46 | { 47 | MessageBox.Show("Dependency graph information is only available in .NET 8 or later."); 48 | return; 49 | } 50 | 51 | if (!data.DgmlAvailable) 52 | { 53 | MessageBox.Show("Dependency graph data was not found. Ensure IlcGenerateDgmlFile=true is specified."); 54 | return; 55 | } 56 | 57 | int? id = treeNode.Tag switch 58 | { 59 | MstatTypeDefinition typedef => typedef.NodeId, 60 | MstatTypeSpecification typespec => typespec.NodeId, 61 | MstatMemberDefinition memberdef => memberdef.NodeId, 62 | MstatMethodSpecification methodspec => methodspec.NodeId, 63 | int val => val, 64 | _ => null 65 | }; 66 | 67 | if (id.HasValue) 68 | { 69 | if (id.Value < 0) 70 | { 71 | MessageBox.Show("This node was not used directly and is included for display purposes only. Try analyzing sub nodes."); 72 | return; 73 | } 74 | 75 | var node = data.GetNodeForId(id.Value, out string name); 76 | if (node == null) 77 | { 78 | MessageBox.Show($"Could not find path to roots from {name}."); 79 | return; 80 | } 81 | 82 | new RootForm(node, compare).ShowDialog(this); 83 | } 84 | } 85 | 86 | private void _rightTree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) 87 | { 88 | NodeMouseDoubleClickCommon(e.Node, _rightDiff, _leftDiff); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /sizoscope/DiffForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 17, 17 62 | 63 | 64 | 55 65 | 66 | 67 | 68 | 69 | AAABAAEAEBAAAAAACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAB 70 | AAAAAAAAQkJCAFhYWACGhoYAmpmZAJycnACko6QA0M/PAObk5QDx7/AA9vb2AAAAAAAAAAAAAAAAAAAA 71 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 72 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 73 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 74 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 75 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 76 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 77 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 78 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 79 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 80 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 81 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 82 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 83 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 84 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 85 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 86 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 87 | AAD///8AAAAAAAAAAAAAAAAAAAAAAAAACgoKCgoKCgoKCgoKAAAAAAoBAQEBAQEBAQEBCgAAAAAKAQkJ 88 | CQkJCQkJAQoAAAAACgEJAQEBAQEJCQEKAAAAAAoBCQkJCQkJCQkBCgAAAAAKAQkJCQEJCQkJAQoAAAAA 89 | CgEJCQkBCQkJCQEKAAAAAAoBCQEBAQEBCQkBCgAAAAAKAQkJCQEJCQkJAQoAAAAACgEJCQkBCQkJAQEK 90 | AAAAAAoBCQkJCQkJAQEDCgAAAAAKAQkJCQkJAQEDCgAAAAAACgEBAQEBAQEFCgAAAAAAAAoKCgoKCgoK 91 | CgAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMAD 92 | AADAAwAAwAMAAMAHAADADwAAwB8AAP//AAA= 93 | 94 | 95 | -------------------------------------------------------------------------------- /sizoscope/ILLink.Descriptors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sizoscope/ILLink.SizeHacks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /sizoscope/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace sizoscope 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 33 | toolStripContainer1 = new ToolStripContainer(); 34 | statusStrip1 = new StatusStrip(); 35 | _toolStripStatusLabel = new ToolStripStatusLabel(); 36 | splitContainer1 = new SplitContainer(); 37 | tableLayoutPanel1 = new TableLayoutPanel(); 38 | _sortByComboBox = new ComboBox(); 39 | _tree = new TreeView(); 40 | _imageList = new ImageList(components); 41 | tableLayoutPanel2 = new TableLayoutPanel(); 42 | _searchTextBox = new TextBox(); 43 | _searchComboBox = new ComboBox(); 44 | _searchResultsListView = new ListView(); 45 | columnHeader1 = new ColumnHeader(); 46 | columnHeader2 = new ColumnHeader(); 47 | columnHeader3 = new ColumnHeader(); 48 | toolStrip1 = new ToolStrip(); 49 | _openButton = new ToolStripButton(); 50 | _reloadButton = new ToolStripButton(); 51 | _diffButton = new ToolStripButton(); 52 | _findButton = new ToolStripButton(); 53 | _aboutButton = new ToolStripButton(); 54 | _openFileDialog = new OpenFileDialog(); 55 | toolStripContainer1.BottomToolStripPanel.SuspendLayout(); 56 | toolStripContainer1.ContentPanel.SuspendLayout(); 57 | toolStripContainer1.TopToolStripPanel.SuspendLayout(); 58 | toolStripContainer1.SuspendLayout(); 59 | statusStrip1.SuspendLayout(); 60 | ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); 61 | splitContainer1.Panel1.SuspendLayout(); 62 | splitContainer1.Panel2.SuspendLayout(); 63 | splitContainer1.SuspendLayout(); 64 | tableLayoutPanel1.SuspendLayout(); 65 | tableLayoutPanel2.SuspendLayout(); 66 | toolStrip1.SuspendLayout(); 67 | SuspendLayout(); 68 | // 69 | // toolStripContainer1 70 | // 71 | // 72 | // toolStripContainer1.BottomToolStripPanel 73 | // 74 | toolStripContainer1.BottomToolStripPanel.Controls.Add(statusStrip1); 75 | // 76 | // toolStripContainer1.ContentPanel 77 | // 78 | toolStripContainer1.ContentPanel.Controls.Add(splitContainer1); 79 | toolStripContainer1.ContentPanel.Margin = new Padding(3, 2, 3, 2); 80 | toolStripContainer1.ContentPanel.Size = new Size(640, 312); 81 | toolStripContainer1.Dock = DockStyle.Fill; 82 | toolStripContainer1.Location = new Point(0, 0); 83 | toolStripContainer1.Margin = new Padding(3, 2, 3, 2); 84 | toolStripContainer1.Name = "toolStripContainer1"; 85 | toolStripContainer1.Size = new Size(640, 361); 86 | toolStripContainer1.TabIndex = 0; 87 | toolStripContainer1.Text = "toolStripContainer1"; 88 | // 89 | // toolStripContainer1.TopToolStripPanel 90 | // 91 | toolStripContainer1.TopToolStripPanel.Controls.Add(toolStrip1); 92 | // 93 | // statusStrip1 94 | // 95 | statusStrip1.Dock = DockStyle.None; 96 | statusStrip1.Items.AddRange(new ToolStripItem[] { _toolStripStatusLabel }); 97 | statusStrip1.Location = new Point(0, 0); 98 | statusStrip1.Name = "statusStrip1"; 99 | statusStrip1.Size = new Size(640, 22); 100 | statusStrip1.TabIndex = 2; 101 | statusStrip1.Text = "statusStrip1"; 102 | // 103 | // _toolStripStatusLabel 104 | // 105 | _toolStripStatusLabel.Name = "_toolStripStatusLabel"; 106 | _toolStripStatusLabel.Size = new Size(72, 17); 107 | _toolStripStatusLabel.Text = "No file open"; 108 | // 109 | // splitContainer1 110 | // 111 | splitContainer1.Dock = DockStyle.Fill; 112 | splitContainer1.Location = new Point(0, 0); 113 | splitContainer1.Margin = new Padding(3, 2, 3, 2); 114 | splitContainer1.Name = "splitContainer1"; 115 | // 116 | // splitContainer1.Panel1 117 | // 118 | splitContainer1.Panel1.Controls.Add(tableLayoutPanel1); 119 | // 120 | // splitContainer1.Panel2 121 | // 122 | splitContainer1.Panel2.Controls.Add(tableLayoutPanel2); 123 | splitContainer1.Panel2Collapsed = true; 124 | splitContainer1.Size = new Size(640, 312); 125 | splitContainer1.SplitterDistance = 278; 126 | splitContainer1.TabIndex = 0; 127 | // 128 | // tableLayoutPanel1 129 | // 130 | tableLayoutPanel1.ColumnCount = 1; 131 | tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); 132 | tableLayoutPanel1.Controls.Add(_sortByComboBox, 0, 0); 133 | tableLayoutPanel1.Controls.Add(_tree, 0, 1); 134 | tableLayoutPanel1.Dock = DockStyle.Fill; 135 | tableLayoutPanel1.Location = new Point(0, 0); 136 | tableLayoutPanel1.Name = "tableLayoutPanel1"; 137 | tableLayoutPanel1.RowCount = 2; 138 | tableLayoutPanel1.RowStyles.Add(new RowStyle()); 139 | tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); 140 | tableLayoutPanel1.Size = new Size(640, 312); 141 | tableLayoutPanel1.TabIndex = 2; 142 | // 143 | // _sortByComboBox 144 | // 145 | _sortByComboBox.Dock = DockStyle.Top; 146 | _sortByComboBox.DropDownStyle = ComboBoxStyle.DropDownList; 147 | _sortByComboBox.FormattingEnabled = true; 148 | _sortByComboBox.Location = new Point(3, 2); 149 | _sortByComboBox.Margin = new Padding(3, 2, 3, 2); 150 | _sortByComboBox.Name = "_sortByComboBox"; 151 | _sortByComboBox.Size = new Size(634, 23); 152 | _sortByComboBox.TabIndex = 1; 153 | _sortByComboBox.SelectedIndexChanged += _sortByComboBox_SelectedIndexChanged; 154 | // 155 | // _tree 156 | // 157 | _tree.AllowDrop = true; 158 | _tree.Dock = DockStyle.Fill; 159 | _tree.ImageIndex = 0; 160 | _tree.ImageList = _imageList; 161 | _tree.Location = new Point(3, 29); 162 | _tree.Margin = new Padding(3, 2, 3, 2); 163 | _tree.Name = "_tree"; 164 | _tree.SelectedImageIndex = 0; 165 | _tree.Size = new Size(634, 281); 166 | _tree.TabIndex = 0; 167 | _tree.BeforeExpand += TreeBeforeExpand; 168 | _tree.NodeMouseDoubleClick += _tree_NodeMouseDoubleClick; 169 | _tree.DragDrop += _tree_DragDrop; 170 | _tree.DragEnter += _tree_DragEnter; 171 | // 172 | // _imageList 173 | // 174 | _imageList.ColorDepth = ColorDepth.Depth32Bit; 175 | _imageList.ImageStream = (ImageListStreamer)resources.GetObject("_imageList.ImageStream"); 176 | _imageList.TransparentColor = Color.Transparent; 177 | _imageList.Images.SetKeyName(0, "Assembly.png"); 178 | _imageList.Images.SetKeyName(1, "NameSpace.png"); 179 | _imageList.Images.SetKeyName(2, "Class.png"); 180 | _imageList.Images.SetKeyName(3, "Method.png"); 181 | _imageList.Images.SetKeyName(4, "SubTypes.png"); 182 | _imageList.Images.SetKeyName(5, "Cube.png"); 183 | _imageList.Images.SetKeyName(6, "Resource.png"); 184 | _imageList.Images.SetKeyName(7, "Field.png"); 185 | // 186 | // tableLayoutPanel2 187 | // 188 | tableLayoutPanel2.ColumnCount = 2; 189 | tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); 190 | tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle()); 191 | tableLayoutPanel2.Controls.Add(_searchTextBox, 0, 0); 192 | tableLayoutPanel2.Controls.Add(_searchComboBox, 1, 0); 193 | tableLayoutPanel2.Controls.Add(_searchResultsListView, 0, 1); 194 | tableLayoutPanel2.Dock = DockStyle.Fill; 195 | tableLayoutPanel2.Location = new Point(0, 0); 196 | tableLayoutPanel2.Name = "tableLayoutPanel2"; 197 | tableLayoutPanel2.RowCount = 2; 198 | tableLayoutPanel2.RowStyles.Add(new RowStyle()); 199 | tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); 200 | tableLayoutPanel2.Size = new Size(96, 100); 201 | tableLayoutPanel2.TabIndex = 1; 202 | // 203 | // _searchTextBox 204 | // 205 | _searchTextBox.Dock = DockStyle.Top; 206 | _searchTextBox.Location = new Point(3, 3); 207 | _searchTextBox.Name = "_searchTextBox"; 208 | _searchTextBox.Size = new Size(1, 23); 209 | _searchTextBox.TabIndex = 1; 210 | _searchTextBox.TextChanged += _searchTextBox_TextChanged; 211 | // 212 | // _searchComboBox 213 | // 214 | _searchComboBox.DropDownStyle = ComboBoxStyle.DropDownList; 215 | _searchComboBox.Items.AddRange(new object[] { "Types and members", "Types", "Members" }); 216 | _searchComboBox.Location = new Point(-48, 3); 217 | _searchComboBox.Name = "_searchComboBox"; 218 | _searchComboBox.Size = new Size(141, 23); 219 | _searchComboBox.TabIndex = 2; 220 | _searchComboBox.SelectedIndexChanged += _searchComboBox_SelectedIndexChanged; 221 | // 222 | // _searchResultsListView 223 | // 224 | _searchResultsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; 225 | _searchResultsListView.Columns.AddRange(new ColumnHeader[] { columnHeader1, columnHeader2, columnHeader3 }); 226 | tableLayoutPanel2.SetColumnSpan(_searchResultsListView, 2); 227 | _searchResultsListView.FullRowSelect = true; 228 | _searchResultsListView.Location = new Point(3, 31); 229 | _searchResultsListView.Margin = new Padding(3, 2, 3, 2); 230 | _searchResultsListView.Name = "_searchResultsListView"; 231 | _searchResultsListView.Size = new Size(90, 67); 232 | _searchResultsListView.TabIndex = 0; 233 | _searchResultsListView.UseCompatibleStateImageBehavior = false; 234 | _searchResultsListView.View = View.Details; 235 | _searchResultsListView.ColumnClick += _searchResultsListView_ColumnClick; 236 | // 237 | // columnHeader1 238 | // 239 | columnHeader1.Text = "Name"; 240 | columnHeader1.Width = 180; 241 | // 242 | // columnHeader2 243 | // 244 | columnHeader2.Text = "Exclusive bytes"; 245 | columnHeader2.Width = 100; 246 | // 247 | // columnHeader3 248 | // 249 | columnHeader3.Text = "Inclusive bytes"; 250 | columnHeader3.Width = 100; 251 | // 252 | // toolStrip1 253 | // 254 | toolStrip1.Dock = DockStyle.None; 255 | toolStrip1.GripStyle = ToolStripGripStyle.Hidden; 256 | toolStrip1.ImageScalingSize = new Size(20, 20); 257 | toolStrip1.Items.AddRange(new ToolStripItem[] { _openButton, _reloadButton, _diffButton, _findButton, _aboutButton }); 258 | toolStrip1.Location = new Point(3, 0); 259 | toolStrip1.Name = "toolStrip1"; 260 | toolStrip1.Size = new Size(123, 27); 261 | toolStrip1.TabIndex = 0; 262 | // 263 | // _openButton 264 | // 265 | _openButton.DisplayStyle = ToolStripItemDisplayStyle.Image; 266 | _openButton.Image = (Image)resources.GetObject("_openButton.Image"); 267 | _openButton.ImageTransparentColor = Color.Magenta; 268 | _openButton.Name = "_openButton"; 269 | _openButton.Size = new Size(24, 24); 270 | _openButton.Text = "Open... (Ctrl-O)"; 271 | _openButton.Click += OpenButtonClick; 272 | // 273 | // _reloadButton 274 | // 275 | _reloadButton.DisplayStyle = ToolStripItemDisplayStyle.Image; 276 | _reloadButton.Enabled = false; 277 | _reloadButton.Image = (Image)resources.GetObject("_reloadButton.Image"); 278 | _reloadButton.ImageTransparentColor = Color.Magenta; 279 | _reloadButton.Name = "_reloadButton"; 280 | _reloadButton.Size = new Size(24, 24); 281 | _reloadButton.Text = "Reload (Ctrl-R)"; 282 | _reloadButton.Click += ReloadButtonClick; 283 | // 284 | // _diffButton 285 | // 286 | _diffButton.DisplayStyle = ToolStripItemDisplayStyle.Image; 287 | _diffButton.Enabled = false; 288 | _diffButton.Image = (Image)resources.GetObject("_diffButton.Image"); 289 | _diffButton.ImageTransparentColor = Color.Magenta; 290 | _diffButton.Name = "_diffButton"; 291 | _diffButton.Size = new Size(24, 24); 292 | _diffButton.Text = "Diff... (Ctrl-D)"; 293 | _diffButton.Click += DiffButtonClick; 294 | // 295 | // _findButton 296 | // 297 | _findButton.CheckOnClick = true; 298 | _findButton.DisplayStyle = ToolStripItemDisplayStyle.Image; 299 | _findButton.Image = (Image)resources.GetObject("_findButton.Image"); 300 | _findButton.ImageTransparentColor = Color.Magenta; 301 | _findButton.Name = "_findButton"; 302 | _findButton.Size = new Size(24, 24); 303 | _findButton.Text = "Find (Ctrl-F)"; 304 | _findButton.CheckedChanged += FindButtonClick; 305 | // 306 | // _aboutButton 307 | // 308 | _aboutButton.DisplayStyle = ToolStripItemDisplayStyle.Image; 309 | _aboutButton.Image = (Image)resources.GetObject("_aboutButton.Image"); 310 | _aboutButton.ImageTransparentColor = Color.Magenta; 311 | _aboutButton.Name = "_aboutButton"; 312 | _aboutButton.Size = new Size(24, 24); 313 | _aboutButton.Text = "About..."; 314 | _aboutButton.Click += _aboutButton_Click; 315 | // 316 | // _openFileDialog 317 | // 318 | _openFileDialog.FileName = "openFileDialog1"; 319 | _openFileDialog.Filter = "Managed statistics|*.mstat"; 320 | // 321 | // MainForm 322 | // 323 | AutoScaleDimensions = new SizeF(7F, 15F); 324 | AutoScaleMode = AutoScaleMode.Font; 325 | ClientSize = new Size(640, 361); 326 | Controls.Add(toolStripContainer1); 327 | Icon = (Icon)resources.GetObject("$this.Icon"); 328 | Margin = new Padding(3, 2, 3, 2); 329 | Name = "MainForm"; 330 | Text = "Sizoscope"; 331 | toolStripContainer1.BottomToolStripPanel.ResumeLayout(false); 332 | toolStripContainer1.BottomToolStripPanel.PerformLayout(); 333 | toolStripContainer1.ContentPanel.ResumeLayout(false); 334 | toolStripContainer1.TopToolStripPanel.ResumeLayout(false); 335 | toolStripContainer1.TopToolStripPanel.PerformLayout(); 336 | toolStripContainer1.ResumeLayout(false); 337 | toolStripContainer1.PerformLayout(); 338 | statusStrip1.ResumeLayout(false); 339 | statusStrip1.PerformLayout(); 340 | splitContainer1.Panel1.ResumeLayout(false); 341 | splitContainer1.Panel2.ResumeLayout(false); 342 | ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); 343 | splitContainer1.ResumeLayout(false); 344 | tableLayoutPanel1.ResumeLayout(false); 345 | tableLayoutPanel2.ResumeLayout(false); 346 | tableLayoutPanel2.PerformLayout(); 347 | toolStrip1.ResumeLayout(false); 348 | toolStrip1.PerformLayout(); 349 | ResumeLayout(false); 350 | } 351 | 352 | #endregion 353 | 354 | private ToolStripContainer toolStripContainer1; 355 | private SplitContainer splitContainer1; 356 | private TreeView _tree; 357 | private ListView _searchResultsListView; 358 | private ToolStrip toolStrip1; 359 | private ToolStripButton _openButton; 360 | private OpenFileDialog _openFileDialog; 361 | private ComboBox _sortByComboBox; 362 | private ToolStripButton _diffButton; 363 | private TableLayoutPanel tableLayoutPanel1; 364 | private TableLayoutPanel tableLayoutPanel2; 365 | private TextBox _searchTextBox; 366 | private ComboBox _searchComboBox; 367 | private ColumnHeader columnHeader1; 368 | private ColumnHeader columnHeader2; 369 | private ColumnHeader columnHeader3; 370 | private ToolStripButton _reloadButton; 371 | private ToolStripButton _findButton; 372 | internal ImageList _imageList; 373 | private ToolStripButton _aboutButton; 374 | private StatusStrip statusStrip1; 375 | private ToolStripStatusLabel _toolStripStatusLabel; 376 | } 377 | } -------------------------------------------------------------------------------- /sizoscope/MainForm.cs: -------------------------------------------------------------------------------- 1 | using static MstatData; 2 | using System.Reflection.Metadata; 3 | using System.Collections; 4 | using System.Reflection; 5 | 6 | #pragma warning disable 8509 // switch is not exhaustive 7 | 8 | namespace sizoscope 9 | { 10 | public partial class MainForm : Form 11 | { 12 | private string _fileName; 13 | private MstatData _data; 14 | 15 | private TreeLogic.Sorter TreeSorter => (TreeLogic.Sorter)_sortByComboBox.SelectedItem; 16 | 17 | public MainForm() 18 | { 19 | InitializeComponent(); 20 | 21 | _sortByComboBox.Items.Add(TreeLogic.Sorter.BySize()); 22 | _sortByComboBox.Items.Add(TreeLogic.Sorter.ByName()); 23 | _sortByComboBox.SelectedIndex = 0; 24 | 25 | _searchComboBox.SelectedIndex = 1; 26 | 27 | _searchResultsListView.ListViewItemSorter = new SearchResultComparer(); 28 | } 29 | 30 | public MainForm(string fileName) 31 | : this() 32 | { 33 | LoadData(fileName); 34 | } 35 | 36 | private void LoadData(string fileName) 37 | { 38 | _fileName = fileName; 39 | 40 | if (_data != null) 41 | _data.Dispose(); 42 | 43 | _data = MstatData.Read(fileName, loadDgmlAsync: true); 44 | 45 | Text = $"{Path.GetFileName(fileName)} - Sizoscope"; 46 | 47 | _toolStripStatusLabel.Text = $"Total accounted size: {TreeLogic.AsFileSize(_data.Size)}"; 48 | 49 | RefreshViews(); 50 | 51 | _reloadButton.Enabled = true; 52 | _diffButton.Enabled = true; 53 | } 54 | 55 | private void OpenButtonClick(object sender, EventArgs e) 56 | { 57 | if (_openFileDialog.ShowDialog() == DialogResult.OK) 58 | { 59 | LoadData(_openFileDialog.FileName); 60 | } 61 | } 62 | 63 | private void RefreshViews() 64 | { 65 | TreeLogic.RefreshTree(_tree, _data, TreeSorter); 66 | 67 | if (_findButton.Checked) 68 | RefreshSearch(); 69 | } 70 | 71 | private void TreeBeforeExpand(object sender, TreeViewCancelEventArgs e) 72 | { 73 | TreeLogic.BeforeExpand(e.Node, TreeSorter); 74 | } 75 | 76 | private void _sortByComboBox_SelectedIndexChanged(object sender, EventArgs e) 77 | { 78 | if (_data != null) 79 | TreeLogic.RefreshTree(_tree, _data, TreeSorter); 80 | } 81 | 82 | private void DiffButtonClick(object sender, EventArgs e) 83 | { 84 | if (_openFileDialog.ShowDialog() == DialogResult.OK) 85 | { 86 | MstatData right = MstatData.Read(_openFileDialog.FileName, loadDgmlAsync: false); 87 | 88 | (MstatData leftDiff, MstatData rightDiff) = MstatData.Diff(_data, right); 89 | 90 | new DiffForm(leftDiff, rightDiff, right.Size - _data.Size).ShowDialog(this); 91 | } 92 | } 93 | 94 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 95 | { 96 | if (keyData == (Keys.Control | Keys.O)) 97 | { 98 | OpenButtonClick(null, null); 99 | return true; 100 | } 101 | 102 | if (keyData == (Keys.Control | Keys.F)) 103 | { 104 | _findButton.Checked = !_findButton.Checked; 105 | if (_findButton.Checked) 106 | RefreshSearch(); 107 | return true; 108 | } 109 | 110 | if (keyData == (Keys.Control | Keys.D) && _diffButton.Enabled) 111 | { 112 | DiffButtonClick(null, null); 113 | return true; 114 | } 115 | 116 | if (keyData == (Keys.Control | Keys.R) && _reloadButton.Enabled) 117 | { 118 | ReloadButtonClick(null, null); 119 | return true; 120 | } 121 | 122 | return base.ProcessCmdKey(ref msg, keyData); 123 | } 124 | 125 | private void RefreshSearch() 126 | { 127 | if (_data == null) 128 | return; 129 | 130 | var sorter = _searchResultsListView.ListViewItemSorter; 131 | _searchResultsListView.ListViewItemSorter = null; 132 | 133 | _searchResultsListView.BeginUpdate(); 134 | _searchResultsListView.Items.Clear(); 135 | 136 | foreach (var asm in _data.GetScopes()) 137 | { 138 | if (asm.Name == "System.Private.CompilerGenerated") 139 | continue; 140 | 141 | AddTypes(this, asm.GetTypes()); 142 | } 143 | 144 | static void AddTypes(MainForm form, Enumerator types) 145 | { 146 | foreach (var t in types) 147 | { 148 | if (form._searchComboBox.SelectedIndex is 0 or 1 149 | && (t.Name.Contains(form._searchTextBox.Text) || t.Namespace.Contains(form._searchTextBox.Text))) 150 | { 151 | var newItem = new ListViewItem(new string[] 152 | { 153 | t.ToString(), 154 | TreeLogic.AsFileSize(t.Size), 155 | TreeLogic.AsFileSize(t.AggregateSize) 156 | }); 157 | 158 | newItem.Tag = t; 159 | 160 | form._searchResultsListView.Items.Add(newItem); 161 | } 162 | 163 | AddTypes(form, t.GetNestedTypes()); 164 | 165 | if (form._searchComboBox.SelectedIndex is 0 or 2) 166 | { 167 | foreach (var s in t.GetTypeSpecifications()) 168 | AddMembers(form, s.GetMembers()); 169 | 170 | AddMembers(form, t.GetMembers()); 171 | } 172 | } 173 | } 174 | 175 | static void AddMembers(MainForm form, Enumerator members) 176 | { 177 | foreach (var m in members) 178 | { 179 | if (m.Name.Contains(form._searchTextBox.Text)) 180 | { 181 | var newItem = new ListViewItem(new string[] 182 | { 183 | m.ToQualifiedString(), 184 | TreeLogic.AsFileSize(m.Size), 185 | TreeLogic.AsFileSize(m.AggregateSize) 186 | }); 187 | 188 | newItem.Tag = m; 189 | 190 | form._searchResultsListView.Items.Add(newItem); 191 | } 192 | } 193 | } 194 | 195 | _searchResultsListView.EndUpdate(); 196 | 197 | _searchResultsListView.ListViewItemSorter = sorter; 198 | } 199 | 200 | class SearchResultComparer : IComparer 201 | { 202 | public bool InvertSort { get; set; } 203 | 204 | public int SortColumn { get; set; } 205 | 206 | public int Compare(object x, object y) 207 | { 208 | var i1 = (ListViewItem)x; 209 | var i2 = (ListViewItem)y; 210 | 211 | int result; 212 | if (SortColumn == 0) 213 | { 214 | string s1 = i1.Text; 215 | string s2 = i2.Text; 216 | result = string.Compare(s1, s2, StringComparison.Ordinal); 217 | } 218 | else 219 | { 220 | int v1 = i1.Tag switch 221 | { 222 | MstatTypeDefinition def => SortColumn == 1 ? def.Size : def.AggregateSize, 223 | MstatTypeSpecification spec => SortColumn == 1 ? spec.Size : spec.AggregateSize, 224 | MstatMemberDefinition mem => SortColumn == 1 ? mem.Size : mem.AggregateSize, 225 | MstatMethodSpecification met => met.Size, 226 | }; 227 | int v2 = i2.Tag switch 228 | { 229 | MstatTypeDefinition def => SortColumn == 1 ? def.Size : def.AggregateSize, 230 | MstatTypeSpecification spec => SortColumn == 1 ? spec.Size : spec.AggregateSize, 231 | MstatMemberDefinition mem => SortColumn == 1 ? mem.Size : mem.AggregateSize, 232 | MstatMethodSpecification met => met.Size, 233 | }; 234 | result = v1.CompareTo(v2); 235 | } 236 | 237 | return InvertSort ? -result : result; 238 | } 239 | } 240 | 241 | private void _searchResultsListView_ColumnClick(object sender, ColumnClickEventArgs e) 242 | { 243 | var sorter = (SearchResultComparer)_searchResultsListView.ListViewItemSorter; 244 | if (e.Column == sorter.SortColumn) 245 | sorter.InvertSort = !sorter.InvertSort; 246 | else 247 | sorter.SortColumn = e.Column; 248 | 249 | _searchResultsListView.Sort(); 250 | } 251 | 252 | private void ReloadButtonClick(object sender, EventArgs e) 253 | { 254 | LoadData(_fileName); 255 | } 256 | 257 | private void _searchTextBox_TextChanged(object sender, EventArgs e) 258 | { 259 | if (_data != null) 260 | RefreshSearch(); 261 | } 262 | 263 | private void _searchComboBox_SelectedIndexChanged(object sender, EventArgs e) 264 | { 265 | RefreshSearch(); 266 | } 267 | 268 | private void FindButtonClick(object sender, EventArgs e) 269 | { 270 | splitContainer1.Panel2Collapsed = !_findButton.Checked; 271 | if (_findButton.Checked) 272 | { 273 | RefreshSearch(); 274 | _searchTextBox.Focus(); 275 | } 276 | } 277 | 278 | private void _tree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) 279 | { 280 | if (!_data.DgmlSupported) 281 | { 282 | MessageBox.Show("Dependency graph information is only available in .NET 8 or later."); 283 | return; 284 | } 285 | 286 | if (!_data.DgmlAvailable) 287 | { 288 | MessageBox.Show("Dependency graph data was not found. Ensure IlcGenerateDgmlFile=true is specified."); 289 | return; 290 | } 291 | 292 | int? id = e.Node.Tag switch 293 | { 294 | MstatTypeDefinition typedef => typedef.NodeId, 295 | MstatTypeSpecification typespec => typespec.NodeId, 296 | MstatMemberDefinition memberdef => memberdef.NodeId, 297 | MstatMethodSpecification methodspec => methodspec.NodeId, 298 | int val => val, 299 | _ => null 300 | }; 301 | 302 | if (id.HasValue) 303 | { 304 | if (id.Value < 0) 305 | { 306 | MessageBox.Show("This node was not used directly and is included for display purposes only. Try analyzing sub nodes."); 307 | return; 308 | } 309 | 310 | var node = _data.GetNodeForId(id.Value, out string name); 311 | if (node == null) 312 | { 313 | MessageBox.Show($"Could not find path to roots from {name}."); 314 | return; 315 | } 316 | 317 | new RootForm(node).ShowDialog(this); 318 | } 319 | } 320 | 321 | private void _tree_DragEnter(object sender, DragEventArgs e) 322 | { 323 | e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None; 324 | } 325 | 326 | private void _tree_DragDrop(object sender, DragEventArgs e) 327 | { 328 | var files = (string[])e.Data.GetData(DataFormats.FileDrop); 329 | 330 | // Holding alt will open a diff 331 | if ((e.KeyState & 32) == 32 && _data != null) 332 | { 333 | // BeginInvoke so that we don't block the drag source while the modal is open 334 | BeginInvoke(() => 335 | { 336 | MstatData right = MstatData.Read(files[0], loadDgmlAsync: false); 337 | (MstatData leftDiff, MstatData rightDiff) = MstatData.Diff(_data, right); 338 | new DiffForm(leftDiff, rightDiff, right.Size - _data.Size).ShowDialog(this); 339 | }); 340 | } 341 | else 342 | { 343 | LoadData(files[0]); 344 | } 345 | } 346 | 347 | private void _aboutButton_Click(object sender, EventArgs e) 348 | { 349 | var page = new TaskDialogPage() 350 | { 351 | Caption = "About Sizoscope", 352 | Heading = $"Sizoscope {GetType().Assembly.GetCustomAttribute().Version}", 353 | Icon = TaskDialogIcon.Information, 354 | Expander = new TaskDialogExpander 355 | { 356 | CollapsedButtonText = "Third party notices", 357 | Text = """ 358 | License notice for SharpDevelop 359 | 360 | The MIT License (MIT) 361 | 362 | Copyright (c) 2002-2016 AlphaSierraPapa 363 | 364 | Permission is hereby granted, free of charge, to any person obtaining a copy 365 | of this software and associated documentation files (the "Software"), to deal 366 | in the Software without restriction, including without limitation the rights 367 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 368 | copies of the Software, and to permit persons to whom the Software is 369 | furnished to do so, subject to the following conditions: 370 | 371 | The above copyright notice and this permission notice shall be included in 372 | all copies or substantial portions of the Software. 373 | 374 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 375 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 376 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 377 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 378 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 379 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 380 | THE SOFTWARE. 381 | 382 | License notice for TurboXml 383 | 384 | Copyright (c) 2024, Alexandre Mutel 385 | All rights reserved. 386 | 387 | Redistribution and use in source and binary forms, with or without modification 388 | , are permitted provided that the following conditions are met: 389 | 390 | 1. Redistributions of source code must retain the above copyright notice, this 391 | list of conditions and the following disclaimer. 392 | 393 | 2. Redistributions in binary form must reproduce the above copyright notice, 394 | this list of conditions and the following disclaimer in the documentation 395 | and/or other materials provided with the distribution. 396 | 397 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 398 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 399 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 400 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 401 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 402 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 403 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 404 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 405 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 406 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 407 | """ 408 | }, 409 | Text = """ 410 | Copyright (c) 2023 Michal Strehovsky 411 | 412 | https://github.com/MichalStrehovsky 413 | 414 | .NET Native AOT binary size analysis tool. 415 | 416 | This program is free software: you can redistribute it and/or modify 417 | it under the terms of the GNU Affero General Public License as published 418 | by the Free Software Foundation, either version 3 of the License, or 419 | (at your option) any later version. 420 | 421 | This program is distributed in the hope that it will be useful, 422 | but WITHOUT ANY WARRANTY; without even the implied warranty of 423 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 424 | GNU Affero General Public License for more details. 425 | 426 | You should have received a copy of the GNU Affero General Public License 427 | along with this program. If not, see . 428 | """, 429 | DefaultButton = TaskDialogButton.OK, 430 | Buttons = new TaskDialogButtonCollection 431 | { 432 | TaskDialogButton.OK, 433 | { new TaskDialogButton("Project website") } 434 | }, 435 | }; 436 | 437 | 438 | if (TaskDialog.ShowDialog(page) != TaskDialogButton.OK) 439 | { 440 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() 441 | { 442 | FileName = "https://github.com/MichalStrehovsky/sizoscope", 443 | UseShellExecute = true, 444 | }); 445 | } 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /sizoscope/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 415, 17 122 | 123 | 124 | 306, 17 125 | 126 | 127 | 128 | AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs 129 | LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu 130 | SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA7BoAAAJNU0Z0AUkBTAIBAQgB 131 | AAEYAQIBGAECARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATD/ 132 | AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AEIAAzgBXANdAcwDOAFcNAAEArgAAzgBXANdAcwB 133 | /wG6ARYB/wNdAcwDOAFcIAADEwEZAzYBWAFWAVUBUgGkAWYBZAFdAcABdAFuAWAB1wFaAVgBVwGtAzgB 134 | WwMUARsoAAMFAQYDDAEPAwwBDwMFAQZwAAM4AVwDXQHMAf8BvQEhAv8BsgEBAv8BuwEbAf8DXQHMAzgB 135 | XBAAAxkBIgNDAXYBawFoAV0ByQGaAYoBXwHzAboBowFlAf8ByAG2AYYB/wG0AZoBWQH/AbIBmgFeAf8B 136 | zQG7AY0B/wG1AaEBZQH/AZsBiQFcAfUBawFoAV8ByQNCAXIDFAEbFAADOQRfAboDawHXA3EB5ANxAeQD 137 | awHXA18BugM5AV9kAAM4AVwDXQHMAfoBvgEzAf8B+AGuAQkB/wH4Aa4BCQH/AfgBrgEJAf8B+gG6AScB 138 | /wNdAcwDOAFcCAADDAEQAY8BgQFhAesBuQGhAWAB/wHSAb0BiwH/AewB2gG0Af8B+gHnAcUB/wHxAdoB 139 | rwH/AbcBnAFbAf8BuwGiAWcB/wH3Ad4BrQH/AfkB5QHAAf8B7AHaAbMB/wHRAbsBiAH/AbYBoAFhAf8B 140 | iQGBAWgB5wMNAREMAAMJAQsDcwHmA4AB/wOAAf8DgAH/A4AB/wOAAf8DgAH/A3MB5gMJAQtcAAM4AVwD 141 | XQHMAfUBvwFJAf8B8gGzAS8B/wHwAa0BIgH/Ae4BpwEXAf8B8AGsASAB/wHxAbABKQH/AfIBtwE3Af8D 142 | XQHMAzgBXAQAAxYBHQGpAZ4BbwH3AdkBsgFnAf8B2wG0AWwB/wHtAdEBnQH/AfIB3wG9Af8B7wHbAbYB 143 | /wG1AZgBUwH/Ab0BpwFxAf8B+gHpAcoB/wH2AeIBvAH/AfQB2QGmAf8B4wG+AXgB/wHVAasBXAH/AacB 144 | mwFmAfgDFgEdDAADCQELA28B3wOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wNvAd8DCQELXAADTwGZA10B 145 | zANdAcwDXQHMAegBrgE+Af8B4gGfASYB/wHoAasBOgH/A10BzANdAcwDXQHMA08BmQQAAxYBHQGqAaEB 146 | cwH3AeABwAGLAf8B7AHDAWkB/wHxAcsBdwH/AesByAF9Af8B1AG7AYQB/wGyAZYBUgH/AbsBoQFkAf8B 147 | 8QHUAZ4B/wHxAc4BhAH/AfEBzAF5Af8B7AHHAWsB/wHcAbMBWQH/AaYBkQFoAfcDFgEdDAADCQELA28B 148 | 3wOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wNvAd8DCQELMAABSQFCAToBWAGZATMBAAH/AT4BNAEuAVMs 149 | AANdAcwB4gGrAU4B/wHYAZgBNgH/AeABqQFLAf8DXQHMEAADFgEdAakBngFvAfcB7wHYAbIB/wHyAc0B 150 | fAH/AfIBzwGEAf8B9AHRAYsB/wHiAcMBhAH/AbABkwFLAf8BugGbAVEB/wH0AdQBkwH/AfMB0QGIAf8B 151 | 8gHNAX4B/wHxAcsBdgH/AeYBwgFoAf8BpwGSAWkB9wMVARwMAAMJAQsDbwHfA4AB/wOAAf8DgAH/A4AB 152 | /wOAAf8DgAH/A28B3wMJAQssAAFNAUUBPQFdAdcBmgFaAf8BmQEzAQAB/wGZATMBAAH/ATgBMAEsAUoo 153 | AANdAcwB3QGqAVoB/wHPAZIBQQH/AdsBpwFXAf8DXQHMEAADFgEdAagBlwFtAfcB7AHSAaMB/wH1AdcB 154 | nAH/AfQB0gGNAf8B9AHUAZMB/wHpAc4BkwH/AbgBnQFbAf8BvQGiAWQB/wH2AdkBnwH/AfMB0wGOAf8B 155 | 8gHNAX0B/wHxAcsBdgH/AekBxQFyAf8BpwGTAWsB9wMVARwMAAMJAQsDbwHfA4AB/wOAAf8DgAH/A4AB 156 | /wOAAf8DgAH/A28B3wMJAQsoAAFRAUgBPgFhAdcBmgFaAf8B2AGbAVsB/wGZATMBAAH/AZkBMwEAAf8B 157 | mQEzAQAB/wE4ATABLAFKIAADBwEJAmABXQHOAdwBqwFfAf8BzAGQAUQB/wHbAakBXQH/A10BzBAAAxUB 158 | HAGoAZYBbAH3AewBzAGOAf8B9wHfAbMB/wH1AdkBogH/AfUB1wGaAf8B6AHNAZUB/wG4AZ4BXQH/Ab4B 159 | pAFmAf8B9gHaAaQB/wH1AdcBnAH/AfQB1AGUAf8B8wHSAY4B/wHvAdEBlgH/AagBlwFsAfcDFQEcDAAD 160 | CQELA28B3wOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wNvAd8DCQELKAAB2AGbAVwB/wHYAZsBWwH/AdgB 161 | mwFbAf8BmQEzAQAB/wGZATMBAAH/AZkBMwEAAf8BmQEzAQAB/wEyASsBKAFBGAADBwEJAzoDYQFcAdkB 162 | 3gGpAV0B/wHQAZQBSAH/Ad4BrQFhAf8CXQFbAcoQAAMVARwBqAGZAWwB9wHwAdIBmgH/AfcB3wGyAf8B 163 | +AHkAb8B/wH4AeMBvgH/AesB0gGgAf8BtAGZAVkB/wHJAbABdwH/AfgB3QGsAf8B9gHcAakB/wH3Ad8B 164 | sQH/AfcB4AG0Af8B9QHgAb4B/wGqAZ8BbwH3AxUBHAwAAwkBCwNvAd8DgAH/A4AB/wOAAf8DgAH/A4AB 165 | /wOAAf8DbwHfAwkBCygAAdgBmwFbAf8B2AGbAVsB/wHpAbQBfAH/AfwB1gGvAf8BtQFjATUB/wGZATMB 166 | AAH/AZkBMwEAAf8BmQEzAQAB/wwAA08BmQNdAcwDXQHMAmABXQHOAmEBXAHZA2oB+QHvAbMBZwH/Ae4B 167 | sgFmAf8CfgF3AfwCXAFZAcEQAAMTARoBowGTAWMB9gH0Ad0BsAH/AfoB5wHHAf8B8QHdAbYB/wHfAc8B 168 | qgH/AcQBsgGCAf8BswGaAVwB/wG6AaMBaQH/AcoBtQF/Af8B4AHOAaIB/wHzAeIBwwH/AfwB7gHYAf8B 169 | +gHxAeIB/wGyAZwBcgH5AxgBIQwAAwkBCwNvAd8DgAH/A4AB/wOAAf8DgAH/A4AB/wOAAf8DbwHfAwkB 170 | CygAAdgBmwFbAf8B6gG3AYIB/wH7AdgBsgH/Af4B0QGjAf8B+wHYAbIB/wGvAVcBKAH/AZkBMwEAAf8B 171 | mQEzAQAB/wwAA10BzAH7AdgBjAH/AfgBzQGBAf8B+AHMAYAB/wH1AcMBdwH/AfEBuAFsAf8B8QG2AWoB 172 | /wHxAbgBbAH/A2gB8ANQAZoQAAMSARcBnAGTAXMB8AHOAbwBiwH/AcABrQF5Af8BuwGmAW0B/wHPAboB 173 | hwH/AeYBzgGcAf8B8gHVAZ8B/wHqAcwBjgH/AdoBwAGHAf8BxwGxAXsB/wG8AagBcQH/Ab8BqwF3Af8B 174 | yAG3AYkB/wGoAZ0BbwH3AxgBIAwAAwkBCwNvAd8DggH/A4IB/wOBAf8DgQH/A4EB/wOCAf8DbwHfAwkB 175 | CygAAecBswF8Af8B8QHDAZEB/wH+Ac8BnQL/Ac0BmQH/Af4B0AGfAf8B+wHYAbIB/wGpAU4BHgH/AZkB 176 | MwEAAf8MAANdAcwB+wHVAYkB/wH3AcQBeAH/AfcBxAF4Af8B9wHEAXgB/wH3AcQBeAH/AfgBxQF5Af8B 177 | cwJfAfsCYwFfAdUDKwFCEAADBAEFAXABagFhAdABpQGPAV0B/gHdAcgBmwH/AfkB5QHCAf8B+gHmAcAB 178 | /wH4AeEBtQH/AfcB3wGyAf8B9QHaAaQB/wH2AdsBpQH/AfgB4gG4Af8B9wHjAb0B/wHVAb0BewH/AaoB 179 | kQFcAfwBbwFqAVsB0wMEAQUMAAMJAQsDagHoA2QB/wNcAf8DWQH/A1kB/wNcAf8DZAH/A2oB6AMJAQso 180 | AAFvAWYBXAF8AeUBsgF7Af8B9AHAAYsC/wHNAZkC/wHNAZkB/wH+AdABoQH/AfIByQGdAf8BsAFaASkB 181 | /wwAA10BzAH/AeABlAH/Af4B2gGOAf8B/gHaAY4B/wH+AdoBjgH/A34B/ANoAfACYwFfAdUCQAE/AW4E 182 | ARQAAw8BEwNCAXQBZQFiAVoBxAGHAX8BXwHqAcIBrwF/AfwB5AHSAasB/wH3AeYByAH/AfcB6gHRAf8B 183 | 5AHQAacB/wG5AasBfQH6AYMBegFjAeQBXQFbAVYBswM2AVkDCQELFAADQgFzA1UBxANSAdwDUAHlA1AB 184 | 5QNSAdwDVQHEA0IBczAAAXIBaAFeAX8B5AGwAXkB/wH2AcQBkAL/Ac0BmQH/AfMBwgGOAf8B4wGwAXkB 185 | /wFqAWEBWAF4DAADTwGZA10BzANdAcwDXQHMAl0BWwHKAlwBWQHBA1ABmgMrAUIEASQAAxIBGAM9AWcC 186 | WwFWAbMBeQFzAWQB3QF7AXUBZAHbAVUBVAFTAaIDMgFPAwgBCigAAwcBCQMMARADDAEQAwcBCTwAAXkB 187 | bQFhAYcB4wGvAXgB/wHuAb0BigH/AeMBsAF5Af8BSAFEAT8BVGQAAwYBBwMEAQWEAAFgAVgBUAFuAeUB 188 | sgF9Af8BMAEtASsBOP8AKQADTwGZA10BzANdAcwDXQHMA10BzANdAcwDXQHMA08BmWwAAxoBIwFeAVsB 189 | XgHNAxoBI0wAA08BmQNdAcwDXQHMA10BzANdAcwDXQHMA10BzANdAcwBNQG9AXkB/wFDAcsBhwH/AT4B 190 | xgGCAf8BPgHGAYIB/wE+AcYBggH/AUMBywGHAf8DXQHMaAADGgEjAXgBPAGhAf8BngFiAccB/wFgAV0B 191 | YAHOAxoBIzAAAUoBAAF7Af8EAAMMAQ8MAANdAcwBLwEeAdwB/wElARMB2AH/ASUBEwHYAf8BJQETAdgB 192 | /wElARMB2AH/ASUBEwHYAf8DXQHMATcBvwF7Af8BQgHKAYYB/wE3Ab8BewH/ATcBvwF7Af8BNwG/AXsB 193 | /wFCAcoBhgH/A10BzBQAAyMBMwNVAf8DVQH/CAADVQH/A1UB/wMjATM0AAF6AT4BowH/Aa4BcgHXAf8B 194 | 0wGXAfwB/wGqAW4B0wH/AV4BWwFeAc0DGgEjKAABewErAawB/wFXAQcBiAH/AU4BAAF/Af8EAAMMAQ8I 195 | AANdAcwBLQEcAd4B/wEVAQAB1QH/ARUBAAHVAf8BFQEAAdUB/wEVAQAB1QH/ARUBAAHVAf8DXQHMATsB 196 | wwF/Af8BSAHPAYwB/wE7AcMBfwH/ATsBwwF/Af8BOwHDAX8B/wFIAc8BjAH/A10BzBAAAyMBMwNVAf8D 197 | VQH/AzgBXAgAAzgBXANVAf8DVQH/AyMBMygAA3wB/wN8Af8DfAH/AY4BUgG3Af8B0wGXAfwB/wHTAZcB 198 | /AH/Ad0BoQL/AXgBPAGhAf8IAAFQAc8BdgH/AWABxgF+Af8BcAG8AYYB/wGAAbMBjwH/AZABqQGXAf8I 199 | AAF7ASsBrAH/AZcBRwHIAf8BVgEGAYcB/wFlARUBlgH/AVIBAgGDAf8EAAMMAQ8EAANdAcwBNgElAeQB 200 | /wEZAQAB2wH/ARkBAAHbAf8BGQEAAdsB/wEZAQAB2wH/ARkBAAHbAf8DXQHMAT4BxgGCAf8BTQHVAZEB 201 | /wE+AcYBggH/AT4BxgGCAf8BPgHGAYIB/wFNAdUBkQH/A10BzBAAAyMBMwNVAf8DVQH/AyMBMwgAAyMB 202 | MwNVAf8DVQH/AyMBMygAA3wB/wgAAxoBIwGdAWEBxgH/Ad0BoQL/AXcBOwGgAf8DGgEjIAABewErAawB 203 | /wGcAUwBzQH/AZ4BTgHPAf8BVwEHAYgB/wFrARsBnAH/AWABEAGRAf8BVAEEAYUB/wgAA10BzAE+AS0B 204 | 7AH/ARwBAAHjAf8BHAEAAeMB/wEcAQAB4wH/ARwBAAHjAf8BHAEAAeMB/wNdAcwBQgHKAYYB/wFRAdkB 205 | lQH/AUIBygGGAf8BQgHKAYYB/wFCAcoBhgH/AVEB2QGVAf8DXQHMEAADIgExA1UB/wNVAf8DIwEzCAAD 206 | IwEzA1UB/wNVAf8DIwEzKAADfAH/BAADGgEjAl4BWwHNAysBQQF3ATsBoAH/AxoBIwgAAVABzwF2Af8B 207 | YAHGAX4B/wFwAbwBhgH/AYABswGPAf8BkAGpAZcB/wQAAX0BLQGuAf8BmwFLAcwB/wGgAVAB0QH/AZYB 208 | RgHHAf8BuwFrAewB/wFqARoBmwH/AWoBGgGbAf8BVgEGAYcB/wgAA10BzAFGATUB8gH/ASABAAHpAf8B 209 | IAEAAekB/wEgAQAB6QH/ASABAAHpAf8BIAEAAekB/wNdAcwBRAHMAYgB/wFlAe0BqQH/AWUB7QGpAf8B 210 | ZQHtAakB/wFlAe0BqQH/AV4BeQFqAe0BVQFWAVUBrhAAAwUBBgNVAf8DVQH/AyMBMwgAAyMBMwNVAf8D 211 | VQH/AyUBNxQAARkCGgEjASIBfQGNAfoBGQIaASMIAAN8Af8DGgEjAasBfQEOAf8B0QGjATQB/wJgAV0B 212 | zgMaASMkAAF6ASoBqwH/AZ0BTQHOAf8BlQFFAcYB/wHFAXUB9gH/AcYBdgH3Af8BwgFyAfMB/wF1ASUB 213 | pgH/AVUBBQGGAf8IAAJVAVYBrgJeAX4B7QF2AXUB/gH/AXYBdQH+Af8BeQFiAaMB/wF7AVABVQH/AXsB 214 | UAFVAf8BWgFZAVIB7wFaAV4BTwHvAVoBXgFPAe8BXwFmAV8B5QNdAcwDXQHMA10BzAMuAUgMAAMjATMD 215 | VQH/A1UB/wMjATMQAAMjATMDVQH/A1UB/wMjATMMAAEZAhoBIwENAYwBqwH/ASEBoAG/Af8BXAJhAdkB 216 | GQIaASMEAAN8Af8BrQF/ARAB/wHhAbMBRAL/AdgBaQH/Ad0BrwFAAf8CXgFbAc0DGgEjDAABYAHGAX4B 217 | /wFwAbwBhgH/AYABswGPAf8BkAGpAZcB/wQAAXoBKgGrAf8BlQFFAcYB/wHFAXUB9gH/AcQBdAH1Af8B 218 | wwFzAfQB/wHEAXQB9QH/Ab0BbQHuAf8BcAEgAaEB/wgAAy4BSANdAcwDXQHMA10BzAFhAVwBYQHmAdwB 219 | qQEyAf8B1wGkAS0B/wHXAaQBLQH/AdcBpAEtAf8B3AGpATIB/wFjAl8B1SAAAyMBMwNVAf8DVQH/AyUB 220 | NwgAAyUBNwNVAf8DVQH/AyMBMwwAARkCGgEjAQ8BjgGtAf8BKAGnAcYB/wESAZEBsAH/ARkBmAG3Af8B 221 | XwJjAdoDfAH/A3wB/wN8Af8BwQGTASQC/wHYAWkC/wHYAWkC/wHiAXMB/wGrAX0BDgH/IAABrAFcAd0B 222 | /wHGAXYB9wH/AcsBewH8Af8BwwFzAfQB/wHDAXMB9AH/AbsBawHsAf8BrAFcAd0B/xwAAmIBXgHXAdwB 223 | qQEyAf8B0QGeAScB/wHRAZ4BJwH/AdEBngEnAf8B3AGpATIB/wJiAV4B1yAAAyMBMwNVAf8DVQH/AyMB 224 | MwgAAyMBMwNVAf8DVQH/AyMBMwwAAV0BZQFnAeEBKQGoAccB/wEmAaUBxAH/ARIBkQGwAf8BEgGRAbAB 225 | /wEgAZ8BvgH/AV8CYwHaARkCGgEjBAADGgEjAdABogEzAv8B4gFzAf8BqgF8AQ0B/wMaASMkAAGsAVwB 226 | 3QH/Ac8BfwL/AcwBfAH9Af8BvAFsAe0B/wGsAVwB3QH/IAACYQFcAdkB4QGuATgB/wHWAaMBLAH/AdYB 227 | owEsAf8B1gGjASwB/wHhAa4BOAH/AmEBXAHZIAADIwEzA1UB/wNVAf8DIwEzCAADIwEzA1UB/wNVAf8D 228 | IwEzDAABGQIaASMBDwGOAa0B/wE4AbcB1gH/AS0BrAHLAf8BIQGgAb8B/wEhAaABvwH/ASgBpwHGAf8B 229 | XwJjAdoBGQIaASMEAAMaASMBqgF8AQ0B/wMaASMsAAGsAVwB3QH/AcQBdAH1Af8BrAFcAd0B/yQAAmQB 230 | XQHbAecBtAE9Af8B2gGnATAB/wHaAacBMAH/AdoBpwEwAf8B5wG0AT0B/wJkAV0B2yAAAyMBMwNVAf8D 231 | VQH/AyMBMwgAAyMBMwNVAf8DVQH/AyMBMxAAARkCGgEjARIBkQGwAf8BRAHDAeIB/wE7AboB2QH/ATAB 232 | rwHOAf8BMAGvAc4B/wFFAcQB4wH/ARwBhQGkAf1AAAGsAVwB3QH/KAABZgFiAV0B3AHrAbgBQQH/Ad0B 233 | qgEzAf8B3QGqATMB/wHdAaoBMwH/AesBuAFBAf8BZgFiAV0B3CQAAyMBMwNVAf8DVQH/CAADVQH/A1UB 234 | /wMjATMYAAEZAhoBIwEUAZMBsgH/AVABzwHuAf8BSQHIAecB/wFIAccB5gH/ARwBhQGkAf0BGQIaASNs 235 | AAJZAVcBvAGSAYEBUQHzAf4BywFUAf8B/gHLAVQB/wH+AcsBVAH/AZIBgQFRAfMCWQFXAbxgAAEZAhoB 236 | IwEXAZYBtQH/AVoB2QH4Af8BMgF8AYoB+AEZAhoBI3AAAzEBTgFlAWEBWwHeAWUBYQFbAd4BZQFhAVsB 237 | 3gFlAWEBWwHeAWUBYQFbAd4DMQFOZAABGQIaASMBRwFzAYAB8wEZAhoBI2QAAUIBTQE+BwABPgMAASgD 238 | AAFAAwABMAMAAQEBAAEBBQABgAEBFgAD/4EAAf8BHwH/AX8E/wH+AQ8B8AEPAfwBPwL/AfwBBwGAAQEB 239 | 8AEPAv8B+AEDAgAB4AEHAv8B8AEBAgAB4AEHAv8B8AEBAgAB4AEHAf8BjwH+AQ8CAAHgAQcB/wEHAf4B 240 | DwIAAeABBwH+AQMB/AEPAgAB4AEHAf4BAQH4AQ8CAAHgAQcB/gEBAcABDwIAAeABBwH+AQEBwAEPAgAB 241 | 4AEHAf4BAQHAAQ8CAAHgAQcB/gEBAcABDwGAAQEB8AEPAf8BAQHAAR8B8AEPAfwBPwH/AYMC/wH+AX8D 242 | /wHHCP8B/gEBA/8BxwL/AQABAQP/AYMB/wHXAQABAQHxAY8B/wGBAf8BiwEAAQEB4QGHAf4BAQGDAQUB 243 | AAEBAeEBhwH+AcEB/gEDAQABAQHhAYcB/gGDAQQBAwEAAQEB4QGHAcYBBwH8AQMBAAEBAsMBggEDAYQB 244 | AwEAAR8B4QGHAQABAwH8AQcB8AEfAeEBhwEAAYMB/gEPAfABHwHhAYcBAAFHAf8BHwHwAR8B4QGHAYAB 245 | fwH/Ab8B8AEfAfEBjwHAAX8C/wHwAR8C/wHgA/8B8AEfAv8B8QP/Cw== 246 | 247 | 248 | 249 | 17, 17 250 | 251 | 252 | 253 | 254 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 255 | YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEqSURBVDhPY0AHX758Mfj27duH79+/J0CFiAcwzdmzDv8/ 256 | cf3pf5IN+fr1awDQgP+6hWv/88UuJM4QoIYJIE3omGhDQIp3nL0PVswQPAcDy6UvBxsI8h5UCyoAScI0 257 | w2yHiYHwsSv3/79/dh1FDoonwA0Aaa5echIuiez817cO/L+zrQoFPzu3AqwO6DUFuAtgGkB8WCyA2F8+ 258 | vQO7ABmDDAHJwV0wY9t5sAHIrsCHMQy4v68TLHD90WsUhbgwzAAQBhsAE3hz7ziYTQy+t6sR04BHx2Zi 259 | KMSFQQEJTHgL4AZ8fvcMQxE+/PHVfVAsOMANeH5pA4YiXPjhoYn/P3369AAWiB9AAiA/YVOMDYPCCuj8 260 | ArABQEYDMK4/osc1PgxOG+/fCzAwMDAAAMDUoyNGO9ZHAAAAAElFTkSuQmCC 261 | 262 | 263 | 264 | 265 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 266 | YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG2SURBVDhPjZLNK4RRFMZfCyVFNsJW5C+wkMlS9kok9rJQ 267 | lvgbrCwYZpiSUBZIsfZRs5PGZiZmaN6mNMznOzNZzHV/x71jaoacOnXec57nOR/3dUqlkvrLM5lMl2Ms 268 | n893m/DHADmTO009lc7WBKrVamcul3stFouHOu4QMvZfAY3b3ryMqI2LByYL10QQALh+ev+rQLlcHk+k 269 | PlTHbEjyiOhJDkRABxNP7rtqn9mT4nLwrkFAN/Exvq21Te+qx8Qbk4w4nucFlgK3UvCtninP+1mpfgXd 270 | qDebLSVHV86ktui/1lgv5BQKhefBxSNJHl1H5fLNBDBdmz++iUqtf+FQMZXcoHUqKMm43pNviDjT1Avo 271 | KfrA2AbSzHXdTyvAoSqVyhAk60qpFsOXNcCAhQOXFeIDZoWT2xiqcwbfYNTAgIUDl6TfHnFs7Vzpv+1F 272 | F3oMp2bkqIEBCwcurzAcS6blaWwBIN0YGScmZxuBhQNX1HWwv3UVkaKdhFHZFye2nXGwcISM8Z/rLmEK 273 | dpJmTg0MWDiG/m0kUGU0RuVIXBonJmfG3m8g1xt7cRwuzDPhxOSoGZgxx/kCc0ZjfDrw0k0AAAAASUVO 274 | RK5CYII= 275 | 276 | 277 | 278 | 279 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 280 | YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABnSURBVDhPvcxtCoAwCIBhz+bhdgrvWP3xbyFMGMuPWZHw 281 | MjbcA58NM59ZfdUeWUBENyKKEQX27bilSGvNRyJAShELsO7SY0DfUkDOuRIwLo93ffsPsFoC5JwrAVEp 282 | sJILVOrf3g7ABTpw+qQW1ue9AAAAAElFTkSuQmCC 283 | 284 | 285 | 286 | 287 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 288 | YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIPSURBVDhPYximYPPJG/9D21f9d6uY/d+uYPJ/s4xuMIax 289 | Nx2/9h+qFDsIbl3xf9qjv/NBuO/e38WN136tKjn3bVP6qa874w9/PqgSVolqwIZjV8E2rj108X9gYKBp 290 | cEn3/5LpK44WzNl4NHftyf05J95vTzr2ZW/EgfdHAvZ8PI1hAEizTmz9/+DS3v853Yv+T1h/4v/R+19e 291 | 77z5+nnFyoN3IqdvuRKy880p310fzjttfXddyicf1YD1Ry//V3BP/j9z5bb/uy6+2Lf+2K3/Fx9//Xj2 292 | 4ddPu29/+Ji3cP9T1znH7jiuf3vLcvXLR0Iu6Zhh4OLiErvx6K0Vuy+8ODBn5+X/C88//bDs3OOP80/e 293 | /zz5+MNPDm2LX5mufvZUf9HLt1gNsLOzy9t36cVekAGnbr/6f+Di/f97z935v/HItf/ztp/9r+IY+l/U 294 | Pes/SPPG41cxDTA3Nw/adOTadpABj958O4eMj117uNXMzCwZqhQ7MDQ0tKxsaJ969MarY0BNZ2GaX374 295 | drm9Z1K/rq6uLVQpJgCazgeyYf36jf9zSyrnXbp+58TL91+v3n/84lBTc+t0ERGRJBkZGU6oclRgY2Mj 296 | CnR+5p49h/9zcHAUMzMzO7KysiYCcRsLC0spkO8OVMYFUY0FWFpa5p8+ff0/FxdXFZArBBElAdy8+eI/ 297 | Ly9vI5ApBREhBTAwAADRlzG3GCdNfwAAAABJRU5ErkJggg== 298 | 299 | 300 | 301 | 302 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 303 | YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJeSURBVDhPrZJfSJNhFIe/b98EMfqjUGNGjprOmcMtsIiZ 304 | poFLp5mMtZKSiWYrKKOowC4adWEMsnYR6yKoiy4CK9ZEcErOwCnWNhwztmpiMBoVuVkXwYhtp/e8vtuF 305 | XQUdeNiP8/6ec/Ex7v/OwWmxWOPWCurJIUEzOcKr3a8QzLjDN+yw9rpRO7cIavctefuczzQY+zZgW0ld 306 | ffALkP7bKyncydrmfNjBLrPYyKYLBdWYrfFMJNo3HM+Yb36Bo5cikBvMuDPbPme05mAUu+gwm+OEale7 307 | suNN8LT1U9Z4bQmQw+cWmA405/bd1o9ZeetsEB2mkwNVzvuG6x+SbRcWQX8+RGm2BEDR4gR583NQ6l1Q 308 | rntBwVx70ptEh+kcx1c+Gz91I5JGqanvLewxekDROgp+vx8CgQBUtLhAoR/LU23ypNFhOjlQ8XT8+OC7 309 | dJ15HlSG17C70wPKjgkqh8NhmnGXxzSTRofp5ID8iaOpP7iq6fKB+sQaKuMMlWOxGM25PVLZ6V1Fh+nk 310 | G+x8dESqHQ3VWxaze3veA6LpClA5kUjQnNvXmheyJZqXIXSYTkb2uJAvezi8Sze1XN+zlNGejZNihMrJ 311 | ZJJmrSUO+7ujme2NU8vYRYfZbMocxXypY6i4ZiSoMoS+7zN/TTUM/ICGiz/zbCRv2MEus9aPVcxJ7Qd4 312 | yb07vMTuEknsXpHk7ixy6MpvwF/a+bc5JnBFvTp+k3UeYcu/hicUEIoI+D/fRthBKCdUEWo4UV0vV3DZ 313 | R/PaG3bzgwcEAh7Bj7OBsJlQQthKkBCkhFKW8Y10Oe4PLmZWPOVVLl4AAAAASUVORK5CYII= 314 | 315 | 316 | 317 | 141, 17 318 | 319 | 320 | 321 | AAABAAIAICAAAAAACACoCAAAJgAAABAQAAAAAAgAaAUAAM4IAAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE 322 | AAAAAAAAAAAAAAABAAAAAQAAAAD/AAD/AAD/AAAAAP//AP//AAD/AP8AwMDAAP/48ADX6/oA1P9/AP8A 323 | AADiK4oAKiqlAAAAAAAEBAQACAgIAAwMDAAREREAFhYWABwcHAAiIiIAKSkpADMzMwA5OTkAQkJCAE1N 324 | TQBVVVUAYGBgAGZmZgBwcHAAgICAAIyMjACUlJQAmZmZAKSkpACsrKwAtra2AMDAwADMzMwA1NTUANra 325 | 2gDg4OAA7OzsAPj4+AD7+/sA////ADMAAABmAAAAmQAAAMwAAAD/AAAAADMAADMzAABmMwAAmTMAAMwz 326 | AAD/MwAAAGYAADNmAABmZgAAmWYAAMxmAAD/ZgAAAJkAADOZAABmmQAAmZkAAMyZAAD/mQAAAMwAADPM 327 | AABmzAAAmcwAAMzMAAD/zAAAAP8AADP/AABm/wAAmf8AAMz/AAD//wAAAAAzADMAMwBmADMAmQAzAMwA 328 | MwD/ADMAADMzAGYzMwCZMzMAzDMzAP8zMwAAZjMAM2YzAGZmMwCZZjMAzGYzAP9mMwAAmTMAM5kzAGaZ 329 | MwCZmTMAzJkzAP+ZMwAAzDMAM8wzAGbMMwCZzDMAzMwzAP/MMwAA/zMAM/8zAGb/MwCZ/zMAzP8zAP// 330 | MwAAAGYAMwBmAGYAZgCZAGYAzABmAP8AZgAAM2YAMzNmAGYzZgCZM2YAzDNmAP8zZgAAZmYAM2ZmAJlm 331 | ZgDMZmYA/2ZmAACZZgAzmWYAZplmAJmZZgDMmWYA/5lmAADMZgAzzGYAZsxmAJnMZgDMzGYA/8xmAAD/ 332 | ZgAz/2YAZv9mAJn/ZgDM/2YA//9mAAAAmQAzAJkAZgCZAJkAmQDMAJkA/wCZAAAzmQAzM5kAZjOZAJkz 333 | mQDMM5kA/zOZAABmmQAzZpkAZmaZAJlmmQDMZpkA/2aZAACZmQAzmZkAZpmZAMyZmQD/mZkAAcyZADPM 334 | mQBmzJkAmcyZAMzMmQD/zJkAAP+ZADP/mQBm/5kAmf+ZAMz/mQD//5kAAADMADMAzABmAMwAmQDMAMwA 335 | zAD/AMwAADPMADMzzABmM8wAmTPMAMwzzAD/M8wAAGbMADNmzABmZswAmWbMAMxmzAD/ZswAAJnMADOZ 336 | zABmmcwAmZnMAMyZzAD/mcwAAMzMADPMzABmzMwAmczMAP/MzAAA/8wAM//MAGb/zACZ/8wAzP/MAP// 337 | zAAAAP8AMwD/AGYA/wCZAP8AzAD/AP8A/wAAM/8AMzP/AGYz/wCZM/8AzDP/AP8z/wAAZv8AM2b/AGZm 338 | /wCZZv8AzGb/AP9m/wAAmf8AM5n/AGaZ/wCZmf8AzJn/AP+Z/wAAzP8AM8z/AGbM/wCZzP8AzMz/AP/M 339 | /wAA//8AM///AGb//wCZ//8AzP//AERERERERERERERERERERERERERERERERERERERERERERERERERE 340 | RERERERERERERERERERERERERERERERERERERCkODg4ODg4ODg4ODjCQkJCQkJAwDg4ODg4ODg5ERERE 341 | DikODg4ODg4ODg4OMDAwMJCQkDAODg4ODg4ODkREREQpDikODg4ODg4ODg4wkJCQkJCQMA4ODg4ODg4O 342 | RERERA4pDikODg4ODg4ODjCQkJCQkJAwDg4ODg4ODg5EREREKQ4pDikODg4ODg4OMDAwMJCQkDAODg4O 343 | Dg4ODkREREQOKQ4pDikODg4ODg4wkJCQkJCQMA4ODg4ODg4ORERERCkOKQ4pDikODg4ODjCQkJCQkJAw 344 | Dg4ODg4ODg5EREREDikOKQ4pDikODg4OMDAwMJCQkDAODg4ODg4ODkREREQpDikOKQ4pDikODg4wkJCQ 345 | kJCQMA4ODg4ODg4ORERERA4pDikOKQ4pDikODjCQkJCQkJAwDg4ODg4ODg5EREREKQ4pDikOKQ4pDik3 346 | Nzc3kJA3Nzc3Dg4ODg4ODkREREQOKQ4pDikOKQ4pDjc3NzeQkDc3NzcODg4ODg4ORERERCkOKQ4pDikO 347 | KQ4pNzc3N5CQNzc3Nw4ODg4ODg5EREREDikOKQ4pDik3Nzc3Nzc3kJA3Nzc3Dg4ODg4ODkREREQpDikO 348 | KQ43Nzc3Nzc3NzeQkDc3NzcODg4ODg4ORERERA4pDik3Nzc3Nzc3Nzc3N5CQNzc3Nzc3Nw4ODg5ERERE 349 | KQ4pDjc3Nzc3Nzc3Nzc3kJA3Nzc3NzcODg4ODkREREQOKQ43Nzc3Nzc3Nzc3NzeQkDc3Nzc3Dg4ODg4O 350 | RERERCkOKQ4pDikOKQ4pDjCQkJCQkJAwDQ4ODg4ODg5EREREDikOKQ4pDikOKQ4pMDAwMJCQkDANDg4O 351 | Dg4ODkREREQpDikOKQ4pDikOKQ4wkJCQkJCQMCkODg4ODg4ORERERA4pDikOKQ4pDikOKTCQkJCQkJAw 352 | DSkODg4ODg5EREREKQ4pMDAwMDAwMDAwMDAwMJCQkDAwMCkODg4ODkREREQOKQ4pMJCQkJCQkJCQkJCQ 353 | kJCQkJCQMCkODg4ORERERCkOKQ4wMJCQkJCQkJCQkJCQkJAwMDAwMCkODg5EREREDikOKQ4pMDCQkJCQ 354 | kJCQkJCQkDANKQ4pDikODkREREQpDikOKQ4pDjAwMDAwMDAwMDAwMCkOKQ4pDikORERERA4pDikOKQ4p 355 | DikOKQ4pDikOKQ4pDikOKQ4pDilERERERERERERERERERERERERERERERERERERERERERERERERERERE 356 | REREREREREREREREREREREREREREREREREQAAAAAAAAAAB/8A/wv/AP8F/wD/Cv8A/wV/AP8KvwD/BV8 357 | A/wqvAP8FVwD/CqsA/wVUAH8KqgB/BVQAfwqgAH8FQAB/CgAADwUAAB8KAAA/BVUA/wqqAP8FVQB/Cqo 358 | AvwQAAB8KAAAPBQAABwqAAKsFUABVCqqqqgAAAAAAAAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAA 359 | AAAAAAAAAAEAAAABAAAAAP8AAP8AAP8AAAAA//8A//8AAP8A/wDAwMAA//jwANfr+gDU/38A/wAAAOIr 360 | igAqKqUAAAAAAAQEBAAICAgADAwMABEREQAWFhYAHBwcACIiIgApKSkAMzMzADk5OQBCQkIATU1NAFVV 361 | VQBgYGAAZmZmAHBwcACAgIAAjIyMAJSUlACZmZkApKSkAKysrAC2trYAwMDAAMzMzADU1NQA2traAODg 362 | 4ADs7OwA+Pj4APv7+wD///8AMwAAAGYAAACZAAAAzAAAAP8AAAAAMwAAMzMAAGYzAACZMwAAzDMAAP8z 363 | AAAAZgAAM2YAAGZmAACZZgAAzGYAAP9mAAAAmQAAM5kAAGaZAACZmQAAzJkAAP+ZAAAAzAAAM8wAAGbM 364 | AACZzAAAzMwAAP/MAAAA/wAAM/8AAGb/AACZ/wAAzP8AAP//AAAAADMAMwAzAGYAMwCZADMAzAAzAP8A 365 | MwAAMzMAZjMzAJkzMwDMMzMA/zMzAABmMwAzZjMAZmYzAJlmMwDMZjMA/2YzAACZMwAzmTMAZpkzAJmZ 366 | MwDMmTMA/5kzAADMMwAzzDMAZswzAJnMMwDMzDMA/8wzAAD/MwAz/zMAZv8zAJn/MwDM/zMA//8zAAAA 367 | ZgAzAGYAZgBmAJkAZgDMAGYA/wBmAAAzZgAzM2YAZjNmAJkzZgDMM2YA/zNmAABmZgAzZmYAmWZmAMxm 368 | ZgD/ZmYAAJlmADOZZgBmmWYAmZlmAMyZZgD/mWYAAMxmADPMZgBmzGYAmcxmAMzMZgD/zGYAAP9mADP/ 369 | ZgBm/2YAmf9mAMz/ZgD//2YAAACZADMAmQBmAJkAmQCZAMwAmQD/AJkAADOZADMzmQBmM5kAmTOZAMwz 370 | mQD/M5kAAGaZADNmmQBmZpkAmWaZAMxmmQD/ZpkAAJmZADOZmQBmmZkAzJmZAP+ZmQABzJkAM8yZAGbM 371 | mQCZzJkAzMyZAP/MmQAA/5kAM/+ZAGb/mQCZ/5kAzP+ZAP//mQAAAMwAMwDMAGYAzACZAMwAzADMAP8A 372 | zAAAM8wAMzPMAGYzzACZM8wAzDPMAP8zzAAAZswAM2bMAGZmzACZZswAzGbMAP9mzAAAmcwAM5nMAGaZ 373 | zACZmcwAzJnMAP+ZzAAAzMwAM8zMAGbMzACZzMwA/8zMAAD/zAAz/8wAZv/MAJn/zADM/8wA///MAAAA 374 | /wAzAP8AZgD/AJkA/wDMAP8A/wD/AAAz/wAzM/8AZjP/AJkz/wDMM/8A/zP/AABm/wAzZv8AZmb/AJlm 375 | /wDMZv8A/2b/AACZ/wAzmf8AZpn/AJmZ/wDMmf8A/5n/AADM/wAzzP8AZsz/AJnM/wDMzP8A/8z/AAD/ 376 | /wAz//8AZv//AJn//wDM//8ADg4ODg4ODjdnZ2dnNw4ODg4ODg4ODg43NzdnZzcODg4ODg4ODg4ON2dn 377 | Z2c3Dg4ODg4ODg43Nzc3Z2c3NzcODg4ODjc3Nzc3N2dnNzc3Dg4ODjc3Nzc3NzdnZzc3NzcODjc3Nzc3 378 | Nzc3Z2c3NzcODg4ODg4ODg43Z2dnZzcODg4ODg4ODg4ONzc3Z2c3Dg4ODg4ODg4ODjdnZ2dnNw4ODg43 379 | Nzc3Nzc3NzdnZzcODg4ODjdnZ2dnZ2dnZ2c3Nw4ODg4ONzdnZ2dnZ2dnNzc3Dg4ODg4ONzc3Nzc3NzcO 380 | Dg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODv4HAAD+BwAA/gcAAPgDAADgAwAAwAEAAIAD 381 | AAD+BwAA/gcAAP4HAACABwAAwAMAAOABAAD4BwAA//8AAP//AAA= 382 | 383 | 384 | -------------------------------------------------------------------------------- /sizoscope/MstatData.Cache.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection.Metadata; 3 | using System.Reflection.Metadata.Ecma335; 4 | using System.Runtime.CompilerServices; 5 | 6 | partial class MstatData 7 | { 8 | // As we parse the file, we populate a sidecar cache with interesting observations 9 | // about rows of the metadata tables. 10 | // This cache has a 1:1 relationship with the file format. E.g. each row in the 11 | // MemberRef table has an entry in the `_memberRefCache` array. One can index 12 | // into the cache using the RID portion of the MemberRef token. 13 | // 14 | // The caches often form linked lists. E.g. all top-level types from a single 15 | // assemly form a linked list. E.g. TypeRefRowCache.NextTypeInScope contains the 16 | // metadata handle of the next type. Using the RID portion of the handle 17 | // we can find the next entry in the TypeRefRowCache. 18 | 19 | private readonly (string Namespace, string Name)[] _typeRefNameCache; 20 | private readonly string[] _memberRefNameCache; 21 | private readonly TypeRefRowCache[] _typeRefCache; 22 | private readonly TypeSpecRowCache[] _typeSpecCache; 23 | private readonly MemberRefRowCache[] _memberRefCache; 24 | private readonly MethodSpecRowCache[] _methodSpecCache; 25 | private readonly AssemblyRefRowCache[] _assemblyRefCache; 26 | 27 | private FrozenObjectHandle _firstUnownedFrozenObject; 28 | 29 | // These are not 1:1 with file format 30 | private ManifestResourceRowCache[] _manifestResourceCache; 31 | private FrozenObjectRowCache[] _frozenObjectCache; 32 | 33 | struct TypeRefRowCache 34 | { 35 | public int NodeId; 36 | public int HashCode; 37 | public int Size; 38 | public int AggregateSize; 39 | 40 | public TypeReferenceHandle FirstNestedType; 41 | public MemberReferenceHandle FirstMember; 42 | public TypeSpecificationHandle FirstTypeSpec; 43 | public FrozenObjectHandle FirstFrozenObject; 44 | public TypeReferenceHandle NextTypeInScope; 45 | 46 | public bool IsInitialized => NodeId != 0; 47 | 48 | public void Initialize(MstatData data, TypeReferenceHandle handle, out string @namespace, out string name) 49 | { 50 | Debug.Assert(!IsInitialized); 51 | 52 | TypeReference typeRef = data._reader.GetTypeReference(handle); 53 | 54 | name = data._reader.GetString(typeRef.Name); 55 | 56 | if (typeRef.ResolutionScope.Kind != HandleKind.TypeReference) 57 | { 58 | @namespace = string.Intern(data._reader.GetString(typeRef.Namespace)); 59 | HashCode = System.HashCode.Combine(@namespace.GetHashCode(), @name.GetHashCode()); 60 | ref AssemblyRefRowCache owningCache = ref data.GetRowCache((AssemblyReferenceHandle)typeRef.ResolutionScope); 61 | NextTypeInScope = owningCache.FirstTypeRef; 62 | owningCache.FirstTypeRef = handle; 63 | } 64 | else 65 | { 66 | @namespace = string.Empty; 67 | ref var owningCache = ref data.GetRowCache((TypeReferenceHandle)typeRef.ResolutionScope); 68 | NextTypeInScope = owningCache.FirstNestedType; 69 | owningCache.FirstNestedType = handle; 70 | HashCode = System.HashCode.Combine(owningCache.HashCode, @name.GetHashCode()); 71 | } 72 | NodeId = -1; 73 | } 74 | 75 | internal void AddSize(MstatData data, TypeReferenceHandle handle, int size) 76 | { 77 | Debug.Assert(Unsafe.AreSame(ref this, ref Unsafe.Add(ref data._typeRefCache[0], MetadataTokens.GetRowNumber(handle)))); 78 | 79 | AggregateSize += size; 80 | 81 | TypeReference typeRef = data._reader.GetTypeReference(handle); 82 | if (typeRef.ResolutionScope.Kind != HandleKind.TypeReference) 83 | data.GetRowCache((AssemblyReferenceHandle)typeRef.ResolutionScope).AddSize(size); 84 | else 85 | data.GetRowCache((TypeReferenceHandle)typeRef.ResolutionScope).AddSize(data, (TypeReferenceHandle)typeRef.ResolutionScope, size); 86 | } 87 | } 88 | 89 | struct TypeSpecRowCache 90 | { 91 | public int NodeId; 92 | public int HashCode; 93 | public int Size; 94 | public int AggregateSize; 95 | 96 | public TypeSpecificationHandle NextTypeSpec; 97 | public MemberReferenceHandle FirstMember; 98 | public FrozenObjectHandle FirstFrozenObject; 99 | 100 | public bool IsInitialized => NodeId != 0; 101 | 102 | public void Initialize(MstatData data, TypeSpecificationHandle handle) 103 | { 104 | TypeSpecification typeSpec = data._reader.GetTypeSpecification(handle); 105 | 106 | BlobReader reader = data._reader.GetBlobReader(typeSpec.Signature); 107 | TypeReferenceHandle declaringTypeRef = GetDeclaringTypeReference(data, reader); 108 | 109 | ref TypeRefRowCache typeRefRow = ref data.GetRowCache(declaringTypeRef); 110 | NextTypeSpec = typeRefRow.FirstTypeSpec; 111 | typeRefRow.FirstTypeSpec = handle; 112 | 113 | // TODO: compute hashcode as part of GetDeclaringTypeReference and make it better 114 | HashCode = typeRefRow.HashCode; 115 | 116 | NodeId = -1; 117 | } 118 | 119 | internal void AddSize(MstatData data, TypeSpecificationHandle handle, int size) 120 | { 121 | Debug.Assert(Unsafe.AreSame(ref this, ref Unsafe.Add(ref data._typeSpecCache[0], MetadataTokens.GetRowNumber(handle)))); 122 | 123 | AggregateSize += size; 124 | 125 | TypeSpecification typeSpec = data._reader.GetTypeSpecification(handle); 126 | 127 | BlobReader reader = data._reader.GetBlobReader(typeSpec.Signature); 128 | TypeReferenceHandle declaringTypeRef = GetDeclaringTypeReference(data, reader); 129 | data.GetRowCache(declaringTypeRef).AddSize(data, declaringTypeRef, size); 130 | } 131 | 132 | private static TypeReferenceHandle GetDeclaringTypeReference(MstatData data, BlobReader reader) 133 | { 134 | SignatureTypeCode typeCode = reader.ReadSignatureTypeCode(); 135 | return typeCode switch 136 | { 137 | SignatureTypeCode.GenericTypeInstance => GetDeclaringTypeReference(data, reader), 138 | SignatureTypeCode.SZArray or SignatureTypeCode.Array or SignatureTypeCode.Pointer or SignatureTypeCode.ByReference => GetDeclaringTypeReference(data, reader), 139 | SignatureTypeCode.TypeHandle => (TypeReferenceHandle)reader.ReadTypeHandle(), 140 | SignatureTypeCode.FunctionPointer => GetDeclaringTypeReferenceForFunctionPointer(data, reader), 141 | 142 | <= SignatureTypeCode.String or SignatureTypeCode.Object or (>= SignatureTypeCode.TypedReference and <= SignatureTypeCode.UIntPtr) => data.GetTypeReferenceForSignatureTypeCode(typeCode), 143 | 144 | _ => throw new Exception($"{typeCode} unexpected"), 145 | }; 146 | 147 | static TypeReferenceHandle GetDeclaringTypeReferenceForFunctionPointer(MstatData data, BlobReader reader) 148 | { 149 | SignatureHeader header = reader.ReadSignatureHeader(); 150 | if (header.IsGeneric) 151 | reader.ReadCompressedInteger(); 152 | reader.ReadCompressedInteger(); 153 | return GetDeclaringTypeReference(data, reader); 154 | } 155 | } 156 | } 157 | 158 | struct MemberRefRowCache 159 | { 160 | public int NodeId; 161 | public int Size; 162 | public int AggregateSize; 163 | public int HashCode; 164 | 165 | public MemberReferenceHandle NextMemberRef; 166 | public MethodSpecificationHandle FirstMethodSpec; 167 | 168 | public bool IsInitialized => NodeId != 0; 169 | 170 | public void Initialize(MstatData data, MemberReferenceHandle handle, out string name) 171 | { 172 | MemberReference memberRef = data.MetadataReader.GetMemberReference(handle); 173 | 174 | name = data.MetadataReader.GetString(memberRef.Name); 175 | 176 | if (memberRef.Parent.Kind == HandleKind.TypeReference) 177 | { 178 | ref TypeRefRowCache parentCache = ref data.GetRowCache((TypeReferenceHandle)memberRef.Parent); 179 | NextMemberRef = parentCache.FirstMember; 180 | parentCache.FirstMember = handle; 181 | HashCode = System.HashCode.Combine(parentCache.HashCode, name.GetHashCode()); 182 | } 183 | else 184 | { 185 | ref TypeSpecRowCache parentCache = ref data.GetRowCache((TypeSpecificationHandle)memberRef.Parent); 186 | NextMemberRef = parentCache.FirstMember; 187 | parentCache.FirstMember = handle; 188 | HashCode = System.HashCode.Combine(parentCache.HashCode, name.GetHashCode()); 189 | } 190 | 191 | NodeId = -1; 192 | } 193 | 194 | internal void AddSize(MstatData data, MemberReferenceHandle handle, int size) 195 | { 196 | Debug.Assert(Unsafe.AreSame(ref this, ref Unsafe.Add(ref data._memberRefCache[0], MetadataTokens.GetRowNumber(handle)))); 197 | 198 | AggregateSize += size; 199 | 200 | MemberReference memberRef = data.MetadataReader.GetMemberReference(handle); 201 | if (memberRef.Parent.Kind == HandleKind.TypeReference) 202 | data.GetRowCache((TypeReferenceHandle)memberRef.Parent).AddSize(data, (TypeReferenceHandle)memberRef.Parent, size); 203 | else 204 | data.GetRowCache((TypeSpecificationHandle)memberRef.Parent).AddSize(data, (TypeSpecificationHandle)memberRef.Parent, size); 205 | } 206 | } 207 | 208 | struct MethodSpecRowCache 209 | { 210 | public int NodeId; 211 | public int Size; 212 | public int HashCode; 213 | 214 | public MethodSpecificationHandle NextMethodSpec; 215 | 216 | public bool IsInitialized => NodeId != 0; 217 | 218 | public void Initialize(MstatData data, MethodSpecificationHandle handle) 219 | { 220 | MethodSpecification methodSpec = data.MetadataReader.GetMethodSpecification(handle); 221 | ref MemberRefRowCache parentCache = ref data.GetRowCache((MemberReferenceHandle)methodSpec.Method); 222 | NextMethodSpec = parentCache.FirstMethodSpec; 223 | parentCache.FirstMethodSpec = handle; 224 | 225 | // TODO: better hashcode 226 | HashCode = parentCache.HashCode; 227 | 228 | NodeId = -1; 229 | } 230 | 231 | internal void AddSize(MstatData data, MethodSpecificationHandle handle, int size) 232 | { 233 | Debug.Assert(Unsafe.AreSame(ref this, ref Unsafe.Add(ref data._methodSpecCache[0], MetadataTokens.GetRowNumber(handle)))); 234 | 235 | MethodSpecification methodSpec = data.MetadataReader.GetMethodSpecification(handle); 236 | data.GetRowCache((MemberReferenceHandle)methodSpec.Method).AddSize(data, (MemberReferenceHandle)methodSpec.Method, size); 237 | } 238 | } 239 | 240 | struct AssemblyRefRowCache 241 | { 242 | public int AggregateSize; 243 | 244 | public TypeReferenceHandle FirstTypeRef; 245 | public ManifestResourceHandle FirstManifestResource; 246 | 247 | internal void AddSize(int size) => AggregateSize += size; 248 | } 249 | 250 | struct ManifestResourceRowCache 251 | { 252 | public int Size; 253 | public string Name; 254 | public AssemblyReferenceHandle OwningAssembly; 255 | 256 | public ManifestResourceHandle NextManifestResource; 257 | } 258 | 259 | struct FrozenObjectRowCache 260 | { 261 | public int Size; 262 | public int NodeId; 263 | public EntityHandle InstanceType; 264 | public EntityHandle OwningEntity; 265 | 266 | public FrozenObjectHandle NextFrozenObject; 267 | } 268 | 269 | public enum FrozenObjectHandle { } 270 | 271 | private ref TypeRefRowCache GetRowCache(TypeReferenceHandle handle) 272 | { 273 | ref TypeRefRowCache result = ref _typeRefCache[MetadataTokens.GetRowNumber(handle)]; 274 | if (!result.IsInitialized) 275 | { 276 | result.Initialize(this, handle, out string @namespace, out string name); 277 | _typeRefNameCache[MetadataTokens.GetRowNumber(handle)] = (@namespace, name); 278 | } 279 | return ref result; 280 | } 281 | 282 | private ref TypeSpecRowCache GetRowCache(TypeSpecificationHandle handle) 283 | { 284 | ref TypeSpecRowCache result = ref _typeSpecCache[MetadataTokens.GetRowNumber(handle)]; 285 | if (!result.IsInitialized) 286 | result.Initialize(this, handle); 287 | return ref result; 288 | } 289 | 290 | private ref MemberRefRowCache GetRowCache(MemberReferenceHandle handle) 291 | { 292 | ref MemberRefRowCache result = ref _memberRefCache[MetadataTokens.GetRowNumber(handle)]; 293 | if (!result.IsInitialized) 294 | { 295 | result.Initialize(this, handle, out string name); 296 | _memberRefNameCache[MetadataTokens.GetRowNumber(handle)] = name; 297 | } 298 | return ref result; 299 | } 300 | 301 | private ref MethodSpecRowCache GetRowCache(MethodSpecificationHandle handle) 302 | { 303 | ref MethodSpecRowCache result = ref _methodSpecCache[MetadataTokens.GetRowNumber(handle)]; 304 | if (!result.IsInitialized) 305 | result.Initialize(this, handle); 306 | return ref result; 307 | } 308 | 309 | private ref AssemblyRefRowCache GetRowCache(AssemblyReferenceHandle handle) 310 | { 311 | return ref _assemblyRefCache[MetadataTokens.GetRowNumber(handle)]; 312 | } 313 | 314 | private ref ManifestResourceRowCache GetRowCache(ManifestResourceHandle handle) 315 | { 316 | return ref _manifestResourceCache[MetadataTokens.GetRowNumber(handle)]; 317 | } 318 | 319 | private ref FrozenObjectRowCache GetRowCache(FrozenObjectHandle handle) 320 | { 321 | return ref _frozenObjectCache[(int)handle]; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /sizoscope/MstatData.Diff.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection.Metadata; 3 | 4 | partial class MstatData 5 | { 6 | public static (MstatData Left, MstatData Right) Diff(MstatData left, MstatData right) 7 | { 8 | MstatData leftDiff = new MstatData(left._peReader); 9 | leftDiff._nameToNode = left._nameToNode; 10 | if (left._manifestResourceCache != null) 11 | leftDiff._manifestResourceCache = new ManifestResourceRowCache[left._manifestResourceCache.Length]; 12 | if (left._frozenObjectCache != null) 13 | leftDiff._frozenObjectCache = new FrozenObjectRowCache[left._frozenObjectCache.Length]; 14 | MstatData rightDiff = new MstatData(right._peReader); 15 | rightDiff._nameToNode = right._nameToNode; 16 | if (right._manifestResourceCache != null) 17 | rightDiff._manifestResourceCache = new ManifestResourceRowCache[right._manifestResourceCache.Length]; 18 | if (right._frozenObjectCache != null) 19 | rightDiff._frozenObjectCache = new FrozenObjectRowCache[right._frozenObjectCache.Length]; 20 | 21 | AddToDiff(left, right, leftDiff); 22 | AddToDiff(right, left, rightDiff); 23 | 24 | return (leftDiff, rightDiff); 25 | } 26 | 27 | private static void AddToDiff(MstatData left, MstatData right, MstatData result) 28 | { 29 | HashSet rightAsms = new HashSet(right.GetScopes()); 30 | foreach (MstatAssembly leftAsm in left.GetScopes()) 31 | { 32 | if (rightAsms.TryGetValue(leftAsm, out MstatAssembly rightAsm)) 33 | { 34 | AddToDiff(leftAsm, rightAsm, result); 35 | } 36 | else 37 | { 38 | foreach (MstatTypeDefinition t in leftAsm.GetTypes()) 39 | AddToDiff(t, result); 40 | foreach (MstatManifestResource m in leftAsm.GetManifestResources()) 41 | AddToDiff(m, result); 42 | } 43 | } 44 | 45 | HashSet rightFrozenObjs = new HashSet(right.GetFrozenObjects()); 46 | foreach (MstatFrozenObject o in left.GetFrozenObjects()) 47 | { 48 | if (!rightFrozenObjs.Contains(o)) 49 | { 50 | AddToDiff(o, result); 51 | } 52 | } 53 | } 54 | 55 | private static void AddToDiff(MstatAssembly left, MstatAssembly right, MstatData result) 56 | { 57 | HashSet rightTypes = new HashSet(right.GetTypes()); 58 | foreach (MstatTypeDefinition leftType in left.GetTypes()) 59 | { 60 | if (rightTypes.TryGetValue(leftType, out MstatTypeDefinition rightType)) 61 | AddToDiff(leftType, rightType, result); 62 | else 63 | AddToDiff(leftType, result); 64 | } 65 | 66 | HashSet rightManifestResources = new HashSet(right.GetManifestResources()); 67 | foreach (MstatManifestResource leftManifestResource in left.GetManifestResources()) 68 | { 69 | if (!rightManifestResources.Contains(leftManifestResource)) 70 | AddToDiff(leftManifestResource, result); 71 | } 72 | } 73 | 74 | private static void AddToDiff(MstatTypeDefinition left, MstatTypeDefinition right, MstatData result) 75 | { 76 | HashSet rightMembers = new HashSet(right.GetMembers()); 77 | foreach (MstatMemberDefinition leftMember in left.GetMembers()) 78 | { 79 | if (rightMembers.TryGetValue(leftMember, out MstatMemberDefinition rightMember)) 80 | AddToDiff(leftMember, rightMember, result); 81 | else 82 | AddToDiff(leftMember, result); 83 | } 84 | 85 | HashSet rightNestedTypes = new HashSet(right.GetNestedTypes()); 86 | foreach (MstatTypeDefinition leftNestedType in left.GetNestedTypes()) 87 | { 88 | if (rightNestedTypes.TryGetValue(leftNestedType, out MstatTypeDefinition rightType)) 89 | AddToDiff(leftNestedType, rightType, result); 90 | else 91 | AddToDiff(leftNestedType, result); 92 | } 93 | 94 | HashSet rightTypeSpecs = new HashSet(right.GetTypeSpecifications()); 95 | foreach (MstatTypeSpecification leftTypeSpec in left.GetTypeSpecifications()) 96 | { 97 | if (rightTypeSpecs.TryGetValue(leftTypeSpec, out MstatTypeSpecification rightType)) 98 | AddToDiff(leftTypeSpec, rightType, result); 99 | else 100 | AddToDiff(leftTypeSpec, result); 101 | } 102 | 103 | HashSet rightFrozenObjs = new HashSet(right.GetFrozenObjects()); 104 | foreach (MstatFrozenObject o in left.GetFrozenObjects()) 105 | { 106 | if (!rightFrozenObjs.Contains(o)) 107 | { 108 | AddToDiff(o, result); 109 | } 110 | } 111 | } 112 | 113 | private static void AddToDiff(MstatMemberDefinition left, MstatMemberDefinition right, MstatData result) 114 | { 115 | HashSet rightInstantiations = new HashSet(right.GetInstantiations()); 116 | foreach (MstatMethodSpecification leftInstantiation in left.GetInstantiations()) 117 | { 118 | if (!rightInstantiations.Contains(leftInstantiation)) 119 | AddToDiff(leftInstantiation, result); 120 | } 121 | } 122 | 123 | private static void AddToDiff(MstatTypeSpecification left, MstatTypeSpecification right, MstatData result) 124 | { 125 | HashSet rightMembers = new HashSet(right.GetMembers()); 126 | foreach (MstatMemberDefinition leftMember in left.GetMembers()) 127 | { 128 | if (rightMembers.TryGetValue(leftMember, out MstatMemberDefinition rightMember)) 129 | AddToDiff(leftMember, rightMember, result); 130 | else 131 | AddToDiff(leftMember, result); 132 | } 133 | 134 | HashSet rightFrozenObjs = new HashSet(right.GetFrozenObjects()); 135 | foreach (MstatFrozenObject o in left.GetFrozenObjects()) 136 | { 137 | if (!rightFrozenObjs.Contains(o)) 138 | { 139 | AddToDiff(o, result); 140 | } 141 | } 142 | } 143 | 144 | private static void AddToDiff(MstatTypeDefinition t, MstatData result) 145 | { 146 | ref TypeRefRowCache cache = ref result.GetRowCache(t.Handle); 147 | cache.Size = t.Size; 148 | cache.NodeId = t.NodeId + RealNodeIdAddend; 149 | cache.AddSize(result, t.Handle, cache.Size); 150 | 151 | foreach (MstatTypeDefinition nested in t.GetNestedTypes()) 152 | AddToDiff(nested, result); 153 | 154 | foreach (MstatTypeSpecification spec in t.GetTypeSpecifications()) 155 | AddToDiff(spec, result); 156 | 157 | foreach (MstatMemberDefinition m in t.GetMembers()) 158 | AddToDiff(m, result); 159 | 160 | foreach (MstatFrozenObject o in t.GetFrozenObjects()) 161 | AddToDiff(o, result); 162 | } 163 | 164 | private static void AddToDiff(MstatTypeSpecification s, MstatData result) 165 | { 166 | ref TypeSpecRowCache cache = ref result.GetRowCache(s.Handle); 167 | cache.Size = s.Size; 168 | cache.NodeId = s.NodeId + RealNodeIdAddend; 169 | cache.AddSize(result, s.Handle, cache.Size); 170 | 171 | foreach (MstatMemberDefinition m in s.GetMembers()) 172 | AddToDiff(m, result); 173 | 174 | foreach (MstatFrozenObject o in s.GetFrozenObjects()) 175 | AddToDiff(o, result); 176 | } 177 | 178 | private static void AddToDiff(MstatMemberDefinition m, MstatData result) 179 | { 180 | ref MemberRefRowCache cache = ref result.GetRowCache(m.Handle); 181 | cache.Size = m.Size; 182 | cache.NodeId = m.NodeId + RealNodeIdAddend; 183 | cache.AddSize(result, m.Handle, cache.Size); 184 | 185 | foreach (MstatMethodSpecification s in m.GetInstantiations()) 186 | AddToDiff(s, result); 187 | } 188 | 189 | private static void AddToDiff(MstatMethodSpecification s, MstatData result) 190 | { 191 | ref MethodSpecRowCache cache = ref result.GetRowCache(s.Handle); 192 | cache.Size = s.Size; 193 | cache.NodeId = s.NodeId + RealNodeIdAddend; 194 | cache.AddSize(result, s.Handle, cache.Size); 195 | } 196 | 197 | private static void AddToDiff(MstatManifestResource r, MstatData result) 198 | { 199 | ref ManifestResourceRowCache cache = ref result.GetRowCache(r.Handle); 200 | cache.Name = r.Name; 201 | cache.Size = r.Size; 202 | cache.OwningAssembly = r.Assembly.Handle; 203 | 204 | ref AssemblyRefRowCache owningAsmRowCache = ref result.GetRowCache(cache.OwningAssembly); 205 | owningAsmRowCache.AddSize(r.Size); 206 | 207 | cache.NextManifestResource = owningAsmRowCache.FirstManifestResource; 208 | owningAsmRowCache.FirstManifestResource = r.Handle; 209 | } 210 | 211 | private static void AddToDiff(MstatFrozenObject o, MstatData result) 212 | { 213 | ref FrozenObjectRowCache cache = ref result.GetRowCache(o.Handle); 214 | cache.InstanceType = o.InstanceType; 215 | cache.NodeId = o.NodeId; 216 | cache.Size = o.Size; 217 | cache.OwningEntity = o.OwningEntity; 218 | 219 | if (o.OwningEntity.IsNil) 220 | { 221 | cache.NextFrozenObject = result._firstUnownedFrozenObject; 222 | result._firstUnownedFrozenObject = o.Handle; 223 | result._unownedFrozenObjectSize += o.Size; 224 | } 225 | else if (o.OwningEntity.Kind == HandleKind.TypeReference) 226 | { 227 | ref TypeRefRowCache owningCache = ref result.GetRowCache((TypeReferenceHandle)o.OwningEntity); 228 | cache.NextFrozenObject = owningCache.FirstFrozenObject; 229 | owningCache.FirstFrozenObject = o.Handle; 230 | owningCache.AddSize(result, (TypeReferenceHandle)o.OwningEntity, o.Size); 231 | } 232 | else if (o.OwningEntity.Kind == HandleKind.TypeSpecification) 233 | { 234 | ref TypeSpecRowCache owningCache = ref result.GetRowCache((TypeSpecificationHandle)o.OwningEntity); 235 | cache.NextFrozenObject = owningCache.FirstFrozenObject; 236 | owningCache.FirstFrozenObject = o.Handle; 237 | owningCache.AddSize(result, (TypeSpecificationHandle)o.OwningEntity, o.Size); 238 | } 239 | } 240 | 241 | private struct SignatureEqualityComparer 242 | { 243 | private readonly MetadataReader _reader1; 244 | private BlobReader _blob1; 245 | private readonly MetadataReader _reader2; 246 | private BlobReader _blob2; 247 | 248 | private SignatureEqualityComparer(MetadataReader reader1, BlobReader blob1, MetadataReader reader2, BlobReader blob2) 249 | => (_reader1, _blob1, _reader2, _blob2) = (reader1, blob1, reader2, blob2); 250 | 251 | public static bool AreMethodSignaturesEqual(MetadataReader reader1, BlobReader blob1, MetadataReader reader2, BlobReader blob2) 252 | => new SignatureEqualityComparer(reader1, blob1, reader2, blob2).AreSignaturesEqual(); 253 | 254 | public static bool AreMethodSpecSignaturesEqual(MetadataReader reader1, BlobReader blob1, MetadataReader reader2, BlobReader blob2) 255 | => new SignatureEqualityComparer(reader1, blob1, reader2, blob2).AreMethodSpecSignaturesEqual(); 256 | 257 | public static bool AreTypeSignaturesEqual(MetadataReader reader1, BlobReader blob1, MetadataReader reader2, BlobReader blob2) 258 | => new SignatureEqualityComparer(reader1, blob1, reader2, blob2).AreTypeSignaturesEqual(); 259 | 260 | private bool AreSignaturesEqual() 261 | { 262 | SignatureHeader header1 = _blob1.ReadSignatureHeader(); 263 | SignatureHeader header2 = _blob2.ReadSignatureHeader(); 264 | 265 | if (header1 != header2) 266 | return false; 267 | 268 | if (header1.Kind == SignatureKind.Method) 269 | return AreMethodSignaturesEqual(header1, header2); 270 | 271 | Debug.Assert(header1.Kind == SignatureKind.Field); 272 | return AreTypeSignaturesEqual(); 273 | } 274 | 275 | private bool AreMethodSignaturesEqual(SignatureHeader header1, SignatureHeader header2) 276 | { 277 | Debug.Assert(header1.Kind == SignatureKind.Method); 278 | 279 | if (header1.IsGeneric 280 | && _blob1.ReadCompressedInteger() != _blob2.ReadCompressedInteger()) 281 | return false; 282 | 283 | int numParams = _blob1.ReadCompressedInteger(); 284 | if (_blob2.ReadCompressedInteger() != numParams) 285 | return false; 286 | 287 | for (int i = 0; i < numParams + 1; i++) 288 | if (!AreTypeSignaturesEqual()) 289 | return false; 290 | 291 | return true; 292 | } 293 | 294 | private bool AreMethodSpecSignaturesEqual() 295 | { 296 | SignatureHeader header1 = _blob1.ReadSignatureHeader(); 297 | SignatureHeader header2 = _blob2.ReadSignatureHeader(); 298 | 299 | if (header1 != header2) 300 | return false; 301 | 302 | Debug.Assert(header1.Kind == SignatureKind.MethodSpecification); 303 | 304 | int numParams = _blob1.ReadCompressedInteger(); 305 | if (_blob2.ReadCompressedInteger() != numParams) 306 | return false; 307 | 308 | for (int i = 0; i < numParams + 1; i++) 309 | if (!AreTypeSignaturesEqual()) 310 | return false; 311 | 312 | return true; 313 | } 314 | 315 | private bool AreTypeSignaturesEqual() 316 | { 317 | SignatureTypeCode typeCode = _blob1.ReadSignatureTypeCode(); 318 | if (_blob2.ReadSignatureTypeCode() != typeCode) 319 | return false; 320 | 321 | return typeCode switch 322 | { 323 | <= SignatureTypeCode.String or SignatureTypeCode.TypedReference or SignatureTypeCode.IntPtr or SignatureTypeCode.UIntPtr or SignatureTypeCode.Object => true, 324 | SignatureTypeCode.Pointer or SignatureTypeCode.ByReference or SignatureTypeCode.SZArray => AreTypeSignaturesEqual(), 325 | SignatureTypeCode.Array => AreArrayTypeSignaturesEqual(), 326 | SignatureTypeCode.GenericMethodParameter or SignatureTypeCode.GenericTypeParameter => _blob1.ReadCompressedInteger() == _blob2.ReadCompressedInteger(), 327 | SignatureTypeCode.GenericTypeInstance => AreGenericTypeInstancesEqual(), 328 | SignatureTypeCode.FunctionPointer => AreSignaturesEqual(), 329 | SignatureTypeCode.RequiredModifier or SignatureTypeCode.OptionalModifier => AreTypeHandlesEqual() && AreTypeSignaturesEqual(), 330 | SignatureTypeCode.TypeHandle => AreTypeHandlesEqual(), 331 | _ => throw new Exception(typeCode.ToString()), 332 | }; 333 | } 334 | 335 | private bool AreArrayTypeSignaturesEqual() 336 | { 337 | if (!AreTypeSignaturesEqual()) 338 | return false; 339 | 340 | int rank = _blob1.ReadCompressedInteger(); 341 | if (_blob2.ReadCompressedInteger() != rank) 342 | return false; 343 | 344 | int boundsCount = _blob1.ReadCompressedInteger(); 345 | if (_blob2.ReadCompressedInteger() != boundsCount) 346 | return false; 347 | 348 | for (int i = 0; i < 2; i++) 349 | { 350 | for (int j = 0; j < boundsCount; i++) 351 | if (_blob1.ReadCompressedInteger() != _blob2.ReadCompressedInteger()) 352 | return false; 353 | } 354 | 355 | return true; 356 | } 357 | 358 | private bool AreGenericTypeInstancesEqual() 359 | { 360 | if (!AreTypeSignaturesEqual()) 361 | return false; 362 | 363 | int numArguments = _blob1.ReadCompressedInteger(); 364 | if (_blob2.ReadCompressedInteger() != numArguments) 365 | return false; 366 | 367 | for (int i = 0; i < numArguments; i++) 368 | if (!AreTypeSignaturesEqual()) 369 | return false; 370 | 371 | return true; 372 | } 373 | 374 | private bool AreTypeHandlesEqual() 375 | { 376 | EntityHandle handle1 = _blob1.ReadTypeHandle(); 377 | EntityHandle handle2 = _blob2.ReadTypeHandle(); 378 | 379 | if (handle1.Kind != handle2.Kind) 380 | return false; 381 | 382 | if (handle1.Kind == HandleKind.TypeReference) 383 | return TypeReferenceComparer.AreEqual(_reader1, (TypeReferenceHandle)handle1, _reader2, (TypeReferenceHandle)handle2); 384 | 385 | TypeSpecification typeSpec1 = _reader1.GetTypeSpecification((TypeSpecificationHandle)handle1); 386 | TypeSpecification typeSpec2 = _reader2.GetTypeSpecification((TypeSpecificationHandle)handle2); 387 | 388 | return new SignatureEqualityComparer(_reader1, _reader1.GetBlobReader(typeSpec1.Signature), _reader2, _reader2.GetBlobReader(typeSpec2.Signature)) 389 | .AreTypeSignaturesEqual(); 390 | } 391 | } 392 | 393 | private struct TypeReferenceComparer 394 | { 395 | public static bool AreEqual(MetadataReader reader1, TypeReferenceHandle handle1, MetadataReader reader2, TypeReferenceHandle handle2) 396 | { 397 | TypeReference typeRef1 = reader1.GetTypeReference(handle1); 398 | TypeReference typeRef2 = reader2.GetTypeReference(handle2); 399 | if (typeRef1.ResolutionScope.Kind != typeRef2.ResolutionScope.Kind) 400 | return false; 401 | 402 | string name = reader1.GetString(typeRef1.Name); 403 | if (!reader2.StringComparer.Equals(typeRef2.Name, name)) 404 | return false; 405 | 406 | if (typeRef1.ResolutionScope.Kind == HandleKind.AssemblyReference) 407 | { 408 | string ns = reader1.GetString(typeRef1.Namespace); 409 | if (!reader2.StringComparer.Equals(typeRef2.Namespace, ns)) 410 | return false; 411 | 412 | AssemblyReference asmRef1 = reader1.GetAssemblyReference((AssemblyReferenceHandle)typeRef1.ResolutionScope); 413 | AssemblyReference asmRef2 = reader2.GetAssemblyReference((AssemblyReferenceHandle)typeRef2.ResolutionScope); 414 | 415 | string asmName = reader1.GetString(asmRef1.Name); 416 | return reader2.StringComparer.Equals(asmRef2.Name, asmName); 417 | } 418 | 419 | return AreEqual(reader1, (TypeReferenceHandle)typeRef1.ResolutionScope, reader2, (TypeReferenceHandle)typeRef2.ResolutionScope); 420 | } 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /sizoscope/MstatData.Enumeration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Reflection.Metadata; 3 | using System.Reflection.Metadata.Ecma335; 4 | 5 | partial class MstatData 6 | { 7 | public Enumerator GetScopes() 8 | => new Enumerator(this, MetadataTokens.AssemblyReferenceHandle(1)); 9 | 10 | public Enumerator GetFrozenObjects() 11 | => new Enumerator(this, _firstUnownedFrozenObject); 12 | 13 | public interface ICanMoveNext 14 | { 15 | bool IsNil(THandle handle); 16 | THandle MoveNext(MstatData data, THandle current); 17 | TRecord GetCurrent(MstatData data, THandle handle); 18 | } 19 | 20 | public struct MoveToNextScope : ICanMoveNext 21 | { 22 | public bool IsNil(AssemblyReferenceHandle handle) => handle.IsNil; 23 | public MstatAssembly GetCurrent(MstatData data, AssemblyReferenceHandle handle) => new MstatAssembly(data, handle); 24 | public AssemblyReferenceHandle MoveNext(MstatData data, AssemblyReferenceHandle current) 25 | => MetadataTokens.GetRowNumber(current) < data.MetadataReader.AssemblyReferences.Count ? 26 | MetadataTokens.AssemblyReferenceHandle(MetadataTokens.GetRowNumber(current) + 1) : default; 27 | } 28 | 29 | public struct MoveToNextInScope : ICanMoveNext 30 | { 31 | public bool IsNil(TypeReferenceHandle handle) => handle.IsNil; 32 | public MstatTypeDefinition GetCurrent(MstatData data, TypeReferenceHandle current) => new MstatTypeDefinition(data, current); 33 | public TypeReferenceHandle MoveNext(MstatData data, TypeReferenceHandle current) => data.GetRowCache(current).NextTypeInScope; 34 | } 35 | 36 | public struct MoveToNextSpecOfType : ICanMoveNext 37 | { 38 | public bool IsNil(TypeSpecificationHandle handle) => handle.IsNil; 39 | public MstatTypeSpecification GetCurrent(MstatData data, TypeSpecificationHandle handle) => new MstatTypeSpecification(data, handle); 40 | public TypeSpecificationHandle MoveNext(MstatData data, TypeSpecificationHandle current) => data.GetRowCache(current).NextTypeSpec; 41 | } 42 | 43 | public struct MoveToNextMemberOfType : ICanMoveNext 44 | { 45 | public bool IsNil(MemberReferenceHandle handle) => handle.IsNil; 46 | public MstatMemberDefinition GetCurrent(MstatData data, MemberReferenceHandle handle) => new MstatMemberDefinition(data, handle); 47 | public MemberReferenceHandle MoveNext(MstatData data, MemberReferenceHandle current) => data.GetRowCache(current).NextMemberRef; 48 | } 49 | 50 | public struct MoveToNextSpecOfMethod : ICanMoveNext 51 | { 52 | public bool IsNil(MethodSpecificationHandle handle) => handle.IsNil; 53 | public MstatMethodSpecification GetCurrent(MstatData data, MethodSpecificationHandle handle) => new MstatMethodSpecification(data, handle); 54 | public MethodSpecificationHandle MoveNext(MstatData data, MethodSpecificationHandle current) => data.GetRowCache(current).NextMethodSpec; 55 | } 56 | 57 | public struct MoveToNextManifestResource : ICanMoveNext 58 | { 59 | public bool IsNil(ManifestResourceHandle handle) => handle.IsNil; 60 | public MstatManifestResource GetCurrent(MstatData data, ManifestResourceHandle handle) => new MstatManifestResource(data, handle); 61 | public ManifestResourceHandle MoveNext(MstatData data, ManifestResourceHandle current) => data.GetRowCache(current).NextManifestResource; 62 | } 63 | 64 | public struct MoveToNextFrozenObject : ICanMoveNext 65 | { 66 | public bool IsNil(FrozenObjectHandle handle) => handle == 0; 67 | public MstatFrozenObject GetCurrent(MstatData data, FrozenObjectHandle handle) => new MstatFrozenObject(data, handle); 68 | public FrozenObjectHandle MoveNext(MstatData data, FrozenObjectHandle current) => data.GetRowCache(current).NextFrozenObject; 69 | } 70 | 71 | public struct Enumerator : IEnumerable, IEnumerator where TNext : struct, ICanMoveNext 72 | { 73 | private readonly MstatData _data; 74 | private readonly THandle _first; 75 | private THandle _current; 76 | 77 | public Enumerator(MstatData data, THandle first) 78 | => (_data, _first, _current) = (data, first, default); 79 | 80 | public TRecord Current => default(TNext).GetCurrent(_data, _current); 81 | 82 | object IEnumerator.Current => Current; 83 | 84 | public bool MoveNext() 85 | { 86 | if (default(TNext).IsNil(_current)) 87 | { 88 | if (default(TNext).IsNil(_first)) 89 | return false; 90 | _current = _first; 91 | } 92 | else 93 | { 94 | THandle next = default(TNext).MoveNext(_data, _current); 95 | if (default(TNext).IsNil(next)) 96 | return false; 97 | _current = next; 98 | } 99 | return true; 100 | } 101 | 102 | public void Reset() => _current = default; 103 | 104 | public Enumerator GetEnumerator() => this; 105 | 106 | IEnumerator IEnumerable.GetEnumerator() => this; 107 | 108 | IEnumerator IEnumerable.GetEnumerator() => this; 109 | 110 | void IDisposable.Dispose() { } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /sizoscope/MstatData.Graph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Drawing; 4 | using System.Reflection.Metadata; 5 | using System.Reflection.PortableExecutable; 6 | using System.Xml; 7 | using System.Xml.Linq; 8 | using TurboXml; 9 | 10 | partial class MstatData 11 | { 12 | private Dictionary _nameToNode; 13 | 14 | public bool DgmlSupported => _version.Major >= 2; 15 | public bool DgmlAvailable => DgmlSupported && _nameToNode != null; 16 | 17 | private string GetNameForId(int id) 18 | { 19 | PEMemoryBlock nameMap = _peReader.GetSectionData(".names"); 20 | BlobReader nameMapReader = nameMap.GetReader(); 21 | nameMapReader.Offset = id; 22 | return nameMapReader.ReadSerializedString(); 23 | } 24 | 25 | public Node GetNodeForId(int id, out string name) 26 | { 27 | name = GetNameForId(id); 28 | return _nameToNode.GetValueOrDefault(name); 29 | } 30 | 31 | public bool ContainsNamedNode(string name) => _nameToNode.ContainsKey(name); 32 | 33 | struct Reader : IXmlReadHandler 34 | { 35 | enum ReadMode { None, Nodes, Links } 36 | 37 | private readonly Dictionary _nameToNode = new Dictionary(StringComparer.Ordinal); 38 | private readonly Dictionary _idToNode = new Dictionary(); 39 | 40 | private ReadMode _readMode; 41 | 42 | private int _id; 43 | private string _name; 44 | 45 | private int _source; 46 | private int _target; 47 | private string _reason; 48 | 49 | public Reader() { } 50 | 51 | public Dictionary ToDictionary() => _nameToNode; 52 | 53 | void IXmlReadHandler.OnAttribute(ReadOnlySpan name, ReadOnlySpan value, int nameLine, int nameColumn, int valueLine, int valueColumn) 54 | { 55 | if (_readMode == ReadMode.Links) 56 | { 57 | if (name.Equals("Source", StringComparison.Ordinal)) 58 | _source = int.Parse(value); 59 | else if (name.Equals("Target", StringComparison.Ordinal)) 60 | _target = int.Parse(value); 61 | else if (name.Equals("Reason", StringComparison.Ordinal)) 62 | _reason = new string(value); 63 | } 64 | else if (_readMode == ReadMode.Nodes) 65 | { 66 | if (name.Equals("Id", StringComparison.Ordinal)) 67 | _id = int.Parse(value); 68 | else if (name.Equals("Label", StringComparison.Ordinal)) 69 | _name = new string(value); 70 | } 71 | } 72 | 73 | void IXmlReadHandler.OnEndTagEmpty() 74 | { 75 | if (_readMode == ReadMode.Links) 76 | { 77 | _idToNode[_target].Edges.Add((_idToNode[_source], _reason)); 78 | } 79 | else if (_readMode == ReadMode.Nodes) 80 | { 81 | var n = new Node(_name); 82 | _idToNode[_id] = n; 83 | _nameToNode[_name] = n; 84 | } 85 | } 86 | 87 | void IXmlReadHandler.OnEndTag(ReadOnlySpan name, int line, int column) 88 | { 89 | if (name.Equals("Nodes", StringComparison.Ordinal) || name.Equals("Links", StringComparison.Ordinal)) 90 | { 91 | _readMode = ReadMode.None; 92 | } 93 | } 94 | 95 | void IXmlReadHandler.OnBeginTag(ReadOnlySpan name, int line, int column) 96 | { 97 | if (_readMode == ReadMode.None) 98 | { 99 | if (name.Equals("Nodes", StringComparison.Ordinal)) 100 | _readMode = ReadMode.Nodes; 101 | else if (name.Equals("Links", StringComparison.Ordinal)) 102 | _readMode = ReadMode.Links; 103 | } 104 | } 105 | 106 | void IXmlReadHandler.OnCData(ReadOnlySpan cdata, int line, int column) { } 107 | void IXmlReadHandler.OnComment(ReadOnlySpan comment, int line, int column) { } 108 | void IXmlReadHandler.OnText(ReadOnlySpan text, int line, int column) { } 109 | 110 | void IXmlReadHandler.OnXmlDeclaration(ReadOnlySpan version, ReadOnlySpan encoding, ReadOnlySpan standalone, int line, int column) 111 | { } 112 | void IXmlReadHandler.OnError(string message, int line, int column) => throw new Exception($"{message} ({line}:{column})"); 113 | } 114 | 115 | private void TryLoadAssociatedDgmlFile(string fileName) 116 | { 117 | fileName = Path.ChangeExtension(fileName, "scan.dgml.xml"); 118 | 119 | if (!File.Exists(fileName)) 120 | return; 121 | 122 | #if true 123 | using FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); 124 | var handler = new Reader(); 125 | XmlParser.Parse(fs, ref handler); 126 | _nameToNode = handler.ToDictionary(); 127 | #elif false 128 | var idToNode = new Dictionary(); 129 | _nameToNode = new Dictionary(StringComparer.Ordinal); 130 | using (XmlReader reader = XmlReader.Create(fileName)) 131 | { 132 | while (reader.Read()) 133 | { 134 | if (reader.NodeType == XmlNodeType.Element && reader.Name == "Node") 135 | { 136 | int id = 0; 137 | string name = null; 138 | bool cont = reader.MoveToFirstAttribute(); 139 | while (cont) 140 | { 141 | string attributeName = reader.Name; 142 | if (attributeName == "Id") 143 | id = int.Parse(reader.Value); 144 | if (attributeName == "Label") 145 | name = reader.Value; 146 | cont = reader.MoveToNextAttribute(); 147 | } 148 | 149 | var n = new Node(name); 150 | idToNode[id] = n; 151 | _nameToNode[name] = n; 152 | } 153 | if (reader.NodeType == XmlNodeType.Element && reader.Name == "Link") 154 | { 155 | int source = 0; 156 | int target = 0; 157 | string reason = null; 158 | bool cont = reader.MoveToFirstAttribute(); 159 | while (cont) 160 | { 161 | string attributeName = reader.Name; 162 | if (attributeName == "Source") 163 | source = int.Parse(reader.Value); 164 | if (attributeName == "Target") 165 | target = int.Parse(reader.Value); 166 | if (attributeName == "Reason") 167 | reason = reader.Value; 168 | cont = reader.MoveToNextAttribute(); 169 | } 170 | 171 | idToNode[target].Edges.Add((idToNode[source], reason)); 172 | } 173 | } 174 | } 175 | #else 176 | var directedGraph = XElement.Load(fileName); 177 | 178 | var idToNode = new Dictionary(); 179 | _nameToNode = new Dictionary(StringComparer.Ordinal); 180 | var nodes = directedGraph.Elements().Single(e => e.Name.LocalName == "Nodes"); 181 | foreach (var node in nodes.Elements()) 182 | { 183 | Debug.Assert(node.Name.LocalName == "Node"); 184 | int id = int.Parse(node.Attribute("Id").Value); 185 | string name = node.Attribute("Label").Value; 186 | var n = new Node(name); 187 | idToNode[id] = n; 188 | _nameToNode[name] = n; 189 | } 190 | 191 | var links = directedGraph.Elements().Single(e => e.Name.LocalName == "Links"); 192 | foreach (var link in links.Elements()) 193 | { 194 | int source = int.Parse(link.Attribute("Source").Value); 195 | int target = int.Parse(link.Attribute("Target").Value); 196 | string reason = link.Attribute("Reason").Value; 197 | idToNode[target].Edges.Add((idToNode[source], reason)); 198 | } 199 | #endif 200 | } 201 | 202 | 203 | public class Node 204 | { 205 | public readonly string Name; 206 | public readonly List<(Node Node, string Label)> Edges; 207 | 208 | public Node(string name) 209 | { 210 | Name = name; 211 | Edges = new List<(Node, string)>(); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /sizoscope/MstatData.ObjectModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Metadata; 2 | using System.Reflection.Metadata.Ecma335; 3 | using System.Text; 4 | 5 | partial class MstatData 6 | { 7 | public struct MstatTypeDefinition : IEquatable 8 | { 9 | private readonly MstatData _data; 10 | public readonly TypeReferenceHandle Handle; 11 | 12 | public MstatTypeDefinition(MstatData data, TypeReferenceHandle handle) 13 | => (_data, Handle) = (data, handle); 14 | 15 | public string Name => _data._typeRefNameCache[MetadataTokens.GetRowNumber(Handle)].Name ?? throw new Exception(); 16 | public string Namespace => _data._typeRefNameCache[MetadataTokens.GetRowNumber(Handle)].Namespace ?? throw new Exception(); 17 | public int Size => _data.GetRowCache(Handle).Size; 18 | public int NodeId => _data.GetRowCache(Handle).NodeId - RealNodeIdAddend; 19 | public int AggregateSize => _data.GetRowCache(Handle).AggregateSize; 20 | public Enumerator GetNestedTypes() 21 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstNestedType); 22 | public Enumerator GetTypeSpecifications() 23 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstTypeSpec); 24 | public Enumerator GetMembers() 25 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstMember); 26 | public Enumerator GetFrozenObjects() 27 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstFrozenObject); 28 | 29 | public override string ToString() => ToString(FormatOptions.NamespaceQualify); 30 | public string ToString(FormatOptions options) => _data._formatter.FormatName(new StringBuilder(), Handle, options).ToString(); 31 | 32 | public override int GetHashCode() => _data.GetRowCache(Handle).HashCode; 33 | 34 | public bool Equals(MstatTypeDefinition other) 35 | { 36 | if (_data == other._data) 37 | return Handle == other.Handle; 38 | 39 | if (Name != other.Name 40 | || Namespace != other.Namespace) 41 | return false; 42 | 43 | TypeReference thisTypeRef = this._data.MetadataReader.GetTypeReference(Handle); 44 | TypeReference otherTypeRef = other._data.MetadataReader.GetTypeReference(other.Handle); 45 | if (thisTypeRef.ResolutionScope.Kind != otherTypeRef.ResolutionScope.Kind) 46 | return false; 47 | 48 | if (thisTypeRef.ResolutionScope.Kind == HandleKind.AssemblyReference) 49 | { 50 | string thisAsmName = this._data.MetadataReader.GetString(this._data.MetadataReader.GetAssemblyReference((AssemblyReferenceHandle)thisTypeRef.ResolutionScope).Name); 51 | return other._data.MetadataReader.StringComparer.Equals(other._data.MetadataReader.GetAssemblyReference((AssemblyReferenceHandle)otherTypeRef.ResolutionScope).Name, thisAsmName); 52 | } 53 | else 54 | { 55 | return new MstatTypeDefinition(_data, (TypeReferenceHandle)thisTypeRef.ResolutionScope).Equals( 56 | new MstatTypeDefinition(other._data, (TypeReferenceHandle)otherTypeRef.ResolutionScope)); 57 | } 58 | } 59 | } 60 | 61 | public struct MstatTypeSpecification : IEquatable 62 | { 63 | private readonly MstatData _data; 64 | public readonly TypeSpecificationHandle Handle; 65 | 66 | public MstatTypeSpecification(MstatData data, TypeSpecificationHandle handle) 67 | => (_data, Handle) = (data, handle); 68 | 69 | public int Size => _data.GetRowCache(Handle).Size; 70 | public int AggregateSize => _data.GetRowCache(Handle).AggregateSize; 71 | public int NodeId => _data.GetRowCache(Handle).NodeId - RealNodeIdAddend; 72 | 73 | public Enumerator GetMembers() 74 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstMember); 75 | 76 | public Enumerator GetFrozenObjects() 77 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstFrozenObject); 78 | 79 | public override string ToString() => ToString(FormatOptions.NamespaceQualify); 80 | public string ToString(FormatOptions options) => _data._formatter.FormatName(new StringBuilder(), Handle, options).ToString(); 81 | 82 | public override int GetHashCode() => _data.GetRowCache(Handle).HashCode; 83 | 84 | public bool Equals(MstatTypeSpecification other) 85 | { 86 | if (_data == other._data) 87 | return Handle == other.Handle; 88 | 89 | BlobReader thisTypeSpecBlob = _data.MetadataReader.GetBlobReader(_data.MetadataReader.GetTypeSpecification(Handle).Signature); 90 | BlobReader otherTypeSpecBlob = other._data.MetadataReader.GetBlobReader(other._data.MetadataReader.GetTypeSpecification(other.Handle).Signature); 91 | 92 | return SignatureEqualityComparer.AreTypeSignaturesEqual(_data.MetadataReader, thisTypeSpecBlob, other._data.MetadataReader, otherTypeSpecBlob); 93 | } 94 | } 95 | 96 | public struct MstatMemberDefinition : IEquatable 97 | { 98 | private readonly MstatData _data; 99 | public readonly MemberReferenceHandle Handle; 100 | 101 | public MstatMemberDefinition(MstatData data, MemberReferenceHandle handle) 102 | => (_data, Handle) = (data, handle); 103 | 104 | public int Size => _data.GetRowCache(Handle).Size; 105 | public int AggregateSize => _data.GetRowCache(Handle).AggregateSize; 106 | public string Name => _data._memberRefNameCache[MetadataTokens.GetRowNumber(Handle)]; 107 | public int NodeId => _data.GetRowCache(Handle).NodeId - RealNodeIdAddend; 108 | 109 | public bool IsField 110 | { 111 | get 112 | { 113 | MemberReference memberRef = _data.MetadataReader.GetMemberReference(Handle); 114 | return _data.MetadataReader.GetBlobReader(memberRef.Signature).ReadSignatureHeader().Kind == SignatureKind.Field; 115 | } 116 | } 117 | 118 | public MstatTypeDefinition OwnerAsDef 119 | { 120 | get 121 | { 122 | MemberReference memberRef = _data.MetadataReader.GetMemberReference(Handle); 123 | if (memberRef.Parent.Kind == HandleKind.TypeReference) 124 | return new MstatTypeDefinition(_data, (TypeReferenceHandle)memberRef.Parent); 125 | return default; 126 | } 127 | } 128 | 129 | public MstatTypeSpecification OwnerAsSpec 130 | { 131 | get 132 | { 133 | MemberReference memberRef = _data.MetadataReader.GetMemberReference(Handle); 134 | if (memberRef.Parent.Kind == HandleKind.TypeSpecification) 135 | return new MstatTypeSpecification(_data, (TypeSpecificationHandle)memberRef.Parent); 136 | return default; 137 | } 138 | } 139 | 140 | public Enumerator GetInstantiations() 141 | => new Enumerator(_data, _data.GetRowCache(Handle).FirstMethodSpec); 142 | 143 | public override string ToString() => _data._formatter.FormatMember(new StringBuilder(), Handle).ToString(); 144 | 145 | public string ToQualifiedString() 146 | { 147 | var sb = new StringBuilder(); 148 | MstatTypeDefinition ownerAsDef = OwnerAsDef; 149 | if (!ownerAsDef.Handle.IsNil) 150 | _data._formatter.FormatName(sb, ownerAsDef.Handle, FormatOptions.NamespaceQualify); 151 | else 152 | _data._formatter.FormatName(sb, OwnerAsSpec.Handle, FormatOptions.NamespaceQualify); 153 | sb.Append('.'); 154 | return _data._formatter.FormatMember(sb, Handle).ToString(); 155 | } 156 | 157 | public override int GetHashCode() => _data.GetRowCache(Handle).HashCode; 158 | 159 | public bool Equals(MstatMemberDefinition other) 160 | { 161 | if (_data == other._data) 162 | return Handle == other.Handle; 163 | 164 | MemberReference thisMemberRef = _data.MetadataReader.GetMemberReference(Handle); 165 | MemberReference otherMemberRef = other._data.MetadataReader.GetMemberReference(other.Handle); 166 | 167 | if (thisMemberRef.Parent.Kind != otherMemberRef.Parent.Kind || Name != other.Name) 168 | return false; 169 | 170 | BlobReader thisSigBlob = _data.MetadataReader.GetBlobReader(thisMemberRef.Signature); 171 | BlobReader otherSigBlob = other._data.MetadataReader.GetBlobReader(otherMemberRef.Signature); 172 | 173 | // TODO: is this legit? 174 | //if (thisSigBlob.RemainingBytes != otherSigBlob.RemainingBytes) 175 | // return false; 176 | 177 | if (thisMemberRef.Parent.Kind == HandleKind.TypeReference) 178 | { 179 | if (!this.OwnerAsDef.Equals(other.OwnerAsDef)) 180 | return false; 181 | } 182 | else 183 | { 184 | if (!this.OwnerAsSpec.Equals(other.OwnerAsSpec)) 185 | return false; 186 | } 187 | 188 | return SignatureEqualityComparer.AreMethodSignaturesEqual( 189 | _data.MetadataReader, thisSigBlob, other._data.MetadataReader, otherSigBlob); 190 | } 191 | } 192 | 193 | public struct MstatMethodSpecification : IEquatable 194 | { 195 | private readonly MstatData _data; 196 | public readonly MethodSpecificationHandle Handle; 197 | 198 | public MstatMethodSpecification(MstatData data, MethodSpecificationHandle handle) 199 | => (_data, Handle) = (data, handle); 200 | 201 | public int Size => _data.GetRowCache(Handle).Size; 202 | public int NodeId => _data.GetRowCache(Handle).NodeId - RealNodeIdAddend; 203 | 204 | public override string ToString() => _data._formatter.FormatMember(new StringBuilder(), Handle).ToString(); 205 | 206 | public override int GetHashCode() => _data.GetRowCache(Handle).HashCode; 207 | 208 | public bool Equals(MstatMethodSpecification other) 209 | { 210 | if (_data == other._data) 211 | return Handle == other.Handle; 212 | 213 | MethodSpecification thisMethodSpec = _data.MetadataReader.GetMethodSpecification(Handle); 214 | MethodSpecification otherMethodSpec = other._data.MetadataReader.GetMethodSpecification(other.Handle); 215 | 216 | if (!new MstatMemberDefinition(_data, (MemberReferenceHandle)thisMethodSpec.Method) 217 | .Equals(new MstatMemberDefinition(other._data, (MemberReferenceHandle)otherMethodSpec.Method))) 218 | return false; 219 | 220 | return SignatureEqualityComparer.AreMethodSpecSignaturesEqual( 221 | _data.MetadataReader, _data.MetadataReader.GetBlobReader(thisMethodSpec.Signature), 222 | other._data.MetadataReader, other._data.MetadataReader.GetBlobReader(otherMethodSpec.Signature)); 223 | } 224 | } 225 | 226 | public struct MstatAssembly : IEquatable 227 | { 228 | private readonly MstatData _data; 229 | private readonly AssemblyReferenceHandle _handle; 230 | 231 | public AssemblyReferenceHandle Handle => _handle; 232 | 233 | public string Name => _data.MetadataReader.GetString(_data.MetadataReader.GetAssemblyReference(_handle).Name); 234 | 235 | public MstatAssembly(MstatData data, AssemblyReferenceHandle handle) 236 | => (_data, _handle) = (data, handle); 237 | 238 | public int AggregateSize => _data.GetRowCache(_handle).AggregateSize; 239 | 240 | public Enumerator GetTypes() 241 | => new Enumerator(_data, _data.GetRowCache(_handle).FirstTypeRef); 242 | 243 | public Enumerator GetManifestResources() 244 | => new Enumerator(_data, _data.GetRowCache(_handle).FirstManifestResource); 245 | 246 | public override int GetHashCode() => Name.GetHashCode(); 247 | 248 | public bool Equals(MstatAssembly other) 249 | { 250 | if (_data == other._data) 251 | return _handle == other._handle; 252 | 253 | return Name == other.Name; 254 | } 255 | } 256 | 257 | public struct MstatManifestResource : IEquatable 258 | { 259 | private readonly MstatData _data; 260 | private readonly ManifestResourceHandle _handle; 261 | 262 | public ManifestResourceHandle Handle => _handle; 263 | public string Name => _data.GetRowCache(_handle).Name; 264 | public int Size => _data.GetRowCache(_handle).Size; 265 | public MstatAssembly Assembly => new MstatAssembly(_data, _data.GetRowCache(_handle).OwningAssembly); 266 | 267 | public MstatManifestResource(MstatData data, ManifestResourceHandle handle) 268 | => (_data, _handle) = (data, handle); 269 | 270 | public bool Equals(MstatManifestResource other) 271 | { 272 | if (_data == other._data) 273 | return _handle == other._handle; 274 | 275 | return Assembly.Equals(other.Assembly) && 276 | Name == other.Name; 277 | } 278 | 279 | public override int GetHashCode() 280 | { 281 | return Name.GetHashCode(); 282 | } 283 | } 284 | 285 | public struct MstatFrozenObject : IEquatable 286 | { 287 | private readonly MstatData _data; 288 | private readonly FrozenObjectHandle _handle; 289 | 290 | public MstatFrozenObject(MstatData data, FrozenObjectHandle handle) 291 | => (_data, _handle) = (data, handle); 292 | 293 | public FrozenObjectHandle Handle => _handle; 294 | 295 | public int Size => _data.GetRowCache(_handle).Size; 296 | 297 | public int NodeId => _data.GetRowCache(_handle).NodeId; 298 | 299 | public EntityHandle OwningEntity => _data.GetRowCache(_handle).OwningEntity; 300 | 301 | public EntityHandle InstanceType => _data.GetRowCache(_handle).InstanceType; 302 | 303 | public override string ToString() 304 | { 305 | EntityHandle type = _data.GetRowCache(_handle).InstanceType; 306 | return _data._formatter.FormatName(new StringBuilder(), type).ToString(); 307 | } 308 | 309 | public override int GetHashCode() 310 | { 311 | return _data.GetNameForId(NodeId).GetHashCode(); 312 | } 313 | 314 | public bool Equals(MstatFrozenObject other) 315 | { 316 | if (_data == other._data) 317 | return NodeId == other.NodeId; 318 | 319 | return this._data.GetNameForId(this.NodeId) 320 | == other._data.GetNameForId(other.NodeId); 321 | } 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /sizoscope/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace sizoscope 4 | { 5 | internal static class Program 6 | { 7 | /// 8 | /// The main entry point for the application. 9 | /// 10 | [STAThread] 11 | static void Main(string[] args) 12 | { 13 | // To customize application configuration such as set high DPI settings or default font, 14 | // see https://aka.ms/applicationconfiguration. 15 | ApplicationConfiguration.Initialize(); 16 | 17 | #pragma warning disable WFO5001 18 | Application.SetColorMode(SystemColorMode.System); 19 | #pragma warning restore 20 | 21 | MainForm form; 22 | if (args.Length > 0 && File.Exists(args[0])) 23 | form = new MainForm(args[0]); 24 | else 25 | form = new MainForm(); 26 | 27 | Application.Run(form); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /sizoscope/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace sizoscope.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("sizoscope.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sizoscope/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /sizoscope/RootForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace sizoscope 2 | { 3 | partial class RootForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | _tree = new TreeView(); 32 | SuspendLayout(); 33 | // 34 | // _tree 35 | // 36 | _tree.Dock = DockStyle.Fill; 37 | _tree.Location = new Point(0, 0); 38 | _tree.Name = "_tree"; 39 | _tree.Size = new Size(502, 335); 40 | _tree.TabIndex = 0; 41 | // 42 | // RootForm 43 | // 44 | AutoScaleDimensions = new SizeF(7F, 15F); 45 | AutoScaleMode = AutoScaleMode.Font; 46 | ClientSize = new Size(502, 335); 47 | Controls.Add(_tree); 48 | Name = "RootForm"; 49 | Text = "RootForm"; 50 | ResumeLayout(false); 51 | } 52 | 53 | #endregion 54 | 55 | private TreeView _tree; 56 | } 57 | } -------------------------------------------------------------------------------- /sizoscope/RootForm.cs: -------------------------------------------------------------------------------- 1 | namespace sizoscope 2 | { 3 | public partial class RootForm : Form 4 | { 5 | public RootForm() 6 | { 7 | InitializeComponent(); 8 | } 9 | 10 | public RootForm(MstatData.Node node, MstatData compare = null) 11 | : this() 12 | { 13 | Text = $"Path from roots to {node.Name}"; 14 | 15 | _tree.BeginUpdate(); 16 | _tree.Nodes.Add(CreateTree(compare, node)); 17 | _tree.EndUpdate(); 18 | 19 | if (_tree.GetNodeCount(true) < 100000) 20 | _tree.ExpandAll(); 21 | } 22 | 23 | private TreeNode CreateTree(MstatData compareData, MstatData.Node node, string label = null) 24 | { 25 | TreeNode result = new TreeNode(label == null ? node.Name : $"({label}) {node.Name}"); 26 | 27 | if (compareData != null && compareData.DgmlAvailable) 28 | { 29 | if (!compareData.ContainsNamedNode(node.Name)) 30 | result.ForeColor = Color.RebeccaPurple; 31 | } 32 | 33 | foreach (var edge in node.Edges) 34 | result.Nodes.Add(CreateTree(compareData, edge.Node, edge.Label)); 35 | 36 | return result; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sizoscope/RootForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /sizoscope/TreeLogic.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Metadata; 2 | using static MstatData; 3 | 4 | namespace sizoscope 5 | { 6 | internal class TreeLogic 7 | { 8 | public static void RefreshTree(TreeView tree, MstatData data, Sorter sorter) 9 | { 10 | tree.BeginUpdate(); 11 | tree.Nodes.Clear(); 12 | 13 | if (data.UnownedFrozenObjectSize > 0) 14 | { 15 | tree.Nodes.Add(new TreeNode($"Frozen objects ({AsFileSize(data.UnownedFrozenObjectSize)})", 16 | FrozenDataImageIndex, FrozenDataImageIndex, 17 | new TreeNode[] { new TreeNode() }) 18 | { 19 | Tag = data, 20 | }); 21 | } 22 | 23 | if (data.BlobsSize > 0) 24 | { 25 | tree.Nodes.Add(new TreeNode($"Blobs ({AsFileSize(data.BlobsSize)})", 26 | ResourceImageIndex, ResourceImageIndex, 27 | new TreeNode[] { new TreeNode() }) 28 | { 29 | Tag = data, 30 | }); 31 | } 32 | 33 | var asms = data.GetScopes(); 34 | foreach (var asm in sorter.Sort(asms)) 35 | { 36 | // Do not show for now. This is currently not possible to diff and just causes problems. 37 | if (asm.Name == "System.Private.CompilerGenerated") 38 | continue; 39 | 40 | // Aggregate size can be zero if this is a diff 41 | if (asm.AggregateSize == 0) 42 | continue; 43 | 44 | string name = $"{asm.Name} ({AsFileSize(asm.AggregateSize)})"; 45 | TreeNode node = new TreeNode(name, 0, 0, 46 | new TreeNode[] { new TreeNode() }); 47 | node.Tag = asm; 48 | tree.Nodes.Add(node); 49 | } 50 | tree.EndUpdate(); 51 | } 52 | 53 | const int FrozenDataImageIndex = 5; 54 | const int ResourceImageIndex = 6; 55 | 56 | public static void BeforeExpand(TreeNode node, Sorter sorter) 57 | { 58 | if (node.FirstNode.Tag != null) 59 | return; 60 | 61 | node.Nodes.Clear(); 62 | 63 | const int instantiationsImageIndex = 4; 64 | 65 | if (node.Tag is MstatData mstat && node.ImageIndex == FrozenDataImageIndex) 66 | { 67 | foreach (var frozenObj in sorter.Sort(mstat.GetFrozenObjects())) 68 | { 69 | node.Nodes.Add(new TreeNode($"Instance of {frozenObj} ({AsFileSize(frozenObj.Size)})", FrozenDataImageIndex, FrozenDataImageIndex) 70 | { 71 | Tag = frozenObj.NodeId, 72 | }); 73 | } 74 | } 75 | else if (node.Tag is MstatData blobData && node.ImageIndex == ResourceImageIndex) 76 | { 77 | foreach (var blob in sorter.Sort(blobData.Blobs)) 78 | { 79 | node.Nodes.Add(new TreeNode($"{blob.Name} ({AsFileSize(blob.AggregateSize)})", ResourceImageIndex, ResourceImageIndex)); 80 | } 81 | } 82 | else if (node.Tag is MstatAssembly resourceAssembly && node.ImageIndex == ResourceImageIndex) 83 | { 84 | foreach (var res in sorter.Sort(resourceAssembly.GetManifestResources().Select(r => (r.Name, r.Size)))) 85 | { 86 | string name = $"{res.Name} ({AsFileSize(res.AggregateSize)})"; 87 | var newNode = new TreeNode(name, ResourceImageIndex, ResourceImageIndex); 88 | node.Nodes.Add(newNode); 89 | } 90 | } 91 | else if (node.Tag is MstatAssembly asm) 92 | { 93 | if (asm.GetManifestResources().MoveNext()) 94 | { 95 | string name = "Resources"; 96 | var newNode = new TreeNode(name, ResourceImageIndex, ResourceImageIndex, 97 | new TreeNode[] { new TreeNode() }); 98 | newNode.Tag = asm; 99 | node.Nodes.Add(newNode); 100 | } 101 | 102 | var namespacesAndSizes = asm.GetTypes() 103 | .Where(t => t.Namespace.Length > 0) 104 | .GroupBy(t => t.Namespace) 105 | .Select(g => (Name: g.Key, AggregateSize: g.Sum(t => t.AggregateSize))); 106 | foreach (var ns in sorter.Sort(namespacesAndSizes)) 107 | { 108 | string name = $"{ns.Name} ({AsFileSize(ns.AggregateSize)})"; 109 | var newNode = new TreeNode(name, 1, 1, 110 | new TreeNode[] { new TreeNode() }); 111 | newNode.Tag = (asm, ns.Name); 112 | node.Nodes.Add(newNode); 113 | } 114 | 115 | AppendTypes(sorter, node, asm.GetTypes(), d => d.Namespace.Length == 0); 116 | } 117 | else if (node.Tag is (MstatAssembly a, string ns)) 118 | { 119 | AppendTypes(sorter, node, a.GetTypes(), d => d.Namespace == ns); 120 | } 121 | else if (node.Tag is MstatTypeDefinition frozenDef && node.ImageIndex == FrozenDataImageIndex) 122 | { 123 | foreach (var frozenObj in sorter.Sort(frozenDef.GetFrozenObjects())) 124 | { 125 | node.Nodes.Add(new TreeNode($"Instance of {frozenObj} ({AsFileSize(frozenObj.Size)})", FrozenDataImageIndex, FrozenDataImageIndex) 126 | { 127 | Tag = frozenObj.NodeId, 128 | }); 129 | } 130 | } 131 | else if (node.Tag is MstatTypeSpecification frozenSpec && node.ImageIndex == FrozenDataImageIndex) 132 | { 133 | foreach (var frozenObj in sorter.Sort(frozenSpec.GetFrozenObjects())) 134 | { 135 | node.Nodes.Add(new TreeNode($"Instance of {frozenObj} ({AsFileSize(frozenObj.Size)})", FrozenDataImageIndex, FrozenDataImageIndex) 136 | { 137 | Tag = frozenObj.NodeId, 138 | }); 139 | } 140 | } 141 | else if (node.Tag is MstatTypeDefinition genericDef && node.ImageIndex == instantiationsImageIndex) 142 | { 143 | foreach (var inst in sorter.Sort(genericDef.GetTypeSpecifications())) 144 | { 145 | string name = $"{inst} ({AsFileSize(inst.AggregateSize)})"; 146 | var newNode = new TreeNode(name, 2, 2); 147 | newNode.Tag = inst; 148 | 149 | if (inst.GetMembers().MoveNext() || inst.GetFrozenObjects().MoveNext()) 150 | newNode.Nodes.Add(new TreeNode()); 151 | 152 | node.Nodes.Add(newNode); 153 | } 154 | } 155 | else if (node.Tag is MstatTypeSpecification spec) 156 | { 157 | if (spec.GetFrozenObjects().MoveNext()) 158 | { 159 | TreeNode newNode = new TreeNode("Frozen objects", 160 | FrozenDataImageIndex, FrozenDataImageIndex, 161 | new TreeNode[] { new TreeNode() }); 162 | newNode.Tag = spec; 163 | node.Nodes.Add(newNode); 164 | } 165 | AppendMembers(sorter, node, spec.GetMembers()); 166 | } 167 | else if (node.Tag is MstatTypeDefinition def) 168 | { 169 | if (def.GetFrozenObjects().MoveNext()) 170 | { 171 | TreeNode newNode = new TreeNode("Frozen objects", 172 | FrozenDataImageIndex, FrozenDataImageIndex, 173 | new TreeNode[] { new TreeNode() }); 174 | newNode.Tag = def; 175 | node.Nodes.Add(newNode); 176 | } 177 | if (def.GetTypeSpecifications().MoveNext()) 178 | { 179 | TreeNode newNode = new TreeNode("Instantiations", 180 | instantiationsImageIndex, instantiationsImageIndex, 181 | new TreeNode[] { new TreeNode() }); 182 | newNode.Tag = def; 183 | node.Nodes.Add(newNode); 184 | } 185 | AppendTypes(sorter, node, def.GetNestedTypes(), x => true); 186 | AppendMembers(sorter, node, def.GetMembers()); 187 | } 188 | else if (node.Tag is MstatMemberDefinition memberDef) 189 | { 190 | foreach (var inst in memberDef.GetInstantiations()) 191 | { 192 | string name = $"{inst} ({AsFileSize(inst.Size)})"; 193 | var newNode = new TreeNode(name, 3, 3); 194 | newNode.Tag = inst; 195 | node.Nodes.Add(newNode); 196 | } 197 | } 198 | 199 | static void AppendTypes(Sorter sorter, TreeNode node, Enumerator list, Func filter) 200 | { 201 | foreach (var t in sorter.Sort(list.Where(filter))) 202 | { 203 | string name = $"{t.Name} ({AsFileSize(t.AggregateSize)})"; 204 | var n = new TreeNode(name, 2, 2, 205 | new TreeNode[] { new TreeNode() }); 206 | n.Tag = t; 207 | node.Nodes.Add(n); 208 | } 209 | } 210 | 211 | static void AppendMembers(Sorter sorter, TreeNode node, Enumerator list) 212 | { 213 | foreach (var t in sorter.Sort(list)) 214 | { 215 | string name = $"{t} ({AsFileSize(t.AggregateSize)})"; 216 | int imageIndex = t.IsField ? 7 : 3; 217 | var n = new TreeNode(name, imageIndex, imageIndex); 218 | if (t.GetInstantiations().Any()) 219 | n.Nodes.Add(new TreeNode()); 220 | n.Tag = t; 221 | node.Nodes.Add(n); 222 | } 223 | } 224 | } 225 | 226 | public static string AsFileSize(int size) 227 | => Math.Abs(size) switch 228 | { 229 | < 1024 => $"{size:F0} B", 230 | < 1024 * 1024 => $"{size / 1024f:F1} kB", 231 | _ => $"{size / (1024f * 1024f):F1} MB", 232 | }; 233 | 234 | public class Sorter 235 | { 236 | private readonly string _key; 237 | 238 | private Sorter(string key) => _key = key; 239 | 240 | public IEnumerable Sort(IEnumerable asms) 241 | => _key == "Name" ? asms.OrderBy(a => a.Name) : asms.OrderByDescending(a => a.AggregateSize); 242 | public IEnumerable<(string Name, int AggregateSize)> Sort(IEnumerable<(string Name, int AggregateSize)> ns) 243 | => _key == "Name" ? ns.OrderBy(n => n.Name) : ns.OrderByDescending(n => n.AggregateSize); 244 | 245 | public IEnumerable Sort(IEnumerable specs) 246 | => _key == "Name" ? specs.OrderBy(s => s.ToString()) : specs.OrderByDescending(s => s.AggregateSize); 247 | 248 | public IEnumerable Sort(IEnumerable types) 249 | => _key == "Name" ? types.OrderBy(t => t.Name) : types.OrderByDescending(t => t.AggregateSize); 250 | 251 | public IEnumerable Sort(IEnumerable members) 252 | => _key == "Name" ? members.OrderBy(t => t.Name) : members.OrderByDescending(t => t.AggregateSize); 253 | public IEnumerable Sort(IEnumerable frozenObjects) 254 | => _key == "Name" ? frozenObjects.OrderBy(t => t.ToString()) : frozenObjects.OrderByDescending(t => t.Size); 255 | 256 | public IEnumerable<(string Name, int Size, int Id)> Sort(IEnumerable<(string Name, int Size, int Id)> members) 257 | => _key == "Name" ? members.OrderBy(t => t.Name) : members.OrderByDescending(t => t.Size); 258 | 259 | public static Sorter ByName() => new Sorter("Name"); 260 | public static Sorter BySize() => new Sorter("Size"); 261 | 262 | public override string ToString() => $"Sort by {_key}"; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /sizoscope/diff.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/sizoscope/44f6ed1318fa0ae004894e6311f856d04c747fa0/sizoscope/diff.ico -------------------------------------------------------------------------------- /sizoscope/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/sizoscope/44f6ed1318fa0ae004894e6311f856d04c747fa0/sizoscope/icon.ico -------------------------------------------------------------------------------- /sizoscope/sizoscope.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows7.0 6 | Major 7 | true 8 | enable 9 | icon.ico 10 | true 11 | 42.42.42.42 12 | 13 | true 14 | 15 | <_SuppressWinFormsTrimError>true 16 | 17 | false 18 | en-US 19 | Size 20 | true 21 | false 22 | true 23 | true 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | True 49 | True 50 | Resources.resx 51 | 52 | 53 | 54 | 55 | 56 | ResXFileCodeGenerator 57 | Resources.Designer.cs 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /sizoscope/sizoscope.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33414.496 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sizoscope", "sizoscope.csproj", "{67B7AFF9-A81C-41AB-97BB-752EA54B2004}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {67B7AFF9-A81C-41AB-97BB-752EA54B2004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {67B7AFF9-A81C-41AB-97BB-752EA54B2004}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {67B7AFF9-A81C-41AB-97BB-752EA54B2004}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {67B7AFF9-A81C-41AB-97BB-752EA54B2004}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0AC53B8C-7420-4E77-BF79-79887383FD4D} 24 | EndGlobalSection 25 | EndGlobal 26 | --------------------------------------------------------------------------------