tdf#106451 admin: graph to monitor network activity

Change-Id: Id71ef4e2a9d16e72f4df442fbf646a39213b61d5
Reviewed-on: https://gerrit.libreoffice.org/38621
Reviewed-by: pranavk <pranavk@collabora.co.uk>
Tested-by: pranavk <pranavk@collabora.co.uk>
This commit is contained in:
Aditya Dewan
2017-06-10 06:58:16 +05:30
committed by pranavk
parent de785d90b2
commit 681138ab54
8 changed files with 361 additions and 26 deletions

View File

@ -27,6 +27,7 @@ l10nstrings.strKill = _('Kill');
l10nstrings.strGraphs = _('Graphs');
l10nstrings.strMemoryGraph = _('Memory Graph');
l10nstrings.strCpuGraph = _('CPU Graph');
l10nstrings.strNetGraph = _('Network Graph');
l10nstrings.strSave = _('Save');
l10nstrings.strMemoryStatsCachesize = _('Cache size of memory statistics');
l10nstrings.strMemoryStatsInterval = _('Time interval of memory statistics (in ms)');

View File

@ -73,6 +73,11 @@
<h4><script>document.write(l10nstrings.strCpuGraph)</script></h3>
</a>
</li>
<li>
<a href="#networkview" data-toggle="tab">
<h4><script>document.write(l10nstrings.strNetGraph)</script></h3>
</a>
</li>
</ul>
<div class="tab-content graph-content">
<div id="memview" class="active tab-pane">
@ -89,6 +94,13 @@
</div>
</div>
</div>
<div id="networkview" class="tab-pane">
<div class="graph-container">
<div>
<svg id="NetVisualisation" width="1010" height="510"></svg>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -12,6 +12,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_memStatsData: [],
_cpuStatsData: [],
_sentStatsData: [],
_sentAvgStats: [],
_recvStatsData: [],
_recvAvgStats: [],
_memStatsSize: 0,
_memStatsInterval: 0,
@ -19,6 +23,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_cpuStatsSize: 0,
_cpuStatsInterval: 0,
_netAvgSize: 10,
_netStatsSize: 0,
_netStatsInterval: 0,
_initStatsData: function(option, size, interval, reset) {
var actualData;
@ -36,14 +44,24 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
this._memStatsData = actualData;
else if (option === 'cpu')
this._cpuStatsData = actualData;
else if (option === 'sent')
this._sentStatsData = actualData;
else if (option === 'recv')
this._recvStatsData = actualData;
else if (option === 'sent_avg')
this._sentAvgStats = actualData;
else if (option === 'recv_avg')
this._recvAvgStats = actualData;
},
onSocketOpen: function() {
// Base class' onSocketOpen handles authentication
this.base.call(this);
this.socket.send('subscribe mem_stats cpu_stats settings');
this.socket.send('subscribe mem_stats cpu_stats sent_activity recv_activity settings');
this.socket.send('settings');
this.socket.send('sent_activity');
this.socket.send('recv_activity');
this.socket.send('mem_stats');
this.socket.send('cpu_stats');
},
@ -60,6 +78,13 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_xCpuScale: null,
_yCpuScale: null,
_d3NetXAxis: null,
_d3NetYAxis: null,
_d3NetSentLine: null,
_d3NetRecvLine: null,
_xNetScale: null,
_yNetScale: null,
_graphWidth: 1000,
_graphHeight: 500,
_graphMargins: {
@ -70,11 +95,14 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
},
_setUpAxis: function(option) {
var data, xScale, yScale, d3XAxis, d3Line;
if (option === 'mem')
data = this._memStatsData;
else if (option === 'cpu')
data = this._cpuStatsData;
else if (option === 'net')
data = this._sentAvgStats.concat(this._recvAvgStats);
xScale = d3.scale.linear().range([this._graphMargins.left, this._graphWidth - this._graphMargins.right]).domain([d3.min(data, function(d) {
return d.time;
@ -133,11 +161,27 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
.orient('left');
this._d3CpuLine = d3Line;
}
else if (option === 'net') {
this._xNetScale = xScale;
this._yNetScale = yScale;
this._d3NetXAxis = d3XAxis;
this._d3NetYAxis = d3.svg.axis()
.scale(this._yNetScale)
.tickFormat(function (d) {
return Util.humanizeMem(d) + '/sec';
})
.orient('left');
this._d3NetSentLine = d3Line;
this._d3NetRecvLine = d3Line;
}
},
_createGraph: function(option) {
var vis, xAxis, yAxis, line, data;
if (option === 'mem') {
var vis = d3.select('#MemVisualisation');
vis = d3.select('#MemVisualisation');
this._setUpAxis('mem');
xAxis = this._d3MemXAxis;
yAxis = this._d3MemYAxis;
@ -145,13 +189,51 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
data = this._memStatsData;
}
else if (option === 'cpu') {
var vis = d3.select('#CpuVisualisation');
vis = d3.select('#CpuVisualisation');
this._setUpAxis('cpu');
xAxis = this._d3CpuXAxis;
yAxis = this._d3CpuYAxis;
line = this._d3CpuLine;
data = this._cpuStatsData;
}
else if (option === 'net') {
vis = d3.select('#NetVisualisation');
this._setUpAxis('net');
xAxis = this._d3NetXAxis;
yAxis = this._d3NetYAxis;
var legend = vis.append('g')
.attr('x', this._graphWidth - 70)
.attr('y', 50)
.style('font-size', '17px');
var legendData = [
{
text: 'Recieved',
color: 'red'
},
{
text: 'Sent',
color: 'green'
}
];
var legendSpacing = 20;
for (var i = legendData.length - 1; i >= 0; i--) {
legend.append('text')
.attr('x', this._graphWidth - 70)
.attr('y', 80 + i * legendSpacing)
.text(legendData[i].text);
legend.append('rect')
.attr('x', this._graphWidth - 90)
.attr('y', 67 + i * legendSpacing)
.attr('width', 15)
.attr('height', 15)
.style('fill', legendData[i].color)
.style('stroke', 'black');
}
}
vis.append('svg:g')
.attr('class', 'x-axis')
@ -163,15 +245,45 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
.attr('transform', 'translate(' + this._graphMargins.left + ',0)')
.call(yAxis);
vis.append('svg:path')
.attr('d', line(data))
.attr('class', 'line')
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
if (option === 'cpu' || option === 'mem') {
vis.append('svg:path')
.attr('d', line(data))
.attr('class', 'line')
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
}
else if (option === 'net') {
vis.append('svg:path')
.attr('d', this._d3NetSentLine(this._sentAvgStats))
.attr('class', 'lineSent')
.attr('stroke', 'red')
.attr('stroke-width', 2)
.attr('fill', 'none');
vis.append('svg:path')
.attr('d', this._d3NetRecvLine(this._recvAvgStats))
.attr('class', 'lineRecv')
.attr('stroke', 'green')
.attr('stroke-width', 2)
.attr('fill', 'none');
}
},
_addNewData: function(oldData, newData) {
_addNewData: function(oldData, newData, option) {
var size;
if (option === 'mem')
size = this._memStatsSize;
else if (option === 'cpu')
size = this._cpuStatsSize;
else if (option === 'sent' || option === 'recv')
size = this._netStatsSize;
else if (option === 'sent_avg' || option === 'recv_avg')
size = this._netStatsSize - this._netAvgSize + 1;
// make a space for new data
for (var i = oldData.length - 1; i > 0; i--) {
oldData[i].time = oldData[i - 1].time;
@ -181,13 +293,13 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
oldData.push({time: 0, value: parseInt(newData)});
// remove extra items
if (oldData.length > this._memStatsSize) {
if (oldData.length > size) {
oldData.shift();
}
},
_updateMemGraph: function() {
svg = d3.select('#MemVisualisation');
var svg = d3.select('#MemVisualisation');
this._setUpAxis('mem');
@ -202,7 +314,7 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
},
_updateCpuGraph: function() {
svg = d3.select('#CpuVisualisation');
var svg = d3.select('#CpuVisualisation');
this._setUpAxis('cpu');
@ -216,6 +328,51 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
.call(this._d3CpuYAxis);
},
_updateNetGraph: function() {
var svg = d3.select('#NetVisualisation');
this._setUpAxis('net');
svg.select('.lineSent')
.attr('d', this._d3NetSentLine(this._sentAvgStats));
svg.select('.lineRecv')
.attr('d', this._d3NetRecvLine(this._recvAvgStats));
svg.select('.x-axis')
.call(this._d3NetXAxis);
svg.select('.y-axis')
.call(this._d3NetYAxis);
},
_updateAverage: function(option, reset) {
var data, res, tempSum;
if (option === 'sent') {
data = this._sentStatsData;
res = this._sentAvgStats;
}
else if (option === 'recv') {
data = this._recvStatsData;
res = this._recvAvgStats;
}
if (reset) {
for (i = 0; i <= this._netStatsSize - this._netAvgSize; i++) {
tempSum = 0;
for (j = 0; j < this._netAvgSize; j++) {
tempSum += data[i + j].value;
}
tempSum /= this._netAvgSize;
res[i].value = tempSum;
}
}
else {
tempSum = res[res.length - 1].value + (data[data.length - 1].value - data[data.length - 1 - this._netAvgSize].value) / this._netAvgSize;
this._addNewData(res, tempSum, 'sent_avg');
}
},
onSocketMessage: function(e) {
var textMsg;
if (typeof e.data === 'string') {
@ -249,6 +406,12 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
else if (setting[0] === 'cpu_stats_interval') {
cpuStatsInterval = parseInt(setting[1]);
}
else if (setting[0] === 'net_stats_size') {
this._netStatsSize = parseInt(setting[1]);
}
else if (setting[0] === 'net_stats_interval') {
this._netStatsInterval = parseInt(setting[1]);
}
}
// Fix the axes according to changed data
@ -284,6 +447,12 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
this._cpuStatsSize = cpuStatsSize;
this._cpuStatsInterval = cpuStatsInterval;
this._initStatsData('sent', this._netStatsSize, this._netStatsInterval, true);
this._initStatsData('recv', this._netStatsSize, this._netStatsInterval, true);
this._initStatsData('sent_avg', this._netStatsSize - this._netAvgSize + 1, this._netStatsInterval, true);
this._initStatsData('recv_avg', this._netStatsSize - this._netAvgSize + 1, this._netStatsInterval, true);
}
else if (textMsg.startsWith('mem_stats')) {
textMsg = textMsg.split(' ')[1];
@ -299,7 +468,7 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
else {
// this is a notification data; append to _memStatsData
data = textMsg.trim();
this._addNewData(this._memStatsData, data);
this._addNewData(this._memStatsData, data, 'mem');
this._updateMemGraph();
}
}
@ -318,10 +487,54 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
else {
// this is a notification data; append to _cpuStatsData
data = textMsg.trim();
this._addNewData(this._cpuStatsData, data);
this._addNewData(this._cpuStatsData, data, 'cpu');
this._updateCpuGraph();
}
}
else if (textMsg.startsWith('sent_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._sentStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
this._sentStatsData[i].value = parseInt(data[j]) / this._netStatsInterval;
}
this._updateAverage('sent', true);
if ($('#NetVisualisation').html() === '')
this._createGraph('net');
}
else {
// this is a notification data; append to _sentStatsData
data = textMsg.trim();
this._addNewData(this._sentStatsData, parseInt(data) / this._netStatsInterval, 'sent');
this._updateAverage('sent', false);
this._updateNetGraph();
}
}
else if (textMsg.startsWith('recv_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._recvStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
this._recvStatsData[i].value = parseInt(data[j]) / this._netStatsInterval;
}
this._updateAverage('recv', true);
if ($('#NetVisualisation').html() === '')
this._createGraph('net');
}
else {
// this is a notification data; append to _recvStatsData
data = textMsg.trim();
this._addNewData(this._recvStatsData, parseInt(data) / this._netStatsInterval, 'recv');
this._updateAverage('recv', false);
this._updateNetGraph();
}
}
},
onSocketClose: function() {
@ -331,4 +544,4 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
Admin.Analytics = function(host) {
return new AdminSocketAnalytics(host);
};
};

View File

@ -104,7 +104,9 @@ void AdminSocketHandler::handleMessage(bool /* fin */, WSOpCode /* code */,
tokens[0] == "active_users_count" ||
tokens[0] == "active_docs_count" ||
tokens[0] == "mem_stats" ||
tokens[0] == "cpu_stats")
tokens[0] == "cpu_stats" ||
tokens[0] == "sent_activity" ||
tokens[0] == "recv_activity")
{
const std::string result = model.query(tokens[0]);
if (!result.empty())
@ -170,7 +172,9 @@ void AdminSocketHandler::handleMessage(bool /* fin */, WSOpCode /* code */,
<< "mem_stats_size=" << model.query("mem_stats_size") << ' '
<< "mem_stats_interval=" << std::to_string(_admin->getMemStatsInterval()) << ' '
<< "cpu_stats_size=" << model.query("cpu_stats_size") << ' '
<< "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval()) << ' ';
<< "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval()) << ' '
<< "net_stats_size=" << model.query("net_stats_size") << ' '
<< "net_stats_interval=" << std::to_string(_admin->getNetStatsInterval()) << ' ';
const DocProcSettings& docProcSettings = _admin->getDefDocProcSettings();
oss << "limit_virt_mem_mb=" << docProcSettings.LimitVirtMemMb << ' '
@ -321,8 +325,11 @@ Admin::Admin() :
_forKitWritePipe(-1),
_lastTotalMemory(0),
_lastJiffies(0),
_lastSentCount(0),
_lastRecvCount(0),
_memStatsTaskIntervalMs(5000),
_cpuStatsTaskIntervalMs(2000)
_cpuStatsTaskIntervalMs(2000),
_networkStatsIntervalMs(5000)
{
LOG_INF("Admin ctor.");
@ -338,28 +345,30 @@ Admin::~Admin()
void Admin::pollingThread()
{
std::chrono::steady_clock::time_point lastCPU, lastMem;
std::chrono::steady_clock::time_point lastCPU, lastMem, lastNet;
_model.setThreadOwner(std::this_thread::get_id());
lastCPU = std::chrono::steady_clock::now();
lastMem = lastCPU;
lastNet = lastCPU;
while (!_stop && !TerminationFlag && !ShutdownRequestFlag)
{
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
int cpuWait = _cpuStatsTaskIntervalMs -
std::chrono::duration_cast<std::chrono::milliseconds>(now - lastCPU).count();
size_t currentJiffies = getTotalCpuUsage();
if (cpuWait <= 0)
{
size_t currentJiffies = getTotalCpuUsage();
auto cpuPercent = 100 * 1000 * currentJiffies / (sysconf (_SC_CLK_TCK) * _cpuStatsTaskIntervalMs);
_model.addCpuStats(cpuPercent);
lastCPU = now;
cpuWait += _cpuStatsTaskIntervalMs;
}
int memWait = _memStatsTaskIntervalMs -
std::chrono::duration_cast<std::chrono::milliseconds>(now - lastMem).count();
if (memWait <= 0)
@ -377,8 +386,25 @@ void Admin::pollingThread()
memWait += _memStatsTaskIntervalMs;
}
int netWait = _networkStatsIntervalMs -
std::chrono::duration_cast<std::chrono::milliseconds>(now - lastNet).count();
if(netWait <= 0)
{
uint64_t sentCount = _model.getSentBytesTotal();
uint64_t recvCount = _model.getRecvBytesTotal();
_model.addSentStats(sentCount - _lastSentCount);
_model.addRecvStats(recvCount - _lastRecvCount);
LOG_TRC("Total Data sent: " << sentCount << ", recv: " << recvCount);
_lastRecvCount = recvCount;
_lastSentCount = sentCount;
lastNet = now;
netWait += _networkStatsIntervalMs;
}
// Handle websockets & other work.
int timeout = std::min(cpuWait, memWait);
int timeout = std::min(std::min(cpuWait, memWait), netWait);
LOG_TRC("Admin poll for " << timeout << "ms");
poll(timeout);
}
@ -463,6 +489,11 @@ unsigned Admin::getCpuStatsInterval()
return _cpuStatsTaskIntervalMs;
}
unsigned Admin::getNetStatsInterval()
{
return _networkStatsIntervalMs;
}
AdminModel& Admin::getModel()
{
return _model;

View File

@ -95,6 +95,8 @@ public:
unsigned getCpuStatsInterval();
unsigned getNetStatsInterval();
void rescheduleMemTimer(unsigned interval);
void rescheduleCpuTimer(unsigned interval);
@ -124,11 +126,13 @@ private:
int _forKitWritePipe;
size_t _lastTotalMemory;
size_t _lastJiffies;
uint64_t _lastSentCount;
uint64_t _lastRecvCount;
std::atomic<int> _memStatsTaskIntervalMs;
std::atomic<int> _cpuStatsTaskIntervalMs;
DocProcSettings _defDocProcSettings;
std::atomic<int> _networkStatsIntervalMs;
};
#endif

View File

@ -238,6 +238,18 @@ std::string AdminModel::query(const std::string& command)
{
return std::to_string(_cpuStatsSize);
}
else if (token == "sent_activity")
{
return getSentActivity();
}
else if (token == "recv_activity")
{
return getRecvActivity();
}
else if (token == "net_stats_size")
{
return std::to_string(std::max(_sentStatsSize, _recvStatsSize));
}
return std::string("");
}
@ -349,6 +361,28 @@ void AdminModel::addCpuStats(unsigned cpuUsage)
notify("cpu_stats " + std::to_string(cpuUsage));
}
void AdminModel::addSentStats(uint64_t sent)
{
assertCorrectThread();
_sentStats.push_back(sent);
if (_sentStats.size() > _sentStatsSize)
_sentStats.pop_front();
notify("sent_activity " + std::to_string(sent));
}
void AdminModel::addRecvStats(uint64_t recv)
{
assertCorrectThread();
_recvStats.push_back(recv);
if (_recvStats.size() > _recvStatsSize)
_recvStats.pop_front();
notify("recv_activity " + std::to_string(recv));
}
void AdminModel::setCpuStatsSize(unsigned size)
{
assertCorrectThread();
@ -550,6 +584,32 @@ std::string AdminModel::getCpuStats()
return oss.str();
}
std::string AdminModel::getSentActivity()
{
assertCorrectThread();
std::ostringstream oss;
for (const auto& i: _sentStats)
{
oss << i << ',';
}
return oss.str();
}
std::string AdminModel::getRecvActivity()
{
assertCorrectThread();
std::ostringstream oss;
for (const auto& i: _recvStats)
{
oss << i << ',';
}
return oss.str();
}
unsigned AdminModel::getTotalActiveViews()
{
assertCorrectThread();

View File

@ -221,6 +221,10 @@ public:
void addCpuStats(unsigned cpuUsage);
void addSentStats(uint64_t sent);
void addRecvStats(uint64_t recv);
void setCpuStatsSize(unsigned size);
void setMemStatsSize(unsigned size);
@ -243,6 +247,10 @@ public:
private:
std::string getMemStats();
std::string getSentActivity();
std::string getRecvActivity();
std::string getCpuStats();
unsigned getTotalActiveViews();
@ -261,6 +269,12 @@ private:
std::list<unsigned> _cpuStats;
unsigned _cpuStatsSize = 100;
std::list<unsigned> _sentStats;
unsigned _sentStatsSize = 100;
std::list<unsigned> _recvStats;
unsigned _recvStatsSize = 100;
uint64_t _sentBytesTotal;
uint64_t _recvBytesTotal;

View File

@ -249,8 +249,8 @@ void DocumentBroker::pollThread()
// send change since last notification.
Admin::instance().addBytes(getDocKey(),
// connection drop transiently reduces this.
std::max(sent - adminSent, uint64_t(0)),
std::max(recv - adminRecv, uint64_t(0)));
(sent > adminSent ? (sent - adminSent): uint64_t(0)),
(recv > adminRecv ? (recv - adminRecv): uint64_t(0)));
LOG_INF("Doc [" << _docKey << "] added sent: " << sent << " recv: " << recv << " bytes to totals");
adminSent = sent;
adminRecv = recv;