mirror of
https://github.com/status-im/status-app.git
synced 2026-08-31 00:51:10 +00:00
The QML engine gets a network access factory that hands out managers with a
disk cache and per-host accounting, so every image, animation and XHR the QML
layer issues can be seen and capped.
HttpStats is a GUI-thread singleton recording per-host totals split by whether
the bytes came from the network or from the cache. Bytes are taken from
downloadProgress rather than Content-Length, which is absent on some responses
and wrong on chunked ones. It is constructed on the GUI thread before any
factory worker can first-touch it with the wrong QObject affinity, and it
reports a finished cache clear on its own cacheCleared() signal — separate from
changed(), which fires on every reply and so cannot be what a reader hangs an
expensive re-measurement on.
The cache takes an explicit budget instead of Qt's 50 MB default, which a
single collectibles page can evict on its own.
The factory also sends Accept: image/webp,image/png,image/jpeg on Cloudinary
delivery URLs, so the f_auto in the rewritten media URLs has something to
resolve against. The list is narrow on purpose: f_auto returns AVIF or JPEG XL
to a client that claims them and Qt decodes neither. It is a statement about
this client's decoders, which is why it lives here and not in QML — see
docs/adr/0006-qt-http-cache.md.
All of it lives in networkaccessfactory.{h,cpp} rather than in externc.cpp,
which exists to be a thin C-ABI shim for Nim: the shim now casts the engine
pointer and delegates. That also lets Storybook install the same factory, so
media traffic measured on a Storybook page is the traffic the application
produces — and it lets the Accept policy be tested, which as a static function
in a .cpp with no header it could not be.
TestHttpStats and TestNetworkAccessFactory cover the thread affinity, both
clear paths, and the Accept policy as a table.
88 lines
2.8 KiB
C++
88 lines
2.8 KiB
C++
#pragma once
|
|
|
|
#include <QHash>
|
|
#include <QMutex>
|
|
#include <QObject>
|
|
#include <QPointer>
|
|
#include <QVariantList>
|
|
#include <QVariantMap>
|
|
#include <QVector>
|
|
|
|
class QNetworkDiskCache;
|
|
class QQmlEngine;
|
|
class QJSEngine;
|
|
|
|
/*!
|
|
* Per-host accounting of everything the QML engine's network access manager
|
|
* fetches. Fed by CountingNetworkAccessManager (src/networkaccessfactory.cpp),
|
|
* read by the HTTP statistics screen in settings.
|
|
*
|
|
* Network and cache are counted separately on purpose: a total that mixes them
|
|
* cannot show that a cache is working, which is the question the screen exists
|
|
* to answer.
|
|
*
|
|
* What this does NOT see, because it never passes through this manager:
|
|
* status-go, messaging, the web engine, and the thread_local managers in
|
|
* systemutilsinternal.cpp and clipboardutils.cpp. The screen says so itself.
|
|
*/
|
|
class HttpStats : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
static HttpStats& instance();
|
|
static QObject* qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine);
|
|
|
|
//! Record one finished reply. Callable from any thread a manager lives on.
|
|
void record(const QString& host, qint64 bytes, bool fromCache);
|
|
|
|
//! Take note of a disk cache so the screen can report and clear it. Call
|
|
//! from the thread the cache lives on, right after it is configured.
|
|
void registerCache(QNetworkDiskCache* cache);
|
|
|
|
//! directory, size (measured on disk) and maximumSize of the disk cache.
|
|
//! Walks the directory, so call it on demand — not on every recorded reply.
|
|
Q_INVOKABLE QVariantMap cache() const;
|
|
|
|
//! Empty every registered cache. Required to reproduce a cold measurement
|
|
//! without reinstalling the application.
|
|
Q_INVOKABLE void clearCache();
|
|
|
|
//! One entry per host: host, networkRequests, networkBytes,
|
|
//! cacheRequests, cacheBytes. Ordered by total bytes, heaviest first.
|
|
Q_INVOKABLE QVariantList hosts() const;
|
|
|
|
//! networkRequests, networkBytes, cacheRequests, cacheBytes across all hosts.
|
|
Q_INVOKABLE QVariantMap totals() const;
|
|
|
|
Q_INVOKABLE void reset();
|
|
|
|
signals:
|
|
//! Emitted when a reply has been recorded, so an open screen can refresh.
|
|
void changed();
|
|
|
|
//! Emitted once per cache when clearCache() has finished on that cache's
|
|
//! thread. Separate from changed(), which fires on every reply and so must
|
|
//! not be what a reader hangs an expensive re-measurement on.
|
|
void cacheCleared();
|
|
|
|
private:
|
|
explicit HttpStats(QObject* parent = nullptr);
|
|
void notifyChanged();
|
|
void emitOnOwnThread(void (HttpStats::*signal)());
|
|
|
|
struct Entry {
|
|
qint64 networkRequests = 0;
|
|
qint64 networkBytes = 0;
|
|
qint64 cacheRequests = 0;
|
|
qint64 cacheBytes = 0;
|
|
};
|
|
|
|
mutable QMutex m_mutex;
|
|
QHash<QString, Entry> m_entries;
|
|
|
|
QString m_cacheDirectory;
|
|
qint64 m_cacheMaximumSize = 0;
|
|
QVector<QPointer<QNetworkDiskCache>> m_caches;
|
|
};
|