diff options
author | Aditya Dewan <iit2015097@iiita.ac.in> | 2017-06-10 06:58:16 +0530 |
---|---|---|
committer | pranavk <pranavk@collabora.co.uk> | 2017-06-16 09:55:01 +0200 |
commit | 681138ab54ac03fbb26b1c2aa3ae7bcf9e7dde82 (patch) | |
tree | 982ffe820bf351d6ac838e55f3b7fd2843d16798 | |
parent | de785d90b26f5462fa21736fef130e9bf775afef (diff) |
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>
-rw-r--r-- | loleaflet/admin.strings.js | 1 | ||||
-rw-r--r-- | loleaflet/dist/admin/adminAnalytics.html | 12 | ||||
-rw-r--r-- | loleaflet/src/admin/AdminSocketAnalytics.js | 245 | ||||
-rw-r--r-- | wsd/Admin.cpp | 45 | ||||
-rw-r--r-- | wsd/Admin.hpp | 6 | ||||
-rw-r--r-- | wsd/AdminModel.cpp | 60 | ||||
-rw-r--r-- | wsd/AdminModel.hpp | 14 | ||||
-rw-r--r-- | wsd/DocumentBroker.cpp | 4 |
8 files changed, 361 insertions, 26 deletions
diff --git a/loleaflet/admin.strings.js b/loleaflet/admin.strings.js index 896dcbc3e..26f232baf 100644 --- a/loleaflet/admin.strings.js +++ b/loleaflet/admin.strings.js @@ -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)'); diff --git a/loleaflet/dist/admin/adminAnalytics.html b/loleaflet/dist/admin/adminAnalytics.html index 10957f249..2dc972033 100644 --- a/loleaflet/dist/admin/adminAnalytics.html +++ b/loleaflet/dist/admin/adminAnalytics.html @@ -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> diff --git a/loleaflet/src/admin/AdminSocketAnalytics.js b/loleaflet/src/admin/AdminSocketAnalytics.js index b09348c24..72d5f2757 100644 --- a/loleaflet/src/admin/AdminSocketAnalytics.js +++ b/loleaflet/src/admin/AdminSocketAnalytics.js @@ -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); -}; +};
\ No newline at end of file diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index 3d4a8fb69..deeaecc03 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -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; diff --git a/wsd/Admin.hpp b/wsd/Admin.hpp index 5ca875720..18acab4a7 100644 --- a/wsd/Admin.hpp +++ b/wsd/Admin.hpp @@ -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 diff --git a/wsd/AdminModel.cpp b/wsd/AdminModel.cpp index bcbdd1d11..073d886cf 100644 --- a/wsd/AdminModel.cpp +++ b/wsd/AdminModel.cpp @@ -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(); diff --git a/wsd/AdminModel.hpp b/wsd/AdminModel.hpp index 96231bdaf..e9eed3737 100644 --- a/wsd/AdminModel.hpp +++ b/wsd/AdminModel.hpp @@ -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; diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 844bff781..d4ac586a9 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -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; |