summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAditya Dewan <iit2015097@iiita.ac.in>2017-06-06 06:47:42 +0530
committerpranavk <pranavk@collabora.co.uk>2017-06-08 12:58:12 +0200
commit9502741590d293155e7b3546654aa696b045362b (patch)
tree7fcae595e6997ccce449403b66b7d80eb1ee9cbf
parent27fdb9b8645d28ce06b45709c6da64220d0af249 (diff)
tdf#107278 admin console: adding graph to track CPU load
Change-Id: Idb07fe4139dd639a49ce1545cc15895f74876b06 Reviewed-on: https://gerrit.libreoffice.org/38425 Reviewed-by: pranavk <pranavk@collabora.co.uk> Tested-by: pranavk <pranavk@collabora.co.uk>
-rw-r--r--common/Util.cpp40
-rw-r--r--common/Util.hpp4
-rw-r--r--loleaflet/admin.strings.js2
-rw-r--r--loleaflet/dist/admin/adminAnalytics.html36
-rw-r--r--loleaflet/dist/admin/bootstrap/dashboard.css11
-rw-r--r--loleaflet/src/admin/AdminSocketAnalytics.js215
-rw-r--r--wsd/Admin.cpp28
-rw-r--r--wsd/Admin.hpp2
-rw-r--r--wsd/AdminModel.cpp25
-rw-r--r--wsd/AdminModel.hpp7
10 files changed, 282 insertions, 88 deletions
diff --git a/common/Util.cpp b/common/Util.cpp
index a94457e80..a61fe6d03 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -228,14 +228,38 @@ namespace Util
size_t getMemoryUsageRSS(const Poco::Process::PID pid)
{
static const auto pageSizeBytes = getpagesize();
+ size_t rss = 0;
if (pid > 0)
{
+ rss = getStatFromPid(pid, 23);
+ rss *= pageSizeBytes;
+ rss /= 1024;
+ return rss;
+ }
+ return 0;
+ }
+
+ size_t getCpuUsage(const Poco::Process::PID pid)
+ {
+ if (pid > 0)
+ {
+ size_t totalJiffies = 0;
+ totalJiffies += getStatFromPid(pid, 13);
+ totalJiffies += getStatFromPid(pid, 14);
+ return totalJiffies;
+ }
+ return 0;
+ }
+
+ size_t getStatFromPid(const Poco::Process::PID pid, int ind)
+ {
+ if (pid > 0)
+ {
const auto cmd = "/proc/" + std::to_string(pid) + "/stat";
FILE* fp = fopen(cmd.c_str(), "r");
if (fp != nullptr)
{
- size_t rss = 0;
char line[4096] = { 0 };
if (fgets(line, sizeof (line), fp))
{
@@ -244,25 +268,17 @@ namespace Util
auto pos = s.find(' ');
while (pos != std::string::npos)
{
- if (index == 23)
+ if (index == ind)
{
- // Convert from memory pages to KB.
- rss = strtol(&s[pos], nullptr, 10);
- rss *= pageSizeBytes;
- rss /= 1024;
- break;
+ fclose(fp);
+ return strtol(&s[pos], nullptr, 10);
}
-
++index;
pos = s.find(' ', pos + 1);
}
}
-
- fclose(fp);
- return rss;
}
}
-
return 0;
}
diff --git a/common/Util.hpp b/common/Util.hpp
index 0fbf794df..4f9906e9d 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -105,6 +105,10 @@ namespace Util
/// Example: "procmemstats: pid=123 rss=12400 pss=566"
std::string getMemoryStats(FILE* file);
+ size_t getCpuUsage(const Poco::Process::PID pid);
+
+ size_t getStatFromPid(const Poco::Process::PID pid, int ind);
+
std::string replace(std::string s, const std::string& a, const std::string& b);
std::string formatLinesForLog(const std::string& s);
diff --git a/loleaflet/admin.strings.js b/loleaflet/admin.strings.js
index a38f9a0ee..9bc38b297 100644
--- a/loleaflet/admin.strings.js
+++ b/loleaflet/admin.strings.js
@@ -25,6 +25,8 @@ l10nstrings.strIdleTime = _('Idle time');
l10nstrings.strModified = _('Modified');
l10nstrings.strKill = _('Kill');
l10nstrings.strGraphs = _('Graphs');
+l10nstrings.strMemoryGraph = _('Memory Graph');
+l10nstrings.strCpuGraph = _('CPU Graph');
l10nstrings.strSave = _('Save');
l10nstrings.strMemoryStatsCachesize = _('Cache size of memory statistics');
l10nstrings.strMemoryStatsInterval = _('Time interval of memory statistics (in ms)');
diff --git a/loleaflet/dist/admin/adminAnalytics.html b/loleaflet/dist/admin/adminAnalytics.html
index b905619ba..10957f249 100644
--- a/loleaflet/dist/admin/adminAnalytics.html
+++ b/loleaflet/dist/admin/adminAnalytics.html
@@ -62,13 +62,35 @@
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
- <h1 class="page-header"><script>document.write(l10nstrings.strGraphs)</script></h1>
- <div class="graph-container">
- <div class="jumbotron">
- <svg id="visualisation" width="1010" height="510"></svg>
- </div>
- </div>
- </div>
+ <ul class="nav nav-tabs">
+ <li class="active">
+ <a href="#memview" data-toggle="tab">
+ <h4><script>document.write(l10nstrings.strMemoryGraph)</script></h3>
+ </a>
+ </li>
+ <li>
+ <a href="#cpuview" data-toggle="tab">
+ <h4><script>document.write(l10nstrings.strCpuGraph)</script></h3>
+ </a>
+ </li>
+ </ul>
+ <div class="tab-content graph-content">
+ <div id="memview" class="active tab-pane">
+ <div class="graph-container">
+ <div>
+ <svg id="MemVisualisation" width="1010" height="510"></svg>
+ </div>
+ </div>
+ </div>
+ <div id="cpuview" class="tab-pane">
+ <div class="graph-container">
+ <div>
+ <svg id="CpuVisualisation" width="1010" height="510"></svg>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
</body>
diff --git a/loleaflet/dist/admin/bootstrap/dashboard.css b/loleaflet/dist/admin/bootstrap/dashboard.css
index cdf37a821..95d62235a 100644
--- a/loleaflet/dist/admin/bootstrap/dashboard.css
+++ b/loleaflet/dist/admin/bootstrap/dashboard.css
@@ -138,4 +138,15 @@ tr:hover .dropdown-menu{
}
.doc_list_label{
cursor: pointer;
+}
+
+/*
+ * Graph view buttons
+ */
+
+.nav-tabs > li.active > a{
+ background-color: #f5f5f5;
+}
+.graph-content {
+ background-color: #f5f5f5;
} \ No newline at end of file
diff --git a/loleaflet/src/admin/AdminSocketAnalytics.js b/loleaflet/src/admin/AdminSocketAnalytics.js
index 898063c03..b09348c24 100644
--- a/loleaflet/src/admin/AdminSocketAnalytics.js
+++ b/loleaflet/src/admin/AdminSocketAnalytics.js
@@ -19,22 +19,23 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
_cpuStatsSize: 0,
_cpuStatsInterval: 0,
- _initMemStatsData: function(memStatsSize, memStatsInterval, reset) {
+ _initStatsData: function(option, size, interval, reset) {
+ var actualData;
+
if (reset) {
- this._memStatsData = [];
+ actualData = [];
}
- var offset = this._memStatsData.length * memStatsInterval;
- for (var i = 0; i < memStatsSize; i++) {
- this._memStatsData.unshift({time: -(offset), value: 0});
- offset += memStatsInterval;
+ var offset = actualData.length * interval;
+ for (var i = 0; i < size; i++) {
+ actualData.unshift({time: -(offset), value: 0});
+ offset += interval;
}
- },
- _initCpuStatsData: function() {
- for (var i = 0; i < this._cpuStatsSize; i++) {
- this._cpuStatsData.push({time: -((this._cpuStatsSize - i - 1) * this._cpuStatsInterval), value: 0});
- }
+ if (option === 'mem')
+ this._memStatsData = actualData;
+ else if (option === 'cpu')
+ this._cpuStatsData = actualData;
},
onSocketOpen: function() {
@@ -44,19 +45,20 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
this.socket.send('subscribe mem_stats cpu_stats settings');
this.socket.send('settings');
this.socket.send('mem_stats');
+ this.socket.send('cpu_stats');
},
- _createMemData: function() {
- for (var i = this._memStatsRawData.length - 1, j = this._memStatsData.length - 1; i >= 0 && j >= 0; i--, j--) {
- this._memStatsData[j].value = parseInt(this._memStatsRawData[i]);
- }
- },
+ _d3MemXAxis: null,
+ _d3MemYAxis: null,
+ _d3MemLine: null,
+ _xMemScale: null,
+ _yMemScale: null,
- _d3xAxis: null,
- _d3yAxis: null,
- _d3line: null,
- _xScale: null,
- _yScale: null,
+ _d3CpuXAxis: null,
+ _d3CpuYAxis: null,
+ _d3CpuLine: null,
+ _xCpuScale: null,
+ _yCpuScale: null,
_graphWidth: 1000,
_graphHeight: 500,
@@ -67,22 +69,28 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
left: 100
},
- _setUpAxis: function() {
- this._xScale = d3.scale.linear().range([this._graphMargins.left, this._graphWidth - this._graphMargins.right]).domain([d3.min(this._memStatsData, function(d) {
+ _setUpAxis: function(option) {
+
+ if (option === 'mem')
+ data = this._memStatsData;
+ else if (option === 'cpu')
+ data = this._cpuStatsData;
+
+ xScale = d3.scale.linear().range([this._graphMargins.left, this._graphWidth - this._graphMargins.right]).domain([d3.min(data, function(d) {
return d.time;
- }), d3.max(this._memStatsData, function(d) {
+ }), d3.max(data, function(d) {
return d.time;
})]);
- this._yScale = d3.scale.linear().range([this._graphHeight - this._graphMargins.bottom, this._graphMargins.top]).domain([d3.min(this._memStatsData, function(d) {
+ yScale = d3.scale.linear().range([this._graphHeight - this._graphMargins.bottom, this._graphMargins.top]).domain([d3.min(data, function(d) {
return d.value;
- }), d3.max(this._memStatsData, function(d) {
+ }), d3.max(data, function(d) {
return d.value;
})]);
- this._d3xAxis = d3.svg.axis()
- .scale(this._xScale)
+ d3XAxis = d3.svg.axis()
+ .scale(xScale)
.tickFormat(function(d) {
d = Math.abs(d / 1000);
var units = ['s', 'min', 'hr'];
@@ -92,76 +100,120 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
return parseInt(d) + units[i] + ' ago';
});
- this._d3yAxis = d3.svg.axis()
- .scale(this._yScale)
- .tickFormat(function (d) {
- return Util.humanizeMem(d);
- })
- .orient('left');
-
- var xScale = this._xScale;
- var yScale = this._yScale;
-
- this._d3line = d3.svg.line()
+ d3Line = d3.svg.line()
.x(function(d) {
return xScale(d.time);
})
.y(function(d) {
return yScale(d.value);
- });
+ })
+ .interpolate('basis');
+
+ if (option === 'mem') {
+ this._xMemScale = xScale;
+ this._yMemScale = yScale;
+ this._d3MemXAxis = d3XAxis;
+ this._d3MemYAxis = d3.svg.axis()
+ .scale(this._yMemScale)
+ .tickFormat(function (d) {
+ return Util.humanizeMem(d);
+ })
+ .orient('left');
+ this._d3MemLine = d3Line;
+ }
+ else if (option === 'cpu') {
+ this._xCpuScale = xScale;
+ this._yCpuScale = yScale;
+ this._d3CpuXAxis = d3XAxis;
+ this._d3CpuYAxis = d3.svg.axis()
+ .scale(this._yCpuScale)
+ .tickFormat(function (d) {
+ return d + '%';
+ })
+ .orient('left');
+ this._d3CpuLine = d3Line;
+ }
},
- _createMemGraph: function() {
- var vis = d3.select('#visualisation');
-
- this._setUpAxis();
+ _createGraph: function(option) {
+ if (option === 'mem') {
+ var vis = d3.select('#MemVisualisation');
+ this._setUpAxis('mem');
+ xAxis = this._d3MemXAxis;
+ yAxis = this._d3MemYAxis;
+ line = this._d3MemLine;
+ data = this._memStatsData;
+ }
+ else if (option === 'cpu') {
+ var vis = d3.select('#CpuVisualisation');
+ this._setUpAxis('cpu');
+ xAxis = this._d3CpuXAxis;
+ yAxis = this._d3CpuYAxis;
+ line = this._d3CpuLine;
+ data = this._cpuStatsData;
+ }
vis.append('svg:g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + (this._graphHeight - this._graphMargins.bottom) + ')')
- .call(this._d3xAxis);
+ .call(xAxis);
vis.append('svg:g')
.attr('class', 'y-axis')
.attr('transform', 'translate(' + this._graphMargins.left + ',0)')
- .call(this._d3yAxis);
+ .call(yAxis);
vis.append('svg:path')
- .attr('d', this._d3line(this._memStatsData))
+ .attr('d', line(data))
.attr('class', 'line')
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
},
- _addNewMemData: function(data) {
+ _addNewData: function(oldData, newData) {
// make a space for new data
- for (var i = this._memStatsData.length - 1; i > 0; i--) {
- this._memStatsData[i].time = this._memStatsData[i - 1].time;
+ for (var i = oldData.length - 1; i > 0; i--) {
+ oldData[i].time = oldData[i - 1].time;
}
// push new data at time '0'
- this._memStatsData.push({time: 0, value: parseInt(data)});
+ oldData.push({time: 0, value: parseInt(newData)});
// remove extra items
- if (this._memStatsData.length > this._memStatsSize) {
- this._memStatsData.shift();
+ if (oldData.length > this._memStatsSize) {
+ oldData.shift();
}
},
_updateMemGraph: function() {
- var svg = d3.select('#visualisation');
+ svg = d3.select('#MemVisualisation');
+
+ this._setUpAxis('mem');
+
+ svg.select('.line')
+ .attr('d', this._d3MemLine(this._memStatsData));
+
+ svg.select('.x-axis')
+ .call(this._d3MemXAxis);
+
+ svg.select('.y-axis')
+ .call(this._d3MemYAxis);
+ },
+
+ _updateCpuGraph: function() {
+ svg = d3.select('#CpuVisualisation');
- this._setUpAxis();
+ this._setUpAxis('cpu');
svg.select('.line')
- .attr('d', this._d3line(this._memStatsData));
+ .attr('d', this._d3CpuLine(this._cpuStatsData));
svg.select('.x-axis')
- .call(this._d3xAxis);
+ .call(this._d3CpuXAxis);
svg.select('.y-axis')
- .call(this._d3yAxis);
+ .call(this._d3CpuYAxis);
},
onSocketMessage: function(e) {
@@ -173,12 +225,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
textMsg = '';
}
-
if (textMsg.startsWith('settings')) {
textMsg = textMsg.substring('settings '.length);
textMsg = textMsg.split(' ');
- //TODO: Add CPU statistics
var memStatsSize, memStatsInterval, cpuStatsSize, cpuStatsInterval;
var i, j, data;
memStatsSize = this._memStatsSize;
@@ -204,10 +254,10 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
// Fix the axes according to changed data
if (memStatsInterval !== this._memStatsInterval) {
// We can possibly reuse the data with a bit of work
- this._initMemStatsData(memStatsSize, memStatsInterval, true);
+ this._initStatsData('mem', memStatsSize, memStatsInterval, true);
}
else if (memStatsSize > this._memStatsSize) {
- this._initMemStatsData(memStatsSize - this._memStatsSize, memStatsInterval, false);
+ this._initStatsData('mem', memStatsSize - this._memStatsSize, memStatsInterval, false);
}
else {
// just strip the extra items
@@ -218,11 +268,24 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
this._memStatsSize = memStatsSize;
this._memStatsInterval = memStatsInterval;
+
+ // Similar Logic as above for CPU stats
+ if (cpuStatsInterval !== this._cpuStatsInterval) {
+ this._initStatsData('cpu', cpuStatsSize, cpuStatsInterval, true);
+ }
+ else if (cpuStatsSize > this._cpuStatsSize) {
+ this._initStatsData('cpu', cpuStatsSize - this._cpuStatsSize, cpuStatsInterval, false);
+ }
+ else {
+ for (i = 0; i < this._cpuStatsSize - cpuStatsSize; i++) {
+ this._cpuStatsData.shift();
+ }
+ }
+
this._cpuStatsSize = cpuStatsSize;
this._cpuStatsInterval = cpuStatsInterval;
}
- else if (textMsg.startsWith('mem_stats') ||
- textMsg.startsWith('cpu_stats')) {
+ else if (textMsg.startsWith('mem_stats')) {
textMsg = textMsg.split(' ')[1];
if (textMsg.endsWith(',')) {
// This is the result of query, not notification
@@ -231,16 +294,34 @@ var AdminSocketAnalytics = AdminSocketBase.extend({
this._memStatsData[i].value = parseInt(data[j]);
}
- //this._createMemData(data);
- this._createMemGraph();
+ this._createGraph('mem');
}
else {
// this is a notification data; append to _memStatsData
data = textMsg.trim();
- this._addNewMemData(data);
+ this._addNewData(this._memStatsData, data);
this._updateMemGraph();
}
}
+ else if (textMsg.startsWith('cpu_stats')) {
+ 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._cpuStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
+ this._cpuStatsData[i].value = parseInt(data[j]);
+ }
+
+ this._createGraph('cpu');
+ }
+ else {
+ // this is a notification data; append to _cpuStatsData
+ data = textMsg.trim();
+ this._addNewData(this._cpuStatsData, data);
+ this._updateCpuGraph();
+ }
+ }
},
onSocketClose: function() {
diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp
index 1c15aa1d0..425a352d7 100644
--- a/wsd/Admin.cpp
+++ b/wsd/Admin.cpp
@@ -12,6 +12,7 @@
#include <cassert>
#include <mutex>
#include <sys/poll.h>
+#include <unistd.h>
#include <Poco/Net/HTTPCookie.h>
#include <Poco/Net/HTTPRequest.h>
@@ -298,8 +299,9 @@ Admin::Admin() :
_model(AdminModel()),
_forKitPid(-1),
_lastTotalMemory(0),
+ _lastJiffies(0),
_memStatsTaskIntervalMs(5000),
- _cpuStatsTaskIntervalMs(5000)
+ _cpuStatsTaskIntervalMs(2000)
{
LOG_INF("Admin ctor.");
@@ -327,9 +329,13 @@ void Admin::pollingThread()
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)
{
- // TODO: implement me ...
+ auto cpuPercent = 100 * 1000 * currentJiffies / (sysconf (_SC_CLK_TCK) * _cpuStatsTaskIntervalMs);
+ _model.addCpuStats(cpuPercent);
+
lastCPU = now;
cpuWait += _cpuStatsTaskIntervalMs;
}
@@ -408,6 +414,24 @@ size_t Admin::getTotalMemoryUsage()
return totalMem;
}
+size_t Admin::getTotalCpuUsage()
+{
+ const size_t forkitJ = Util::getCpuUsage(_forKitPid);
+ const size_t wsdJ = Util::getCpuUsage(Poco::Process::id());
+ const size_t kitsJ = _model.getKitsJiffies();
+
+ if(_lastJiffies == 0)
+ {
+ _lastJiffies = forkitJ + wsdJ;
+ return 0;
+ }
+
+ const size_t totalJ = ((forkitJ + wsdJ) - _lastJiffies) + kitsJ;
+ _lastJiffies = forkitJ + wsdJ;
+
+ return totalJ;
+}
+
unsigned Admin::getMemStatsInterval()
{
return _memStatsTaskIntervalMs;
diff --git a/wsd/Admin.hpp b/wsd/Admin.hpp
index 2c77e391f..91df81084 100644
--- a/wsd/Admin.hpp
+++ b/wsd/Admin.hpp
@@ -70,6 +70,7 @@ public:
void pollingThread() override;
size_t getTotalMemoryUsage();
+ size_t getTotalCpuUsage();
void modificationAlert(const std::string& dockey, Poco::Process::PID pid, bool value);
/// Update the Admin Model.
@@ -109,6 +110,7 @@ private:
AdminModel _model;
int _forKitPid;
size_t _lastTotalMemory;
+ size_t _lastJiffies;
std::atomic<int> _memStatsTaskIntervalMs;
std::atomic<int> _cpuStatsTaskIntervalMs;
diff --git a/wsd/AdminModel.cpp b/wsd/AdminModel.cpp
index 1ee088eb8..bcbdd1d11 100644
--- a/wsd/AdminModel.cpp
+++ b/wsd/AdminModel.cpp
@@ -271,6 +271,31 @@ unsigned AdminModel::getKitsMemoryUsage()
return totalMem;
}
+size_t AdminModel::getKitsJiffies()
+{
+ assertCorrectThread();
+
+ size_t totalJ = 0;
+ for (auto& it : _documents)
+ {
+ if (!it.second.isExpired())
+ {
+ const auto pid = it.second.getPid();
+ if (pid > 0)
+ {
+ unsigned newJ = Util::getCpuUsage(pid);
+ unsigned prevJ = it.second.getLastJiffies();
+ if(newJ >= prevJ)
+ {
+ totalJ += (newJ - prevJ);
+ it.second.setLastJiffies(newJ);
+ }
+ }
+ }
+ }
+ return totalJ;
+}
+
void AdminModel::subscribe(int sessionId, const std::weak_ptr<WebSocketHandler>& ws)
{
assertCorrectThread();
diff --git a/wsd/AdminModel.hpp b/wsd/AdminModel.hpp
index ed83d8cd0..907169640 100644
--- a/wsd/AdminModel.hpp
+++ b/wsd/AdminModel.hpp
@@ -54,6 +54,7 @@ public:
_pid(pid),
_filename(filename),
_memoryDirty(0),
+ _lastJiffy(0),
_start(std::time(nullptr)),
_lastActivity(_start),
_sentBytes(0),
@@ -77,6 +78,9 @@ public:
unsigned getActiveViews() const { return _activeViews; }
+ unsigned getLastJiffies() const { return _lastJiffy; }
+ void setLastJiffies(size_t newJ) { _lastJiffy = newJ; }
+
const std::map<std::string, View>& getViews() const { return _views; }
void updateLastActivityTime() { _lastActivity = std::time(nullptr); }
@@ -110,6 +114,8 @@ private:
std::string _filename;
/// The dirty (ie. un-shared) memory of the document's Kit process.
int _memoryDirty;
+ /// Last noted Jiffy count
+ unsigned _lastJiffy;
std::time_t _start;
std::time_t _lastActivity;
@@ -184,6 +190,7 @@ public:
/// Returns memory consumed by all active loolkit processes
unsigned getKitsMemoryUsage();
+ size_t getKitsJiffies();
void subscribe(int sessionId, const std::weak_ptr<WebSocketHandler>& ws);
void subscribe(int sessionId, const std::string& command);