import Ember from "ember"; import Socket from "ui/utils/socket"; import C from 'ui/utils/constants'; /* Usage: import MultiStatsSocket from "./multi-stats" var sock = StatsSocket.create({resource: a_resource_with_stats, linkName: 'containerStats or hostStats'}); sock.on('dataPoint', function(point) { // Do something with data }); // When done sock.close(); */ export default Ember.Object.extend(Ember.Evented, { resource: null, linkName: 'containerStats', connected: false, prev: null, closed: false, init() { this._super(); this.onAvailableChanged(); }, available: function() { return C.ACTIVEISH_STATES.indexOf(this.get('resource.state')) >= 0 && this.get('resource.healthState') !== 'started-once'; }.property('resource.{state,healthState}'), active: Ember.computed.and('available', 'connected'), loading: function() { return this.get('available') && !this.get('connected'); }.property('available','connected'), onAvailableChanged: function() { if ( this.get('available') ) { this.connect(); } else { this.disconnect(); } }.observes('available'), connect() { if ( this.get('socket') || this.get('closed') ) { return; } this.set('prev', {}); if ( this.get('resource').hasLink(this.get('linkName')) ) { this.get('resource').followLink(this.get('linkName')).then((response) => { if (response.get('url') && response.get('token')) { var url = response.get('url') + '?token=' + encodeURIComponent(response.get('token')); var socket = Socket.create({url: url}); socket.on('message', (event) => { if ( this.get('connected') ) { //console.log('message', event); JSON.parse(event.data).forEach((row) => { //console.log('row', row); this.process(row); }); } }); socket.on('connected', (/*tries, after*/) => { this.set('connected',true); this.trigger('connected'); }); socket.on('disconnected', (/*tries*/) => { this.set('connected',false); this.trigger('disconnected'); }); this.set('socket', socket); socket.connect(); } }); } }, disconnect() { this.set('connected',false); var socket = this.get('socket'); if ( socket ) { socket.disconnect(); this.set('socket', null); } }, close() { this.set('closed', true); this.disconnect(); }, process(data) { //console.log('process', data.id, data); var key = data.resourceType + '/' + data.id; var prev = this.get('prev')[key]; var ts = roundTsFromString(data.timestamp); var out = { key: key, id: data.id, resourceType: data.resourceType, ts: ts, }; if ( prev ) { // Don't use `ts` here, need the unrounded time to get accurate CPU usage var time_diff_ms = (tsFromString(data.timestamp) - tsFromString(prev.timestamp)); // Duplicate data point, ignore if ( time_diff_ms === 0 ) { return; } var time_diff_ns = time_diff_ms*1e6; var time_diff_s = time_diff_ms/1000; out.time_diff_ms = time_diff_ms; // CPU var count = 1; if ( data.cpu.usage.per_cpu_usage ) { count = data.cpu.usage.per_cpu_usage.length; } out.cpu_user = toPercent((data.cpu.usage.user - prev.cpu.usage.user )/time_diff_ns, count*100); out.cpu_system = toPercent((data.cpu.usage.system - prev.cpu.usage.system )/time_diff_ns, count*100); out.cpu_total = toPercent((data.cpu.usage.total - prev.cpu.usage.total )/time_diff_ns, count*100); out.cpu_count = count; var read = 0; var write = 0; if ( data.diskio && data.diskio.io_service_bytes && prev.diskio && prev.diskio.io_service_bytes) { // Minus the last point prev.diskio.io_service_bytes.forEach((io) => { if ( io && io.stats ) { read -= io.stats.Read || 0; write -= io.stats.Write || 0; } }); // Plus the current point data.diskio.io_service_bytes.forEach((io) => { if ( io && io.stats ) { read += io.stats.Read || 0; write += io.stats.Write || 0; } }); } out.disk_read_kb = Math.max(0, read/(time_diff_s*1024)); out.disk_write_kb = Math.max(0, write/(time_diff_s*1024)); // Network if ( data.network ) { if ( data.network.interfaces && prev.network.interfaces ) { data.network.interfaces.forEach((iface) => { var prev_iface = prev.network.interfaces.filterBy('name', iface.name)[0]; if ( prev_iface ) { out.net_rx_kb = (out.net_rx_kb||0) + Math.max(0, (iface.rx_bytes - prev_iface.rx_bytes)/(time_diff_s*1024)); out.net_tx_kb = (out.net_tx_kb||0) + Math.max(0, (iface.tx_bytes - prev_iface.tx_bytes)/(time_diff_s*1024)); } }); } else if ( data.network && prev.network ) { out.net_rx_kb = Math.max(0, (data.network.rx_bytes - prev.network.rx_bytes)/(time_diff_s*1024)); out.net_tx_kb = Math.max(0, (data.network.tx_bytes - prev.network.tx_bytes)/(time_diff_s*1024)); } } } // Memory if ( data.memory ) { if ( data.memory.usage ) { out.mem_used_mb = Math.round(data.memory.usage/1048576); } else { out.mem_used_mb = 0; } if ( data.memory.limit ) { out.mem_total_mb = Math.round(data.memory.limit/1048576); } } // Convert any NaNs to 0 in case time_diff is 0 Object.keys(out).forEach((key) => { if ( typeof out[key] === 'number' && isNaN(out[key]) ) { out[key] = 0; } }); this.get('prev')[key] = data; this.trigger('dataPoint', out); }, }); function tsFromString(str) { return new Date(str).getTime(); } function roundTsFromString(str) { var ts = tsFromString(str); var ms = ts % 1000; if ( ms >= 500 ) { return ts-ms+1000; } else { return ts-ms; } } function toPercent(decimal,max=100) { var percent = Math.max(0, Math.round(decimal*10000)/100); if ( max ) { return Math.min(percent,max); } else { return percent; } }