├── .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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------