/* * Copyright (C) 2011, 2012 Shaun Reich * * This program 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 program 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "hdd_activity.h" #include "monitoricon.h" #include "plotter.h" #include #include #include #include #include #include #include #include #include /** * Examples of what the regexp has to handle... * * Note actually it's now set to an inclusive-only mode, far cleaner. * * Included items only: * * * RAID blocks (there *could* be more if not using mdadm, I think): * * disk/md/Rate/rio * disk/md/Rate/wio * * SATA disks: * * disk/sd/Rate/rio * disk/sd/Rate/wio * * IDE/PATA disks: * * disk/hd/Rate/rio * disk/hd/Rate/wio * * Does NOT include partitions, since I don't think many people will use that. * * RAID regexp handling needs further testing, I have a feeling it works just for me. * */ Hdd_Activity::Hdd_Activity(QObject *parent, const QVariantList &args) : SM::Applet(parent, args), m_regexp("disk/(?:md|sd|hd)[a-z|0-9]_.*/Rate/(?:rblk|wblk)") { setHasConfigurationInterface(true); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } Hdd_Activity::~Hdd_Activity() { } void Hdd_Activity::init() { KGlobal::locale()->insertCatalog("plasma_applet_system-monitor"); setEngine(dataEngine("systemmonitor")); setTitle(i18n("Disk Activity")); /* At the time this method is running, not all sources may be connected. */ connect(engine(), SIGNAL(sourceAdded(QString)), this, SLOT(sourceChanged(QString))); connect(engine(), SIGNAL(sourceRemoved(QString)), this, SLOT(sourceChanged(QString))); foreach (const QString& source, engine()->sources()) { sourceChanged(source); } configChanged(); } void Hdd_Activity::sourceChanged(const QString& name) { //kDebug() << "######## sourceChanged name: " << name; //kDebug() << "###### regexp captures: " << m_regexp.capturedTexts(); if (m_regexp.indexIn(name) != -1) { m_possibleHdds.append(name); } } void Hdd_Activity::sourcesChanged() { configChanged(); } void Hdd_Activity::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data) { const double value = data["value"].toDouble(); QVector& valueVector = m_data[source]; if (valueVector.size() < 2) { valueVector.resize(2); } QString sneakySource = source; // we're interested in all source for a device which are // rblk and wblk. however, only 1 vis per that. so it wouldn't be unique and we'd only // get the values for rblk. // so add data to the hash, since we obtain the pair // on separate dataUpdated calls, so we'll need to map them if (sneakySource.endsWith("rblk")) { valueVector[0] = value; } else if (sneakySource.endsWith("wblk")) { valueVector[1] = value; //make the source *appear* to be a rblk. sneakySource.remove("wblk"); sneakySource.append("rblk"); } // we look up the visualization that is /...disk/rblk only. there is // not a rblk vis, so one just holds all data. SM::Plotter *plotter = qobject_cast(visualization(sneakySource)); // plotter is invalid if we're e.g. switching monitored stuff if (!plotter) { return; } //only graph it if it's got both rblk and wblk if (valueVector.count() == 2) { QString read = KGlobal::locale()->formatNumber(valueVector.at(0), 1); QString write = KGlobal::locale()->formatNumber(valueVector.at(1), 1); //FIXME: allow plotter->addSample overload for QVector. plotter->addSample(valueVector.toList()); if (mode() == SM::Applet::Panel) { const QString tooltip = QString("%1 rio: %2%wio: %3") .arg(plotter->title()).arg(read).arg(write); setToolTip(source, tooltip); } } } void Hdd_Activity::createConfigurationInterface(KConfigDialog *parent) { QWidget *widget = new QWidget(); ui.setupUi(widget); m_hddModel.clear(); m_hddModel.setHorizontalHeaderLabels(QStringList() << i18n("Name")); QStandardItem *parentItem = m_hddModel.invisibleRootItem(); foreach (const QString& hdd, m_possibleHdds) { if (m_regexp.indexIn(hdd) != -1) { if (hdd.endsWith("rblk")) { // so the user only sees 1 device (which includes both rblk/wblk QString trimmedHdd = hdd; trimmedHdd.remove("rblk"); QStandardItem *item1 = new QStandardItem(trimmedHdd); item1->setEditable(false); item1->setCheckable(true); // store the pair of real sources (rblk, wblk) into data QStringList realSources; realSources.append(trimmedHdd + "rblk"); realSources.append(trimmedHdd + "wblk"); item1->setData(realSources); // but the behind the scenes still uses the source separation, // so be sure to use that. if (sources().contains(hdd)) { item1->setCheckState(Qt::Checked); } parentItem->appendRow(QList() << item1); } } } ui.treeView->setModel(&m_hddModel); ui.treeView->resizeColumnToContents(0); ui.intervalSpinBox->setValue(interval() / 1000.0); ui.intervalSpinBox->setSuffix(i18nc("second", " s")); parent->addPage(widget, i18n("Hard Disks"), "drive-harddisk"); connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted())); connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted())); connect(ui.treeView, SIGNAL(clicked(QModelIndex)), parent, SLOT(settingsModified())); connect(ui.intervalSpinBox, SIGNAL(valueChanged(QString)), parent, SLOT(settingsModified())); } void Hdd_Activity::configChanged() { //kDebug() << "#### configChanged m_hdds:" << m_hdds; KConfigGroup cg = config(); QStringList default_hdds = m_possibleHdds; // default to 2 seconds (2000 ms interval setInterval(cg.readEntry("interval", 2.0) * 1000.0); setSources(cg.readEntry("hdds", default_hdds)); connectToEngine(); } void Hdd_Activity::configAccepted() { KConfigGroup cg = config(); QStandardItem *parentItem = m_hddModel.invisibleRootItem(); clear(); for (int i = 0; i < parentItem->rowCount(); ++i) { QStandardItem *item = parentItem->child(i, 0); if (item) { if (item->checkState() == Qt::Checked) { QStringList actualSources = item->data().toStringList(); const QString& rblk = actualSources.at(0); const QString& wblk = actualSources.at(1); appendSource(rblk); appendSource(wblk); } } } cg.writeEntry("hdds", sources()); uint interval = ui.intervalSpinBox->value(); cg.writeEntry("interval", interval); emit configNeedsSaving(); } bool Hdd_Activity::addVisualization(const QString& source) { QStringList splits = source.split('/'); // 0 == "disk" 1 == "sde_(8:64)" 2 == "Rate" 3 == "rblk" Q_ASSERT(splits.count() == 4); // only monitor rblk for each device. we watch all sources/connect to them // but only need 1 vis per actual device (otherwise there'd be 1 for rblk, 1 // for wblk, which isn't what I have in mind -sreich if (splits.at(3) == "rblk") { QString hdd = source; hdd = hdd.remove("rblk"); //kDebug() << "#### ADD VIS hdd: " << hdd; SM::Plotter *plotter = new SM::Plotter(this); plotter->setTitle(hdd); // FIXME: localize properly..including units. // ksysguard just gives us 1024 KiB for sources, we need to convert for anything else. plotter->setUnit("KiB/s"); // should be read, write, respectively plotter->setCustomPlots(QList() << QColor("#0057AE") << QColor("#E20800")); appendVisualization(source, plotter); setPreferredItemHeight(80); } // if we return false here, it thinks we don't want // source updates for this source // which makes sense (since there's no vis). but not in // this case. return true; } #include "moc_hdd_activity.cpp"