[Libreoffice-commits] online.git: test/TileCacheTests.cpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/TileCache.cpp wsd/TileCache.hpp
Michael Meeks (via logerrit)
logerrit at kemper.freedesktop.org
Mon Oct 28 20:29:22 UTC 2019
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.meeks at collabora.com>
AuthorDate: Mon Oct 28 13:26:15 2019 +0000
Commit: Michael Meeks <michael.meeks at 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,
More information about the Libreoffice-commits
mailing list