test/TileCacheTests.cpp |   31 ++++++++++++
 wsd/DocumentBroker.cpp  |   12 ++++
 wsd/DocumentBroker.hpp  |    3 +
 wsd/TileCache.cpp       |  116 ++++++++++++++++++++++++++++++++++++++++++------
 wsd/TileCache.hpp       |   18 +++++++
 5 files changed, 165 insertions(+), 15 deletions(-)

New commits:
commit 7890533419f2fda99e5d0a4f6c54e10b0516787d
Author:     Michael Meeks <michael.me...@collabora.com>
AuthorDate: Mon Oct 28 13:26:15 2019 +0000
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Mon Oct 28 20:29:01 2019 +0000

    TileCache: track and limit size to control memory usage.
    
    Implement a basic WSD-side memory sizing approach and tell the
    Admin console about it.
    
    Change-Id: I1f0b5cf9fe29cb23ea574371e81e981b7af7a954

diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp
index ad0f46823..174f3d5bb 100644
--- a/test/TileCacheTests.cpp
+++ b/test/TileCacheTests.cpp
@@ -60,6 +60,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST(testDesc);
     CPPUNIT_TEST(testSimple);
     CPPUNIT_TEST(testSimpleCombine);
+    CPPUNIT_TEST(testSize);
     CPPUNIT_TEST(testCancelTiles);
     // unstable
     // CPPUNIT_TEST(testCancelTilesMultiView);
@@ -95,6 +96,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
     void testDesc();
     void testSimple();
     void testSimpleCombine();
+    void testSize();
     void testCancelTiles();
     void testCancelTilesMultiView();
     void testDisconnectMultiView();
@@ -262,6 +264,35 @@ void TileCacheTests::testSimpleCombine()
     CPPUNIT_ASSERT_MESSAGE("did not receive a tile: message as expected", 
!tile2b.empty());
 }
 
