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