forked from microsoft/perfview
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOverweightReport.cs
More file actions
256 lines (220 loc) · 9.3 KB
/
OverweightReport.cs
File metadata and controls
256 lines (220 loc) · 9.3 KB
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
using Microsoft.Diagnostics.Tracing.Stacks;
using PerfView.GuiUtilities;
using PerfViewExtensibility;
using System;
using System.Collections.Generic;
using System.IO;
namespace PerfView
{
internal class OverWeigthReport
{
private static StackWindow _sourceWindow;
private static StackWindow _baselineWindow;
public static void ViewOverweightReport(string htmlReport, string reportName)
{
CommandEnvironment.OpenHtmlReport(htmlReport, reportName,
delegate (string command, TextWriter log, WebBrowserWindow window)
{
// We are not on the GUI thread, so any time we interact with the GUI thread we must dispatch to it.
window.Dispatcher.BeginInvoke((Action)delegate
{
// If you have any active hyperlinks, they come here.
ProcessHyperlinkCommand(command);
// If you want to invoke other GUI Actions do them here.
});
});
}
private static void ProcessHyperlinkCommand(string command)
{
GuiApp.MainWindow.StatusBar.LogWriter.WriteLine("Got command " + command);
string[] args = command.Split(new char[] { ',' });
StackWindow stackWindow = null;
switch (args[0])
{
case "ShowBaseStacks":
stackWindow = _baselineWindow;
break;
case "ShowStacks":
stackWindow = _sourceWindow;
break;
}
try
{
stackWindow.SetFocus(args[1]);
stackWindow.CallersTab.IsSelected = true;
stackWindow.Focus();
}
catch (System.NullReferenceException)
{
GuiApp.MainWindow.StatusBar.LogWriter.WriteLine("Failed to find Stack Window. (Has it been closed?)");
}
}
public static void GenerateOverweightReport(string outputHtmlReport, StackWindow sourceWindow, StackWindow baselineWindow)
{
_sourceWindow = sourceWindow;
_baselineWindow = baselineWindow;
StackSource source = sourceWindow.CallTree.StackSource;
StackSource baselineSource = baselineWindow.CallTree.StackSource;
TextWriter log = GuiApp.MainWindow.StatusBar.LogWriter;
var d1 = new Dictionary<string, float>();
var d2 = new Dictionary<string, float>();
var results = new List<Result>();
float total1 = LoadOneTrace(baselineSource, d1);
float total2 = LoadOneTrace(source, d2);
if (total1 != total2)
{
ComputeOverweights(d1, d2, results, total1, total2);
}
using (var w = File.CreateText(outputHtmlReport))
{
w.WriteLine("<h1>Overweight report for symbols common between both files</h1>");
w.WriteLine("<br>");
float delta = total2 - total1;
float growthPercent = delta / total1 * 100;
w.WriteLine("<table style=\"font-size:10pt;\" border=\"1\">");
w.WriteLine("<tr><td>Base (old) Time:</td><td>{0:f1}</td></tr>", total1);
w.WriteLine("<tr><td>Test (new) Time:</td><td>{0:f1}</td></tr>", total2);
w.WriteLine("<tr><td>Delta:</td><td>{0:f1}</td></tr>", delta);
w.WriteLine("<tr><td>Delta %:</td><td>{0:f1}</td></tr>", growthPercent);
w.WriteLine("</table>");
w.WriteLine("<br>");
w.WriteLine("In this report, overweight is ratio of actual growth compared to {0:f1}%.<br>", growthPercent);
w.WriteLine("Interest level attempts to identify smaller methods which changed a lot. These are likely the most interesting frames to sart investigating<br>");
w.WriteLine("An overweight of greater than 100% indicates the symbol grew in cost more than average.<br>");
w.WriteLine("High overweights are a likely source of regressions and represent good places to investigate.<br>");
w.WriteLine("Only symbols that have at least 2% impact are shown.<br>");
w.WriteLine("<br>");
if (results.Count == 0)
{
w.WriteLine("There was no growth or no matching symbols, overweight analysis not possible<br>");
}
else
{
w.WriteLine("<table style=\"font-size:10pt;\" border=\"1\">");
w.WriteLine("<tr><td>{0}</td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td><td>{6}</td></tr>",
"<b>Name</b>",
"<b>Base</b>",
"<b>Test</b>",
"<b>Delta</b>",
"<b>Responsibility %</b>",
"<b>Overweight %</b>",
"<b>Interest Level</b>"
);
foreach (var r in results)
{
// filter out the small stuff
if (Math.Abs(r.percent) < 2)
{
continue;
}
w.WriteLine("<tr><td>{0}</td><td><a href='command:ShowBaseStacks,{0}' title='View Callers of {0} in Base Stacks'>{1:f1}</a></td><td><a href='command:ShowStacks,{0}' title='View Callers of {0} in Test Stacks'>{2:f1}</a></td><td>{3:f1}</td><td>{4:f2}</td><td>{5:f2}</td><td>{6}</td></tr>",
r.name,
r.before,
r.after,
r.delta,
r.percent,
r.overweight,
r.interest
);
}
w.WriteLine("</table>");
}
}
}
#region private
private class Result
{
public string name;
public float before;
public float after;
public float delta;
public float overweight;
public float percent;
public int interest;
}
private static void ComputeOverweights(
Dictionary<string, float> d1,
Dictionary<string, float> d2,
List<Result> results,
float total1,
float total2)
{
float totalDelta = total2 - total1;
float growth = total2 / total1;
foreach (var key in d1.Keys)
{
// skip symbols that are not in both traces
if (!d2.ContainsKey(key))
{
continue;
}
var v1 = d1[key];
var v2 = d2[key];
var r = new Result();
r.name = key;
r.before = v1;
r.after = v2;
r.delta = v2 - v1;
var expectedDelta = v1 * (growth - 1);
r.overweight = r.delta / expectedDelta * 100;
r.percent = r.delta / totalDelta * 100;
// Calculate interest level
r.interest += Math.Abs(r.overweight) > 110 ? 1 : 0;
r.interest += Math.Abs(r.percent) > 5 ? 1 : 0;
r.interest += Math.Abs(r.percent) > 20 ? 1 : 0;
r.interest += Math.Abs(r.percent) > 100 ? 1 : 0;
r.interest += r.after / total2 < 0.95 ? 1 : 0; // Ignore top of the stack frames
r.interest += r.after / total2 < 0.75 ? 1 : 0; // Bonus point for being further down the stack.
results.Add(r);
}
results.Sort((Result r1, Result r2) =>
{
if (r1.interest < r2.interest)
{
return 1;
}
if (r1.interest > r2.interest)
{
return -1;
}
if (r1.overweight < r2.overweight)
{
return 1;
}
if (r1.overweight > r2.overweight)
{
return -1;
}
if (r1.delta < r2.delta)
{
return -1;
}
if (r1.delta > r2.delta)
{
return 1;
}
return 0;
});
}
private static float LoadOneTrace(StackSource source, Dictionary<string, float> dict)
{
var calltree = new CallTree(ScalingPolicyKind.ScaleToData);
calltree.StackSource = source;
float total = 0;
foreach (var node in calltree.ByID)
{
if (node.InclusiveMetric == 0)
{
continue;
}
float weight = 0;
string key = node.Name;
dict.TryGetValue(key, out weight);
dict[key] = weight + node.InclusiveMetric;
total += node.ExclusiveMetric;
}
return total;
}
#endregion
}
}