+void TileCacheTests::testSize()
+{
+    // Create TileCache and pretend the file was modified as recently as
+    // now, so it discards the cached data.
+    TileCache tc("doc.ods", std::chrono::system_clock::time_point());
+
+    int nviewid = 0;
+    int part = 0;
+    int width = 256;
+    int height = 256;
+    int tilePosX = 0;
+    int tileWidth = 3840;
+    int tileHeight = 3840;
+    TileWireId id = 0;
+    const std::vector<char> data = genRandomData(4096);
+
+    // Churn the cache somewhat
+    size_t maxSize = (data.size() + sizeof (TileDesc)) * 10;
+    tc.setMaxCacheSize(maxSize);
+    for (int tilePosY = 0; tilePosY < 20; tilePosY++)
+    {
+        TileDesc tile(nviewid, part, width, height, tilePosX, tilePosY * 
tileHeight,
+                      tileWidth, tileHeight, -1, 0, -1, false);
+        tile.setWireId(id++);
+        tc.saveTileAndNotify(tile, data.data(), data.size());
+    }
+    CPPUNIT_ASSERT_MESSAGE("tile cache too big", tc.getMemorySize() < maxSize);
+}
+
 void TileCacheTests::testCancelTiles()
 {
     const char* testname = "cancelTiles ";
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index c3f0dd04c..081a07c55 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -290,6 +290,9 @@ void DocumentBroker::pollThread()
         const auto now = std::chrono::steady_clock::now();
 
 #if !MOBILEAPP
+        // a tile's data is ~8k, a 4k screen is ~128 256x256 tiles
+        _tileCache->setMaxCacheSize(8 * 1024 * 128 * _sessions.size());
+
         if (!_isLoaded && (limit_load_secs > 0) && (now > loadDeadline))
         {
             LOG_WRN("Doc [" << _docKey << "] is taking too long to load. Will 
kill process ["
@@ -1444,7 +1447,8 @@ bool DocumentBroker::handleInput(const std::vector<char>& 
payload)
             int dirty;
             if (message->getTokenInteger("dirty", dirty))
             {
-                Admin::instance().updateMemoryDirty(_docKey, dirty);
+                Admin::instance().updateMemoryDirty(
+                    _docKey, dirty + getMemorySize()/1024);
             }
         }
 #endif
@@ -1458,6 +1462,12 @@ bool DocumentBroker::handleInput(const 
std::vector<char>& payload)
     return true;
 }
 
+size_t DocumentBroker::getMemorySize() const
+{
+    return sizeof(DocumentBroker) + _tileCache->getMemorySize() +
+        _sessions.size() * sizeof(ClientSession);
+}
+
 void DocumentBroker::invalidateTiles(const std::string& tiles, int 
normalizedViewId)
 {
     // Remove from cache.
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 4d66ffae3..b64c7c28c 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -362,6 +362,9 @@ public:
     /// For testing only [!]
     std::vector<std::shared_ptr<ClientSession>> getSessionsTestOnlyUnsafe();
 
+    /// Estimate memory usage / bytes
+    size_t getMemorySize() const;
+
 private:
 
     /// Loads a document from the public URI into the jail.
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index df4141dfa..4c34577d2 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -35,7 +35,9 @@ TileCache::TileCache(const std::string& docURL,
                      const std::chrono::system_clock::time_point& modifiedTime,
                      bool dontCache) :
     _docURL(docURL),
-    _dontCache(dontCache)
+    _dontCache(dontCache),
+    _cacheSize(0),
+    _maxCacheSize(512*1024)
 {
 #ifndef BUILDING_TESTS
     LOG_INF("TileCache ctor for uri [" << LOOLWSD::anonymizeUrl(_docURL) <<
@@ -56,6 +58,7 @@ TileCache::~TileCache()
 void TileCache::clear()
 {
     _cache.clear();
+    _cacheSize = 0;
     for (auto i : _streamCache)
         i.clear();
     LOG_INF("Completely cleared tile cache for: " << _docURL);
@@ -301,6 +304,7 @@ void TileCache::invalidateTiles(int part, int x, int y, int 
width, int height, i
         if (intersectsTile(it->first, part, x, y, width, height, 
normalizedViewId))
         {
             LOG_TRC("Removing tile: " << it->first.serialize());
+            _cacheSize -= itemCacheSize(it->second);
             it = _cache.erase(it);
         }
         else
@@ -538,9 +542,100 @@ void TileCache::saveDataToCache(const TileDesc &desc, 
const char *data, const si
     if (_dontCache)
         return;
 
+    ensureCacheSize();
+
     TileCache::Tile tile = std::make_shared<std::vector<char>>(size);
     std::memcpy(tile->data(), data, size);
-    _cache[desc] = tile;
+    auto res = _cache.insert(std::make_pair(desc, tile));
+    if (!res.second)
+    {
+        _cacheSize -= itemCacheSize(res.first->second);
+        _cache[desc] = tile;
+    }
+    _cacheSize += itemCacheSize(tile);
+}
+
+size_t TileCache::itemCacheSize(const Tile &tile)
+{
+    return tile->size() + sizeof(TileDesc);
+}
+
+void TileCache::assertCacheSize()
+{
+#ifdef ENABLE_DEBUG
+    {
+        size_t recalcSize = 0;
+        for (auto &it : _cache) {
+            recalcSize += itemCacheSize(it.second);
+        }
+        assert(recalcSize == _cacheSize);
+    }
+#endif
+}
+
+void TileCache::ensureCacheSize()
+{
+    assertCacheSize();
+
+    if (_cacheSize < _maxCacheSize || _cache.size() < 2)
+        return;
+
+    LOG_TRC("Cleaning tile cache of size " << _cacheSize << " vs. " << 
_maxCacheSize <<
+            " with " << _cache.size() << " entries");
+
+    struct WidSize {
+        TileWireId _wid;
+        size_t     _size;
+        WidSize(TileWireId w, size_t s) : _wid(w), _size(s) {}
+    };
+    std::vector<WidSize> wids;
+    for (auto &it : _cache)
+        wids.push_back(WidSize(it.first.getWireId(),
+                               itemCacheSize(it.second)));
+    std::sort(wids.begin(), wids.end(),
+              [](const WidSize &a, const WidSize &b) { return a._wid < b._wid; 
});
+
+    TileWireId maxToRemove;
+    // do we have (the very rare) WID wrap-around
+    if (wids.back()._wid - wids.front()._wid > 256*256*256)
+        maxToRemove = wids.back()._wid;
+    // calculate which wid to start at.
+    else
+    {
+        size_t total = 0;
+        for (auto &it : wids)
+        {
+            total += it._size;
+            maxToRemove = it._wid;
+            if (total > _maxCacheSize/4)
+                break;
+        }
+    }
+    LOG_TRC("cleaning up to wid " << maxToRemove << " between " <<
+            wids.front()._wid << " and " << wids.back()._wid);
+
+    for (auto it = _cache.begin(); it != _cache.end();)
+    {
+        if (it->first.getWireId() <= maxToRemove)
+        {
+            LOG_TRC("cleaned out tile: " << it->first.serialize());
+            _cacheSize -= itemCacheSize(it->second);
+            it = _cache.erase(it);
+        }
+        else
+            ++it;
+    }
+
+    LOG_TRC("Cache is now of size " << _cacheSize << " and " <<
+            _cache.size() << " entries after cleaning");
+
+    assertCacheSize();
+}
+
+void TileCache::setMaxCacheSize(size_t cacheSize)
+{
+    _maxCacheSize = cacheSize;
+    ensureCacheSize();
 }
 
 void TileCache::saveDataToStreamCache(StreamType type, const std::string 
&fileName, const char *data, const size_t size)
@@ -565,19 +660,12 @@ void 
TileCache::TileBeingRendered::dumpState(std::ostream& os)
 
 void TileCache::dumpState(std::ostream& os)
 {
+    os << "  tile cache: num: " << _cache.size() << " size: " << _cacheSize << 
" bytes\n";
+    for (const auto& it : _cache)
     {
-        size_t num = 0, size = 0;
-        for (const auto& it : _cache)
-        {
-            num++; size += it.second->size();
-        }
-        os << "  tile cache: num: " << num << " size: " << size << " bytes\n";
-        for (const auto& it : _cache)
-        {
-            os << "    " << std::setw(4) << it.first.getWireId()
-               << "\t" << std::setw(6) << it.second->size() << " bytes"
-               << "\t'" << it.first.serialize() << "'\n" ;
-        }
+        os << "    " << std::setw(4) << it.first.getWireId()
+           << "\t" << std::setw(6) << it.second->size() << " bytes"
+           << "\t'" << it.first.serialize() << "'\n" ;
     }
 
     int type = 0;
diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp
index 5d43abd7e..bdf17e211 100644
--- a/wsd/TileCache.hpp
+++ b/wsd/TileCache.hpp
@@ -89,6 +89,7 @@ public:
     void clear();
 
     TileCache(const TileCache&) = delete;
+    TileCache& operator=(const TileCache&) = delete;
 
     /// Subscribes if no subscription exists and returns the version number.
     /// Otherwise returns 0 to signify a subscription exists.
@@ -141,12 +142,22 @@ public:
     bool hasTileBeingRendered(const TileDesc& tileDesc);
     int  getTileBeingRenderedVersion(const TileDesc& tileDesc);
 
+    /// Set the high watermark for tilecache size
+    void setMaxCacheSize(size_t cacheSize);
+
+    /// Get the current memory use.
+    size_t getMemorySize() const { return _cacheSize; }
+
     // Debugging bits ...
     void dumpState(std::ostream& os);
     void setThreadOwner(const std::thread::id &id) { _owner = id; }
     void assertCorrectThread();
+    void assertCacheSize();
 
 private:
+    void ensureCacheSize();
+    static size_t itemCacheSize(const Tile &tile);
+
     void invalidateTiles(int part, int x, int y, int width, int height, int 
normalizedViewId);
 
     /// Lookup tile in our cache.
@@ -172,6 +183,13 @@ private:
     std::thread::id _owner;
 
     bool _dontCache;
+
+    /// Approximate size of tilecache in bytes
+    size_t _cacheSize;
+
+    /// Maximum (high watermark) size of the tilecache in bytes
+    size_t _maxCacheSize;
+
     // FIXME: should we have a tile-desc to WID map instead and a simpler 
lookup ?
     std::unordered_map<TileCacheDesc, Tile,
                        TileCacheDescHasher,
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to