Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пишем свой системный монитор (Linux)

Anna | 25.06.2014 | нет комментариев

В этой статье я опишу процесс создания собственного системного монитора. Данный системный монитор показывает:

  • Hostname
  • Имя пользователя
  • Uptime
  • Имя модели процессора
  • Частоту процессора
  • Загрузку процессора
  • число оперативной памяти
  • число применяемой оперативной памяти без кэшируемых данных
  • Запущенные процессы


Если кому-то в предпоследнем пунтке не ясно что значит без кэшируемых данных, то почитайте вот это, там описано что это значит. Также в этом системном мониторе реализована вероятность заключения процессов.
В итоге у нас должен получится вот такой системный монитор:
imageimage
Сейчас его основные вероятности определены, и я могу приступать к изложению кода, предупреждаю сразу что код будет трудиться только в линуксе, так как примерно все его вероятности основаны на парсинге папки /proc. Свой системный монитор я поделил на три класса, в первом классе реализуется всеобщая системная информация, во втором классе таблица процессов, ну и 3-й класс объединят это в одно целое. Вот каркас первого класса, я его назвал Info:

#ifndef INFO_H
#define INFO_H

#include <QtGui>
#include <iostream>
#include <fstream>
#include <pwd.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include <vector>
using namespace std;
class Info : public QWidget
{
    Q_OBJECT
public:
    explicit Info(QWidget *parent = 0);
private:
    QLabel       * hostname;
    QLabel       * user;
    QLabel       * uptime;
    QLabel       * proc;
    QLabel       * freq;
    QLabel       * cpuload;
    QLabel       * mem;
    QLabel       * memload;
    QProgressBar * cpubar;
    QProgressBar * membar;
    QVBoxLayout  * layout;
    QHBoxLayout  * hlayout;
    vector<float> readCpuStats();
    int getCpuLoad(double dt);
public slots:
    void update();
};

#endif // INFO_H

Заголовочные файлы pwd.h и unistd.h применяются для приобретения имени пользователя, а заголовочный файл sys/sysinfo.h для приобретения аптайма. Сейчас опишу реализацию класса Info, она располгается в файле info.cpp. В самом начале файла info.cpp мы обязаны подключить info.h(каркас класса info). Конструктор класса Info выглядит дальнейшим образом:

Info::Info(QWidget *parent) :
    QWidget(parent)
{
    hostname   = new QLabel("Hostname: ");
    user       = new QLabel("Пользователь: ");
    uptime     = new QLabel("Uptime: ");
    proc       = new QLabel("Процессор: ");
    freq       = new QLabel("Частота:");
    cpuload    = new QLabel("Загрузка процессора: ");
    mem        = new QLabel("Оперативная память: ");
    memload    = new QLabel("Применяемая оперативная память: ");
    cpubar     = new QProgressBar;
    membar     = new QProgressBar;
    layout     = new QVBoxLayout;
    hlayout    = new QHBoxLayout;

    cpubar->setMaximumHeight(21); membar->setMaximumHeight(21);
    hlayout->addWidget(cpuload); hlayout->addWidget(cpubar);
    layout->addWidget(hostname); layout->addWidget(user);
    layout->addWidget(uptime); layout->addWidget(proc);
    layout->addWidget(freq); layout->addLayout(hlayout);
    layout->addWidget(mem); layout->addWidget(memload);
    layout->addWidget(membar); setLayout(layout);

    update();

    QTimer *timer = new QTimer;
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(2000);
}

в самом начале мы инициализируем все графические элементы. Дальнейшим действием мы раставляем графические элементы в лэйаутах. Позже мы вызываем слот update для того Дабы все метки и прогрессбары заполнились значениями. Дабы информация регулярно обновлялась мы создаём объект класса QTimer, и соединяем его сигнал timeout, с теснее приятелем нам слотом update.
Думаю логичным будет описать дальнейшим слот update, вот его код:

