Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(991)

Side by Side Diff: appengine/swarming/elements/res/imp/botpage/bot-page-summary.html

Issue 2381853003: Add bot-page summary with utilization stats (Closed) Base URL: git@github.com:luci/luci-py@page-everywhere
Patch Set: rebase Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!--
2 This in an HTML Import-able file that contains the definition
3 of the following elements:
4
5 <bot-page-summary>
6
7 Usage:
8
9 <bot-page-summary></bot-page-summary>
10
11 Properties:
12 None.
13
14 Methods:
15 None.
16
17 Events:
18 None.
19 -->
20 <link rel="import" href="/res/imp/bower_components/paper-checkbox/paper-checkbox .html">
21
22 <link rel="import" href="/res/imp/common/single-page-style.html">
23 <link rel="import" href="/res/imp/common/sort-toggle.html">
24 <link rel="import" href="/res/imp/common/url-param.html">
25
26 <link rel="import" href="bot-page-shared-behavior.html">
27
28 <dom-module id="bot-page-summary">
29 <template>
30 <style include="single-page-style">
31 .wrapper {
32 display: table;
33 margin-left: auto;
34 margin-bottom: 10px;
35 }
36 paper-checkbox {
37 margin-left: 5px;
38 }
39 .thick {
40 border-top-style: solid;
41 }
42 </style>
43
44 <url-param name="show_full_names"
45 value="{{_show_full_names}}">
46 </url-param>
47 <url-param name="show_all_tasks"
48 value="{{_show_all_tasks}}">
49 </url-param>
50 <url-param name="sort_stats"
51 value="{{_sortstr}}"
52 default_value="total:desc">
53 </url-param>
54
55 <div class="wrapper">
56 <table>
57 <thead on-sort_change="_sortChange">
58 <tr>
59 <th>
60 <span>Name</span>
61 <sort-toggle
62 name="full_name"
63 current="[[_sort]]">
64 </sort-toggle>
65 </th>
66 <th>
67 <span>Total</span>
68 <sort-toggle
69 name="total"
70 current="[[_sort]]">
71 </sort-toggle>
72 </th>
73 <th>
74 <span>Success</span>
75 <sort-toggle
76 name="success"
77 current="[[_sort]]">
78 </sort-toggle>
79 </th>
80 <th>
81 <span>Failed</span>
82 <sort-toggle
83 name="failed"
84 current="[[_sort]]">
85 </sort-toggle>
86 </th>
87 <th>
88 <span>Died</span>
89 <sort-toggle
90 name="bot_died"
91 current="[[_sort]]">
92 </sort-toggle>
93 </th>
94 <th>
95 <span>Average Duration</span>
96 <sort-toggle
97 name="avg_duration"
98 current="[[_sort]]">
99 </sort-toggle>
100 </th>
101 <th>
102 <span>Total Duration</span>
103 <sort-toggle
104 name="total_time"
105 current="[[_sort]]">
106 </sort-toggle>
107 </th>
108 <th>Percent of Total</th>
109 </tr>
110 </thead>
111 <tbody>
112 <template is="dom-repeat" items="[[_tasksToShow]]" as="task">
113 <tr>
114 <td hidden$="[[_truthy(_show_full_names)]]" title="[[task.full_nam e]]">[[task.name]]</td>
115 <td hidden$="[[_not(_show_full_names)]]" title="[[task.full_name]] ">[[task.full_name]]</td>
116 <td>[[task.total]]</td>
117 <td>[[task.success]]</td>
118 <td>[[task.failed]]</td>
119 <td>[[task.bot_died]]</td>
120 <td>[[_humanDuration(task.avg_duration)]]</td>
121 <td>[[_humanDuration(task.total_time)]]</td>
122 <td>[[task.total_time_percent]]%</td>
123 </tr>
124 </template>
125 </tbody>
126 <tr class="thick">
127 <td>Total</td>
128 <td>[[_totalStats.total]]</td>
129 <td>[[_totalStats.success]]</td>
130 <td>[[_totalStats.failed]]</td>
131 <td>[[_totalStats.bot_died]]</td>
132 <td>[[_humanDuration(_totalStats.avg_duration)]]</td>
133 <td>[[_humanDuration(_totalStats.total_time)]]</td>
134 <td>100.0%</td>
135 </tr>
136 </table>
137
138 <div>
139 <table>
140 <thead>
141 <tr>
142 <th title="How much time passed between the oldest task fetched an d now.">
143 Total Wall Time
144 </th>
145 <th title="How much of the wall time this bot was busy with a task .">
146 Wall Time Utilization
147 </th>
148 </tr>
149 </thead>
150 <tbody>
151 <tr>
152 <td>[[_humanDuration(_totalStats.wall_time)]]</td>
153 <td>[[_totalStats.wall_time_utilization]]%</td>
154 </tr>
155 </tbody>
156 </table>
157
158 <paper-checkbox
159 checked="{{_show_full_names}}">
160 Show Full Names
161 </paper-checkbox>
162 <paper-checkbox
163 hidden$="[[_cannotExpand]]"
164 checked="{{_show_all_tasks}}">
165 Show All Tasks
166 </paper-checkbox>
167 </div>
168 </div>
169
170 </template>
171 <script>
172 (function(){
173 var SHOW_LIMIT = 15;
174 Polymer({
175 is: 'bot-page-summary',
176
177 behaviors: [
178 SwarmingBehaviors.BotPageBehavior,
179 ],
180
181 properties: {
182 // input
183 tasks: {
184 type: Array,
185 },
186
187 _cannotExpand: {
188 type: Boolean,
189 computed: "_countTasks(_taskStats.*)"
190 },
191 _show_all_tasks: {
192 type: Boolean
193 },
194 _show_full_names: {
195 type: Boolean
196 },
197 _sortstr: {
198 type: String
199 },
200 _sort: {
201 type: Object,
202 computed: "_makeSortObject(_sortstr)",
203 },
204 // _taskStats in an Array<Object> where each object represents one
205 // type of task (e.g. test_windows_release) and aggregate stats
206 // about it (e.g. average duration)
207 _taskStats: {
208 type: Array
209 },
210 // _tasksToShow is a sorted subset of _taskStats. This allows us to
211 // hide some of the tasks (if there are many)
212 _tasksToShow: {
213 type: Array,
214 computed: "_sortAndLimitTasks(_taskStats.*,_sort.*,_show_all_tasks)"
215 },
216 // _totalStats contains the aggregate stats for all tasks.
217 _totalStats: {
218 type: Object
219 }
220 },
221
222 // We define this down here to listen to all array events (e.g. splices)
223 // otherwise we don't update when more tasks are added.
224 observers: ["_aggregate(tasks.*)"],
225
226 _aggregate: function() {
227 if (!this.tasks || !this.tasks.length) {
228 return;
229 }
230 // TODO(kjlubick): Fix sk.now() to be less awkward to use.
231 var now = new Date(sk.now() * 1000);
232 var taskNames = [];
233 var taskAgg = {};
234 var totalStats = {
235 total: this.tasks.length,
236 success: 0,
237 failed: 0,
238 bot_died: 0,
239 avg_duration: 0,
240 total_time: 0,
241 // to compute wall_time, we find the latest task (and assume tasks
242 // come to us chronologically) and find the difference between then
243 // and now.
244 wall_time: (now - this.tasks[this.tasks.length - 1].started_ts) / 1000 ,
245 };
246 this.tasks.forEach(function(t) {
247 var n = t.name.trim();
248 var pieces = n.split('/');
249 if (pieces.length === 5) {
250 // this appears to be a buildbot name
251 // piece 0 is tag "name", piece 3 is "buildername"
252 // We throw the rest away (OS, commit hash, build number) so we
253 // can identify the "true name".
254 n = pieces[0] + "/" + pieces[3];
255 }
256
257 if (!taskAgg[n]) {
258 taskAgg[n] = {
259 full_name: n,
260 name: n,
261 total: 0,
262 success: 0,
263 failed: 0,
264 bot_died: 0,
265 avg_duration: 0,
266 total_time: 0,
267 }
268 }
269
270 taskAgg[n].total++;
271 if (t.failure) {
272 totalStats.failed++;
273 taskAgg[n].failed++;
274 } else if (t.internal_failure) {
275 totalStats.bot_died++;
276 taskAgg[n].bot_died++;
277 } else {
278 totalStats.success++;
279 taskAgg[n].success++;
280 }
281 totalStats.total_time += t.duration;
282 taskAgg[n].total_time += t.duration;
283 });
284
285 totalStats.avg_duration = totalStats.total_time / totalStats.total;
286 totalStats.wall_time_utilization = (totalStats.total_time * 100 / totalS tats.wall_time).toFixed(1);
287 this.set("_totalStats", totalStats);
288
289 // Turn the map into the array and compute total time percent.
290 var names = Object.keys(taskAgg);
291 var taskStats = [];
292 names.forEach(function(n) {
293 taskAgg[n].avg_duration = taskAgg[n].total_time / taskAgg[n].total;
294 taskAgg[n].total_time_percent = (taskAgg[n].total_time * 100 /totalSta ts.total_time).toFixed(1);
295 taskStats.push(taskAgg[n]);
296 });
297
298 // Shorten names if possible by finding the longest common substring
299 // of at least n-1 of the tasks. These parameters can be tweaked; see
300 // https://www.npmjs.com/package/common-substrings for documentation
301 // n-1 seems to be good to avoid not finding decent matches if there
302 // is an oddball task.
303 var substrings = new Substrings({
304 minOccurrence: Math.max(2, names.length-1),
305 minLength: 6,
306 });
307 substrings.build(names);
308 var result = substrings.weighByAverage() || [];
309 // result is an Array<{name:String, source:Array<int>} where the
310 // ints in source are the indices of names that have the substring.
311 // result is sorted with the "best" results first.
312 if (result.length) {
313 result[0].source.forEach(function(idx){
314 var name = taskStats[idx].full_name;
315 taskStats[idx].name = name.replace(result[0].name, "...");
316 });
317 }
318
319 this.set("_taskStats", taskStats);
320 },
321
322 _compare: function(a,b) {
323 if (!this._sort) {
324 return 0;
325 }
326 var dir = 1;
327 if (this._sort.direction === "desc") {
328 dir = -1;
329 }
330 return dir * swarming.naturalCompare(a[this._sort.name], b[this._sort.na me]);
331 },
332
333 _countTasks: function(){
334 return this._taskStats.length <= SHOW_LIMIT;
335 },
336
337 _makeSortObject: function(sortstr){
338 if (!sortstr) {
339 return undefined;
340 }
341 var pieces = sortstr.split(":");
342 if (pieces.length != 2) {
343 // fail safe
344 return {name: "full_name", direction: "asc"};
345 }
346 return {
347 name: pieces[0],
348 direction: pieces[1],
349 }
350 },
351
352 _sortAndLimitTasks: function() {
353 swarming.stableSort(this._taskStats, this._compare.bind(this));
354 var limit = this._taskStats.length;
355 if (!this._show_all_tasks && this._taskStats.length > SHOW_LIMIT) {
356 limit = SHOW_LIMIT;
357 }
358 return this._taskStats.slice(0, limit);
359 },
360
361 _sortChange: function(e) {
362 // The event we get from sort-toggle tells us the name of what needs
363 // to be sorting and how to sort it.
364 if (!(e && e.detail && e.detail.name)) {
365 return;
366 }
367 e.preventDefault();
368 e.stopPropagation();
369 this.set("_sortstr", e.detail.name + ":" + e.detail.direction);
370 },
371
372 });
373 })();
374 </script>
375 </dom-module>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698