blob: a6ea7d90da553647cde2cee69abd417e7c697cd2 [file] [log] [blame]
<!--
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>