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