#include "networkusage.hpp" #include #include #include #include #include #include #include "config/rootnodes.hpp" #include "config/dashboard.hpp" namespace ZShell::services { using Qt::StringLiterals::operator""_s; NetworkUsage::NetworkUsage(QObject* parent) : TickingService(parent) , m_downloadBuffer(new internal::CircularBuffer(this)) , m_uploadBuffer(new internal::CircularBuffer(this)) { m_downloadBuffer->setSmoothingRadius(2); m_uploadBuffer->setSmoothingRadius(2); auto* resources = config::ConfigSingleton::instance()->dashboard()->performance(); setHistoryLength(resources->showVram() ? 70 : 30); QObject::connect( resources, &ZShell::config::Performance::showVramChanged, this, [this, resources] { setHistoryLength(resources->showVram() ? 70 : 30); }); } qreal NetworkUsage::downloadSpeed() const { return m_downloadSpeed; } qreal NetworkUsage::uploadSpeed() const { return m_uploadSpeed; } qreal NetworkUsage::downloadTotal() const { return m_downloadTotal; } qreal NetworkUsage::uploadTotal() const { return m_uploadTotal; } int NetworkUsage::historyLength() const { return m_historyLength; } void NetworkUsage::setHistoryLength(int length) { if (length <= 0 || length == m_historyLength) { return; } m_historyLength = length; m_downloadBuffer->setCapacity(m_historyLength + 1); m_uploadBuffer->setCapacity(m_historyLength + 1); Q_EMIT historyLengthChanged(); } internal::CircularBuffer* NetworkUsage::downloadBuffer() const { return m_downloadBuffer; } internal::CircularBuffer* NetworkUsage::uploadBuffer() const { return m_uploadBuffer; } void NetworkUsage::tick() { QFile f(u"/proc/net/dev"_s); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } // Skip headers f.readLine(); f.readLine(); quint64 totalRx = 0; quint64 totalTx = 0; while (!f.atEnd()) { const QByteArray line = f.readLine(); const qsizetype splitIdx = line.indexOf(':'); if (splitIdx == -1) { continue; } const QByteArray iface = line.left(splitIdx).trimmed(); if (iface == QByteArrayView("lo")) { continue; // Skip loopback interface } // Parse every counter through tx bytes to validate the row const char* pos = line.constData() + splitIdx + 1; const char* const end = line.constData() + line.size(); std::array fields{}; bool valid = true; for (unsigned long long& field : fields) { while (pos < end && (*pos == ' ' || *pos == '\t')) ++pos; const auto [next, ec] = std::from_chars(pos, end, field); if (ec != std::errc{}) { valid = false; break; } pos = next; } if (!valid) continue; totalRx += static_cast(fields[0]); totalTx += static_cast(fields[8]); } f.close(); if (!m_initialized) { m_prevRx = totalRx; m_prevTx = totalTx; m_timer.start(); m_initialized = true; return; } const qreal elapsed = static_cast(m_timer.restart()) / 1000.0; const quint64 rxDelta = totalRx >= m_prevRx ? totalRx - m_prevRx : 0; const quint64 txDelta = totalTx >= m_prevTx ? totalTx - m_prevTx : 0; m_downloadTotal += static_cast(rxDelta); m_uploadTotal += static_cast(txDelta); if (elapsed > 0.0) { // Calculate speeds m_downloadSpeed = static_cast(rxDelta) / elapsed; m_uploadSpeed = static_cast(txDelta) / elapsed; m_downloadBuffer->push(m_downloadSpeed); m_uploadBuffer->push(m_uploadSpeed); } m_prevRx = totalRx; m_prevTx = totalTx; emit changed(); } } // namespace ZShell::services