-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystemworker.cpp
More file actions
365 lines (285 loc) · 13.4 KB
/
Copy pathsystemworker.cpp
File metadata and controls
365 lines (285 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#include <QDebug>
#include "systemworker.h"
#include <QThread>
#include <ifaddrs.h>
#include <net/if.h>
#include <libproc.h>
#include <QDateTime>
#include <QRegularExpression>
#if defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
SystemWorker::SystemWorker(QObject *parent) : QObject(parent) {}
SystemWorker::~SystemWorker()
{
// Checking whether the nettop process actually exists and running
if(nettopProcess && nettopProcess->state() == QProcess::Running){
// qDebug() << "[CLEANUP] Terminating background nettop process...";
// Politely ask the UNIX process to stop
nettopProcess->terminate();
// Giving it 1000 ms to shut down gracefully
if(!nettopProcess->waitForFinished(1000)){
// qDebug() << "[CLEANUP] Nettop refused to close. Force killing.";
// If it refuses to stop, kill it forcefully
nettopProcess->kill();
}
}
}
void SystemWorker::startPolling() {
// Start Continuous IPC Stream
nettopProcess = new QProcess(this);
// // Debugging whether nettop launches
// connect(nettopProcess, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) {
// qDebug() << "CRITICAL QPROCESS ERROR!" << error;
// });
// // 2. NEW: Catch UNIX terminal text errors!
// connect(nettopProcess, &QProcess::readyReadStandardError, this, [this]() {
// qDebug() << "NETTOP UNIX ERROR:" << nettopProcess->readAllStandardError();
// });
connect(nettopProcess, &QProcess::readyReadStandardOutput, this, &SystemWorker::readNettopStream);
// Run nettop indefinitely
nettopProcess->start("nettop", QStringList() << "-x" << "-P" << "-L" << "0" << "-J" << "bytes_in,bytes_out");
// Setup a timer that lives on the worker thread
pollTimer = new QTimer(this);
connect(pollTimer, &QTimer::timeout, this, [this]() {
SystemStats stats = fetchMacStats();
emit statsUpdated(stats); // Send data to GUI
});
pollTimer->start(2000); // Poll every 1000 ms
}
void SystemWorker::readNettopStream() {
// This regex looks for:
// A literal dot \.
// Followed by the PID (\d+)
// Followed by a comma ,
// Followed by Bytes In (\d+)
// Followed by a comma ,
// Followed by Bytes Out (\d+)
QRegularExpression regex("\\.(\\d+),(\\d+),(\\d+)");
while (nettopProcess->canReadLine()) {
QString line = nettopProcess->readLine().trimmed();
// Skip header lines and also checking for leading commas
if (line.startsWith("time") || line.startsWith("bytes") || line.startsWith(",")) {
continue;
}
// Running the regex engine on the raw string
QRegularExpressionMatch match = regex.match(line);
if (match.hasMatch()) {
// match.captured(1) is the PID
// match.captured(2) is Bytes In
// match.captured(3) is Bytes Out
int pid = match.captured(1).toInt();
uint64_t bytesIn = match.captured(2).toULongLong();
uint64_t bytesOut = match.captured(3).toULongLong();
// Instantly update the master totals!
activeNetworkTotals[pid] = qMakePair(bytesIn, bytesOut);
// // --- DEBUG 1: Did the regex successfully extract the numbers? ---
// qDebug() << "[REGEX OK] PID:" << pid << "In:" << bytesIn << "Out:" << bytesOut;
}
// else {
// // --- DEBUG 2: If it failed, what was the exact text? ---
// qDebug() << "[REGEX FAILED] Text:" << line;
// }
}
}
SystemStats SystemWorker::fetchMacStats() {
SystemStats stats;
#if defined(__APPLE__)
// Fetching RAM info
int mib[2] = {CTL_HW, HW_MEMSIZE};
int64_t totalRam = 0;
size_t len = sizeof(totalRam);
sysctl(mib, 2, &totalRam, &len, NULL, 0);
stats.totalRamMB = totalRam / (1024 * 1024);
vm_size_t pageSize;
host_page_size(mach_host_self(), &pageSize);
vm_statistics64_data_t vmStats;
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info_t)&vmStats, &count) == KERN_SUCCESS) {
uint64_t appMemoryPages = (vmStats.internal_page_count > vmStats.purgeable_count)
? (vmStats.internal_page_count - vmStats.purgeable_count)
: vmStats.internal_page_count;
uint64_t wiredPages = vmStats.wire_count;
uint64_t compressedPages = vmStats.compressor_page_count;
uint64_t usedBytes = (appMemoryPages + wiredPages + compressedPages) * pageSize;
stats.usedRamMB = usedBytes / (1024 * 1024);
stats.ramUsagePercent = ((double)stats.usedRamMB / stats.totalRamMB) * 100.0;
stats.ramLabelText = QString("RAM: %1 MB / %2 MB").arg(stats.usedRamMB).arg(stats.totalRamMB);
}
// Fetching CPU info
natural_t numCPUs = 0;
processor_info_array_t cpuInfo;
mach_msg_type_number_t numCpuInfo;
kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUs, &cpuInfo, &numCpuInfo);
if (err == KERN_SUCCESS) {
unsigned long long totalTicks = 0;
unsigned long long idleTicks = 0;
// Loop through every logical core and sum up the ticks
for (unsigned i = 0; i < numCPUs; ++i) {
int offset = i * CPU_STATE_MAX;
totalTicks += cpuInfo[offset + CPU_STATE_USER] +
cpuInfo[offset + CPU_STATE_SYSTEM] +
cpuInfo[offset + CPU_STATE_NICE] +
cpuInfo[offset + CPU_STATE_IDLE];
idleTicks += cpuInfo[offset + CPU_STATE_IDLE];
}
// Preventing memory leak by freeing the kernel array
size_t cpuInfoSize = sizeof(integer_t) * numCpuInfo;
vm_deallocate(mach_task_self(), (vm_address_t)cpuInfo, cpuInfoSize);
// Calculate Delta (Difference since last second)
unsigned long long totalTicksDelta = totalTicks - previousTotalTicks;
unsigned long long idleTicksDelta = idleTicks - previousIdleTicks;
// Prevent division by zero on the very first poll
if (previousTotalTicks > 0 && totalTicksDelta > 0) {
unsigned long long activeTicksDelta = totalTicksDelta - idleTicksDelta;
stats.cpuUsagePercent = (static_cast<double>(activeTicksDelta) / totalTicksDelta) * 100.0;
} else {
stats.cpuUsagePercent = 0.0; // First tick always yields 0
}
// Save current ticks for the next second's calculation
previousTotalTicks = totalTicks;
previousIdleTicks = idleTicks;
stats.cpuLabelText = QString("CPU Load: %1%").arg(stats.cpuUsagePercent, 0, 'f', 1);
}
// Fetching Network info
struct ifaddrs *ifa_list = nullptr;
struct ifaddrs *ifa = nullptr;
uint64_t currentBytesIn = 0;
uint64_t currentBytesOut = 0;
// Asking the kernel for the list of network interfaces
if (getifaddrs(&ifa_list) == 0) {
for (ifa = ifa_list; ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) continue;
// AF_LINK contains the hardware-level byte counters
if (ifa->ifa_addr->sa_family == AF_LINK) {
QString ifName = QString(ifa->ifa_name);
// en0 is usually Wi-Fi on macOS, en1 is usually Ethernet/Thunderbolt
if (ifName == "en0" || ifName == "en1") {
struct if_data *ifData = (struct if_data *)ifa->ifa_data;
if (ifData != nullptr) {
currentBytesIn += ifData->ifi_ibytes;
currentBytesOut += ifData->ifi_obytes;
}
}
}
}
// Freeing the memory allocated by getifaddrs to prevent leaks!
freeifaddrs(ifa_list);
}
// Calculate Delta (Bytes per second)
uint64_t bytesInPerSec = 0;
uint64_t bytesOutPerSec = 0;
// Prevent a massive spike on the very first tick
if (previousBytesIn > 0 && previousBytesOut > 0) {
bytesInPerSec = currentBytesIn - previousBytesIn;
bytesOutPerSec = currentBytesOut - previousBytesOut;
}
// Save current bytes for next second's calculation
previousBytesIn = currentBytesIn;
previousBytesOut = currentBytesOut;
// Converting to KB/s or MB/s for readability
double dlSpeed = bytesInPerSec / 1024.0; // KB/s
double ulSpeed = bytesOutPerSec / 1024.0; // KB/s
QString dlUnit = "KB/s";
QString ulUnit = "KB/s";
if (dlSpeed > 1024.0) { dlSpeed /= 1024.0; dlUnit = "MB/s"; }
if (ulSpeed > 1024.0) { ulSpeed /= 1024.0; ulUnit = "MB/s"; }
stats.networkLabelText = QString("Network: ▼ %1 %2 | ▲ %3 %4")
.arg(dlSpeed, 0, 'f', 1).arg(dlUnit)
.arg(ulSpeed, 0, 'f', 1).arg(ulUnit);
// Fetching the Process list
qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
qint64 timeDeltaMs = currentTimestamp - previousTimestamp;
if (timeDeltaMs <= 0) timeDeltaMs = 1; // Prevent division by zero on edge cases
QMap<int, uint64_t> currentProcessTimes; // Fresh map for this second
// Asking the kernel for the hardware's timebase fraction
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
int numPids = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
if (numPids > 0) {
pid_t pids[numPids];
int actualPids = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
int count = actualPids / sizeof(pid_t);
for (int i = 0; i < count; ++i) {
pid_t currentPid = pids[i];
if (currentPid == 0) continue;
ProcessInfo pInfo;
pInfo.pid = currentPid;
char nameBuffer[256];
if (proc_name(currentPid, nameBuffer, sizeof(nameBuffer)) > 0) {
pInfo.name = QString(nameBuffer);
} else {
pInfo.name = "Unknown";
}
struct proc_taskinfo pti;
if (proc_pidinfo(currentPid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) == sizeof(pti)) {
pInfo.memoryMB = pti.pti_resident_size / (1024 * 1024);
// Apple Silicon way
uint64_t totalTicks = pti.pti_total_user + pti.pti_total_system;
// Convert ticks to real nanoseconds using the hardware timebase
uint64_t currentProcessTimeNs = (totalTicks * timebase.numer) / timebase.denom;
// Convert to milliseconds
uint64_t currentProcessTimeMs = currentProcessTimeNs / 1000000;
if (previousProcessTimes.contains(currentPid) && previousTimestamp > 0) {
uint64_t processTimeDelta = currentProcessTimeMs - previousProcessTimes[currentPid];
pInfo.cpuUsage = (static_cast<double>(processTimeDelta) / timeDeltaMs) * 100.0;
} else {
pInfo.cpuUsage = 0.0;
}
// Store the process time for the next second's calculation
currentProcessTimes[currentPid] = currentProcessTimeMs;
}
if (pInfo.memoryMB > 0) {
stats.processList.append(pInfo);
}
}
}
// Replace the old state with the new state (auto-deletes closed apps from the map)
previousProcessTimes = currentProcessTimes;
previousTimestamp = currentTimestamp;
// Sort by CPU usage instead of Memory to show the most demanding apps at the top
std::sort(stats.processList.begin(), stats.processList.end(),
[](const ProcessInfo& a, const ProcessInfo& b) {
return a.cpuUsage > b.cpuUsage;
});
// Fetching Per-Process Network Info
QMap<int, QPair<uint64_t, uint64_t>> currentNetworkBytes;
// qDebug() << "--- TICK START: Active PIDs in Map:" << activeNetworkTotals.size() << "---";
for (int i = 0; i < stats.processList.size(); ++i) {
int pid = stats.processList[i].pid;
if (activeNetworkTotals.contains(pid)) {
// These are all-time odometer totals
uint64_t bytesIn = activeNetworkTotals[pid].first;
uint64_t bytesOut = activeNetworkTotals[pid].second;
double dlSpeed = 0.0;
double ulSpeed = 0.0;
// Calculate the delta from the last second
if (previousNetworkBytes.contains(pid)) {
uint64_t prevIn = previousNetworkBytes[pid].first;
uint64_t prevOut = previousNetworkBytes[pid].second;
if (bytesIn >= prevIn) dlSpeed = (bytesIn - prevIn) / 1024.0;
if (bytesOut >= prevOut) ulSpeed = (bytesOut - prevOut) / 1024.0;
// // --- DEBUG 3: Trace the Delta Math for any app doing network activity ---
// if (dlSpeed > 0 || ulSpeed > 0) {
// qDebug() << "[MATH] Name:" << stats.processList[i].name
// << "| Old In:" << prevIn << "| New In:" << bytesIn
// << "| Speed:" << dlSpeed << "KB/s";
// }
}
stats.processList[i].netDownKBs = dlSpeed;
stats.processList[i].netUpKBs = ulSpeed;
// Store for next tick's math
currentNetworkBytes[pid] = qMakePair(bytesIn, bytesOut);
} else {
stats.processList[i].netDownKBs = 0.0;
stats.processList[i].netUpKBs = 0.0;
}
}
// Update the master state
previousNetworkBytes = currentNetworkBytes;
#endif
return stats;
}