| <!-- |
| This in an HTML Import-able file that contains the definition |
| of the following elements: |
| |
| <capacity-stats-sk> |
| |
| This is a top level element. |
| |
| Properties: |
| repos: Array of repo names. |
| |
| Methods: |
| None. |
| |
| Events: |
| None. |
| --> |
| |
| <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-spinner/paper-spinner.html"> |
| |
| <link rel="import" href="/res/common/imp/app-sk.html"> |
| <link rel="import" href="/res/common/imp/sort-toggle.html"> |
| <link rel="import" href="/res/common/imp/url-params-sk.html"> |
| |
| |
| <link rel="import" href="status-menu-section-sk.html"> |
| |
| <dom-module id="capacity-stats-sk"> |
| <template> |
| <style include="iron-flex iron-flex-alignment iron-positioning"> |
| :host{ |
| --app-sk-main: { |
| background-color: transparent; |
| font-family: sans-serif; |
| }; |
| |
| login-sk{ |
| --login-sk-color: white; |
| }; |
| } |
| app-sk { |
| --app-sk-toolbar: { |
| color: #FFFFFF; |
| font-size: 15px; |
| font-family: sans-serif; |
| text-align: center; |
| |
| background-color: #66A61E; |
| }; |
| } |
| |
| table { |
| border-collapse: collapse; |
| margin: 5px; |
| } |
| td, th { |
| border: 1px solid #DDD; |
| padding: 5px; |
| } |
| th { |
| position: relative; |
| } |
| |
| a { |
| color: black; |
| } |
| |
| .inputs { |
| min-height: 60px; |
| } |
| |
| .inputs > * { |
| margin: 5px; |
| } |
| |
| .notEnough { |
| background: #f4c7c3; |
| } |
| |
| .beatsOptimistic { |
| background: #fce8b2; |
| } |
| |
| .beatsPessimistic { |
| background: #b7e1cd; |
| } |
| |
| </style> |
| |
| <!-- these must go above the points at which their bound values are used. Otherwise, |
| the bound values get set to "" and it clobbers the stored values --> |
| <url-param-sk name="commits" value="{{commits_per_day}}" default="30"></url-param-sk> |
| <url-param-sk name="optimistic" value="{{optimistic_util}}" default="90"></url-param-sk> |
| <url-param-sk name="pessimistic" value="{{pessimistic_util}}" default="60"></url-param-sk> |
| <url-param-sk name="cq" value="{{cq_multiplier}}" default="1.5"></url-param-sk> |
| <url-param-sk name="backfill" value="{{target_backfill}}" default="100"></url-param-sk> |
| <url-param-sk name="sort" value="{{_sortStr}}" default="opt_est:desc"></url-param-sk> |
| |
| <app-sk id="app" class="fit" |
| mode="waterfall" |
| tall_class="tall" |
| responsive_width="800px"> |
| <status-menu-section-sk repos="[[repos]]" navigation></status-menu-section-sk> |
| |
| <h1 header>Capacity Statistics for Skia Bots</h1> |
| |
| <div class="inputs horizontal layout"> |
| <paper-input label="Commits Per Day (typically 15-35)" value="{{commits_per_day}}"></paper-input> |
| <paper-input label="CQ attempts per commit" value="{{cq_multiplier}}"></paper-input> |
| <!-- TODO(kjlubick) actually compute utilization (metrics) and display the range here for reference.--> |
| <paper-input label="Optimistic Utilization % Estimate" value="{{optimistic_util}}"></paper-input> |
| <paper-input label="Pessimistic Utilization % Estimate" value="{{pessimistic_util}}"></paper-input> |
| <paper-input label="Target Backfill %" value="{{target_backfill}}"></paper-input> |
| </div> |
| |
| <table> |
| <thead on-sort_change="_sortChange"> |
| <tr> |
| <th> |
| <span>Bot Config</span> |
| <sort-toggle |
| current="[[_sortOrder]]" |
| name="bot_config"> |
| </sort-toggle> |
| </th> |
| <th>Minutes per Commit </th> |
| <th>Tasks per Commit</th> |
| <th>Minutes per CQ run</th> |
| <th>Tasks on CQ</th> |
| <th>Bot days of work / actual day</th> |
| <th>Required Bots (optimistic)</th> |
| <th>Required Bots (pessimistic)</th> |
| <th>Actual Bot Count</th> |
| <th> |
| <span>Percent of Optimistic Estimate</span> |
| <sort-toggle |
| current="[[_sortOrder]]" |
| name="opt_est"> |
| </sort-toggle> |
| </th> |
| </tr> |
| </thead> |
| <tbody> |
| <template id="list" is="dom-repeat" items="[[stats]]" as="item" sort="_sort"> |
| <tr> |
| <td><a href$="[[_taskLink(item)]]">[[_botConfig(item)]]</a></td> |
| <td>[[_minutes(item.ms_per_commit)]]</td> |
| <td>[[item.total_tasks]]</td> |
| <td>[[_minutes(item.ms_per_cq)]]</td> |
| <td>[[item.cq_tasks]]</td> |
| <td>[[_botDays(item, commits_per_day, cq_multiplier, target_backfill)]]</td> |
| <td>[[_estimate(item, commits_per_day, cq_multiplier, optimistic_util, target_backfill)]]</td> |
| <td>[[_estimate(item, commits_per_day, cq_multiplier, pessimistic_util, target_backfill)]]</td> |
| <td class$="[[_class(item, commits_per_day, cq_multiplier, optimistic_util, pessimistic_util, target_backfill)]]"> |
| [[item.num_bots]] |
| </td> |
| <td class$="[[_class(item, commits_per_day, cq_multiplier, optimistic_util, pessimistic_util, target_backfill)]]"> |
| [[_weHave(item, commits_per_day, cq_multiplier, optimistic_util, target_backfill)]] % |
| </td> |
| </tr> |
| </template> |
| </tbody> |
| </table> |
| </app-sk> |
| |
| </template> |
| <script> |
| (function(){ |
| var ANDROID_ALIASES = { |
| "angler": "Nexus 6p", |
| "athene": "Moto G4", |
| "bullhead": "Nexus 5X", |
| "dragon": "Pixel C", |
| "flo": "Nexus 7 [2013]", |
| "flounder": "Nexus 9", |
| "foster": "NVIDIA Shield", |
| "fugu": "Nexus Player", |
| "gce_x86": "Android on GCE", |
| "goyawifi": "Galaxy Tab 3", |
| "grouper": "Nexus 7 [2012]", |
| "hammerhead": "Nexus 5", |
| "herolte": "Galaxy S7 [Global]", |
| "heroqlteatt": "Galaxy S7 [AT&T]", |
| "j5xnlte": "Galaxy J5", |
| "m0": "Galaxy S3", |
| "mako": "Nexus 4", |
| "manta": "Nexus 10", |
| "marlin": "Pixel XL", |
| "sailfish": "Pixel", |
| "shamu": "Nexus 6", |
| "sprout": "Android One", |
| "zerofltetmo": "Galaxy S6", |
| }; |
| |
| var GPU_ALIASES = { |
| "1002": "AMD", |
| "1002:6613": "AMD Radeon R7 240", |
| "1002:6646": "AMD Radeon R9 M280X", |
| "1002:6779": "AMD Radeon HD 6450/7450/8450", |
| "1002:679e": "AMD Radeon HD 7800", |
| "1002:6821": "AMD Radeon HD 8870M", |
| "1002:683d": "AMD Radeon HD 7770/8760", |
| "1002:9830": "AMD Radeon HD 8400", |
| "1002:9874": "AMD Carrizo", |
| "102b": "Matrox", |
| "102b:0522": "Matrox MGA G200e", |
| "102b:0532": "Matrox MGA G200eW", |
| "102b:0534": "Matrox G200eR2", |
| "10de": "NVIDIA", |
| "10de:08a4": "NVIDIA GeForce 320M", |
| "10de:08aa": "NVIDIA GeForce 320M", |
| "10de:0a65": "NVIDIA GeForce 210", |
| "10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition", |
| "10de:0ffa": "NVIDIA Quadro K600", |
| "10de:104a": "NVIDIA GeForce GT 610", |
| "10de:11c0": "NVIDIA GeForce GTX 660", |
| "10de:1244": "NVIDIA GeForce GTX 550 Ti", |
| "10de:1401": "NVIDIA GeForce GTX 960", |
| "10de:1ba1": "NVIDIA GeForce GTX 1070", |
| "10de:1cb3": "NVIDIA Quadro P400", |
| "8086": "Intel", |
| "8086:0046": "Intel Ironlake HD Graphics", |
| "8086:0102": "Intel Sandy Bridge HD Graphics 2000", |
| "8086:0116": "Intel Sandy Bridge HD Graphics 3000", |
| "8086:0166": "Intel Ivy Bridge HD Graphics 4000", |
| "8086:0412": "Intel Haswell HD Graphics 4600", |
| "8086:041a": "Intel Haswell HD Graphics", |
| "8086:0a16": "Intel Haswell HD Graphics 4400", |
| "8086:0a26": "Intel Haswell HD Graphics 5000", |
| "8086:0a2e": "Intel Haswell Iris Graphics 5100", |
| "8086:0d26": "Intel Haswell Iris Pro Graphics 5200", |
| "8086:0f31": "Intel Bay Trail HD Graphics", |
| "8086:1616": "Intel Broadwell HD Graphics 5500", |
| "8086:161e": "Intel Broadwell HD Graphics 5300", |
| "8086:1626": "Intel Broadwell HD Graphics 6000", |
| "8086:162b": "Intel Broadwell Iris Graphics 6100", |
| "8086:1912": "Intel Skylake HD Graphics 530", |
| "8086:1926": "Intel Skylake Iris 540/550", |
| "8086:193b": "Intel Skylake Iris Pro 580", |
| "8086:22b1": "Intel Braswell HD Graphics", |
| "8086:591e": "Intel Kaby Lake HD Graphics 615", |
| "8086:5926": "Intel Kaby Lake Iris Plus Graphics 640", |
| }; |
| |
| Polymer({ |
| is: 'capacity-stats-sk', |
| properties: { |
| // input. |
| repos: { |
| type: Array, |
| }, |
| |
| // output. |
| commits_per_day: { |
| type: Number, |
| }, |
| cq_multiplier: { |
| type: Number, |
| }, |
| optimistic_util: { |
| type: Number, |
| }, |
| pessimistic_util: { |
| type: Number, |
| }, |
| target_backfill: { |
| type: Number, |
| }, |
| stats: { |
| type: Array |
| }, |
| |
| // _sortOrder is an Object {name:String, direction:String}. |
| _sortOrder: { |
| type: Object, |
| computed: "_makeSortObject(_sortStr)", |
| }, |
| _sortStr: { |
| type: String, |
| }, |
| }, |
| |
| observers: ["_resort(commits_per_day, cq_multiplier, optimistic_util, pessimistic_util, target_backfill, _sortStr)"], |
| |
| ready: function() { |
| sk.get("/capacity/json").then(JSON.parse).then(function(map){ |
| var arr = []; |
| map = map || {}; |
| for (k in map) { |
| var item = map[k]; |
| if (!item) { |
| continue; |
| } |
| // preprocess dimensions |
| item.dimensions = item.dimensions || []; |
| for (var i = 0; i < item.dimensions.length; i++) { |
| var d = item.dimensions[i]; |
| var split = d.split(":", 1); |
| var dim = split[0]; |
| var val = d.substring(dim.length + 1); |
| item[dim] = val; |
| } |
| // preprocess total time per commit/cq from nanoseconds to millis. |
| item.ms_per_commit = 0; |
| item.task_times_ns = item.tasks || []; |
| item.total_tasks = 0; |
| item.cq_tasks = 0; |
| item.ms_per_cq = 0; |
| for (var i = 0;i < item.tasks.length; i++) { |
| item.ms_per_commit += item.tasks[i].task_duration_ns/1000000; |
| if (item.tasks[i].on_cq_also) { |
| item.cq_tasks++; |
| item.ms_per_cq += item.tasks[i].task_duration_ns/1000000; |
| } |
| item.total_tasks++; |
| } |
| // preprocess num bots |
| item.num_bots = Object.keys(item.bots).length; |
| |
| arr.push(item); |
| } |
| this.set("stats", arr); |
| }.bind(this)); |
| }, |
| |
| _alias: function(str, lookup) { |
| nodash = str.split("-")[0]; |
| var alias = lookup[nodash]; |
| if (alias) { |
| return `${alias} (${str})`; |
| } |
| return str; |
| }, |
| |
| _botConfig: function(item) { |
| var os = "(unspecified)"; |
| if (item.os) { |
| os = item.os; |
| } |
| var pool = "(unspecified)"; |
| if (item.pool) { |
| pool = item.pool; |
| } |
| |
| var rest = ""; |
| if (item.device) { |
| rest = `Device: ${item.device}`; |
| } |
| if (item.device_type) { |
| var alias = this._alias(item.device_type, ANDROID_ALIASES); |
| rest = `Device Type: ${alias}`; |
| } |
| if (item.gpu) { |
| var alias = this._alias(item.gpu, GPU_ALIASES); |
| rest = `GPU: ${alias}`; |
| } |
| if (item.cpu) { |
| rest += `, CPU: ${item.cpu}`; |
| } |
| |
| |
| if (pool !== "Skia") { |
| return `OS: ${os}, Pool: ${pool}, ${rest}`; |
| } |
| return `OS: ${os}, ${rest}`; |
| }, |
| |
| _botDaysPrecise: function(item, commits_per_day, cq_multiplier, target_backfill) { |
| var days = (item.ms_per_commit * commits_per_day * target_backfill/100); |
| days += item.ms_per_cq * cq_multiplier * commits_per_day; |
| return days / (24 * 60 * 60 * 1000); |
| }, |
| |
| _botDays: function(item, commits_per_day, cq_multiplier, target_backfill) { |
| var days = this._botDaysPrecise(item, commits_per_day, cq_multiplier, target_backfill); |
| return days.toFixed(1); |
| }, |
| |
| _class: function(item, commits_per_day, cq_multiplier, optimistic_util, pessimistic_util, target_backfill) { |
| var pessimistic = this._estimate(item, commits_per_day, cq_multiplier, pessimistic_util, target_backfill); |
| var optimistic = this._estimate(item, commits_per_day, cq_multiplier, optimistic_util, target_backfill); |
| if (item.num_bots < optimistic) { |
| return "notEnough"; |
| } |
| if (item.num_bots > pessimistic) { |
| return "beatsPessimistic"; |
| } |
| return "beatsOptimistic"; |
| }, |
| |
| _estimate: function(item, commits_per_day, cq_multiplier, util, target_backfill) { |
| var days = this._botDaysPrecise(item, commits_per_day, cq_multiplier, target_backfill); |
| return (days / (util/100)).toFixed(1); |
| }, |
| |
| _makeSortObject: function(sortstr){ |
| if (!sortstr) { |
| return undefined; |
| } |
| var pieces = sortstr.split(":"); |
| if (pieces.length != 2) { |
| // fail safe |
| return {name: "opt_est", direction: "desc"}; |
| } |
| return { |
| name: pieces[0], |
| direction: pieces[1], |
| } |
| }, |
| |
| _minutes: function(ms) { |
| // convert from ms to minutes |
| return (ms/(60 * 1000)).toFixed(1); |
| }, |
| |
| _resort: function() { |
| this.$.list.render(); |
| }, |
| |
| _sort: function(a, b) { |
| if (!this._sortOrder) { |
| return 0; |
| } |
| var dir = 1; |
| if (this._sortOrder.direction === "desc") { |
| dir = -1; |
| } |
| if (this._sortOrder.name === "opt_est") { |
| var aVal = this._weHave(a, this.commits_per_day, this.cq_multiplier, this.optimistic_util, this.target_backfill); |
| var bVal = this._weHave(b, this.commits_per_day, this.cq_multiplier, this.optimistic_util, this.target_backfill); |
| return dir * (bVal - aVal); |
| } |
| if (this._sortOrder.name === "bot_config") { |
| var aVal = this._botConfig(a); |
| var bVal = this._botConfig(b); |
| return dir * bVal.localeCompare(aVal); |
| } |
| |
| }, |
| |
| _sortChange: function(e) { |
| // The event we get from sort-toggle tells us the name of what needs |
| // to be sorting and how to sort it. |
| if (!(e && e.detail && e.detail.name)) { |
| return; |
| } |
| // should trigger the computation of _sort |
| this.set("_sortStr", e.detail.name + ":" + e.detail.direction); |
| }, |
| |
| _taskLink: function(item) { |
| var base = "https://chromium-swarm.appspot.com/tasklist?c=name&c=state&c=created_ts&c=user&c=gpu&c=device_type&c=os&l=50&s=created_ts%3Adesc"; |
| for (var i = 0;i<item.dimensions.length;i++) { |
| base += "&f=" + item.dimensions[i]; |
| } |
| return base; |
| }, |
| |
| _weHave: function(item, commits_per_day, cq_multiplier, util, target_backfill) { |
| var est = this._estimate(item, commits_per_day, cq_multiplier, util, target_backfill); |
| return (100 * item.num_bots / est).toFixed(1); |
| } |
| }); |
| })(); |
| </script> |
| </dom-module> |