Skip to content

Commit

Permalink
AdminModel (C++/JS): Add (external) connections stats to #NetVisualis…
Browse files Browse the repository at this point in the history
…ation graph

Have AdminModel accumulate StreamSocket::getExternalConnectionCount() statistics
in same size and interval as net recv/sent.

The added statistic is rendered into the #NetVisualisation graph
using a second y-axis on the right.
Here we use a human-readable quantity tick with added multiple, percentage (1/100)
or permyriad (1/10'000) to net::Defaults.maxExtConnections.

Graph is accessible via `/browser/dist/admin/adminAnalytics.html#networkview`.

Signed-off-by: Sven Göthel <sven.gothel@collabora.com>
Change-Id: I614801e7901a23847ea35a2021ecc53a1c000110
  • Loading branch information
Sven Göthel committed Nov 13, 2024
1 parent be58fac commit 60f900a
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 9 deletions.
96 changes: 89 additions & 7 deletions browser/admin/src/AdminSocketAnalytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_cpuStatsData: [],
_sentStatsData: [],
_recvStatsData: [],
_connStatsData: [],

_memStatsSize: 0,
_memStatsInterval: 0,
Expand All @@ -33,6 +34,9 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_netStatsSize: 0,
_netStatsInterval: 0,

_connStatsSize: 0,
_maxConnections: 0,

_initStatsData: function(option, size, interval, reset) {
var actualData;

Expand All @@ -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');
},
Expand All @@ -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
},
Expand Down Expand Up @@ -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');
Expand All @@ -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 = [
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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');
}

},
Expand All @@ -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') {
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')) {
Expand Down Expand Up @@ -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() {
Expand Down
31 changes: 31 additions & 0 deletions browser/admin/src/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions wsd/Admin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ void AdminSocketHandler::handleMessage(const std::vector<char> &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())
Expand Down Expand Up @@ -219,7 +220,9 @@ void AdminSocketHandler::handleMessage(const std::vector<char> &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() << ' '
Expand Down Expand Up @@ -659,6 +662,7 @@ void Admin::pollingThread()

_model.addSentStats(sentCount - _lastSentCount);
_model.addRecvStats(recvCount - _lastRecvCount);
_model.addConnectionStats(StreamSocket::getExternalConnectionCount());

if (_lastRecvCount != recvCount || _lastSentCount != sentCount)
{
Expand Down
32 changes: 32 additions & 0 deletions wsd/AdminModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions wsd/AdminModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ class AdminModel

void addRecvStats(uint64_t recv);

void addConnectionStats(size_t connections);

void setCpuStatsSize(unsigned size);

void setMemStatsSize(unsigned size);
Expand Down Expand Up @@ -452,6 +454,8 @@ class AdminModel

std::string getRecvActivity();

std::string getConnectionActivity();

std::string getCpuStats();

unsigned getTotalActiveViews();
Expand All @@ -478,6 +482,9 @@ class AdminModel
std::list<unsigned> _recvStats;
unsigned _recvStatsSize = 200;

std::list<size_t> _connStats;
unsigned _connStatsSize = 200;

uint64_t _sentBytesTotal = 0;
uint64_t _recvBytesTotal = 0;

Expand Down

0 comments on commit 60f900a

Please sign in to comment.