common/Util.cpp | 40 +++-- common/Util.hpp | 4 loleaflet/admin.strings.js | 2 loleaflet/dist/admin/adminAnalytics.html | 36 +++- loleaflet/dist/admin/bootstrap/dashboard.css | 11 + loleaflet/src/admin/AdminSocketAnalytics.js | 215 ++++++++++++++++++--------- wsd/Admin.cpp | 28 +++ wsd/Admin.hpp | 2 wsd/AdminModel.cpp | 25 +++ wsd/AdminModel.hpp | 7 10 files changed, 282 insertions(+), 88 deletions(-)
New commits: commit 9502741590d293155e7b3546654aa696b045362b Author: Aditya Dewan <iit2015...@iiita.ac.in> Date: Tue Jun 6 06:47:42 2017 +0530 tdf#107278 admin console: adding graph to track CPU load Change-Id: Idb07fe4139dd639a49ce1545cc15895f74876b06 Reviewed-on: https://gerrit.libreoffice.org/38425 Reviewed-by: pranavk <pran...@collabora.co.uk> Tested-by: pranavk <pran...@collabora.co.uk> diff --git a/common/Util.cpp b/common/Util.cpp index a94457e8..a61fe6d0 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 0fbf794d..4f9906e9 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 a38f9a0e..9bc38b29 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 b905619b..10957f24 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 cdf37a82..95d62235 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 898063c0..b09348c2 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 1c15aa1d..425a352d 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 2c77e391..91df8108 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 1ee088eb..bcbdd1d1 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 ed83d8cd..90716964 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); _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits