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

Unified Diff: appengine/swarming/elements/build/elements.html

Issue 2211163003: Update new botlist to use dimensions endpoint (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@limiting
Patch Set: put demo data in luci-py wiki Created 4 years, 4 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 side-by-side diff with in-line comments
Download patch
Index: appengine/swarming/elements/build/elements.html
diff --git a/appengine/swarming/elements/build/elements.html b/appengine/swarming/elements/build/elements.html
index b6428c3f6474354a74649176d7d2baa72c0a3625..2fc78ef346c57309f55aab5fe12c4304f953669b 100644
--- a/appengine/swarming/elements/build/elements.html
+++ b/appengine/swarming/elements/build/elements.html
@@ -20554,6 +20554,185 @@ is separate from validation, and `allowed-pattern` does not affect how the input
]
});
</script>
+<script>
+
+ window.SwarmingBehaviors = window.SwarmingBehaviors || {};
+ (function(){
+ var ANDROID_ALIASES = {
+ "bullhead": "Nexus 5X",
+ "flo": "Nexus 7",
+ "flounder": "Nexus 9",
+ "hammerhead": "Nexus 5",
+ "mako": "Nexus 4",
+ "shamu": "Nexus 6",
+ };
+ // Taken from http://developer.android.com/reference/android/os/BatteryManager.html
+ var BATTERY_HEALTH_UNKNOWN = 1;
+ var BATTERY_HEALTH_GOOD = 2;
+ var BATTERY_STATUS_CHARGING = 2;
+
+ var UNAUTHENTICATED = "unauthenticated";
+ var AVAILABLE = "available";
+ var UNKNOWN = "unknown";
+
+ var GPU_ALIASES = {
+ "1002": "AMD",
+ "1002:6779": "AMD Radeon HD 6450/7450/8450",
+ "1002:6821": "AMD Radeon HD 8870M",
+ "1002:9830": "AMD Radeon HD 8400",
+ "102b": "Matrox",
+ "102b:0522": "Matrox MGA G200e",
+ "102b:0532": "Matrox MGA G200eW",
+ "102b:0534": "Matrox G200eR2",
+ "10de": "NVIDIA",
+ "10de:08aa": "NVIDIA GeForce 320M",
+ "10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition",
+ "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",
+ "8086": "Intel",
+ "8086:041a": "Intel Xeon Integrated",
+ "8086:0a2e": "Intel Haswell Integrated",
+ "8086:0d26": "Intel Crystal Well Integrated",
+ }
+
+ // For consistency, all aliases are displayed like:
+ // Nexus 5X (bullhead)
+ // This regex matches a string like "ALIAS (ORIG)", with ORIG as group 1.
+ var ALIAS_REGEXP = /.+ \((.*)\)/;
+
+ // This behavior wraps up all the shared bot-list functionality.
+ SwarmingBehaviors.BotListBehavior = {
+
+ properties: {
+ // TODO(kjlubick): Add more of these things from state, as they
+ // needed/useful/requested.
+ DIMENSIONS: {
+ type: Array,
+ value: function(){
+ return ["android_devices", "cores", "cpu", "device_type",
+ "device_os", "gpu", "id", "os", "pool"];
+ },
+ },
+ DIMENSIONS_WITH_ALIASES: {
+ type: Array,
+ value: function(){
+ return ["device_type", "gpu"];
+ },
+ },
+ BOT_PROPERTIES: {
+ type: Array,
+ value: function() {
+ return ["disk_space", "task", "status"];
+ }
+ },
+ },
+
+ _androidAlias: function(dt) {
+ return ANDROID_ALIASES[dt] || UNKNOWN;
+ },
+
+ // _applyAlias is the consistent way to modify a string to show its alias.
+ _applyAlias: function(orig, alias) {
+ return alias +" ("+orig+")";
+ },
+
+ // _attribute looks first in dimension and then in state for the
+ // specified attribute. This will always return an array. If there is
+ // no matching attribute, ["unknown"] will be returned.
+ _attribute: function(bot, attr, none) {
+ none = none || UNKNOWN;
+ return this._dimension(bot, attr) || this._state(bot, attr) || [none];
+ },
+
+ _devices: function(bot) {
+ var devices = [];
+ var d = (bot && bot.state && bot.state.devices) || {};
+ // state.devices is like {Serial:Object}, so we need to keep the serial
+ for (key in d) {
+ var o = d[key];
+ o.serial = key;
+ o.okay = (o.state === AVAILABLE);
+ devices.push(o);
+ }
+ return devices;
+ },
+
+ // _deviceType returns the codename of a given Android device.
+ _deviceType: function(device) {
+ if (!device || !device.build) {
+ return UNKNOWN;
+ }
+ var t = device.build["build.product"] || device.build["product.board"] ||
+ device.build["product.device"] || UNKNOWN;
+ return t.toLowerCase();
+ },
+
+ // _dimension returns the given dimension of a bot. If it is defined, it
+ // is an array of strings.
+ _dimension: function(bot, dim) {
+ if (!bot || !bot.dimensions || !dim) {
+ return undefined;
+ }
+ for (var i = 0; i < bot.dimensions.length; i++) {
+ if (bot.dimensions[i].key === dim) {
+ return bot.dimensions[i].value;
+ }
+ }
+ return undefined;
+ },
+
+ _gpuAlias: function(gpu) {
+ return GPU_ALIASES[gpu] || UNKNOWN;
+ },
+
+ _not: function(a) {
+ return !a;
+ },
+
+ _or: function() {
+ var result = false;
+ // can't use .foreach, as arguments isn't really a function.
+ for (var i = 0; i < arguments.length; i++) {
+ result = result || arguments[i];
+ }
+ return result;
+ },
+
+ // _state returns the requested attribute from a bot's state.
+ // For consistency with _dimension, if the attribute is not an array,
+ // it is put as the only element in an array.
+ _state: function(bot, attr) {
+ if (!bot || !bot.state || !bot.state[attr]) {
+ return undefined
+ }
+ var state = bot.state[attr];
+ if (Array.isArray(state)) {
+ return state;
+ }
+ return [state];
+ },
+
+ _taskId: function(bot) {
+ if (bot && bot.task_id) {
+ return bot.task_id;
+ }
+ return "idle";
+ },
+
+ // _unalias will return the base dimension/state with its alias removed
+ // if it had one. This is handy for sorting and filtering.
+ _unalias: function(str) {
+ var match = ALIAS_REGEXP.exec(str);
+ if (match) {
+ return match[1];
+ }
+ return str;
+ },
+ }
+ })()
+</script>
<dom-module id="bot-filters" assetpath="/res/imp/botlist/">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning">
@@ -20692,50 +20871,51 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</template>
<script>
(function(){
- var FILTER_SEP = " | ";
+ var FILTER_SEP = ":";
// filterMap is a map of primary -> function. The function returns a
// boolean "does the bot (first arg) match the second argument". These
// functions will have "this" be the botlist, and will have access to all
// functions defined in bot-list and bot-list-shared.
var filterMap = {
- cores: function(bot, cores){
- var o = this._cores(bot);
+ android_devices: function(bot, num) {
+ var o = this._attribute(bot, "android_devices", "0");
+ return o.indexOf(num) !== -1;
+ },
+ cores: function(bot, cores) {
+ var o = this._attribute(bot, "cores");
return o.indexOf(cores) !== -1;
},
- cpu: function(bot, cpu){
- var o = this._dimension(bot, "cpu") || ["none"];
+ cpu: function(bot, cpu) {
+ var o = this._attribute(bot, "cpu");
return o.indexOf(cpu) !== -1;
},
- devices: function(bot, device){
- if (device === "none") {
- return this._devices(bot).length === 0;
- }
- // extract the deviceType, if it is not "unknown".
- device = this._unalias(device);
- var found = false;
- this._devices(bot).forEach(function(d) {
- if (this._deviceType(d) === device) {
- found = true;
- }
- }.bind(this));
- return found;
+ device_os: function(bot, os) {
+ var o = this._attribute(bot, "device_os", "none");
+ return o.indexOf(os) !== -1;
},
- gpu: function(bot, gpu){
- var o = this._dimension(bot, "gpu") || ["none"];
+ device_type: function(bot, dt) {
+ var o = this._attribute(bot, "device_type", "none");
+ return o.indexOf(this._unalias(dt)) !== -1;
+ },
+ disk_space: function(bot, space) {
+ return true;
+ },
+ gpu: function(bot, gpu) {
+ var o = this._attribute(bot, "gpu", "none");
return o.indexOf(this._unalias(gpu)) !== -1;
},
id: function(bot, id) {
- return bot.bot_id === id;
+ return true;
},
- os: function(bot, os){
- var o = this._dimension(bot, "os") || ["Unknown"];
+ os: function(bot, os) {
+ var o = this._attribute(bot, "os");
return o.indexOf(os) !== -1;
},
- pool: function(bot, pool){
- var o = this._dimension(bot, "pool") || ["Unknown"];
+ pool: function(bot, pool) {
+ var o = this._attribute(bot, "pool");
return o.indexOf(pool) !== -1;
},
- status: function(bot, status){
+ status: function(bot, status) {
if (status === "quarantined") {
return bot.quarantined;
} else if (status === "dead") {
@@ -20788,6 +20968,9 @@ is separate from validation, and `allowed-pattern` does not affect how the input
Polymer({
is: "bot-filters",
+
+ behaviors: [SwarmingBehaviors.BotListBehavior],
+
properties: {
// input
primary_map: {
@@ -20807,6 +20990,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
notify: true,
},
+ dimensions: {
+ type: Array,
+ computed: "_extractDimensions(DIMENSIONS.*,_filters.*)",
+ notify: true,
+ },
filter: {
type: Object,
computed: "_makeFilter(_filters.*)",
@@ -21032,178 +21220,36 @@ is separate from validation, and `allowed-pattern` does not affect how the input
return item.substring(match.idx + match.part.length);
},
+ _extractDimensions: function() {
+ var dims = []
+ this._filters.forEach(function(f) {
+ var split = f.split(FILTER_SEP, 1)
+ var col = split[0];
+ if (this.DIMENSIONS.indexOf(col) !== -1) {
+ var rest = f.substring(col.length + FILTER_SEP.length);
+ dims.push(col + FILTER_SEP + this._unalias(rest))
+ };
+ }.bind(this));
+ return dims;
+ }
+
});
})();
</script>
-</dom-module><script>
-
- window.SwarmingBehaviors = window.SwarmingBehaviors || {};
- (function(){
- var ANDROID_ALIASES = {
- "bullhead": "Nexus 5X",
- "flo": "Nexus 7",
- "hammerhead": "Nexus 5",
- "mako": "Nexus 4",
- "shamu": "Nexus 6",
- };
- // Taken from http://developer.android.com/reference/android/os/BatteryManager.html
- var BATTERY_HEALTH_UNKNOWN = 1;
- var BATTERY_HEALTH_GOOD = 2;
- var BATTERY_STATUS_CHARGING = 2;
-
- var UNAUTHENTICATED = "unauthenticated";
- var AVAILABLE = "available";
-
- var GPU_ALIASES = {
- "1002": "AMD",
- "1002:6779": "AMD Radeon HD 6450/7450/8450",
- "1002:6821": "AMD Radeon HD 8870M",
- "102b": "Matrox",
- "102b:0522": "Matrox MGA G200e",
- "102b:0532": "Matrox MGA G200eW",
- "102b:0534": "Matrox G200eR2",
- "10de": "NVIDIA",
- "10de:08aa": "NVIDIA GeForce 320M",
- "10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition",
- "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",
- "8086": "Intel",
- "8086:041a": "Intel Xeon Integrated",
- "8086:0a2e": "Intel Haswell Integrated",
- "8086:0d26": "Intel Crystal Well Integrated",
- }
-
- // For consistency, all aliases are displayed like:
- // Nexus 5X (bullhead)
- // This regex matches a string like "ALIAS (ORIG)", with ORIG as group 1.
- var ALIAS_REGEXP = /.+ \((.*)\)/;
-
- // This behavior wraps up all the shared bot-list functionality.
- SwarmingBehaviors.BotListBehavior = {
-
- _androidAlias: function(device) {
- if (device.notReady) {
- return UNAUTHENTICATED.toUpperCase();
- }
- var t = this._deviceType(device);
- var a = ANDROID_ALIASES[t];
- if (!a) {
- return "UNKNOWN";
- }
- return a;
- },
-
- // _applyAlias is the consistent way to modify a string to show its alias.
- _applyAlias: function(orig, alias) {
- return alias +" ("+orig+")";
- },
-
- _cores: function(bot) {
- // For whatever reason, sometimes cores are in dimensions and sometimes
- // they are in state, but never both.
- var c = (bot && bot.state && bot.state.cores);
- if (c && c.length > 0) {
- return c;
- }
- c = this._dimension(bot, "cores") || ["Unknown"];
- return c;
- },
-
- _devices: function(bot) {
- var devices = [];
- var d = (bot && bot.state && bot.state.devices) || {};
- // state.devices is like {Serial:Object}, so we need to keep the serial
- for (key in d) {
- var o = d[key];
- o.serial = key;
- o.okay = (o.state === AVAILABLE);
- devices.push(o);
- }
- return devices;
- },
-
- // _deviceType returns the codename of a given Android device.
- _deviceType: function(device) {
- if (!device || !device.build) {
- return "unknown";
- }
- var t = device.build["build.product"] || device.build["product.board"] ||
- device.build["product.device"] || "unknown";
- return t.toLowerCase();
- },
-
- // _dimension returns the given dimension of a bot. If it is defined, it
- // is typically an array of strings.
- _dimension: function(bot, dim) {
- if (!bot || !bot.dimensions || !dim) {
- return undefined;
- }
- for (var i = 0; i < bot.dimensions.length; i++) {
- if (bot.dimensions[i].key === dim) {
- return bot.dimensions[i].value;
- }
- }
- return undefined;
- },
-
- _gpuAlias: function(gpu) {
- var a = GPU_ALIASES[gpu];
- if (!a) {
- return "UNKNOWN";
- }
- return a;
- },
-
- _not: function(a) {
- return !a;
- },
-
- _or: function() {
- var result = false;
- // can't use .foreach, as arguments isn't really a function.
- for (var i = 0; i < arguments.length; i++) {
- result = result || arguments[i];
- }
- return result;
- },
-
- _taskId: function(bot) {
- if (bot && bot.task_id) {
- return bot.task_id;
- }
- return "idle";
- },
-
- // _unalias will return the base dimension/state with its alias removed
- // if it had one. This is handy for sorting and filtering.
- _unalias: function(str) {
- var match = ALIAS_REGEXP.exec(str);
- if (match) {
- return match[1];
- }
- return str;
- },
- }
- })()
-</script>
-<dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
+</dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
<template>
- <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth_headers]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}">
+ <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth_headers]]" params="[[_botlistParams(dimensions.*)]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}">
</iron-ajax>
- <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy2}}">
+ <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" headers="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{{_busy2}}">
+ </iron-ajax>
+
+ <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}">
</iron-ajax>
</template>
<script>
(function(){
- // TODO(kjlubick): Add more of these as well as things from state
- // i.e. disk space remaining.
- var DIMENSIONS = ["cores", "cpu", "id", "os", "pool"];
- // "gpu" and "devices" are added separately because we need to
- // deal with aliases.
- var BOT_PROPERTIES = ["gpu", "devices", "task", "status"];
+
Polymer({
is: 'bot-list-data',
@@ -21215,6 +21261,9 @@ is separate from validation, and `allowed-pattern` does not affect how the input
type: Object,
observer: "signIn",
},
+ dimensions: {
+ type: Array,
+ },
//outputs
bots: {
@@ -21224,7 +21273,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
busy: {
type: Boolean,
- computed: "_or(_busy1,_busy2)",
+ computed: "_or(_busy1,_busy2,_busy3)",
notify: true,
},
fleet: {
@@ -21234,14 +21283,13 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
primary_map: {
type:Object,
- computed: "_primaryMap(bots)",
+ computed: "_primaryMap(_dimensions)",
notify: true,
},
primary_arr: {
- type:Array,
- value: function() {
- return DIMENSIONS.concat(BOT_PROPERTIES);
- },
+ type: Array,
+ // DIMENSIONS and BOT_PROPERTIES are inherited from BotListBehavior
+ computed: "_primaryArr(DIMENSIONS, BOT_PROPERTIES)",
notify: true,
},
@@ -21249,22 +21297,54 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_count: {
type: Object,
},
+ _dimensions: {
+ type: Object,
+ },
_list: {
type: Object,
},
},
signIn: function(){
- this.$.botlist.generateRequest();
- this.$.fleet.generateRequest();
+ this.$.botlist.auto = true;
+ this.$.dimensions.auto = true;
+ this.$.fleet.auto = true;
+ },
+
+ _botlistParams: function() {
+ if (!this.dimensions) {
+ return {};
+ }
+ return {
+ dimensions: this.dimensions,
+ };
},
_bots: function(){
if (!this._list || !this._list.items) {
return [];
}
- this._list.items.forEach(function(o){
- o.state = JSON.parse(o.state);
+ // Do any preprocessing here
+ this._list.items.forEach(function(bot){
+ // Parse the state, which is a JSON string. This contains a lot of
+ // interesting information like details about the devices attached.
+ bot.state = JSON.parse(bot.state);
+ // get the disks in an easier to deal with format, sorted by size.
+ var disks = bot.state["disks"];
+ var keys = Object.keys(disks);
+ if (!keys || !keys.length) {
+ bot.disks = [{"id": "unknown", "mb": 0}];
+ } else {
+ bot.disks = [];
+ for (var i = 0; i < keys.length; i++) {
+ bot.disks.push({"id":keys[i], "mb":disks[keys[i]].free_mb});
+ }
+ // Sort these so the biggest disk comes first.
+ bot.disks.sort(function(a, b) {
+ return b.mb - a.mb;
+ });
+ }
+
});
return this._list.items;
},
@@ -21283,58 +21363,62 @@ is separate from validation, and `allowed-pattern` does not affect how the input
}
},
- _primaryMap: function(bots){
+ _primaryArr: function(dimensions, properties) {
+ return dimensions.concat(properties);
+ },
+
+ _primaryMap: function(dimensions){
// map will keep track of dimensions that we have seen at least once.
// This will then basically get turned into an array to be used for
// filtering.
- var map = {};
- DIMENSIONS.forEach(function(p){
- map[p] = {};
- });
- map["devices"] = {};
- map["gpu"] = {};
- bots.forEach(function(b){
- DIMENSIONS.forEach(function(d){
- var dims = this._dimension(b, d) || [];
- dims.forEach(function(e){
- map[d][e] = true;
- });
- }.bind(this));
+ dimensions = dimensions.bots_dimensions;
- // Add Android devices and their aliases
- this._devices(b).forEach(function(d){
- var dt = this._deviceType(d);
- var alias = this._androidAlias(d);
- if (dt !== "unknown") {
- dt = this._applyAlias(dt,alias);
- }
- map["devices"][dt] = true;
- }.bind(this));
- map["devices"]["none"] = true;
-
- // Add GPUs and their aliases
- var gpus = this._dimension(b, "gpu") || [];
- gpus.forEach(function(g){
- var alias = this._gpuAlias(g);
- if (alias !== "UNKNOWN") {
- map["gpu"][this._applyAlias(g, alias)] = true;
- } else {
- map["gpu"][g] = true;
- }
-
- }.bind(this));
- }.bind(this));
-
- // Turn the Map<Object,Map<Boolean>> into a Map<Object,Array<String>>
- // with all of the secondary elements sorted appropriately.
var pMap = {};
- for (key in map){
- pMap[key] = Object.keys(map[key]).sort(swarming.naturalCompare);
- }
+ dimensions.forEach(function(d){
+ if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) {
+ // value is an array of all seen values for the dimension d.key
+ pMap[d.key] = d.value;
+ } else if (d.key === "gpu") {
+ var gpus = [];
+ d.value.forEach(function(g){
+ var alias = this._gpuAlias(g);
+ if (alias !== "unknown") {
+ gpus.push(this._applyAlias(g, alias));
+ } else {
+ gpus.push(g);
+ }
+ }.bind(this));
+ pMap["gpu"] = gpus;
+ } else if (d.key === "device_type") {
+ var devs = [];
+ d.value.forEach(function(dt){
+ var alias = this._androidAlias(dt);
+ if (alias !== "unknown") {
+ devs.push(this._applyAlias(dt, alias));
+ } else {
+ devs.push(dt);
+ }
+ }.bind(this));
+ pMap["device_type"] = devs;
+ } else {
+ console.log("Unknown alias type: ", d);
+ }
+ }.bind(this));
+
+ // Add some options that might not show up.
+ pMap["android_devices"].push("0");
+ pMap["device_os"].push("none");
+ pMap["device_type"].push("none");
+
+ pMap["id"] = [];
// Create custom filter options
+ pMap["disk_space"] = [];
pMap["task"] = ["busy", "idle"];
pMap["status"] = ["available", "dead", "quarantined"];
+
+ // No need to sort any of this, bot-filters sorts secondary items
+ // automatically, especially when the user types a query.
return pMap;
},
@@ -21525,7 +21609,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
<div class="horizontal layout">
- <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_arr]]" columns="{{_columns}}" filter="{{_filter}}" verbose="{{_verbose}}">
+ <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_arr]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" verbose="{{_verbose}}">
</bot-filters>
<bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedBots]]">
@@ -21533,7 +21617,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</div>
- <bot-list-data auth_headers="[[_auth_headers]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_primary_arr}}">
+ <bot-list-data auth_headers="[[_auth_headers]]" dimensions="[[_dimensions]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_primary_arr}}">
</bot-list-data>
<table class="bot-list">
@@ -21582,7 +21666,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</tr>
<template is="dom-repeat" items="[[_devices(bot)]]" as="device">
- <tr hidden$="[[_hide('devices', _columns.*)]]" class$="[[_deviceClass(device)]]">
+ <tr hidden$="[[_hide('android_devices', _columns.*)]]" class$="[[_deviceClass(device)]]">
<td></td>
<td hidden$="[[_hide('task', _columns.*)]]"></td>
<template is="dom-repeat" items="[[_plain_columns]]" as="c">
@@ -21607,9 +21691,12 @@ is separate from validation, and `allowed-pattern` does not affect how the input
var headerMap = {
// "id" and "task" are special, so they don't go here and have their
// headers hard-coded below.
+ "android_devices": "Android Devices",
"cores": "Cores",
"cpu": "CPU",
- "devices": "Devices",
+ "device_os": "Device OS",
+ "device_type": "Device Type",
+ "disk_space": "Free Space (MB)",
"gpu": "GPU",
"os": "OS",
"pool": "Pool",
@@ -21620,35 +21707,62 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// given bot. These functions are bound to this element, and have access
// to all functions defined here and in bot-list-shared.
var columnMap = {
+ android_devices: function(bot) {
+ var devs = this._attribute(bot, "android_devices", "0");
+ if (this._verbose) {
+ return devs.join(" | ") + " devices available";
+ }
+ // max() works on strings as long as they can be coerced to Number.
+ return Math.max(...devs) + " devices available";
+ },
cores: function(bot){
- var cores = this._cores(bot);
+ var cores = this._attribute(bot, "cores");
if (this._verbose){
return cores.join(" | ");
}
return cores[0];
},
cpu: function(bot){
- var cpus = this._dimension(bot, 'cpu') || ['Unknown'];
+ var cpus = this._attribute(bot, "cpu");
if (this._verbose){
return cpus.join(" | ");
}
return cpus[0];
},
- devices: function(bot){
- return this._devices(bot).length + " devices attached";
+ device_os: function(bot){
+ var os = this._attribute(bot, "device_os", "none");
+ if (this._verbose) {
+ return os.join(" | ");
+ }
+ return os[0];
+ },
+ device_type: function(bot){
+ var dt = this._attribute(bot, "device_type", "none");
+ if (this._verbose) {
+ return dt.join(" | ");
+ }
+ return dt[0];
+ },
+ disk_space: function(bot) {
+ var aliased = [];
+ bot.disks.forEach(function(disk){
+ var alias = swarming.humanBytes(disk.mb, swarming.MB);
+ aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias));
+ }.bind(this));
+ if (this._verbose) {
+ return aliased.join(" | ");
+ }
+ return aliased[0];
},
gpu: function(bot){
- var gpus = this._dimension(bot, 'gpu')
- if (!gpus) {
- return "none";
- }
+ var gpus = this._attribute(bot, "gpu", "none")
var verbose = []
var named = [];
// non-verbose mode has only the top level GPU info "e.g. NVidia"
// which is found by looking for gpu ids w/o a colon.
gpus.forEach(function(g){
var alias = this._gpuAlias(g);
- if (alias === "UNKNOWN") {
+ if (alias === "unknown") {
verbose.push(g);
if (g.indexOf(":") === -1) {
named.push(g);
@@ -21669,24 +21783,25 @@ is separate from validation, and `allowed-pattern` does not affect how the input
return bot.bot_id;
},
os: function(bot) {
- var os = this._dimension(bot, 'os') || ['Unknown'];
+ var os = this._attribute(bot, "os");
if (this._verbose){
return os.join(" | ");
}
return os[0];
},
pool: function(bot) {
- var pool = this._dimension(bot, 'pool') || ['Unknown'];
+ var pool = this._attribute(bot, "pool");
return pool.join(" | ");
},
status: function(bot) {
// If a bot is both dead and quarantined, show the deadness over the
// quarentinedness.
if (bot.is_dead) {
- return "Dead: " + bot.is_dead;
+ return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) +
+ " ago";
}
if (bot.quarantined) {
- return "Quarantined: " + bot.quarantined;
+ return "Quarantined: " + this._attribute(bot, "quarantined");
}
return "Alive";
},
@@ -21695,6 +21810,47 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
};
+ var deviceColumnMap = {
+ android_devices: function(device) {
+ var str = this._androidAliasDevice(device);
+ if (device.okay) {
+ str = this._applyAlias(this._deviceType(device), str);
+ }
+ str += " S/N:";
+ str += device.serial;
+ return str;
+ },
+ device_os: function(device) {
+ if (device.build) {
+ return device.build["build.id"];
+ }
+ return "unknown";
+ },
+ status: function(device) {
+ return device.state;
+ }
+ }
+
+ // specialSort defines any custom sorting rules. By default, a
+ // naturalCompare of the column content is done.
+ var specialSort = {
+ device_type: function(dir, botA, botB) {
+ // We sort on the number of attached devices. Note that this
+ // may not be the same as android_devices, because _devices().length
+ // counts all devices plugged into the bot, whereas android_devices
+ // counts just devices ready for work.
+ var botACol = this._devices(botA).length;
+ var botBCol = this._devices(botB).length;
+ return dir * swarming.naturalCompare(botACol, botBCol);
+ },
+ disk_space: function(dir, botA, botB) {
+ // We sort based on the raw number of MB of the first disk.
+ var botACol = botA.disks[0].mb;
+ var botBCol = botB.disks[0].mb;;
+ return dir * swarming.naturalCompare(botACol, botBCol);
+ },
+ };
+
Polymer({
is: 'bot-list',
behaviors: [SwarmingBehaviors.BotListBehavior],
@@ -21762,20 +21918,19 @@ is separate from validation, and `allowed-pattern` does not affect how the input
return columnMap[col].bind(this)(bot);
},
+ _androidAliasDevice: function(device) {
+ if (device.notReady) {
+ return UNAUTHENTICATED.toUpperCase();
+ }
+ return this._androidAlias(this._deviceType(device));
+ },
+
_deviceColumn: function(col, device) {
- if (col === "devices") {
- var str = this._androidAlias(device);
- if (device.okay) {
- str = this._applyAlias(this._deviceType(device), str);
- }
- str += " S/N:";
- str += device.serial;
- return str;
+ var f = deviceColumnMap[col];
+ if (!f || !device) {
+ return "";
}
- if (col === "status") {
- return device.state;
- }
- return "";
+ return f.bind(this)(device);
},
_deviceClass: function(device) {
@@ -21818,6 +21973,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
if (this._sort.direction === "desc") {
dir = -1;
}
+ var sort = specialSort[this._sort.name];
+ if (sort) {
+ return sort.bind(this)(dir, botA, botB);
+ }
+ // Default to a natural compare of the columns.
var botACol = this._column(this._sort.name, botA);
var botBCol = this._column(this._sort.name, botB);

Powered by Google App Engine
This is Rietveld 408576698