void Info::update()
{
    ifstream stream("/proc/sys/kernel/hostname"); string str;
    getline(stream,str);
    hostname->setText("Hostname: "   QString::fromStdString(str));
    passwd *pw;
    uid_t uid;
    uid = geteuid();
    pw = getpwuid(uid);
    user->setText("Пользователь: "   QString::fromAscii(pw->pw_name));
    struct sysinfo o;
    sysinfo(&o);
    long up = o.uptime;
    int hour = up/60/60;
    int min = (up - hour*60*60) / 60;
    int sec =  ((up - hour*60*60) - min*60);
    QString e = QString::number(hour)    QString(" h. ")   QString::number(min)   QString(" m. ")
                  QString::number(sec)   QString(" s.");
    uptime->setText("Uptime: "   e);
    stream.close(); stream.open("/proc/cpuinfo");
    for(int i = 0; i < 16;i  ) stream >> str;
    getline(stream,str);
    proc->setText("Процессор: "   QString::fromStdString(str));
    for(int i = 0; i< 7; i  )  stream >> str;
    freq->setText("Частота: "   QString::fromStdString(str)   " MHz");
    cpubar->setValue(getCpuLoad(0.3));
    stream.close(); stream.open("/proc/meminfo");
    stream >> str; stream >> str;
    int num = atoi(str.c_str());
    int percent = num / 100;
    int gb = (num / 1024) / 1024;
    int mb = (num-gb*1024*1024) /1024;
    int kb = (num - (gb*1024*1024 mb*1024));
    e = QString::number(gb)   QString(" Gb ")   QString::number(mb)   QString(" Mb ")
          QString::number(kb)   QString(" Kb");
    mem->setText("Оперативная память: "   e);
    stream >> str; stream >> str; stream >> str;
    int free = atoi(str.c_str());
    stream >> str; stream >> str; stream >> str;
    free  = atoi(str.c_str());
    stream >> str; stream >> str; stream >> str;
    free  = atoi(str.c_str());
    num -= free;
    gb = num / 1024 / 1024;
    mb = (num - gb*1024*1024) / 1024;
    kb = (num - ((mb*1024)   (gb * 1024 * 1024)));
    if (gb != 0 )
    {
        e = QString::number(gb)   " Gb "   QString::number(mb)   QString(" Mb ")   QString::number(kb) 
                QString(" Kb");
    } else
    {
        e = QString::number(mb)   QString(" Mb ")   QString::number(kb)   QString(" Kb");
    }
    memload->setText("Применяемая оперативная память: "   e);
    percent = num / percent; membar->setValue(percent);
}

Вначале мы открываем для чтения(именно следственно для программы не требуются права суперпользователя) файл /proc/sys/kernel/hostname и получаем имя машины, позже чего мы заполняем метку hostname. позже чего мы создаём экземпляр конструкции passwd и uid_t, с поддержкой которых получаем имя пользователя для заполнения метки user. Информация о том как узнать имя пользователя была взята из исходников программы whoami. Позже чего мы создаём и заполняем значениями экземпляр конструкции sysinfo, из которой берём информацию об аптайме системе. Позже чего мы объединяем теснее сделанный поток stream с файлом /proc/cpuinfo, в котором находим информацию об имени модели процессора и его частоте. Загрузка процессора мы получаем при помощи способа getCpuLoad, тот, что я опишу ниже. Сейчас мы объединяем поток stream с файлом /proc/meminfo из которого мы берём число оперативной памяти, и приводим его к комфортно читаемому виду. Потом из этого же файла мы получем число свободной операивной памяти, а так же объём кэшированных данных. Для приобретения числа занятой оперативной памяти мы вычитаем из все оперативной памяти свободную оперативку и объём кэшированных данных, позже вновь же приводим к комфортно читаемому виду. Сейчас как я обещал выше изложение функции getCpuLoad. Эту функцию я писал не сам(я её взял вот отсель), следственно дюже подробного её изложения я привести не могу, я опишу её в всеобщих чертах. Вот она:

int Info::getCpuLoad(double dt)
{
    vector<float> stats1 = readCpuStats();
    QProcess::execute("sleep",QStringList() << QString::number(dt));
    vector<float> stats2 = readCpuStats();
    int size1 = stats1.size();
    int size2 = stats2.size();
    if (!size1 || !size2 || size1 != size2) return 2;
    for (int i = 0; i < size1;   i) stats2[i] -= stats1[i];
    int sum = 1;
    for (int i = 0; i < size1;   i) sum  = stats2[i];
    int load = 100 - (stats2[size2 - 1] * 100 / sum);
    return load;
}

мы два раза получаем три значения с поддержкой функции readCpuStats(её я тоже опишу ниже), но вызываем мы эту функцию с некоторым интервалом во времени. Позже загрузка процессора вычисляется исходя из этих значений. А вот сейчас изложение функции readCpuStats:

vector<float> Info::readCpuStats()
{
    vector<float> ret;
    ifstream stat_file("/proc/stat");
    if (!stat_file.is_open())
    {
        cout << "Unable to open /proc/stat" << std::endl;
        return ret;
    }
    int val;
    string tmp;
    stat_file >> tmp;
    for (int i = 0; i < 4;   i)
    {
        stat_file >> val;
        ret.push_back(val);
    }
    stat_file.close();
    return ret;
}

Эта функция была взята с того же сайта что и функция getCpuLoad. В этой функции мы считываем значения из файла /proc/stat, и заносим их в вектор, тот, что потом возвращаем оператором return.
Сейчас класс Info написан, и дозволено переходит к написанию класса ProcessTable. Вот каркас этого класса:

#ifndef PROCESSTABLE_H
#define PROCESSTABLE_H

#include <QtGui>
#include <iostream>
#include <fstream>
using namespace std;
class ProcessTable : public QWidget
{
    Q_OBJECT
public:
    explicit ProcessTable(QWidget *parent = 0);
private:
    QTableWidget * table;
    QHBoxLayout* hlayout;
    QPushButton* button;
    QVBoxLayout* layout;
public slots:
    void update();
    void kill();
};

#endif // PROCESSTABLE_H

Слот update применяется для заполнения таблицы, а слот kill для заключения процессов. В файле реализации этого класса сначала мы обязаны подключить файл с его каркасом. Сейчас перехожу к изложению конструктора этого класса, вот код конструктора:

ProcessTable::ProcessTable(QWidget *parent) :
    QWidget(parent)
{
    hlayout = new QHBoxLayout;
    button = new QPushButton("Закончить");
    button->setToolTip("Для заключения процесса вы обязаны выделить его PID и нажать на кнопку "Закончить"");
    connect(button,SIGNAL(clicked()),this,SLOT(kill()));
    hlayout->addStretch();
    hlayout->addWidget(button);
    layout = new QVBoxLayout;
    table = new QTableWidget;
    update();
    layout->addWidget(table);
    layout->addLayout(hlayout);
    this->setLayout(layout);
    QTimer *timer = new QTimer;
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(4000);
}

Вначале мы инициализируем графические элементы, и компануем их, а так же соединяем сигнал clicked у кнопки «Закончить» со слотом нашего класса — kill. Так же мы вызываем слот update для заполнения таблицы, и реализуем обновление таблицы при помощи таймера. Сейчас изложение слота update, вот его код:

