common/Util.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++++-- common/Util.hpp | 10 ++++++ kit/Kit.cpp | 29 +++++++++++++++++++ wsd/Admin.cpp | 41 ++++++++++++++++++++------- wsd/Admin.hpp | 11 ++++--- wsd/AdminModel.cpp | 48 +++++++++++++++++++++++++------- wsd/AdminModel.hpp | 7 ++++ wsd/DocumentBroker.cpp | 8 +++++ 8 files changed, 195 insertions(+), 31 deletions(-)
New commits: commit d7a9a76ddbd0590542b6c0f765c31d7af9fe09c4 Author: Ashod Nakashian <[email protected]> Date: Fri Feb 3 01:29:53 2017 -0500 wsd: report PSS of kit processes Each Kit process now reports its own PSS, which is much more accurate as they share a significant ratio of their pages with one another. Admin tracks the PSS values of the Kits and reports to the console. Change-Id: Ifa66d17749c224f0dc211db80c44f7c913f2d6c4 Reviewed-on: https://gerrit.libreoffice.org/33864 Reviewed-by: Ashod Nakashian <[email protected]> Tested-by: Ashod Nakashian <[email protected]> diff --git a/common/Util.cpp b/common/Util.cpp index 48c0acf..bb64025 100644 --- a/common/Util.cpp +++ b/common/Util.cpp @@ -125,7 +125,76 @@ namespace Util return std::getenv("DISPLAY") != nullptr; } - int getMemoryUsage(const Poco::Process::PID pid) + static const char *startsWith(const char *line, const char *tag) + { + int len = strlen(tag); + if (!strncmp(line, tag, len)) + { + while (!isdigit(line[len]) && line[len] != '\0') + ++len; + + const auto str = std::string(line + len, strlen(line + len) - 1); + return line + len; + } + + return nullptr; + } + + std::pair<size_t, size_t> getPssAndDirtyFromSMaps(FILE* file) + { + size_t numPSSKb = 0; + size_t numDirtyKb = 0; + if (file) + { + rewind(file); + char line[4096] = { 0 }; + while (fgets(line, sizeof (line), file)) + { + const char *value; + if ((value = startsWith(line, "Private_Dirty:")) || + (value = startsWith(line, "Shared_Dirty:"))) + { + numDirtyKb += atoi(value); + } + else if ((value = startsWith(line, "Pss:"))) + { + numPSSKb += atoi(value); + } + } + } + + return std::make_pair(numPSSKb, numDirtyKb); + } + + std::string getMemoryStats(FILE* file) + { + const auto pssAndDirtyKb = getPssAndDirtyFromSMaps(file); + std::ostringstream oss; + oss << "procmemstats: pid=" << getpid() + << " pss=" << pssAndDirtyKb.first + << " dirty=" << pssAndDirtyKb.second; + LOG_TRC("Collected " << oss.str()); + return oss.str(); + } + + size_t getMemoryUsagePSS(const Poco::Process::PID pid) + { + if (pid > 0) + { + const auto cmd = "/proc/" + std::to_string(pid) + "/smaps"; + FILE* fp = fopen(cmd.c_str(), "r"); + if (fp != nullptr) + { + const auto pss = getPssAndDirtyFromSMaps(fp).first; + fclose(fp); + return pss; + } + } + + return 0; + } + + size_t getMemoryUsageRSS(const Poco::Process::PID pid) { if (pid == -1) { @@ -134,7 +203,6 @@ namespace Util try { - //TODO: Instead of RSS, return PSS const auto cmd = "ps o rss= -p " + std::to_string(pid); FILE* fp = popen(cmd.c_str(), "r"); if (fp == nullptr) diff --git a/common/Util.hpp b/common/Util.hpp index 3e3d706..ad0fea8 100644 --- a/common/Util.hpp +++ b/common/Util.hpp @@ -82,7 +82,15 @@ namespace Util assert(!mtx.try_lock()); } - int getMemoryUsage(const Poco::Process::PID pid); + /// Returns the process PSS in KB (works only when we have perms for /proc/pid/smaps). + size_t getMemoryUsagePSS(const Poco::Process::PID pid); + + /// Returns the process RSS in KB. + size_t getMemoryUsageRSS(const Poco::Process::PID pid); + + /// Returns the RSS and PSS of the current process in KB. + /// Example: "procmemstats: pid=123 rss=12400 pss=566" + std::string getMemoryStats(FILE* file); std::string replace(const std::string& s, const std::string& a, const std::string& b); diff --git a/kit/Kit.cpp b/kit/Kit.cpp index cb9c778..b527fda 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -419,6 +419,8 @@ public: } }; +static FILE* ProcSMapsFile = nullptr; + /// A document container. /// Owns LOKitDocument instance and connections. /// Manages the lifetime of a document. @@ -1352,11 +1354,29 @@ private: LOG_DBG("Thread started."); + // Update memory stats every 5 seconds. + const auto memStatsPeriodMs = 5000; + auto lastMemStatsTime = std::chrono::steady_clock::now(); + sendTextFrame(Util::getMemoryStats(ProcSMapsFile)); + try { while (!_stop && !TerminationFlag) { - const TileQueue::Payload input = _tileQueue->get(); + const TileQueue::Payload input = _tileQueue->get(POLL_TIMEOUT_MS * 2); + if (input.empty()) + { + const auto duration = (std::chrono::steady_clock::now() - lastMemStatsTime); + const auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + if (durationMs > memStatsPeriodMs) + { + sendTextFrame(Util::getMemoryStats(ProcSMapsFile)); + lastMemStatsTime = std::chrono::steady_clock::now(); + } + + continue; + } + LOG_TRC("Kit Recv " << LOOLProtocol::getAbbreviatedMessage(input)); if (_stop || TerminationFlag) @@ -1649,6 +1669,7 @@ void lokit_main(const std::string& childRoot, { LOG_SYS("mknod(" << jailPath.toString() << "/dev/random) failed."); } + if (mknod((jailPath.toString() + "/dev/urandom").c_str(), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, makedev(1, 9)) != 0) @@ -1656,6 +1677,12 @@ void lokit_main(const std::string& childRoot, LOG_SYS("mknod(" << jailPath.toString() << "/dev/urandom) failed."); } + ProcSMapsFile = fopen("/proc/self/smaps", "r"); + if (ProcSMapsFile == nullptr) + { + LOG_SYS("Failed to symlink /proc/self/smaps. Memory stats will be missing."); + } + LOG_INF("chroot(\"" << jailPath.toString() << "\")"); if (chroot(jailPath.toString().c_str()) == -1) { diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index 6d62878..9dc608b 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -289,7 +289,12 @@ Admin::Admin() : { LOG_INF("Admin ctor."); - _memStatsTask.reset(new MemoryStats(this)); + std::unique_lock<std::mutex> modelLock(getLock()); + const auto totalMem = getTotalMemoryUsage(); + LOG_TRC("Total memory used: " << totalMem); + _model.addMemStats(totalMem); + + _memStatsTask.reset(new MemoryStatsTask(this)); _memStatsTimer.schedule(_memStatsTask.get(), _memStatsTaskInterval, _memStatsTaskInterval); _cpuStatsTask = new CpuStats(this); @@ -323,19 +328,18 @@ void Admin::rmDoc(const std::string& docKey) _model.removeDocument(docKey); } -void MemoryStats::run() +void MemoryStatsTask::run() { std::unique_lock<std::mutex> modelLock(_admin->getLock()); - AdminModel& model = _admin->getModel(); - const auto totalMem = model.getKitsMemoryUsage(); + const auto totalMem = _admin->getTotalMemoryUsage(); if (totalMem != _lastTotalMemory) { LOG_TRC("Total memory used: " << totalMem); + _lastTotalMemory = totalMem; } - _lastTotalMemory = totalMem; - model.addMemStats(totalMem); + _admin->getModel().addMemStats(totalMem); } void CpuStats::run() @@ -349,7 +353,7 @@ void Admin::rescheduleMemTimer(unsigned interval) { _memStatsTask->cancel(); _memStatsTaskInterval = interval; - _memStatsTask.reset(new MemoryStats(this)); + _memStatsTask.reset(new MemoryStatsTask(this)); _memStatsTimer.schedule(_memStatsTask.get(), _memStatsTaskInterval, _memStatsTaskInterval); LOG_INF("Memory stats interval changed - New interval: " << interval); } @@ -365,10 +369,19 @@ void Admin::rescheduleCpuTimer(unsigned interval) unsigned Admin::getTotalMemoryUsage() { - unsigned totalMem = Util::getMemoryUsage(_forKitPid); - totalMem += _memStatsTask->getLastTotalMemory(); - totalMem += Util::getMemoryUsage(Poco::Process::id()); - + Util::assertIsLocked(_modelMutex); + + // PSS would be wrong for forkit since we will have one or + // more prespawned kits that will share their pages with forkit, + // but we don't count the kits unless and until a document is loaded. + // So RSS is a decent approximation (albeit slightly on the high side). + const size_t forkitRssKb = Util::getMemoryUsageRSS(_forKitPid); + const size_t wsdPssKb = Util::getMemoryUsagePSS(Poco::Process::id()); + const size_t kitsPssKb = _model.getKitsMemoryUsage(); + const size_t totalMem = wsdPssKb + forkitRssKb + kitsPssKb; + + LOG_TRC("Total mem: " << totalMem << ", wsd pss: " << wsdPssKb << + ", forkit rss: " << forkitRssKb << ", kits pss: " << kitsPssKb); return totalMem; } @@ -393,4 +406,10 @@ void Admin::updateLastActivityTime(const std::string& docKey) _model.updateLastActivityTime(docKey); } +void Admin::updateMemoryPss(const std::string& docKey, int pss) +{ + std::unique_lock<std::mutex> modelLock(_modelMutex); + _model.updateMemoryPss(docKey, pss); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/Admin.hpp b/wsd/Admin.hpp index d02a195..6e7ef58 100644 --- a/wsd/Admin.hpp +++ b/wsd/Admin.hpp @@ -48,7 +48,7 @@ private: bool _isAuthenticated; }; -class MemoryStats; +class MemoryStatsTask; /// An admin command processor. class Admin @@ -97,6 +97,7 @@ public: std::unique_lock<std::mutex> getLock() { return std::unique_lock<std::mutex>(_modelMutex); } void updateLastActivityTime(const std::string& docKey); + void updateMemoryPss(const std::string& docKey, int pss); private: Admin(); @@ -107,7 +108,7 @@ private: int _forKitPid; Poco::Util::Timer _memStatsTimer; - std::unique_ptr<MemoryStats> _memStatsTask; + std::unique_ptr<MemoryStatsTask> _memStatsTask; unsigned _memStatsTaskInterval = 5000; Poco::Util::Timer _cpuStatsTimer; @@ -116,17 +117,17 @@ private: }; /// Memory statistics. -class MemoryStats : public Poco::Util::TimerTask +class MemoryStatsTask : public Poco::Util::TimerTask { public: - MemoryStats(Admin* admin) + MemoryStatsTask(Admin* admin) : _admin(admin), _lastTotalMemory(0) { LOG_DBG("Memory stat ctor"); } - ~MemoryStats() + ~MemoryStatsTask() { LOG_DBG("Memory stat dtor"); } diff --git a/wsd/AdminModel.cpp b/wsd/AdminModel.cpp index 43d7d46..87a8336 100644 --- a/wsd/AdminModel.cpp +++ b/wsd/AdminModel.cpp @@ -131,14 +131,13 @@ std::string AdminModel::query(const std::string& command) /// Returns memory consumed by all active loolkit processes unsigned AdminModel::getKitsMemoryUsage() { - Poco::Timestamp ts; unsigned totalMem = 0; unsigned docs = 0; for (const auto& it : _documents) { if (!it.second.isExpired()) { - const auto bytes = Util::getMemoryUsage(it.second.getPid()); + const auto bytes = it.second.getMemoryPss(); if (bytes > 0) { totalMem += bytes; @@ -149,9 +148,8 @@ unsigned AdminModel::getKitsMemoryUsage() if (docs > 0) { - LOG_TRC("Got total Kits memory of " << totalMem << " bytes in " << ts.elapsed()/1001. << - " ms for " << docs << " docs, avg: " << static_cast<double>(totalMem) / docs << - " bytes / doc in " << ts.elapsed() / 1000. / docs << " ms per doc."); + LOG_TRC("Got total Kits memory of " << totalMem << " bytes for " << docs << + " docs, avg: " << static_cast<double>(totalMem) / docs << " bytes / doc."); } return totalMem; @@ -256,16 +254,35 @@ void AdminModel::addDocument(const std::string& docKey, Poco::Process::PID pid, ret.first->second.addView(sessionId); LOG_DBG("Added admin document [" << docKey << "]."); - // Notify the subscribers - const unsigned memUsage = Util::getMemoryUsage(pid); - std::ostringstream oss; std::string encodedFilename; Poco::URI::encode(filename, " ", encodedFilename); + + // Notify the subscribers + std::ostringstream oss; oss << "adddoc " << pid << ' ' << encodedFilename << ' ' - << sessionId << ' ' - << memUsage; + << sessionId << ' '; + + // We have to wait until the kit sends us its PSS. + // Here we guestimate until we get an update. + if (_documents.size() < 2) // If we aren't the only one. + { + if (_memStats.empty()) + { + oss << 0; + } + else + { + // Estimate half as much as wsd+forkit. + oss << _memStats.front() / 2; + } + } + else + { + oss << _documents.begin()->second.getMemoryPss(); + } + notify(oss.str()); } @@ -360,7 +377,7 @@ std::string AdminModel::getDocuments() const oss << it.second.getPid() << ' ' << encodedFilename << ' ' << it.second.getActiveViews() << ' ' - << Util::getMemoryUsage(it.second.getPid()) << ' ' + << it.second.getMemoryPss() << ' ' << it.second.getElapsedTime() << ' ' << it.second.getIdleTime() << " \n "; } @@ -382,4 +399,13 @@ void AdminModel::updateLastActivityTime(const std::string& docKey) } } +void AdminModel::updateMemoryPss(const std::string& docKey, int pss) +{ + auto docIt = _documents.find(docKey); + if (docIt != _documents.end()) + { + docIt->second.updateMemoryPss(pss); + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/AdminModel.hpp b/wsd/AdminModel.hpp index 1251470..0e46ca5 100644 --- a/wsd/AdminModel.hpp +++ b/wsd/AdminModel.hpp @@ -49,6 +49,7 @@ public: : _docKey(docKey), _pid(pid), _filename(filename), + _memoryPss(0), _start(std::time(nullptr)), _lastActivity(_start) { @@ -73,6 +74,8 @@ public: const std::map<std::string, View>& getViews() const { return _views; } void updateLastActivityTime() { _lastActivity = std::time(nullptr); } + void updateMemoryPss(int pss) { _memoryPss = pss; } + int getMemoryPss() const { return _memoryPss; } private: const std::string _docKey; @@ -83,6 +86,8 @@ private: unsigned _activeViews = 0; /// Hosted filename std::string _filename; + /// The PSS of the document's Kit process. + int _memoryPss; std::time_t _start; std::time_t _lastActivity; @@ -173,6 +178,7 @@ public: void removeDocument(const std::string& docKey); void updateLastActivityTime(const std::string& docKey); + void updateMemoryPss(const std::string& docKey, int pss); private: std::string getMemStats(); @@ -187,6 +193,7 @@ private: std::map<int, Subscriber> _subscribers; std::map<std::string, Document> _documents; + /// The last N total memory PSS. std::list<unsigned> _memStats; unsigned _memStatsSize = 100; diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 91f3ce4..c454612 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -747,6 +747,14 @@ bool DocumentBroker::handleInput(const std::vector<char>& payload) LOG_CHECK_RET(kind != "", false); Util::alertAllUsers(cmd, kind); } + else if (command == "procmemstats:") + { + int pss; + if (message->getTokenInteger("pss", pss)) + { + Admin::instance().updateMemoryPss(_docKey, pss); + } + } else { LOG_ERR("Unexpected message: [" << msg << "]."); _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
