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