diff --git a/browser/admin/src/AdminSocketAnalytics.js b/browser/admin/src/AdminSocketAnalytics.js index 6806f38e4bc7..5d596a3ff3fa 100644 --- a/browser/admin/src/AdminSocketAnalytics.js +++ b/browser/admin/src/AdminSocketAnalytics.js @@ -23,6 +23,7 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ _cpuStatsData: [], _sentStatsData: [], _recvStatsData: [], + _connStatsData: [], _memStatsSize: 0, _memStatsInterval: 0, @@ -33,6 +34,9 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ _netStatsSize: 0, _netStatsInterval: 0, + _connStatsSize: 0, + _maxConnections: 0, + _initStatsData: function(option, size, interval, reset) { var actualData; @@ -54,16 +58,19 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ this._sentStatsData = actualData; else if (option === 'recv') this._recvStatsData = actualData; + else if (option === 'conn') + this._connStatsData = actualData; }, onSocketOpen: function() { // Base class' onSocketOpen handles authentication this.base.call(this); - this.socket.send('subscribe mem_stats cpu_stats sent_activity recv_activity settings'); + this.socket.send('subscribe mem_stats cpu_stats sent_activity recv_activity connection_activity settings'); this.socket.send('settings'); this.socket.send('sent_activity'); this.socket.send('recv_activity'); + this.socket.send('connection_activity'); this.socket.send('mem_stats'); this.socket.send('cpu_stats'); }, @@ -82,16 +89,19 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ _d3NetXAxis: null, _d3NetYAxis: null, + _d3NetYAxis2: null, _d3NetSentLine: null, _d3NetRecvLine: null, + _d3NetConnLine: null, _xNetScale: null, _yNetScale: null, + _yNetScale2: null, _graphWidth: 1000, _graphHeight: 500, _graphMargins: { top: 20, - right: 20, + right: 50, bottom: 20, left: 100 }, @@ -166,21 +176,41 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ this._d3CpuLine = d3Line; } else if (option === 'net') { + var data2, yScale2, d3Line2; + data2 = this._connStatsData; + yScale2 = d3.scaleLinear().range([this._graphHeight - this._graphMargins.bottom, this._graphMargins.top]).domain([d3.min(data2, function(d) { + return d.value; + }), d3.max(data2, function(d) { + return d.value; + })]); + d3Line2 = d3.line() + .x(function(d) { + return xScale(d.time); + }) + .y(function(d) { + return yScale2(d.value); + }) + .curve(d3.curveMonotoneX); this._xNetScale = xScale; this._yNetScale = yScale; + this._yNetScale2 = yScale2; this._d3NetXAxis = d3XAxis; this._d3NetYAxis = d3.axisLeft(this._yNetScale) .tickFormat(function (d) { return Util.humanizeMem(d/1000) + '/sec'; }); + this._d3NetYAxis2 = d3.axisRight(this._yNetScale2) + .tickFormat(function (d) { + return Util.humanizeQty(d, this._maxConnections); + }.bind(this)); this._d3NetSentLine = d3Line; this._d3NetRecvLine = d3Line; - + this._d3NetConnLine = d3Line2; } }, _createGraph: function(option) { - var vis, xAxis, yAxis, line, data; + var vis, xAxis, yAxis, yAxis2, line, data; if (option === 'mem') { vis = d3.select('#MemVisualisation'); @@ -199,14 +229,17 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ data = this._cpuStatsData; } else if (option === 'net') { + var legendSpacing = 20; vis = d3.select('#NetVisualisation'); this._setUpAxis('net'); xAxis = this._d3NetXAxis; yAxis = this._d3NetYAxis; + yAxis2 = this._d3NetYAxis2; var legend = vis.append('g') .attr('x', this._graphWidth - 70) .attr('y', 50) + .attr('y2', 50+legendSpacing) .style('font-size', '17px'); var legendData = [ @@ -217,18 +250,21 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ { text: _('Sent'), color: 'red' - } + }, + { + text: _('Connections'), + color: 'black' + } ]; - var legendSpacing = 20; for (var i = legendData.length - 1; i >= 0; i--) { legend.append('text') - .attr('x', this._graphWidth - 70) + .attr('x', this._graphWidth - this._graphMargins.right - 120) .attr('y', 80 + i * legendSpacing) .text(legendData[i].text); legend.append('rect') - .attr('x', this._graphWidth - 90) + .attr('x', this._graphWidth - this._graphMargins.right - 120 - legendSpacing) .attr('y', 67 + i * legendSpacing) .attr('width', 15) .attr('height', 15) @@ -257,6 +293,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ .attr('fill', 'none'); } else if (option === 'net') { + vis.append('svg:g') + .attr('class', 'y-axis2 axis') + .attr('transform', 'translate(' + (this._graphWidth - this._graphMargins.right) + ',0)') + .call(yAxis2); vis.append('svg:path') .attr('d', this._d3NetSentLine(this._sentStatsData)) @@ -271,6 +311,13 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ .attr('stroke', 'green') .attr('stroke-width', 1) .attr('fill', 'none'); + + vis.append('svg:path') + .attr('d', this._d3NetConnLine(this._connStatsData)) + .attr('class', 'lineConn') + .attr('stroke', 'black') + .attr('stroke-width', 0.5) + .attr('fill', 'none'); } }, @@ -291,6 +338,8 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ } else if (option === 'sent' || option === 'recv') size = this._netStatsSize; + else if (option === 'connect') + size = this._connStatsSize; if (graphName === '#MemVisualisation' || graphName === '#CpuVisualisation' || graphName === '#NetVisualisation') { @@ -358,6 +407,8 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ .attr('d', this._d3NetSentLine(this._sentStatsData)); svg.select('.lineRecv') .attr('d', this._d3NetRecvLine(this._recvStatsData)); + svg.select('.lineConn') + .attr('d', this._d3NetConnLine(this._connStatsData)); svg.select('.x-axis') .call(this._d3NetXAxis); @@ -366,6 +417,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ .select('.y-axis') .duration(500) .call(this._d3NetYAxis); + svg.transition() + .select('.y-axis2') + .duration(500) + .call(this._d3NetYAxis2); }, onSocketMessage: function(e) { @@ -407,6 +462,12 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ else if (setting[0] === 'net_stats_interval') { this._netStatsInterval = parseInt(setting[1]); } + else if (setting[0] === 'connection_stats_size') { + this._connStatsSize = parseInt(setting[1]); + } + else if (setting[0] === 'global_host_tcp_connections') { + this._maxConnections = parseInt(setting[1]); + } } // Fix the axes according to changed data @@ -445,6 +506,7 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ this._initStatsData('sent', this._netStatsSize, this._netStatsInterval, true); this._initStatsData('recv', this._netStatsSize, this._netStatsInterval, true); + this._initStatsData('conn', this._connStatsSize, this._netStatsInterval, true); } else if (textMsg.startsWith('mem_stats')) { @@ -524,6 +586,26 @@ var AdminSocketAnalytics = AdminSocketBase.extend({ this._updateNetGraph(); } } + else if (textMsg.startsWith('connection_activity')) { + textMsg = textMsg.split(' ')[1]; + if (textMsg.endsWith(',')) { + // This is the result of query, not notification + data = textMsg.substring(0, textMsg.length - 1).split(','); + + for (i = this._connStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) { + this._connStatsData[i].value = parseInt(data[j]); + } + + if ($('#NetVisualisation').html() === '') + this._createGraph('net'); + } + else { + // this is a notification data; append to _connStatsData + data = textMsg.trim(); + this._addNewData(this._connStatsData, parseInt(data), 'connect'); + this._updateNetGraph(); + } + } }, onSocketClose: function() { diff --git a/browser/admin/src/Util.js b/browser/admin/src/Util.js index a5e450952af7..b13e239844c9 100644 --- a/browser/admin/src/Util.js +++ b/browser/admin/src/Util.js @@ -31,6 +31,37 @@ var Util = Base.extend({ return kbytes.toFixed(1) + ' ' + units[i]; }, + /// Return human readable quantity with added multiple, percentage (1/100) or permyriad (1/10'000) to maximum, if maximum > 1. + humanizeQty: function (quantity, maximum) { + var qtyPrecision = 1; + var pct_s = ''; + if (maximum > 1) { + qtyPrecision = 0; + var pct = ( 100 * quantity ) / maximum; + if( pct > 100 ) { + pct = quantity / maximum; + pct_s = ', ' + pct.toFixed(1) + 'x'; + } else if( pct >= 10 ) { + pct_s = ', ' + pct.toFixed(0) + '%'; + } else if( pct >= 0.1 ) { + pct_s = ', ' + pct.toFixed(1) + '%'; + } else { + pct = ( 10000 * quantity ) / maximum; + if( pct >= 10 ) { + pct_s = ', ' + pct.toFixed(0) + '‱'; + } else { + pct_s = ', ' + pct.toFixed(1) + '‱'; + } + } + } + var unit = 1000; + var units = [_(''), ('k'), _('M'), _('G'), _('T'), _('P'), _('E'), _('Z'), _('Y'), _('B')]; + for (var i = 0; Math.abs(quantity) >= unit && i < units.length; i++) { + quantity /= unit; + } + return quantity.toFixed(qtyPrecision) + units[i] + pct_s; + }, + humanizeSecs: function(secs) { var mins = 0; var hrs = 0; diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index f62ad46d2a11..7727411601d6 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -122,7 +122,8 @@ void AdminSocketHandler::handleMessage(const std::vector &payload) tokens.equals(0, "mem_stats") || tokens.equals(0, "cpu_stats") || tokens.equals(0, "sent_activity") || - tokens.equals(0, "recv_activity")) + tokens.equals(0, "recv_activity") || + tokens.equals(0, "connection_activity")) { const std::string result = model.query(tokens[0]); if (!result.empty()) @@ -219,7 +220,9 @@ void AdminSocketHandler::handleMessage(const std::vector &payload) << "cpu_stats_size=" << model.query("cpu_stats_size") << ' ' << "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval()) << ' ' << "net_stats_size=" << model.query("net_stats_size") << ' ' - << "net_stats_interval=" << std::to_string(_admin->getNetStatsInterval()) << ' '; + << "net_stats_interval=" << std::to_string(_admin->getNetStatsInterval()) << ' ' + << "connection_stats_size=" << model.query("connection_stats_size") << ' ' + << "global_host_tcp_connections=" << net::Defaults.maxExtConnections << ' '; const DocProcSettings& docProcSettings = _admin->getDefDocProcSettings(); oss << "limit_virt_mem_mb=" << docProcSettings.getLimitVirtMemMb() << ' ' @@ -659,6 +662,7 @@ void Admin::pollingThread() _model.addSentStats(sentCount - _lastSentCount); _model.addRecvStats(recvCount - _lastRecvCount); + _model.addConnectionStats(StreamSocket::getExternalConnectionCount()); if (_lastRecvCount != recvCount || _lastSentCount != sentCount) { diff --git a/wsd/AdminModel.cpp b/wsd/AdminModel.cpp index b64db4754756..e1eb4e5b6c08 100644 --- a/wsd/AdminModel.cpp +++ b/wsd/AdminModel.cpp @@ -277,6 +277,14 @@ std::string AdminModel::query(const std::string& command) { return std::to_string(std::max(_sentStatsSize, _recvStatsSize)); } + else if (token == "connection_activity") + { + return getConnectionActivity(); + } + else if (token == "connection_stats_size") + { + return std::to_string(_connStatsSize); + } return std::string(""); } @@ -410,6 +418,17 @@ void AdminModel::addRecvStats(uint64_t recv) notify("recv_activity " + std::to_string(recv)); } +void AdminModel::addConnectionStats(size_t connections) +{ + ASSERT_CORRECT_THREAD_OWNER(_owner); + + _connStats.push_back(connections); + if (_connStats.size() > _connStatsSize) + _connStats.pop_front(); + + notify("connection_activity " + std::to_string(connections)); +} + void AdminModel::setCpuStatsSize(unsigned size) { ASSERT_CORRECT_THREAD_OWNER(_owner); @@ -695,6 +714,19 @@ std::string AdminModel::getRecvActivity() return oss.str(); } +std::string AdminModel::getConnectionActivity() +{ + ASSERT_CORRECT_THREAD_OWNER(_owner); + + std::ostringstream oss; + for (const auto& i: _connStats) + { + oss << i << ','; + } + + return oss.str(); +} + unsigned AdminModel::getTotalActiveViews() { ASSERT_CORRECT_THREAD_OWNER(_owner); diff --git a/wsd/AdminModel.hpp b/wsd/AdminModel.hpp index b14651c1f95b..c87dd95ed545 100644 --- a/wsd/AdminModel.hpp +++ b/wsd/AdminModel.hpp @@ -383,6 +383,8 @@ class AdminModel void addRecvStats(uint64_t recv); + void addConnectionStats(size_t connections); + void setCpuStatsSize(unsigned size); void setMemStatsSize(unsigned size); @@ -452,6 +454,8 @@ class AdminModel std::string getRecvActivity(); + std::string getConnectionActivity(); + std::string getCpuStats(); unsigned getTotalActiveViews(); @@ -478,6 +482,9 @@ class AdminModel std::list _recvStats; unsigned _recvStatsSize = 200; + std::list _connStats; + unsigned _connStatsSize = 200; + uint64_t _sentBytesTotal = 0; uint64_t _recvBytesTotal = 0;