void ProcessTable::update()
{
    table->setColumnCount(2);
    table->setRowCount(0);
    QStringList list;
    list << "Name" << "PID";
    table->setHorizontalHeaderLabels(list);
    QDir * dir = new QDir("/proc");
    list = dir->entryList(QStringList("*"),QDir::AllDirs);
    foreach(QString str, list) {
        if(str.toInt()) {
            ifstream stream;

            stream.open("/proc/"   str.toAscii()   "/comm");
            string s; getline(stream,s);
            int lastRow = table->rowCount();
            QString icon = "/usr/share/icons/hicolor/32x32/apps/"   QString::fromStdString(s)   ".png";
            QFile file(icon);
            table->insertRow(lastRow);
            table->setColumnWidth(0,150);

            if(!file.exists()) {
                icon = ":/binary.png";
            }
            table->setItem(lastRow,0,new QTableWidgetItem(QPixmap(icon),QString::fromStdString(s)));
            table->setItem(lastRow,1,new QTableWidgetItem(str));
        } else {
            continue;
        }
    }
}

Вначале мы устанавливаем число столбцов и строк с таблице, а так же имена столбцов. Позже чего мы получаем список директорий в папке /proc. Для заполнения таблицы мы циклом проходим по каждому списку, пропуская директории которые не принадлежат процессам. Имя процесса мы получаем из файла /proc/pid/comm. Иконку мы берём из папки /usr/share/icons/hicolor/32×32/apps, а если иконки там не будет то применяется вшитая в бинарник картинка binary.png. Вот эта картинка:
image
Для того Дабы зашить её в исходник я применял rcc, об применении rcc дозволено почитать в книге Макса Шлее — «Qt — профессиональное программирование на C », либо можете погуглить, я думаю что в интернете много манов по применению rcc.
Сейчас переходу к изложению слота kill, вот его код:

void ProcessTable::kill()
{
    QList<QTableWidgetItem*> list = table->selectedItems();
    QTableWidgetItem* item = list.value(0);
    QString str = item->text();
    QProcess::execute("kill", QStringList() << str);
    update();
}

Для заключения процесса я использую программу которая есть во всех линуксах(если в вашем линуксе её нет, то я даже не представляю что это за линукс такой), для запуска этой программы я пользуюсь способом execute класса QProcess, 2-й довод этой функции, это список параметров для программы, я передаю только PID процесса тот, что необходимо закончить, позже вызова этой программы я обновляю таблицу Дабы завершённый процесс исчез из неё. Сейчас я перехожу к изложению класса SystemMonitor, в котором я соединяю эти два класса. Каркас данного класса вовсе примитивный:

#ifndef SYSTEMMONITOR_H
#define SYSTEMMONITOR_H

#include <QtGui>
#include "info.h"
#include "processtable.h"
class SystemMonitor : public QWidget
{
    Q_OBJECT
public:
    explicit SystemMonitor(QWidget *parent = 0);
};

#endif // SYSTEMMONITOR_H

Ну, способ в этом классе каждого один, следственно я теперь его опишу. Вот код этого способа-конструктора(добавьте в предисловие файла реализации подключение файла каркаса):

SystemMonitor::SystemMonitor(QWidget *parent) :
    QWidget(parent)
{
    QTabWidget * tab = new QTabWidget;
    Info* info = new Info;
    ProcessTable* pt = new ProcessTable;
    tab->addTab(info,"Информация о системе");
    tab->addTab(pt,"Процессы");
    QVBoxLayout * layout = new QVBoxLayout;
    layout->addWidget(tab);
    this->setLayout(layout);
    this->show();
}

В конструкторе мы легко создаём виджет вкладок, и добавляем туда наш виджет с информацией, и виджет с таблицей, так как в качестве агрумента способ addTab принимает виджет, то для этого мы и наследовали классы Info и ProcessTable от QWidget. И теперь конечный штришок, создание отправной точки программы, то есть файла main.cpp. Вот его код:

#include "systemmonitor.h"
int main(int argc,char** argv)
{
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
    QApplication app(argc,argv);
    QStyle * style = QStyleFactory::create("Cleanlooks");
    app.setStyle(style);
    SystemMonitor sys;
    return app.exec();
}

Первые три строки делают так, Дабы русские буквы в программе правильно отображались. Строки 8 и 9 обеспечивают что в всякий системе наше приложение будет выглядеть идентично.
Ну вот и всё, системный монитор готов, Каждому пока!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB