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

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

Powered by Google App Engine
This is Rietveld 408576698