File indexing completed on 2024-04-21 05:46:38

0001 // SPDX-License-Identifier: GPL-3.0-or-later
0002 /*
0003   Copyright 2017 - 2023 Martin Koller, kollix@aon.at
0004 
0005   This file is part of liquidshell.
0006 
0007   liquidshell is free software: you can redistribute it and/or modify
0008   it under the terms of the GNU General Public License as published by
0009   the Free Software Foundation, either version 3 of the License, or
0010   (at your option) any later version.
0011 
0012   liquidshell is distributed in the hope that it will be useful,
0013   but WITHOUT ANY WARRANTY; without even the implied warranty of
0014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015   GNU General Public License for more details.
0016 
0017   You should have received a copy of the GNU General Public License
0018   along with liquidshell.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include <SysLoad.hxx>
0022 
0023 #include <QFile>
0024 #include <QPainter>
0025 #include <QToolTip>
0026 #include <QMouseEvent>
0027 #include <QDebug>
0028 
0029 #include <kio_version.h>
0030 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0031 #  include <KIO/CommandLauncherJob>
0032 #  include <KIO/JobUiDelegateFactory>
0033 #else
0034 #  include <KRun>
0035 #endif
0036 
0037 #include <KLocalizedString>
0038 
0039 #include <NetworkManagerQt/Manager>
0040 
0041 //--------------------------------------------------------------------------------
0042 
0043 const int INTERVAL_MS = 800;
0044 const int NET_INTERVAL_S = 60;
0045 const int BAR_WIDTH = 8;
0046 const int SMALL_BAR_WIDTH = 4;
0047 
0048 // to avoid that a constant small network traffic always shows a full bar,
0049 // we set some arbitrary higher maximum value for the scale
0050 const size_t NET_INIT_SCALE = 50 * 1024;
0051 
0052 //--------------------------------------------------------------------------------
0053 
0054 SysLoad::SysLoad(QWidget *parent)
0055   : QFrame(parent)
0056 {
0057   maxScale = NET_INIT_SCALE;
0058 
0059   setFrameShape(QFrame::StyledPanel);
0060 
0061   timeoutTimer.setInterval(INTERVAL_MS);
0062   timeoutTimer.start();
0063   connect(&timeoutTimer, &QTimer::timeout, this, &SysLoad::fetch);
0064 
0065   connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionChanged,
0066           [this]() { maxScale = NET_INIT_SCALE; maxBytes = 0; });  // reset since we're changing network (which might be slower)
0067 
0068   // gradually decrease max network throughput to really be able to see when network traffic occurs
0069   netLoadTimer.setInterval(NET_INTERVAL_S * 1000);
0070   netLoadTimer.start();
0071   connect(&netLoadTimer, &QTimer::timeout, this, [this]() { maxBytes = 0; if ( maxScale > NET_INIT_SCALE ) maxScale /= 2; });
0072 
0073   fetch();
0074 
0075   const int cpuBars = cpuSummaryBar ? 1 : cpus.count();
0076   const int cpuBarWidth = (cpuBars <= 4) ? BAR_WIDTH : SMALL_BAR_WIDTH;
0077   setFixedWidth((cpuBars * cpuBarWidth) + ((2 + 1) * BAR_WIDTH) +  // 2 memory bars, 1 net - they shall be more prominent
0078                 contentsMargins().left() + contentsMargins().right());
0079 }
0080 
0081 //--------------------------------------------------------------------------------
0082 
0083 void SysLoad::fetch()
0084 {
0085   QFile f("/proc/stat");
0086   if ( !f.open(QIODevice::ReadOnly) )
0087     return;
0088 
0089   int num = 0;
0090   bool first = cpus.isEmpty();
0091   while ( true )
0092   {
0093     QByteArray line = f.readLine();
0094     if ( line.isEmpty() )
0095       break;
0096 
0097     if ( line.startsWith("cpu") && !line.startsWith("cpu ") )
0098     {
0099       // time is in 1/100s units https://www.kernel.org/doc/Documentation/filesystems/proc.txt
0100       CpuData data;
0101       sscanf(line.constData(), "%*s %d %d %d", &data.userCPU, &data.niceCPU, &data.systemCPU);
0102 
0103       if ( first )
0104         cpus.append(data);
0105       else
0106       {
0107         cpus[num].userPercent = (data.userCPU - cpus[num].userCPU) * 1000.0 / INTERVAL_MS;
0108         cpus[num].nicePercent = (data.niceCPU - cpus[num].niceCPU) * 1000.0 / INTERVAL_MS;
0109         cpus[num].systemPercent = (data.systemCPU - cpus[num].systemCPU) * 1000.0 / INTERVAL_MS;
0110         cpus[num].userCPU = data.userCPU;
0111         cpus[num].niceCPU = data.niceCPU;
0112         cpus[num].systemCPU = data.systemCPU;
0113         num++;
0114       }
0115     }
0116   }
0117   f.close();
0118 
0119   // get memory information
0120   size_t memTotal = 0, memFree = 0, swapTotal = 0, swapFree = 0, cached = 0, buffers = 0;
0121   f.setFileName("/proc/meminfo");
0122   if ( f.open(QIODevice::ReadOnly) )
0123   {
0124     while ( true )
0125     {
0126       QByteArray line = f.readLine();
0127       if ( line.isEmpty() )
0128         break;
0129 
0130       if (      line.startsWith("MemTotal:")  ) sscanf(line, "%*s %zd kB", &memTotal);
0131       else if ( line.startsWith("MemFree:")   ) sscanf(line, "%*s %zd kB", &memFree);
0132       else if ( line.startsWith("SwapTotal:") ) sscanf(line, "%*s %zd kB", &swapTotal);
0133       else if ( line.startsWith("SwapFree:")  ) sscanf(line, "%*s %zd kB", &swapFree);
0134       else if ( line.startsWith("Cached:")    ) sscanf(line, "%*s %zd kB", &cached);
0135       else if ( line.startsWith("Buffers:")   ) sscanf(line, "%*s %zd kB", &buffers);
0136     }
0137     cached += buffers;
0138     memData.memPercent = memTotal ? (double(memTotal - memFree) / double(memTotal)) * 100.0 : 0.0;
0139     memData.memCachedPercent = memTotal ? (double(cached) / double(memTotal)) * 100.0 : 0.0;
0140     memData.swapPercent = swapTotal ? (double(swapTotal - swapFree) / double(swapTotal)) * 100.0 : 0.0;
0141     f.close();
0142   }
0143 
0144   f.setFileName("/proc/cpuinfo");  // get speed of cores
0145   if ( f.open(QIODevice::ReadOnly) )
0146   {
0147     int num = 0;
0148     while ( true )
0149     {
0150       QByteArray line = f.readLine();
0151       if ( line.isEmpty() )
0152         break;
0153 
0154       if ( line.startsWith("cpu MHz")  )
0155       {
0156         if ( num < cpus.count() )
0157           sscanf(line, "cpu MHz %*s %lf", &cpus[num++].MHz);
0158       }
0159     }
0160     f.close();
0161   }
0162 
0163   f.setFileName("/proc/net/dev");
0164   sumSent = sumReceived = 0;
0165   if ( f.open(QIODevice::ReadOnly) )
0166   {
0167     while ( true )
0168     {
0169       QByteArray line = f.readLine();
0170       if ( line.isEmpty() )
0171         break;
0172 
0173       int colon = line.indexOf(':');
0174       if ( colon > 1 )
0175       {
0176         QByteArray device = line.left(colon).trimmed();
0177         size_t received = 0, sent = 0;
0178         sscanf(line.data() + colon + 1, "%zd %*d %*d %*d %*d %*d %*d %*d %zd", &received, &sent);
0179         if ( !netDevs.contains(device) )
0180         {
0181           NetworkData data;
0182           data.prevReceived = received;
0183           data.prevSent = sent;
0184           data.valid = false;  // first scan not valid since we count differences
0185           netDevs.insert(device, data);
0186         }
0187         else
0188         {
0189           NetworkData &data = netDevs[device];
0190           data.received = received - data.prevReceived;
0191           data.sent = sent - data.prevSent;
0192           data.prevReceived = received;
0193           data.prevSent = sent;
0194           data.valid = true;
0195 
0196           if ( (device != "lo") &&         // don't count loopback adapter
0197                !device.startsWith("tun") ) // TODO: correct ?
0198           {
0199             sumReceived += data.received;
0200             sumSent += data.sent;
0201           }
0202         }
0203       }
0204     }
0205     f.close();
0206   }
0207 
0208   maxBytes = std::max(maxBytes, (sumReceived + sumSent));
0209   maxScale = std::max(maxBytes, maxScale);
0210 
0211   update();
0212 
0213   QString tip;
0214   for (int i = 0; i < cpus.count(); i++)
0215   {
0216     if ( i ) tip += "<br>";
0217     tip += i18n("CPU %1: %2% (%3 MHz)", i,
0218                 locale().toString(std::min(100.0, cpus[i].userPercent + cpus[i].nicePercent + cpus[i].systemPercent), 'f', 1),
0219                 static_cast<int>(cpus[i].MHz));
0220   }
0221   tip += "<hr>";
0222   memFree += cached;  // show also the cached memory as free (for user applications)
0223   size_t memUsed = memTotal - memFree;
0224   size_t swapUsed = swapTotal - swapFree;
0225   int memUsedPercent = memTotal ? memUsed * 100 / memTotal : 0;
0226   int swapUsedPercent = swapTotal ? swapUsed * 100 / swapTotal : 0;
0227   tip += i18n("Memory Total: %1 MB (%2 GB)"   , memTotal  / 1024, locale().toString(memTotal  / 1024.0 / 1024.0, 'f', 2));
0228   tip += "<br>";
0229   tip += i18n("Memory Used: %1 MB (%2 GB) %3%", memUsed   / 1024, locale().toString(memUsed   / 1024.0 / 1024.0, 'f', 2), memUsedPercent);
0230   tip += "<br>";
0231   tip += i18n("Memory Free: %1 MB (%2 GB)"    , memFree   / 1024, locale().toString(memFree   / 1024.0 / 1024.0, 'f', 2));
0232   tip += "<hr>";
0233   tip += i18n("Swap Total: %1 MB (%2 GB)"     , swapTotal / 1024, locale().toString(swapTotal / 1024.0 / 1024.0, 'f', 2));
0234   tip += "<br>";
0235   tip += i18n("Swap Used: %1 MB (%2 GB) %3%"  , swapUsed  / 1024, locale().toString(swapUsed  / 1024.0 / 1024.0, 'f', 2), swapUsedPercent);
0236   tip += "<br>";
0237   tip += i18n("Swap Free: %1 MB (%2 GB)"      , swapFree  / 1024, locale().toString(swapFree  / 1024.0 / 1024.0, 'f', 2));
0238 
0239   tip += "<hr>";
0240   tip += i18n("Net send/receive: %1/%2 KB/sec",
0241               locale().toString((sumSent / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2),
0242               locale().toString((sumReceived / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2));
0243   tip += "<br>";
0244   tip += i18n("Net max (last %2 secs): %1 KB/sec", locale().toString((maxBytes / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2), NET_INTERVAL_S);
0245 
0246   if ( underMouse() )
0247     QToolTip::showText(QCursor::pos(), QLatin1String("<html>") + tip + QLatin1String("</html>"), this, rect());
0248 }
0249 
0250 //--------------------------------------------------------------------------------
0251 
0252 void SysLoad::paintEvent(QPaintEvent *event)
0253 {
0254   QFrame::paintEvent(event);
0255 
0256   const int cpuBars = cpuSummaryBar ? 1 : cpus.count();
0257   const int cpuBarWidth = (cpuBars <= 4) ? BAR_WIDTH : SMALL_BAR_WIDTH;
0258   int x = contentsRect().x(), y = contentsRect().y() + contentsRect().height();
0259 
0260   QPainter painter(this);
0261 
0262   // cpu
0263   QVector<CpuData> drawCpus;
0264 
0265   if ( !cpuSummaryBar )
0266     drawCpus = cpus;
0267   else
0268   {
0269     CpuData sum;
0270     for (const CpuData &data : cpus)
0271     {
0272       sum.userPercent   += data.userPercent;
0273       sum.systemPercent += data.systemPercent;
0274       sum.nicePercent   += data.nicePercent;
0275     }
0276     sum.userPercent   /= cpus.count();
0277     sum.systemPercent /= cpus.count();
0278     sum.nicePercent   /= cpus.count();
0279 
0280     drawCpus.append(sum);
0281   }
0282 
0283   for (const CpuData &data : drawCpus)
0284   {
0285     int h = contentsRect().height() * (data.userPercent / 100.0);
0286     painter.fillRect(x, y - h, cpuBarWidth, h, cpuUserColor);
0287     y -= h;
0288     h = contentsRect().height() * (data.systemPercent / 100.0);
0289     painter.fillRect(x, y - h, cpuBarWidth, h, cpuSystemColor);
0290     y -= h;
0291     h = contentsRect().height() * (data.nicePercent / 100.0);
0292     painter.fillRect(x, y - h, cpuBarWidth, h, cpuNiceColor);
0293 
0294     x += cpuBarWidth;
0295     y = contentsRect().y() + contentsRect().height();
0296   }
0297 
0298   // memory
0299   int h = contentsRect().height() * (memData.memPercent / 100.0);
0300   painter.fillRect(x, y - h, BAR_WIDTH, h, memUsedColor);
0301   y -= h;
0302   h = contentsRect().height() * (memData.memCachedPercent / 100.0);
0303   painter.fillRect(x, y, BAR_WIDTH, h, memCachedColor);
0304 
0305   x += BAR_WIDTH;
0306   y = contentsRect().y() + contentsRect().height();
0307   h = contentsRect().height() * (memData.swapPercent / 100.0);
0308   painter.fillRect(x, y - h, BAR_WIDTH, h, memSwapColor);
0309 
0310 
0311   // net
0312   x += BAR_WIDTH;
0313   y = contentsRect().y() + contentsRect().height();
0314   h = contentsRect().height() * (double(sumReceived) / maxScale);
0315   painter.fillRect(x, y - h, BAR_WIDTH, h, netReceivedColor);
0316   y -= h;
0317   h = contentsRect().height() * (double(sumSent) / maxScale);
0318   painter.fillRect(x, y - h, BAR_WIDTH, h, netSentColor);
0319 }
0320 
0321 //--------------------------------------------------------------------------------
0322 
0323 void SysLoad::mousePressEvent(QMouseEvent *event)
0324 {
0325   if ( event->button() == Qt::LeftButton )
0326   {
0327 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0328     auto *job = new KIO::CommandLauncherJob("ksysguard");
0329     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0330     job->start();
0331 #else
0332     KRun::runCommand("ksysguard", this);
0333 #endif
0334   }
0335 }
0336 
0337 //--------------------------------------------------------------------------------