mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Add tools to help debug image GC (#4578)
`/debug-force-image-gc` will force garbage collection on all unused images `/debug-force-image-unload` will force unload all images Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
5ca7d387e4
commit
fb02d59b48
5 changed files with 88 additions and 6 deletions
|
@ -9,6 +9,7 @@
|
||||||
- Dev: Added test cases for emote and tab completion. (#4644)
|
- Dev: Added test cases for emote and tab completion. (#4644)
|
||||||
- Dev: Fixed `clang-tidy-review` action not picking up dependencies. (#4648)
|
- Dev: Fixed `clang-tidy-review` action not picking up dependencies. (#4648)
|
||||||
- Dev: Expanded upon `$$$` test channels. (#4655)
|
- Dev: Expanded upon `$$$` test channels. (#4655)
|
||||||
|
- Dev: Added tools to help debug image GC. (#4578)
|
||||||
|
|
||||||
## 2.4.4
|
## 2.4.4
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "controllers/commands/CommandModel.hpp"
|
#include "controllers/commands/CommandModel.hpp"
|
||||||
#include "controllers/plugins/PluginController.hpp"
|
#include "controllers/plugins/PluginController.hpp"
|
||||||
#include "controllers/userdata/UserDataController.hpp"
|
#include "controllers/userdata/UserDataController.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -38,6 +39,7 @@
|
||||||
#include "util/FormatTime.hpp"
|
#include "util/FormatTime.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
|
#include "util/PostToThread.hpp"
|
||||||
#include "util/Qt.hpp"
|
#include "util/Qt.hpp"
|
||||||
#include "util/StreamerMode.hpp"
|
#include "util/StreamerMode.hpp"
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
|
@ -3211,6 +3213,28 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this->registerCommand(
|
||||||
|
"/debug-force-image-gc",
|
||||||
|
[](const QStringList & /*words*/, auto /*channel*/) -> QString {
|
||||||
|
runInGuiThread([] {
|
||||||
|
using namespace chatterino::detail;
|
||||||
|
auto &iep = ImageExpirationPool::instance();
|
||||||
|
iep.freeOld();
|
||||||
|
});
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
this->registerCommand(
|
||||||
|
"/debug-force-image-unload",
|
||||||
|
[](const QStringList & /*words*/, auto /*channel*/) -> QString {
|
||||||
|
runInGuiThread([] {
|
||||||
|
using namespace chatterino::detail;
|
||||||
|
auto &iep = ImageExpirationPool::instance();
|
||||||
|
iep.freeAll();
|
||||||
|
});
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
this->registerCommand("/shield", &commands::shieldModeOn);
|
this->registerCommand("/shield", &commands::shieldModeOn);
|
||||||
this->registerCommand("/shieldoff", &commands::shieldModeOff);
|
this->registerCommand("/shieldoff", &commands::shieldModeOff);
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,8 @@ namespace detail {
|
||||||
60000);
|
60000);
|
||||||
}
|
}
|
||||||
this->processOffset();
|
this->processOffset();
|
||||||
|
DebugCount::increase("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever loaded)", this->memoryUsage());
|
||||||
}
|
}
|
||||||
|
|
||||||
Frames::~Frames()
|
Frames::~Frames()
|
||||||
|
@ -91,10 +93,27 @@ namespace detail {
|
||||||
{
|
{
|
||||||
DebugCount::decrease("animated images");
|
DebugCount::decrease("animated images");
|
||||||
}
|
}
|
||||||
|
DebugCount::decrease("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever unloaded)",
|
||||||
|
this->memoryUsage());
|
||||||
|
|
||||||
this->gifTimerConnection_.disconnect();
|
this->gifTimerConnection_.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t Frames::memoryUsage() const
|
||||||
|
{
|
||||||
|
int64_t usage = 0;
|
||||||
|
for (const auto &frame : this->items_)
|
||||||
|
{
|
||||||
|
auto sz = frame.image.size();
|
||||||
|
auto area = sz.width() * sz.height();
|
||||||
|
auto memory = area * frame.image.depth();
|
||||||
|
|
||||||
|
usage += memory;
|
||||||
|
}
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
void Frames::advance()
|
void Frames::advance()
|
||||||
{
|
{
|
||||||
this->durationOffset_ += GIF_FRAME_LENGTH;
|
this->durationOffset_ += GIF_FRAME_LENGTH;
|
||||||
|
@ -131,6 +150,9 @@ namespace detail {
|
||||||
{
|
{
|
||||||
DebugCount::decrease("loaded images");
|
DebugCount::decrease("loaded images");
|
||||||
}
|
}
|
||||||
|
DebugCount::decrease("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever unloaded)",
|
||||||
|
this->memoryUsage());
|
||||||
|
|
||||||
this->items_.clear();
|
this->items_.clear();
|
||||||
this->index_ = 0;
|
this->index_ = 0;
|
||||||
|
@ -589,14 +611,26 @@ void ImageExpirationPool::removeImagePtr(Image *rawPtr)
|
||||||
this->allImages_.erase(rawPtr);
|
this->allImages_.erase(rawPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageExpirationPool::freeAll()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
||||||
|
{
|
||||||
|
auto img = it->second.lock();
|
||||||
|
img->expireFrames();
|
||||||
|
it = this->allImages_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->freeOld();
|
||||||
|
}
|
||||||
|
|
||||||
void ImageExpirationPool::freeOld()
|
void ImageExpirationPool::freeOld()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
# ifndef NDEBUG
|
|
||||||
size_t numExpired = 0;
|
size_t numExpired = 0;
|
||||||
size_t eligible = 0;
|
size_t eligible = 0;
|
||||||
# endif
|
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
||||||
|
@ -617,17 +651,13 @@ void ImageExpirationPool::freeOld()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifndef NDEBUG
|
|
||||||
++eligible;
|
++eligible;
|
||||||
# endif
|
|
||||||
|
|
||||||
// Check if image has expired and, if so, expire its frame data
|
// Check if image has expired and, if so, expire its frame data
|
||||||
auto diff = now - img->lastUsed_;
|
auto diff = now - img->lastUsed_;
|
||||||
if (diff > IMAGE_POOL_IMAGE_LIFETIME)
|
if (diff > IMAGE_POOL_IMAGE_LIFETIME)
|
||||||
{
|
{
|
||||||
# ifndef NDEBUG
|
|
||||||
++numExpired;
|
++numExpired;
|
||||||
# endif
|
|
||||||
img->expireFrames();
|
img->expireFrames();
|
||||||
// erase without mutex locking issue
|
// erase without mutex locking issue
|
||||||
it = this->allImages_.erase(it);
|
it = this->allImages_.erase(it);
|
||||||
|
@ -641,6 +671,9 @@ void ImageExpirationPool::freeOld()
|
||||||
qCDebug(chatterinoImage) << "freed frame data for" << numExpired << "/"
|
qCDebug(chatterinoImage) << "freed frame data for" << numExpired << "/"
|
||||||
<< eligible << "eligible images";
|
<< eligible << "eligible images";
|
||||||
# endif
|
# endif
|
||||||
|
DebugCount::set("last image gc: expired", numExpired);
|
||||||
|
DebugCount::set("last image gc: eligible", eligible);
|
||||||
|
DebugCount::set("last image gc: left after gc", this->allImages_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace detail {
|
||||||
boost::optional<QPixmap> first() const;
|
boost::optional<QPixmap> first() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int64_t memoryUsage() const;
|
||||||
void processOffset();
|
void processOffset();
|
||||||
QVector<Frame<QPixmap>> items_;
|
QVector<Frame<QPixmap>> items_;
|
||||||
int index_{0};
|
int index_{0};
|
||||||
|
@ -111,6 +112,7 @@ class ImageExpirationPool
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
friend class Image;
|
friend class Image;
|
||||||
|
friend class CommandController;
|
||||||
|
|
||||||
ImageExpirationPool();
|
ImageExpirationPool();
|
||||||
static ImageExpirationPool &instance();
|
static ImageExpirationPool &instance();
|
||||||
|
@ -126,6 +128,12 @@ private:
|
||||||
*/
|
*/
|
||||||
void freeOld();
|
void freeOld();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Debug function that unloads all images in the pool. This is intended to
|
||||||
|
* test for possible memory leaks from tracked images.
|
||||||
|
*/
|
||||||
|
void freeAll();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Timer to periodically run freeOld()
|
// Timer to periodically run freeOld()
|
||||||
QTimer *freeTimer_;
|
QTimer *freeTimer_;
|
||||||
|
|
|
@ -27,6 +27,22 @@ public:
|
||||||
reinterpret_cast<int64_t &>(it.value())++;
|
reinterpret_cast<int64_t &>(it.value())++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set(const QString &name, const int64_t &amount)
|
||||||
|
{
|
||||||
|
auto counts = counts_.access();
|
||||||
|
|
||||||
|
auto it = counts->find(name);
|
||||||
|
if (it == counts->end())
|
||||||
|
{
|
||||||
|
counts->insert(name, amount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reinterpret_cast<int64_t &>(it.value()) = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void increase(const QString &name, const int64_t &amount)
|
static void increase(const QString &name, const int64_t &amount)
|
||||||
{
|
{
|
||||||
auto counts = counts_.access();
|
auto counts = counts_.access();
|
||||||
|
|
Loading…
Reference in a new issue