File indexing completed on 2024-05-12 17:00:14
0001 /* 0002 SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de> 0003 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "disks.h" 0009 0010 #ifdef Q_OS_FREEBSD 0011 #include <devstat.h> 0012 #include <libgeom.h> 0013 #endif 0014 0015 #include <QUrl> 0016 0017 #include <KIO/FileSystemFreeSpaceJob> 0018 #include <KLocalizedString> 0019 #include <KPluginFactory> 0020 #include <Solid/Block> 0021 #include <Solid/Device> 0022 #include <Solid/DeviceNotifier> 0023 #include <Solid/Predicate> 0024 #include <Solid/StorageAccess> 0025 #include <Solid/StorageDrive> 0026 #include <Solid/StorageVolume> 0027 0028 #include <systemstats/AggregateSensor.h> 0029 #include <systemstats/SensorContainer.h> 0030 #include <systemstats/SensorObject.h> 0031 0032 class VolumeObject : public KSysGuard::SensorObject { 0033 public: 0034 VolumeObject(const Solid::Device &device, KSysGuard::SensorContainer *parent); 0035 void update(); 0036 void setBytes(quint64 read, quint64 written, qint64 elapsedTime); 0037 0038 const QString udi; 0039 const QString mountPoint; 0040 private: 0041 static QString idHelper(const Solid::Device &device); 0042 0043 KSysGuard::SensorProperty *m_name = nullptr; 0044 KSysGuard::SensorProperty *m_total = nullptr; 0045 KSysGuard::SensorProperty *m_used = nullptr; 0046 KSysGuard::SensorProperty *m_free = nullptr; 0047 KSysGuard::SensorProperty *m_readRate = nullptr; 0048 KSysGuard::SensorProperty *m_writeRate = nullptr; 0049 quint64 m_bytesRead = 0; 0050 quint64 m_bytesWritten = 0; 0051 }; 0052 0053 QString VolumeObject::idHelper(const Solid::Device &device) 0054 { 0055 auto volume = device.as<Solid::StorageVolume>(); 0056 return volume->uuid().isEmpty() ? volume->label() : volume->uuid(); 0057 } 0058 0059 0060 VolumeObject::VolumeObject(const Solid::Device &device, KSysGuard::SensorContainer* parent) 0061 : SensorObject(idHelper(device), device.displayName(), parent) 0062 , udi(device.udi()) 0063 , mountPoint(device.as<Solid::StorageAccess>()->filePath()) 0064 { 0065 auto volume = device.as<Solid::StorageVolume>(); 0066 0067 m_name = new KSysGuard::SensorProperty("name", i18nc("@title", "Name"), device.displayName(), this); 0068 m_name->setShortName(i18nc("@title", "Name")); 0069 m_name->setVariantType(QVariant::String); 0070 0071 m_total = new KSysGuard::SensorProperty("total", i18nc("@title", "Total Space"), volume->size(), this); 0072 m_total->setPrefix(name()); 0073 m_total->setShortName(i18nc("@title Short for 'Total Space'", "Total")); 0074 m_total->setUnit(KSysGuard::UnitByte); 0075 m_total->setVariantType(QVariant::ULongLong); 0076 0077 m_used = new KSysGuard::SensorProperty("used", i18nc("@title", "Used Space"), this); 0078 m_used->setPrefix(name()); 0079 m_used->setShortName(i18nc("@title Short for 'Used Space'", "Used")); 0080 m_used->setUnit(KSysGuard::UnitByte); 0081 m_used->setVariantType(QVariant::ULongLong); 0082 m_used->setMax(volume->size()); 0083 0084 m_free = new KSysGuard::SensorProperty("free", i18nc("@title", "Free Space"), this); 0085 m_free->setPrefix(name()); 0086 m_free->setShortName(i18nc("@title Short for 'Free Space'", "Free")); 0087 m_free->setUnit(KSysGuard::UnitByte); 0088 m_free->setVariantType(QVariant::ULongLong); 0089 m_free->setMax(volume->size()); 0090 0091 m_readRate = new KSysGuard::SensorProperty("read", i18nc("@title", "Read Rate"), 0, this); 0092 m_readRate->setPrefix(name()); 0093 m_readRate->setShortName(i18nc("@title Short for 'Read Rate'", "Read")); 0094 m_readRate->setUnit(KSysGuard::UnitByteRate); 0095 m_readRate->setVariantType(QVariant::Double); 0096 0097 m_writeRate = new KSysGuard::SensorProperty("write", i18nc("@title", "Write Rate"), 0, this); 0098 m_writeRate->setPrefix(name()); 0099 m_writeRate->setShortName(i18nc("@title Short for 'Write Rate'", "Write")); 0100 m_writeRate->setUnit(KSysGuard::UnitByteRate); 0101 m_writeRate->setVariantType(QVariant::Double); 0102 0103 auto usedPercent = new KSysGuard::PercentageSensor(this, "usedPercent", i18nc("@title", "Percentage Used")); 0104 usedPercent->setPrefix(name()); 0105 usedPercent->setBaseSensor(m_used); 0106 0107 auto freePercent = new KSysGuard::PercentageSensor(this, "freePercent", i18nc("@title", "Percentage Free")); 0108 freePercent->setPrefix(name()); 0109 freePercent->setBaseSensor(m_free); 0110 } 0111 0112 void VolumeObject::update() 0113 { 0114 auto job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(mountPoint)); 0115 connect(job, &KIO::FileSystemFreeSpaceJob::result, this, [this] (KJob *job, KIO::filesize_t size, KIO::filesize_t available) { 0116 if (!job->error()) { 0117 m_total->setValue(size); 0118 m_free->setValue(available); 0119 m_free->setMax(size); 0120 m_used->setValue(size - available); 0121 m_used->setMax(size); 0122 } 0123 }); 0124 } 0125 0126 void VolumeObject::setBytes(quint64 read, quint64 written, qint64 elapsed) 0127 { 0128 if (elapsed != 0) { 0129 double seconds = elapsed / 1000.0; 0130 m_readRate->setValue((read - m_bytesRead) / seconds); 0131 m_writeRate->setValue((written - m_bytesWritten) / seconds); 0132 } 0133 m_bytesRead = read; 0134 m_bytesWritten = written; 0135 } 0136 0137 DisksPlugin::DisksPlugin(QObject *parent, const QVariantList &args) 0138 : SensorPlugin(parent, args) 0139 { 0140 auto container = new KSysGuard::SensorContainer("disk", i18n("Disks"), this); 0141 auto storageAccesses = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); 0142 for (const auto &storageAccess : storageAccesses) { 0143 addDevice(storageAccess); 0144 } 0145 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, [this] (const QString &udi) { 0146 addDevice(Solid::Device(udi)); 0147 }); 0148 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, [this, container] (const QString &udi) { 0149 Solid::Device device(udi); 0150 if (device.isDeviceInterface(Solid::DeviceInterface::StorageAccess)) { 0151 auto it = std::find_if(m_volumesByDevice.begin(), m_volumesByDevice.end(), [&udi] (VolumeObject *volume) { 0152 return volume->udi == udi; 0153 }); 0154 if (it != m_volumesByDevice.end()) { 0155 container->removeObject(*it); 0156 m_volumesByDevice.erase(it); 0157 } 0158 } 0159 }); 0160 addAggregateSensors(); 0161 #ifdef Q_OS_FREEBSD 0162 geom_stats_open(); 0163 #endif 0164 } 0165 0166 DisksPlugin::~DisksPlugin() 0167 { 0168 #ifdef Q_OS_FREEBSD 0169 geom_stats_close(); 0170 #endif 0171 } 0172 0173 void DisksPlugin::addDevice(const Solid::Device& device) 0174 { 0175 auto container = containers()[0]; 0176 const auto volume = device.as<Solid::StorageVolume>(); 0177 auto access = device.as<Solid::StorageAccess>(); 0178 if (!access || !volume || volume->isIgnored()) { 0179 return; 0180 } 0181 Solid::Device drive = device; 0182 // Only exlude volumes if we know that they are for sure not on a hard disk 0183 while (drive.isValid()) { 0184 if (drive.is<Solid::StorageDrive>()) { 0185 auto type = drive.as<Solid::StorageDrive>()->driveType(); 0186 if (type == Solid::StorageDrive::HardDisk) { 0187 break; 0188 } else { 0189 return; 0190 } 0191 } 0192 drive = drive.parent(); 0193 } 0194 0195 if (access->filePath() != QString()) { 0196 createAccessibleVolumeObject(device); 0197 } 0198 connect(access, &Solid::StorageAccess::accessibilityChanged, this, [this, container] (bool accessible, const QString &udi) { 0199 if (accessible) { 0200 Solid::Device device(udi); 0201 createAccessibleVolumeObject(device); 0202 } else { 0203 auto it = std::find_if(m_volumesByDevice.begin(), m_volumesByDevice.end(), [&udi] (VolumeObject *disk) { 0204 return disk->udi == udi; 0205 }); 0206 if (it != m_volumesByDevice.end()) { 0207 container->removeObject(*it); 0208 m_volumesByDevice.erase(it); 0209 } 0210 } 0211 }); 0212 } 0213 0214 void DisksPlugin::createAccessibleVolumeObject(const Solid::Device &device) 0215 { 0216 auto block = device.as<Solid::Block>(); 0217 auto access = device.as<Solid::StorageAccess>(); 0218 Q_ASSERT(access->isAccessible()); 0219 const QString mountPoint = access->filePath(); 0220 const bool hasMountPoint = std::any_of(m_volumesByDevice.cbegin(), m_volumesByDevice.cend(), [mountPoint] (const VolumeObject* volume) { 0221 return volume->mountPoint == mountPoint; 0222 }); 0223 if (hasMountPoint) { 0224 return; 0225 } 0226 m_volumesByDevice.insert(block->device(), new VolumeObject(device, containers()[0])); 0227 } 0228 0229 void DisksPlugin::addAggregateSensors() 0230 { 0231 auto container = containers()[0]; 0232 auto allDisks = new KSysGuard::SensorObject("all", i18nc("@title", "All Disks"), container); 0233 0234 auto total = new KSysGuard::AggregateSensor(allDisks, "total", i18nc("@title", "Total Space")); 0235 total->setShortName(i18nc("@title Short for 'Total Space'", "Total")); 0236 total->setUnit(KSysGuard::UnitByte); 0237 total->setVariantType(QVariant::ULongLong); 0238 total->setMatchSensors(QRegularExpression("^(?!all).*$"), "total"); 0239 0240 auto free = new KSysGuard::AggregateSensor(allDisks, "free", i18nc("@title", "Free Space")); 0241 free->setShortName(i18nc("@title Short for 'Free Space'", "Free")); 0242 free->setUnit(KSysGuard::UnitByte); 0243 free->setVariantType(QVariant::ULongLong); 0244 free->setMax(total->value().toULongLong()); 0245 free->setMatchSensors(QRegularExpression("^(?!all).*$"), "free"); 0246 0247 auto used = new KSysGuard::AggregateSensor(allDisks, "used", i18nc("@title", "Used Space")); 0248 used->setShortName(i18nc("@title Short for 'Used Space'", "Used")); 0249 used->setUnit(KSysGuard::UnitByte); 0250 used->setVariantType(QVariant::ULongLong); 0251 used->setMax(total->value().toULongLong()); 0252 used->setMatchSensors(QRegularExpression("^(?!all).*$"), "used"); 0253 0254 auto readRate = new KSysGuard::AggregateSensor(allDisks, "read", i18nc("@title", "Read Rate"), 0); 0255 readRate->setShortName(i18nc("@title Short for 'Read Rate'", "Read")); 0256 readRate->setUnit(KSysGuard::UnitByteRate); 0257 readRate->setVariantType(QVariant::Double); 0258 readRate->setMatchSensors(QRegularExpression("^(?!all).*$"), "read"); 0259 0260 auto writeRate = new KSysGuard::AggregateSensor(allDisks, "write", i18nc("@title", "Write Rate"), 0); 0261 writeRate->setShortName(i18nc("@title Short for 'Write Rate'", "Write")); 0262 writeRate->setUnit(KSysGuard::UnitByteRate); 0263 writeRate->setVariantType(QVariant::Double); 0264 writeRate->setMatchSensors(QRegularExpression("^(?!all).*$"), "write"); 0265 0266 auto freePercent = new KSysGuard::PercentageSensor(allDisks, "freePercent", i18nc("@title", "Percentage Free")); 0267 freePercent->setShortName(i18nc("@title, Short for `Percentage Free", "Free")); 0268 freePercent->setBaseSensor(free); 0269 0270 auto usedPercent = new KSysGuard::PercentageSensor(allDisks, "usedPercent", i18nc("@title", "Percentage Used")); 0271 usedPercent->setShortName(i18nc("@title, Short for `Percentage Used", "Used")); 0272 usedPercent->setBaseSensor(used); 0273 0274 connect(total, &KSysGuard::SensorProperty::valueChanged, this, [total, free, used] () { 0275 free->setMax(total->value().toULongLong()); 0276 used->setMax(total->value().toULongLong()); 0277 }); 0278 } 0279 0280 void DisksPlugin::update() 0281 { 0282 bool anySubscribed = false; 0283 for (auto volume : m_volumesByDevice) { 0284 if (volume->isSubscribed()) { 0285 anySubscribed = true; 0286 volume->update(); 0287 } 0288 } 0289 0290 if (!anySubscribed) { 0291 return; 0292 } 0293 0294 qint64 elapsed = 0; 0295 if (m_elapsedTimer.isValid()) { 0296 elapsed = m_elapsedTimer.restart(); 0297 } else { 0298 m_elapsedTimer.start(); 0299 } 0300 #if defined Q_OS_LINUX 0301 QFile diskstats("/proc/diskstats"); 0302 if (!diskstats.exists()) { 0303 return; 0304 } 0305 diskstats.open(QIODevice::ReadOnly | QIODevice::Text); 0306 /* procfs-diskstats (See https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats) 0307 The /proc/diskstats file displays the I/O statistics 0308 of block devices. Each line contains the following 14 0309 fields: 0310 - major number 0311 - minor mumber 0312 - device name 0313 - reads completed successfully 0314 - reads merged 0315 - sectors read 0316 - time spent reading (ms) 0317 - writes completed 0318 - writes merged 0319 - sectors written 0320 [...] 0321 */ 0322 for (QByteArray line = diskstats.readLine(); !line.isNull(); line = diskstats.readLine()) { 0323 QList<QByteArray> fields = line.simplified().split(' '); 0324 const QString device = QStringLiteral("/dev/%1").arg(QString::fromLatin1(fields[2])); 0325 if (m_volumesByDevice.contains(device)) { 0326 // A sector as reported in diskstats is 512 Bytes, see https://stackoverflow.com/a/38136179 0327 m_volumesByDevice[device]->setBytes(fields[5].toULongLong() * 512, fields[9].toULongLong() * 512, elapsed); 0328 } 0329 } 0330 #elif defined Q_OS_FREEBSD 0331 std::unique_ptr<void, decltype(&geom_stats_snapshot_free)> stats(geom_stats_snapshot_get(), geom_stats_snapshot_free); 0332 gmesh mesh; 0333 geom_gettree(&mesh); 0334 while (devstat *dstat = geom_stats_snapshot_next(stats.get())) { 0335 gident *id = geom_lookupid(&mesh, dstat->id); 0336 if (id && id->lg_what == gident::ISPROVIDER) { 0337 auto provider = static_cast<gprovider*>(id->lg_ptr); 0338 const QString device = QStringLiteral("/dev/%1").arg(QString::fromLatin1(provider->lg_name)); 0339 if (m_volumesByDevice.contains(device)) { 0340 uint64_t bytesRead, bytesWritten; 0341 devstat_compute_statistics(dstat, nullptr, 0, DSM_TOTAL_BYTES_READ, &bytesRead, DSM_TOTAL_BYTES_WRITE, &bytesWritten, DSM_NONE); 0342 m_volumesByDevice[device]->setBytes(bytesRead, bytesWritten, elapsed); 0343 } 0344 } 0345 } 0346 geom_deletetree(&mesh); 0347 #endif 0348 } 0349 0350 K_PLUGIN_CLASS_WITH_JSON(DisksPlugin, "metadata.json") 0351 #include "disks.moc"