kde-workspace/plasma/applets/system-monitor/system-monitor.cpp

632 lines
20 KiB
C++
Raw Normal View History

/*
This file is part of the KDE project
Copyright (C) 2024 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
2014-11-13 19:30:51 +02:00
#include "system-monitor.h"
#include "ksysguard/ksgrd/SensorClient.h"
#include "ksysguard/ksgrd/SensorManager.h"
#include <QMutex>
#include <QTimer>
#include <QGraphicsGridLayout>
2014-11-13 19:30:51 +02:00
#include <QGraphicsLinearLayout>
#include <Plasma/Theme>
#include <Plasma/Frame>
#include <Plasma/SignalPlotter>
#include <Plasma/Meter>
2014-11-13 19:30:51 +02:00
#include <KDebug>
static const int s_monitorsid = -1;
static const int s_updatetimeout = 1000;
static const QSizeF s_minimumvisualizersize = QSizeF(120, 70);
static const QSizeF s_minimummetersize = QSizeF(70, 70);
enum KSensorType {
UnknownSensor = 0,
CPUSensor = 1,
NetReceiverSensor = 2,
NetTransmitterSensor = 3,
DiskFreeSensor = 4,
DiskUsedSensor = 5,
ThermalSensor = 6
};
static KSensorType kSensorType(const QByteArray &sensor)
{
// qDebug() << Q_FUNC_INFO << sensor;
// the only CPU sensor required
if (sensor == "cpu/system/TotalLoad") {
return KSensorType::CPUSensor;
// any network receiver or transmitter except loopback
} else if (sensor.startsWith("network/interfaces/") && sensor.endsWith("/receiver/data")) {
if (sensor.contains("/interfaces/lo/")) {
return KSensorType::UnknownSensor;
}
return KSensorType::NetReceiverSensor;
} else if (sensor.startsWith("network/interfaces/") && sensor.endsWith("/transmitter/data")) {
if (sensor.contains("/interfaces/lo/")) {
return KSensorType::UnknownSensor;
}
return KSensorType::NetTransmitterSensor;
// any partitions
} else if (sensor.startsWith("partitions/") && sensor.endsWith("/freespace")) {
return KSensorType::DiskFreeSensor;
} else if (sensor.startsWith("partitions/") && sensor.endsWith("/usedspace")) {
return KSensorType::DiskUsedSensor;
} else if (sensor.startsWith("acpi/Thermal_Zone/")) {
return KSensorType::ThermalSensor;
}
return KSensorType::UnknownSensor;
}
static QByteArray kNetID(const QByteArray &sensor)
{
if (sensor.endsWith("/receiver/data")) {
return sensor.mid(0, sensor.size() - 14);
} else if (sensor.endsWith("/transmitter/data")) {
return sensor.mid(0, sensor.size() - 17);
}
kWarning() << "invalid network sensor" << sensor;
return sensor;
}
static QByteArray kPartitionID(const QByteArray &sensor)
{
if (sensor.endsWith("/freespace") || sensor.endsWith("/usedspace")) {
return sensor.mid(0, sensor.size() - 10);
}
kWarning() << "invalid partition sensor" << sensor;
return sensor;
}
static QByteArray kThermalID(const QByteArray &sensor)
{
// as-is
return sensor;
}
static void kSetupFrame(Plasma::Frame* plasmaframe)
{
plasmaframe->setFrameShadow(Plasma::Frame::Sunken);
plasmaframe->setMinimumSize(s_minimumvisualizersize);
plasmaframe->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
QGraphicsLinearLayout* plasmaframelayout = new QGraphicsLinearLayout(plasmaframe);
plasmaframelayout->setContentsMargins(0, 0, 0, 0);
plasmaframe->setLayout(plasmaframelayout);
}
static void kAddItem(QGraphicsWidget *parent, QGraphicsWidget *widget)
{
QGraphicsLinearLayout* parentlayout = static_cast<QGraphicsLinearLayout*>(parent->layout());
Q_ASSERT(parentlayout);
parentlayout->addItem(widget);
}
2014-11-13 19:30:51 +02:00
// TODO: hardcoded
static QColor kCPUVisualizerColor()
2014-11-13 19:30:51 +02:00
{
return Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
2014-11-13 19:30:51 +02:00
}
static QColor kNetReceiverVisualizerColor()
{
return Plasma::Theme::defaultTheme()->color(Plasma::Theme::VisitedLinkColor);
}
static QColor kNetTransmitterVisualizerColor()
{
return Plasma::Theme::defaultTheme()->color(Plasma::Theme::LinkColor);
}
class SystemMonitorNet : public Plasma::Frame
{
Q_OBJECT
public:
SystemMonitorNet(QGraphicsWidget *parent, const QByteArray &netid);
QByteArray netID() const;
void resetSample();
void addReceiveSample(const float value);
void addTransmitSample(const float value);
private:
Plasma::SignalPlotter* m_netplotter;
QByteArray m_netid;
QList<double> m_netsample;
};
SystemMonitorNet::SystemMonitorNet(QGraphicsWidget *parent, const QByteArray &netid)
: Plasma::Frame(parent),
m_netplotter(nullptr),
m_netid(netid)
{
kSetupFrame(this);
m_netplotter = new Plasma::SignalPlotter(this);
m_netplotter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_netplotter->setTitle(i18n("Network"));
m_netplotter->setUnit("KiB/s");
m_netplotter->setShowTopBar(true);
m_netplotter->setShowLabels(true);
m_netplotter->setShowVerticalLines(false);
m_netplotter->setShowHorizontalLines(false);
m_netplotter->setThinFrame(false);
m_netplotter->setUseAutoRange(true);
m_netplotter->setStackPlots(true);
m_netplotter->addPlot(kNetReceiverVisualizerColor());
m_netplotter->addPlot(kNetTransmitterVisualizerColor());
kAddItem(this, m_netplotter);
}
QByteArray SystemMonitorNet::netID() const
{
return m_netid;
}
void SystemMonitorNet::resetSample()
{
m_netsample.clear();
m_netsample.reserve(2);
m_netsample.append(0.0);
m_netsample.append(0.0);
}
void SystemMonitorNet::addReceiveSample(const float value)
{
m_netsample[0] = double(value);
m_netplotter->addSample(m_netsample);
}
void SystemMonitorNet::addTransmitSample(const float value)
{
m_netsample[1] = double(value);
m_netplotter->addSample(m_netsample);
}
class SystemMonitorPartition : public Plasma::Meter
{
Q_OBJECT
public:
SystemMonitorPartition(QGraphicsWidget *parent, const QByteArray &partitionid);
QByteArray partitionID() const;
private:
QByteArray m_partitionid;
};
SystemMonitorPartition::SystemMonitorPartition(QGraphicsWidget *parent, const QByteArray &partitionid)
: Plasma::Meter(parent),
m_partitionid(partitionid)
{
setMeterType(Plasma::Meter::BarMeterHorizontal);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
setMinimum(0);
setMaximum(0);
}
QByteArray SystemMonitorPartition::partitionID() const
{
return m_partitionid;
}
class SystemMonitorThermal : public Plasma::Meter
{
Q_OBJECT
public:
SystemMonitorThermal(QGraphicsWidget *parent, const QByteArray &thermalid);
QByteArray thermalID() const;
private:
QByteArray m_thermalid;
};
SystemMonitorThermal::SystemMonitorThermal(QGraphicsWidget *parent, const QByteArray &thermalid)
: Plasma::Meter(parent),
m_thermalid(thermalid)
{
setMeterType(Plasma::Meter::AnalogMeter);
setMinimumSize(s_minimummetersize);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// TODO: units?
setMinimum(0);
setMaximum(120);
}
QByteArray SystemMonitorThermal::thermalID() const
{
return m_thermalid;
}
class SystemMonitorClient : public QObject, public KSGRD::SensorClient
2014-11-13 19:30:51 +02:00
{
Q_OBJECT
public:
SystemMonitorClient(QObject *parent);
~SystemMonitorClient();
2014-11-13 19:30:51 +02:00
QList<QByteArray> sensors() const;
void requestValue(const QByteArray &sensor) const;
2014-11-13 19:30:51 +02:00
Q_SIGNALS:
void sensorsChanged();
void sensorValue(const QByteArray &sensor, const float value);
2014-11-13 19:30:51 +02:00
private Q_SLOTS:
void slotUpdate();
2014-11-13 19:30:51 +02:00
protected:
void answerReceived(int id, const QList<QByteArray> &answer) final;
void sensorLost(int id) final;
2014-11-13 19:30:51 +02:00
private:
QList<QByteArray> m_sensors;
};
2014-11-13 19:30:51 +02:00
SystemMonitorClient::SystemMonitorClient(QObject *parent)
: QObject(parent)
{
KSGRD::SensorMgr = new KSGRD::SensorManager(this);
KSGRD::SensorMgr->engage("localhost", "", "ksysguardd");
connect(KSGRD::SensorMgr, SIGNAL(update()), this, SLOT(slotUpdate()));
slotUpdate();
2014-11-13 19:30:51 +02:00
}
SystemMonitorClient::~SystemMonitorClient()
2014-11-13 19:30:51 +02:00
{
}
2014-11-13 19:30:51 +02:00
QList<QByteArray> SystemMonitorClient::sensors() const
{
return m_sensors;
2014-11-13 19:30:51 +02:00
}
void SystemMonitorClient::requestValue(const QByteArray &sensor) const
2014-11-13 19:30:51 +02:00
{
const int sensorid = m_sensors.indexOf(sensor);
if (sensorid < 0) {
// this can actually happen if there was no answer yet for "monitors"
kWarning() << "unmapped sensor" << sensor;
return;
}
const QString sensorstring = QString::fromLatin1(sensor.constData(), sensor.size());
KSGRD::SensorMgr->sendRequest("localhost", sensorstring, (KSGRD::SensorClient*)this, sensorid);
}
2014-11-13 19:30:51 +02:00
void SystemMonitorClient::slotUpdate()
{
KSGRD::SensorMgr->sendRequest("localhost", "monitors", (KSGRD::SensorClient*)this, s_monitorsid);
}
2014-11-13 19:30:51 +02:00
void SystemMonitorClient::answerReceived(int id, const QList<QByteArray> &answer)
{
if (id == s_monitorsid) {
m_sensors.clear();
foreach (const QByteArray &sensoranswer, answer) {
const QList<QByteArray> splitsensoranswer = sensoranswer.split('\t');
if (splitsensoranswer.size() != 2) {
kWarning() << "invalid sensor answer" << sensoranswer;
continue;
2014-11-13 19:30:51 +02:00
}
const QByteArray sensortype = splitsensoranswer.at(1);
if (sensortype != "integer" && sensortype != "float") {
continue;
2014-11-13 19:30:51 +02:00
}
const QByteArray sensorname = splitsensoranswer.at(0);
kDebug() << "mapping sensor" << sensorname << sensortype;
// pre-sort, order of occurance matters
const KSensorType ksensortype = kSensorType(sensorname);
switch (ksensortype) {
case KSensorType::DiskFreeSensor:
case KSensorType::DiskUsedSensor: {
m_sensors.append(sensorname);
break;
}
default: {
m_sensors.prepend(sensorname);
break;
}
}
}
emit sensorsChanged();
} else if (id < m_sensors.size()) {
foreach (const QByteArray &sensoranswer, answer) {
const QByteArray sensorname = m_sensors.at(id);
const float sensorvalue = sensoranswer.toFloat();
kDebug() << "got sensor value" << id << sensorname << sensorvalue;
emit sensorValue(sensorname, sensorvalue);
2014-11-13 19:30:51 +02:00
}
} else {
kWarning() << "invalid sensor ID" << id;
2014-11-13 19:30:51 +02:00
}
2014-11-13 19:30:51 +02:00
}
void SystemMonitorClient::sensorLost(int id)
2014-11-13 19:30:51 +02:00
{
kDebug() << "sensor lost" << id;
slotUpdate();
}
2014-11-13 19:30:51 +02:00
class SystemMonitorWidget : public QGraphicsWidget
{
Q_OBJECT
public:
SystemMonitorWidget(SystemMonitor* systemmonitor);
~SystemMonitorWidget();
public Q_SLOTS:
void slotUpdateLayout();
private Q_SLOTS:
void slotRequestValues();
void slotSensorValue(const QByteArray &sensor, const float value);
private:
QMutex m_mutex;
SystemMonitor* m_systemmonitor;
QGraphicsGridLayout* m_layout;
SystemMonitorClient* m_systemmonitorclient;
Plasma::Frame* m_cpuframe;
Plasma::SignalPlotter* m_cpuplotter;
QList<SystemMonitorNet*> m_netmonitors;
QList<SystemMonitorPartition*> m_partitionmonitors;
QList<SystemMonitorThermal*> m_thermalmonitors;
QList<QByteArray> m_requestsensors;
};
SystemMonitorWidget::SystemMonitorWidget(SystemMonitor* systemmonitor)
: QGraphicsWidget(systemmonitor),
m_systemmonitor(systemmonitor),
m_layout(nullptr),
m_systemmonitorclient(nullptr),
m_cpuframe(nullptr),
m_cpuplotter(nullptr)
{
m_layout = new QGraphicsGridLayout(this);
m_systemmonitorclient = new SystemMonitorClient(this);
connect(
m_systemmonitorclient, SIGNAL(sensorValue(QByteArray,float)),
this, SLOT(slotSensorValue(QByteArray,float))
);
m_cpuframe = new Plasma::Frame(this);
kSetupFrame(m_cpuframe);
m_cpuplotter = new Plasma::SignalPlotter(m_cpuframe);
m_cpuplotter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_cpuplotter->setTitle(i18n("CPU"));
m_cpuplotter->setUnit("%");
m_cpuplotter->setShowTopBar(true);
m_cpuplotter->setShowLabels(true);
m_cpuplotter->setShowVerticalLines(false);
m_cpuplotter->setShowHorizontalLines(false);
m_cpuplotter->setThinFrame(false);
m_cpuplotter->setUseAutoRange(false);
m_cpuplotter->setVerticalRange(0.0, 100.0);
m_cpuplotter->addPlot(kCPUVisualizerColor());
kAddItem(m_cpuframe, m_cpuplotter);
m_layout->addItem(m_cpuframe, 0, 0);
setLayout(m_layout);
connect(
m_systemmonitorclient, SIGNAL(sensorsChanged()),
this, SLOT(slotUpdateLayout())
);
2014-11-13 19:30:51 +02:00
}
SystemMonitorWidget::~SystemMonitorWidget()
2014-11-13 19:30:51 +02:00
{
}
void SystemMonitorWidget::slotUpdateLayout()
{
QMutexLocker locker(&m_mutex);
foreach (SystemMonitorNet* netmonitor, m_netmonitors) {
m_layout->removeItem(netmonitor);
}
qDeleteAll(m_netmonitors);
m_netmonitors.clear();
foreach (SystemMonitorPartition* partitionmonitor, m_partitionmonitors) {
m_layout->removeItem(partitionmonitor);
}
qDeleteAll(m_partitionmonitors);
m_partitionmonitors.clear();
foreach (SystemMonitorThermal* thermalmonitor, m_thermalmonitors) {
m_layout->removeItem(thermalmonitor);
}
qDeleteAll(m_thermalmonitors);
m_thermalmonitors.clear();
m_requestsensors.clear();
foreach (const QByteArray &sensor, m_systemmonitorclient->sensors()) {
const KSensorType ksensortype = kSensorType(sensor);
if (ksensortype != KSensorType::UnknownSensor) {
m_requestsensors.append(sensor);
kDebug() << "monitoring" << sensor << ksensortype;
}
if (ksensortype == KSensorType::NetReceiverSensor) {
SystemMonitorNet* netmonitor = new SystemMonitorNet(this, kNetID(sensor));
m_layout->addItem(netmonitor, 1, 0);
m_netmonitors.append(netmonitor);
}
if (ksensortype == KSensorType::DiskFreeSensor) {
SystemMonitorPartition* partitionmonitor = new SystemMonitorPartition(this, kPartitionID(sensor));
m_layout->addItem(partitionmonitor, m_layout->rowCount(), 0, 1, 2);
m_partitionmonitors.append(partitionmonitor);
}
}
foreach (const QByteArray &sensor, m_systemmonitorclient->sensors()) {
const KSensorType ksensortype = kSensorType(sensor);
if (ksensortype == KSensorType::ThermalSensor) {
// bottom row is reserved, thermal monitors are aligned to the CPU monitor + the
// network monitors count
if (m_thermalmonitors.size() >= (m_netmonitors.size() + 1)) {
kWarning() << "not enough space for thermal sensor" << sensor;
continue;
}
SystemMonitorThermal* thermalmonitor = new SystemMonitorThermal(this, kThermalID(sensor));
m_layout->addItem(thermalmonitor, m_thermalmonitors.size(), 1);
m_thermalmonitors.append(thermalmonitor);
}
}
QTimer::singleShot(s_updatetimeout, this, SLOT(slotRequestValues()));
}
void SystemMonitorWidget::slotRequestValues()
2014-11-13 19:30:51 +02:00
{
QMutexLocker locker(&m_mutex);
foreach (SystemMonitorNet* netmonitor, m_netmonitors) {
netmonitor->resetSample();
}
locker.unlock();
foreach (const QByteArray &request, m_requestsensors) {
m_systemmonitorclient->requestValue(request);
}
QTimer::singleShot(s_updatetimeout, this, SLOT(slotRequestValues()));
2014-11-13 19:30:51 +02:00
}
void SystemMonitorWidget::slotSensorValue(const QByteArray &sensor, const float value)
2014-11-13 19:30:51 +02:00
{
const KSensorType ksensortype = kSensorType(sensor);
switch (ksensortype) {
case KSensorType::CPUSensor: {
m_cpuplotter->addSample(QList<double>() << double(value));
break;
}
case KSensorType::NetReceiverSensor: {
QMutexLocker locker(&m_mutex);
const QByteArray netid = kNetID(sensor);
foreach (SystemMonitorNet* netmonitor, m_netmonitors) {
if (netmonitor->netID() == netid) {
netmonitor->addReceiveSample(value);
break;
}
}
break;
}
case KSensorType::NetTransmitterSensor: {
QMutexLocker locker(&m_mutex);
const QByteArray netid = kNetID(sensor);
foreach (SystemMonitorNet* netmonitor, m_netmonitors) {
if (netmonitor->netID() == netid) {
netmonitor->addTransmitSample(value);
break;
}
}
break;
}
case KSensorType::DiskFreeSensor: {
QMutexLocker locker(&m_mutex);
const QByteArray partitionid = kPartitionID(sensor);
foreach (SystemMonitorPartition* partitionmonitor, m_partitionmonitors) {
if (partitionmonitor->partitionID() == partitionid) {
const int roundvalue = qRound(value);
const int maxvalue = qMax(roundvalue + partitionmonitor->value(), roundvalue);
partitionmonitor->setMaximum(maxvalue);
// qDebug() << Q_FUNC_INFO << "disk free" << sensor << roundvalue << maxvalue;
break;
}
}
break;
}
case KSensorType::DiskUsedSensor: {
QMutexLocker locker(&m_mutex);
const QByteArray partitionid = kPartitionID(sensor);
foreach (SystemMonitorPartition* partitionmonitor, m_partitionmonitors) {
if (partitionmonitor->partitionID() == partitionid) {
const int roundvalue = qRound(value);
const int maxvalue = qMax(roundvalue, partitionmonitor->maximum());
partitionmonitor->setMaximum(maxvalue);
partitionmonitor->setValue(roundvalue);
// qDebug() << Q_FUNC_INFO << "disk used" << sensor << roundvalue << maxvalue;
break;
}
}
break;
}
case KSensorType::ThermalSensor: {
QMutexLocker locker(&m_mutex);
const QByteArray thermalid = kThermalID(sensor);
foreach (SystemMonitorThermal* thermalmonitor, m_thermalmonitors) {
if (thermalmonitor->thermalID() == thermalid) {
const int roundvalue = qRound(value);
thermalmonitor->setValue(roundvalue);
break;
}
}
break;
}
case KSensorType::UnknownSensor: {
break;
}
}
if (ksensortype == KSensorType::DiskFreeSensor || ksensortype == KSensorType::DiskUsedSensor) {
// force an update, apparently sometimes the sensors have bogus values (e.g. / reported to
// have zero free space, probably bug in ksysguard)
update();
2014-11-13 19:30:51 +02:00
}
}
2014-11-13 19:30:51 +02:00
SystemMonitor::SystemMonitor(QObject *parent, const QVariantList &args)
: Plasma::PopupApplet(parent, args),
m_systemmonitorwidget(nullptr)
{
KGlobal::locale()->insertCatalog("plasma_applet_system-monitor");
setAspectRatioMode(Plasma::IgnoreAspectRatio);
setHasConfigurationInterface(true);
m_systemmonitorwidget = new SystemMonitorWidget(this);
setPopupIcon("utilities-system-monitor");
2014-11-13 19:30:51 +02:00
}
SystemMonitor::~SystemMonitor()
2014-11-13 19:30:51 +02:00
{
delete m_systemmonitorwidget;
2014-11-13 19:30:51 +02:00
}
void SystemMonitor::init()
2014-11-13 19:30:51 +02:00
{
QTimer::singleShot(500, m_systemmonitorwidget, SLOT(slotUpdateLayout()));
}
void SystemMonitor::createConfigurationInterface(KConfigDialog *parent)
{
// TODO: implement
}
2014-11-13 19:30:51 +02:00
QGraphicsWidget *SystemMonitor::graphicsWidget()
{
return m_systemmonitorwidget;
2014-11-13 19:30:51 +02:00
}
K_EXPORT_PLASMA_APPLET(system-monitor_applet, SystemMonitor)
2015-02-27 09:28:46 +00:00
#include "moc_system-monitor.cpp"
#include "system-monitor.moc"