End to End GUI Development with Qt5
上QQ阅读APP看书,第一时间看更新

Adding the Linux implementation

Let's make the Linux implementation of our ch02-sysinfo project. If you have already done the Windows implementation, it will be a piece of cake! If you have not, you should take a look at it. Some information and tips will not be repeated in this part, such as how to create a SysInfo implementation class, keyboard shortcuts, and details about the SysInfo interface.

Create a new C++ class called SysInfoLinuxImpl that inherits from the SysInfo class, and insert virtual functions from the base class:

#include "SysInfo.h" 
 
class SysInfoLinuxImpl : public SysInfo 
{ 
public: 
    SysInfoLinuxImpl(); 
 
    void init() override; 
    double cpuLoadAverage() override; 
    double memoryUsed() override; 
}; 

We will start by implementing the memoryUsed() function in the file SysInfoLinuxImpl.cpp:

#include "SysInfoLinuxImpl.h" 
 
#include <sys/types.h> 
#include <sys/sysinfo.h> 
 
SysInfoLinuxImpl::SysInfoLinuxImpl() : 
    SysInfo(), 
{ 
} 
 
double SysInfoLinuxImpl::memoryUsed() 
{ 
    struct sysinfo memInfo; 
    sysinfo(&memInfo); 
 
    qulonglong totalMemory = memInfo.totalram; 
    totalMemory += memInfo.totalswap; 
    totalMemory *= memInfo.mem_unit; 
 
    qulonglong totalMemoryUsed = memInfo.totalram - memInfo.freeram; 
    totalMemoryUsed += memInfo.totalswap - memInfo.freeswap; 
    totalMemoryUsed *= memInfo.mem_unit; 
 
    double percent = (double)totalMemoryUsed / 
        (double)totalMemory * 100.0; 
    return qBound(0.0, percent, 100.0); 
} 

This function uses Linux-specific API. After adding the required includes, you can use the Linux sysinfo() function that returns information on the overall system statistics. With the total memory and the total memory used, we can easily return the percent value. Note that swap memory has been taken into account.

The CPU load feature is a little more complex than the memory feature. Indeed, we will retrieve from Linux the total amount of time the CPU spent performing different kinds of work. That is not exactly what we want. We must return the instantaneous CPU load. A common way to get it is to retrieve two sample values in a short period of time and use the difference to get the instantaneous CPU load:

#include <QtGlobal> 
#include <QVector> 
 
#include "SysInfo.h" 
 
class SysInfoLinuxImpl : public SysInfo 
{ 
public: 
    SysInfoLinuxImpl(); 
 
    void init() override; 
    double cpuLoadAverage() override; 
    double memoryUsed() override; 
 
private: 
    QVector<qulonglong> cpuRawData(); 
 
private: 
    QVector<qulonglong> mCpuLoadLastValues; 
}; 

In this implementation, we will only add one helper function and one member variable:

  • The cpuRawData() is a function that will perform the Linux API call to retrieve system timing information and return values in a QVector class of qulonglong type. We retrieve and return four values containing the time the CPU has spent on the following: normal processes in User mode, nice processes in User mode, processes in Kernel mode, and idle.
  • The mCpuLoadLastValues is a variable that will store a sample of system timing at a given moment.

Let's go to the SysInfoLinuxImpl.cpp file to update it:

#include "SysInfoLinuxImpl.h" 
 
#include <sys/types.h> 
#include <sys/sysinfo.h> 
 
#include <QFile> 
 
SysInfoLinuxImpl::SysInfoLinuxImpl() : 
    SysInfo(), 
    mCpuLoadLastValues() 
{ 
} 
 
void SysInfoLinuxImpl::init() 
{ 
    mCpuLoadLastValues = cpuRawData(); 
} 

As discussed before, the cpuLoadAverage function will need two samples to be able to compute an instantaneous CPU load average. Calling the init() function allows us to set mCpuLoadLastValues for the first time:

QVector<qulonglong> SysInfoLinuxImpl::cpuRawData() 
{ 
    QFile file("/proc/stat"); 
    file.open(QIODevice::ReadOnly); 
 
    QByteArray line = file.readLine(); 
    file.close(); 
    qulonglong totalUser = 0, totalUserNice = 0, 
        totalSystem = 0, totalIdle = 0; 
    std::sscanf(line.data(), "cpu %llu %llu %llu %llu", 
        &totalUser, &totalUserNice, &totalSystem, 
        &totalIdle); 
 
    QVector<qulonglong> rawData; 
    rawData.append(totalUser); 
    rawData.append(totalUserNice); 
    rawData.append(totalSystem); 
    rawData.append(totalIdle); 
 
    return rawData; 
} 

To retrieve the CPU raw information on a Linux system, we chose to parse information available in the /proc/stat file. All we need is available on the first line, so a single readLine() is enough. Even though Qt provides some useful features, sometimes the C standard library functions are simpler. This is the case here; we are using std::sscanf to extract variables from a string. Now let's look at the cpuLoadAvearge() body:

double SysInfoLinuxImpl::cpuLoadAverage() 
{ 
    QVector<qulonglong> firstSample = mCpuLoadLastValues; 
    QVector<qulonglong> secondSample = cpuRawData(); 
    mCpuLoadLastValues = secondSample; 
 
    double overall = (secondSample[0] - firstSample[0]) 
        + (secondSample[1] - firstSample[1]) 
        + (secondSample[2] - firstSample[2]); 
 
    double total = overall + (secondSample[3] - firstSample[3]); 
    double percent = (overall / total) * 100.0; 
    return qBound(0.0, percent, 100.0); 
} 

This is where the magic happens. In this last function, we put all the puzzle pieces together. This function uses two samples of the CPU raw data. The first sample comes from our member variable mCpuLoadLastValues, set the first time by the init() function. The second sample is requested by the cpuLoadAverage() function. Then the mCpuLoadLastValues variable will store the new sample that will be used as the first sample on the next cpuLoadAverage() function call.

The percent equation should be easy to understand:

  • overall is equal to user + nice + kernel
  • total is equal to overall + idle
You can find more information about /proc/stat in the Linux Kernel documentation at  https://www.kernel.org/doc/Documentation/filesystems/proc.txt.

Like the other implementations, the last thing to do is to edit the ch02-sysinfo.pro file like this:

QT       += core gui 
CONFIG   += C++14 
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 
 
TARGET = ch02-sysinfo 
TEMPLATE = app 
 
SOURCES += main.cpp  
    MainWindow.cpp  
    SysInfo.cpp  
    CpuWidget.cpp  
    MemoryWidget.cpp  
    SysInfoWidget.cpp 
 
HEADERS += MainWindow.h  
    SysInfo.h  
    CpuWidget.h  
    MemoryWidget.h  
    SysInfoWidget.h 
 
windows { 
    SOURCES += SysInfoWindowsImpl.cpp 
    HEADERS += SysInfoWindowsImpl.h 
} 
 
linux { 
    SOURCES += SysInfoLinuxImpl.cpp 
    HEADERS += SysInfoLinuxImpl.h 
} 
 
FORMS    += MainWindow.ui 

With this Linux scope condition in the ch02-sysinfo.pro file, our Linux-specific files will not be processed by the qmake command on other platforms.