| 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..9f04f7f7354d73edd8e670825f0ed35ad84e0477 100644
|
| --- a/appengine/swarming/elements/build/elements.html
|
| +++ b/appengine/swarming/elements/build/elements.html
|
| @@ -20554,6 +20554,193 @@ 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) {
|
| + var a = ANDROID_ALIASES[dt];
|
| + 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+")";
|
| + },
|
| +
|
| + // _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) {
|
| + 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;
|
| + },
|
| +
|
| + // _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 theonly 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 +20879,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 +20976,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 +20998,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 +21228,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 +21269,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 +21281,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 +21291,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 +21305,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 +21371,61 @@ 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 the values the
|
| + 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 +21616,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 +21624,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 +21673,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 +21698,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 +21714,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 +21790,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 +21817,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 +21925,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 +21980,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);
|
|
|
|
|