[Libreoffice-commits] online.git: common/Util.cpp common/Util.hpp kit/Kit.cpp wsd/Admin.cpp wsd/Admin.hpp wsd/AdminModel.cpp wsd/AdminModel.hpp wsd/DocumentBroker.cpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Fri Feb 3 07:12:50 UTC 2017


 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 <ashod.nakashian at collabora.co.uk>
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 <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

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 << "].");


More information about the Libreoffice-commits mailing list