diff --git a/app/components/info-multi-stats/component.js b/app/components/info-multi-stats/component.js index ba332925f..ff5ab7d95 100644 --- a/app/components/info-multi-stats/component.js +++ b/app/components/info-multi-stats/component.js @@ -4,6 +4,8 @@ import { formatPercent, formatMib, formatKbps } from 'ui/utils/util'; const MAX_POINTS = 60; const TICK_COUNT = 6; +const COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; +const ALT_COLORS = ["#ff7f0e", "#1f77b4", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; export default Ember.Component.extend({ model: null, @@ -119,10 +121,10 @@ export default Ember.Component.extend({ { if ( this.get('single') ) { - row = getOrCreateDataRow(graph, data, 'User'); - row.push(point.cpu_user); row = getOrCreateDataRow(graph, data, 'System'); row.push(point.cpu_system); + row = getOrCreateDataRow(graph, data, 'User'); + row.push(point.cpu_user); } else { @@ -166,10 +168,10 @@ export default Ember.Component.extend({ { if ( this.get('single') ) { - row = getOrCreateDataRow(graph, data, 'Receive'); - row.push(point.net_rx_kb); row = getOrCreateDataRow(graph, data, 'Transmit'); row.push(point.net_tx_kb); + row = getOrCreateDataRow(graph, data, 'Receive'); + row.push(point.net_rx_kb); } else { @@ -185,10 +187,10 @@ export default Ember.Component.extend({ { if ( this.get('single') ) { - row = getOrCreateDataRow(graph, data, 'Read'); - row.push(point.disk_read_kb); row = getOrCreateDataRow(graph, data, 'Write'); row.push(point.disk_write_kb); + row = getOrCreateDataRow(graph, data, 'Read'); + row.push(point.disk_read_kb); } else { @@ -222,11 +224,13 @@ export default Ember.Component.extend({ size: { height: 110, }, + color: { pattern: ALT_COLORS.slice() }, data: { type: 'area-step', x: 'x', columns: this.get('cpuData'), groups: [[]], // Stacked graph, populated by getOrCreateDataRow... + order: null, }, transition: { duration: 0 }, legend: { show: false }, @@ -280,6 +284,7 @@ export default Ember.Component.extend({ size: { height: 110, }, + color: { pattern: COLORS.slice() }, data: { type: 'area-step', x: 'x', @@ -337,11 +342,13 @@ export default Ember.Component.extend({ size: { height: 110, }, + color: { pattern: ALT_COLORS.slice() }, data: { type: 'area-step', x: 'x', columns: this.get('storageData'), groups: [[]], // Stacked graph, populated by getOrCreateDataRow... + order: null, }, transition: { duration: 0 }, legend: { show: false }, @@ -393,11 +400,13 @@ export default Ember.Component.extend({ size: { height: 110, }, + color: { pattern: ALT_COLORS.slice() }, data: { type: 'area-step', x: 'x', columns: this.get('networkData'), groups: [[]], // Stacked graph, populated by getOrCreateDataRow... + order: null, }, transition: { duration: 0 }, legend: { show: false }, diff --git a/app/components/spark-line/component.js b/app/components/spark-line/component.js index 1769c22e4..3241fbdbb 100644 --- a/app/components/spark-line/component.js +++ b/app/components/spark-line/component.js @@ -95,7 +95,7 @@ export default Ember.Component.extend({ update: function() { var svg = this.get('svg'); - var data = this.get('data').slice(); + var data = (this.get('data')||[]).slice(); var x = this.get('x'); var y = this.get('y'); var line = this.get('line'); diff --git a/app/host/containers/controller.js b/app/host/containers/controller.js index 8b06d3cb6..142d5b37b 100644 --- a/app/host/containers/controller.js +++ b/app/host/containers/controller.js @@ -3,6 +3,8 @@ import Sortable from 'ui/mixins/sortable'; import ContainerSparkStats from 'ui/mixins/container-spark-stats'; export default Ember.Controller.extend(Sortable, ContainerSparkStats, { + statsSocket: null, + sortableContent: Ember.computed.alias('model.instances'), sortBy: 'name', sorts: { diff --git a/app/host/model.js b/app/host/model.js index 81cd8bf15..8b3b20ce3 100644 --- a/app/host/model.js +++ b/app/host/model.js @@ -1,4 +1,6 @@ +import Ember from 'ember'; import Resource from 'ember-api-store/models/resource'; +import { formatMib } from 'ui/utils/util'; var Host = Resource.extend({ type: 'host', @@ -53,12 +55,13 @@ var Host = Resource.extend({ }.property('physicalHostId'), osBlurb: function() { - // @TODO this always sends back Ubuntu - if ( false && this.get('info.osInfo') ) + if ( this.get('info.osInfo.operatingSystem') ) { - return this.get('info.osInfo.distribution') + ' ' + this.get('info.osInfo.version'); + return this.get('info.osInfo.operatingSystem').replace(/\s+\(.*?\)/,''); } - }.property('info.osInfo.{distribution,version}'), + }.property('info.osInfo.operatingSystem'), + + osDetail: Ember.computed.alias('info.osInfo.operatingSystem'), dockerBlurb: function() { // @TODO this always sends back Ubuntu @@ -84,28 +87,53 @@ var Host = Resource.extend({ } }.property('info.cpuInfo.{count,mhz}'), + cpuTooltip: Ember.computed.alias('info.cpuInfo.modelName'), + memoryBlurb: function() { if ( this.get('info.memoryInfo') ) { - var gb = Math.round((this.get('info.memoryInfo.memTotal')/1024)*100)/100; - - return gb + ' GiB'; + return formatMib(this.get('info.memoryInfo.memTotal')); } }.property('info.memoryInfo.memTotal'), diskBlurb: function() { - if ( this.get('info.diskInfo.mountPoints') ) + var totalMb = 0; + + // New hotness + if ( this.get('info.diskInfo.fileSystems') ) { - var totalMb = 0; + var fses = this.get('info.diskInfo.fileSystems')||[]; + Object.keys(fses).forEach((fs) => { + totalMb += fses[fs].capacity; + }); + + return formatMib(totalMb); + } + else if ( this.get('info.diskInfo.mountPoints') ) + { + // Old & busted var mounts = this.get('info.diskInfo.mountPoints')||[]; Object.keys(mounts).forEach((mountPoint) => { totalMb += mounts[mountPoint].total; }); - var gb = Math.round((totalMb/1024)*10)/10; - return gb + ' GiB'; + return formatMib(totalMb); } - }.property('info.diskInfo.mountPoints.@each.total'), + }.property('info.diskInfo.mountPoints.@each.total','info.diskInfo.fileSystems.@each.capacity'), + + diskDetail: function() { + // New hotness + if ( this.get('info.diskInfo.fileSystems') ) + { + var out = []; + var fses = this.get('info.diskInfo.fileSystems')||[]; + Object.keys(fses).forEach((fs) => { + out.pushObject(Ember.Object.create({label: fs, value: formatMib(fses[fs].capacity)})); + }); + + return out; + } + }.property('info.diskInfo.fileSystems.@each.capacity'), }); Host.reopenClass({ diff --git a/app/host/template.hbs b/app/host/template.hbs index 5bb5d562f..581291e86 100644 --- a/app/host/template.hbs +++ b/app/host/template.hbs @@ -39,7 +39,7 @@ {{#if model.cpuBlurb}}
  • - {{model.cpuBlurb}} + {{model.cpuBlurb}}{{#if model.cpuTooltip}} {{/if}}
  • {{/if}} @@ -53,14 +53,20 @@ {{#if model.diskBlurb}}
  • - {{model.diskBlurb}} + {{#if model.diskDetail}} + {{#each model.diskDetail as |disk|}} + {{disk.value}} + {{/each}} + {{else}} + {{model.diskBlurb}} (total) + {{/if}}
  • {{/if}} - {{#if model.osBlurb}} + {{#if model.osDetail}}
  • - {{model.osBlurb}} + {{model.osDetail}}
  • {{/if}} diff --git a/app/mixins/container-spark-stats.js b/app/mixins/container-spark-stats.js index c46fd7103..870d8e425 100644 --- a/app/mixins/container-spark-stats.js +++ b/app/mixins/container-spark-stats.js @@ -1,17 +1,8 @@ import Ember from 'ember'; const MAX_POINTS = 60; -const keys = ['cpu','memory','network','storage']; export default Ember.Mixin.create({ - init() { - this._super(); - keys.forEach((key) => { - this.set(key+'Data', Ember.Object.create()); - this.set(key+'Max', 0); - }); - }, - cpuData: null, memoryData: null, networkData: null, @@ -85,13 +76,20 @@ export default Ember.Mixin.create({ getOrCreateDataRow(key, id) { var data = this.get(key+'Data'); - var row = data[id]; + if ( !data ) + { + data = Ember.Object.create(); + this.set(key+'Max', 0); + this.set(key+'Data', data); + } + + var row = data.get(id); if ( !row ) { row = []; for ( var i = 0 ; i < MAX_POINTS ; i++ ) { - row.push(0); + row.pushObject(0); } data.set(id,row); } diff --git a/app/utils/multi-stats.js b/app/utils/multi-stats.js index 537fd693f..344ce8aeb 100644 --- a/app/utils/multi-stats.js +++ b/app/utils/multi-stats.js @@ -117,7 +117,9 @@ export default Ember.Object.extend(Ember.Evented, { if ( prev ) { // Don't use `ts` here, need the unrounded time to get accurate CPU usage - var time_diff_ns = (tsFromString(data.timestamp) - tsFromString(prev.timestamp))*1e6; + var time_diff_ms = (tsFromString(data.timestamp) - tsFromString(prev.timestamp)); + var time_diff_ns = time_diff_ms*1e6; + var time_diff_s = time_diff_ms/1000; var count = 1; // CPU @@ -135,6 +137,37 @@ export default Ember.Object.extend(Ember.Evented, { out.cpu_total = toPercent((data.cpu.usage.total - prev.cpu.usage.total )/time_diff_ns); out.cpu_count = count; } + + if ( data.diskio && data.diskio.io_service_bytes ) + { + var read = 0; + var write = 0; + data.diskio.io_service_bytes.forEach((io) => { + if ( io && io.stats ) + { + read += io.stats.Read || 0; + write += io.stats.Write || 0; + } + }); + + prev.diskio.io_service_bytes.forEach((io) => { + if ( io && io.stats ) + { + read -= io.stats.Read || 0; + write -= io.stats.Write || 0; + } + }); + + out.disk_read_kb = read/(time_diff_s*1024); + out.disk_write_kb = write/(time_diff_s*1024); + } + + // network + if ( data.network ) + { + out.net_rx_kb = (data.network.rx_bytes - prev.network.rx_bytes)/(time_diff_s*1024); + out.net_tx_kb = (data.network.tx_bytes - prev.network.tx_bytes)/(time_diff_s*1024); + } } // Memory @@ -155,40 +188,6 @@ export default Ember.Object.extend(Ember.Evented, { } } - // Storage - if ( prev ) - { - if ( data.diskio && data.diskio.io_serviced ) - { - var read = 0; - var write = 0; - data.diskio.io_serviced.forEach((io) => { - if ( io && io.stats ) - { - read += io.stats.Read || 0; - write += io.stats.Write || 0; - } - }); - - prev.diskio.io_serviced.forEach((io) => { - if ( io && io.stats ) - { - read -= io.stats.Read || 0; - write -= io.stats.Write || 0; - } - }); - - out.disk_read_kb = read; - out.disk_write_kb = write; - } - } - - // network - if ( data.network ) - { - out.net_rx_kb = Math.round(data.network.rx_bytes/1000); - out.net_tx_kb = Math.round(data.network.tx_bytes/1000); - } this.get('prev')[key] = data; this.trigger('dataPoint', out); diff --git a/app/utils/socket.js b/app/utils/socket.js index b7af39adb..b9e4d72b7 100644 --- a/app/utils/socket.js +++ b/app/utils/socket.js @@ -89,11 +89,20 @@ export default Ember.Object.extend(Ember.Evented, { this.setProperties({ connected: true, - tries: 0, disconnectedAt: null, }); this.trigger('connected', this.get('tries'), after); + + // Don't reset tries for a little bit, in case the socket immediately closes again. + // This prevents open/imediate close loops from hammering the server because the tries count is never incrementing. + Ember.run.later(this, '_resetTries', 1000); + }, + + _resetTries: function() { + if ( this.get('connected') ) { + this.set('tries', 0); + } }, _message: function(event) { diff --git a/bower.json b/bower.json index 1c9753d0a..ea077e1db 100644 --- a/bower.json +++ b/bower.json @@ -1,27 +1,28 @@ { "name": "ui", "dependencies": { + "async": "~0.9.2", + "bootstrap-multiselect": "~0.9.10", + "bootstrap-sass-official": "~3.3.1", + "c3": "~0.4.10", + "dagre": "~0.7.1", + "dagre-d3": "~0.4.3", "ember": "1.13.3", - "jquery": "^2.1.4", - "ember-data": "1.13.5", - "ember-resolver": "~0.1.18", - "loader.js": "ember-cli/loader.js#3.2.0", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", + "ember-data": "1.13.5", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", "ember-qunit": "0.4.1", "ember-qunit-notifications": "0.0.7", - "qunit": "~1.17.1", + "ember-resolver": "~0.1.18", + "font-awesome": "~4.4.0", "jgrowl": "~1.4.2", - "c3": "~0.4.10", + "jquery": "^2.1.4", "jquery.cookie": "~1.4.1", - "bootstrap-sass-official": "~3.3.1", - "bootstrap-multiselect": "~0.9.10", - "zeroclipboard": "~2.2.0", + "loader.js": "ember-cli/loader.js#3.2.0", + "position-calculator": "~1.1.2", "prism": "gh-pages", - "dagre": "~0.7.1", - "dagre-d3": "~0.4.3", - "async": "~0.9.2", - "position-calculator": "~1.1.2" + "qunit": "~1.17.1", + "zeroclipboard": "~2.2.0" } }