File indexing completed on 2024-05-05 14:22:40
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 //--------------------------------------------------------------------------------