/* ksim - a system monitor for kde * * Copyright (C) 2001 Robbie Ward * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 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 "ksimdisk.h" #include "ksimdisk.moc" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__DragonFly__) #include #include #include #include #include #elif defined(Q_OS_FREEBSD) #include #if __FreeBSD_version < 500101 #include #else #include #endif #include #include #endif #ifdef Q_OS_LINUX #include #endif #include #define DISK_SPEED 1000 KSIM_INIT_PLUGIN(DiskPlugin) DiskPlugin::DiskPlugin(const char *name) : KSim::PluginObject(name) { setConfigFileName(instanceName()); } DiskPlugin::~DiskPlugin() { } KSim::PluginView *DiskPlugin::createView(const char *className) { return new DiskView(this, className); } KSim::PluginPage *DiskPlugin::createConfigPage(const char *className) { return new DiskConfig(this, className); } void DiskPlugin::showAbout() { TQString version = kapp->aboutData()->version(); KAboutData aboutData(instanceName(), I18N_NOOP("KSim Disk Plugin"), version.latin1(), I18N_NOOP("A disk monitor plugin for KSim"), KAboutData::License_GPL, "(C) 2001 Robbie Ward"); aboutData.addAuthor("Robbie Ward", I18N_NOOP("Author"), "linuxphreak@gmx.co.uk"); KAboutApplication(&aboutData).exec(); } DiskView::DiskView(KSim::PluginObject *parent, const char *name) : KSim::PluginView(parent, name) { #ifdef Q_OS_LINUX m_bLinux24 = true; m_procStream = 0L; if ((m_procFile = fopen("/proc/stat", "r"))) m_procStream = new TQTextStream(m_procFile, IO_ReadOnly); #endif config()->setGroup("DiskPlugin"); m_list = config()->readListEntry("Disks", TQStringList() << "complete"); m_useSeperatly = config()->readBoolEntry("UseSeperatly", true); m_firstTime = 1; m_addAll = false; m_layout = new TQVBoxLayout(this); TQSpacerItem *item = new TQSpacerItem(0, 0, TQSizePolicy::Expanding, TQSizePolicy::Expanding); m_layout->addItem(item); init(); m_timer = new TQTimer(this); connect(m_timer, TQT_SIGNAL(timeout()), TQT_SLOT(updateDisplay())); m_timer->start(DISK_SPEED); updateDisplay(); } DiskView::~DiskView() { #ifdef Q_OS_LINUX delete m_procStream; if (m_procFile) fclose(m_procFile); #endif } void DiskView::reparseConfig() { config()->setGroup("DiskPlugin"); TQStringList list = config()->readListEntry("Disks", TQStringList() << "complete"); m_useSeperatly = config()->readBoolEntry("UseSeperatly", true); if (list != m_list) { m_list = list; m_timer->stop(); cleanup(); TQPtrListIterator it(m_diskList); for (; it.current(); ++it) { delete it.current()->first; delete it.current()->second; } m_diskList.clear(); init(); m_timer->start(DISK_SPEED); updateDisplay(); } } DiskView::DiskData DiskView::findDiskData(const DiskList& diskList, TQString diskName) { if (diskName == "complete") diskName = i18n("All Disks"); DiskView::DiskList::ConstIterator disk; for (disk = diskList.begin(); disk != diskList.end(); ++disk) if ((*disk).name == diskName) return *disk; // Not found DiskView::DiskData dummy; dummy.name = "["+diskName+"]"; return dummy; } // Kind of messy code, dont ya think? void DiskView::updateDisplay() { DiskList diskList; updateData(diskList); if (m_addAll) { DiskData all; all.name = i18n("All Disks"); for (DiskList::ConstIterator disk = diskList.begin(); disk != diskList.end(); ++disk) { all += (*disk); } diskList.prepend(all); } // merge all the disks into one TQPtrListIterator it(m_diskList); for (int i = 0; it.current(); ++it, ++i) { DiskData diskData = findDiskData(diskList, m_list[i]); m_data[i].second = m_data[i].first; m_data[i].first = diskData; diskData -= m_data[i].second; unsigned long diff = diskData.readBlocks + diskData.writeBlocks; if (m_firstTime) diff = diskData.readBlocks = diskData.writeBlocks = 0; if (m_useSeperatly) { it.current()->first->setValue(diskData.readBlocks, diskData.writeBlocks); it.current()->first->setText(i18n("in: %1k") .tqarg(KGlobal::locale()->formatNumber((float)diskData.readBlocks / 1024.0, 1)), i18n("out: %1k").tqarg(KGlobal::locale()->formatNumber((float)diskData.writeBlocks / 1024.0, 1))); } else { it.current()->first->setValue(diff, 0); it.current()->first->setText(i18n("%1k") .tqarg(KGlobal::locale()->formatNumber((float)diff / 1024.0, 1))); } it.current()->second->setMaxValue(it.current()->first->maxValue()); it.current()->second->setText(diskData.name); it.current()->second->setValue(diff); } m_firstTime = 0; } void DiskView::updateData(DiskList &disks) { #ifdef Q_OS_LINUX if (!m_procStream) return; m_procStream->tqdevice()->reset(); fseek(m_procFile, 0L, SEEK_SET); if (m_bLinux24) { // here we need a regexp to match something like: // (3,0):(108911,48080,1713380,60831,1102644) TQRegExp regexp("\\([0-9]+,[0-9]+\\):\\([0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+\\)"); TQString content = m_procStream->read(); if (content.find("disk_io") == -1) { m_bLinux24 = false; delete m_procStream; m_procStream = 0; fclose(m_procFile); if ((m_procFile = fopen("/proc/diskstats", "r"))) m_procStream = new TQTextStream(m_procFile, IO_ReadOnly); updateData(disks); return; } int idx = 0; while ((idx = regexp.search(content, idx)) != -1) { idx += regexp.matchedLength(); TQString diskStr = regexp.cap(0); diskStr.replace(':', ','); diskStr.replace(TQRegExp("\\)?\\(?"), TQString()); TQStringList list = TQStringList::split(',', diskStr); if (list.count() < 7) continue; DiskData diskData; diskData.major = list[0].toInt(); diskData.minor = list[1].toInt(); diskData.name = diskName( diskData.major, diskData.minor ); diskData.total = list[2].toULong(); diskData.readIO = list[3].toULong(); diskData.readBlocks = list[4].toULong(); diskData.writeIO = list[5].toULong(); diskData.writeBlocks = list[6].toULong(); disks.append(diskData); } } else { // 3 0 hda 564142 160009 14123957 12369403 1052983 2801992 30905928 78981451 0 4531584 91518334 // The 11 fields after the device name are defined as follows: // Field 1 -- # of reads issued // This is the total number of reads completed successfully. // Field 2 -- # of reads merged, // Reads and writes which are adjacent to each other may be merged for // efficiency. Thus two 4K reads may become one 8K read before it is // ultimately handed to the disk, and so it will be counted (and queued) // as only one I/O. This field lets you know how often this was done. // Field 3 -- # of sectors read // This is the total number of sectors read successfully. // Field 4 -- # of milliseconds spent reading // This is the total number of milliseconds spent by all reads (as // measured from __make_request() to end_that_request_last()). // Field 5 -- # of writes completed // This is the total number of writes completed successfully. // Field 6 -- # of writes merged // See field 2 // Field 7 -- # of sectors written // This is the total number of sectors written successfully. // Field 8 -- # of milliseconds spent writing // This is the total number of milliseconds spent by all writes (as // measured from __make_request() to end_that_request_last()). // Field 9 -- # of I/Os currently in progress // The only field that should go to zero. Incremented as requests are // given to appropriate request_queue_t and decremented as they finish. // Field 10 -- # of milliseconds spent doing I/Os // This field is increases so long as field 9 is nonzero. // Field 11 -- weighted # of milliseconds spent doing I/Os // This field is incremented at each I/O start, I/O completion, I/O // merge, or read of these stats by the number of I/Os in progress // (field 9) times the number of milliseconds spent doing I/O since the // last update of this field. This can provide an easy measure of both // I/O completion time and the backlog that may be accumulating. TQString content = m_procStream->read(); TQStringList lines = TQStringList::split('\n', content); for(TQStringList::ConstIterator it = lines.begin(); it != lines.end(); ++it) { TQString diskStr = (*it).simplifyWhiteSpace(); TQStringList list = TQStringList::split(' ', diskStr); if (list.count() < 14) continue; DiskData diskData; diskData.major = list[0].toInt(); diskData.minor = list[1].toInt(); diskData.name = list[2]; diskData.readIO = 0; diskData.readBlocks = list[3+2].toULong(); diskData.writeIO = 0; diskData.writeBlocks = list[7+2].toULong(); diskData.total = diskData.readBlocks + diskData.writeBlocks; disks.append(diskData); } } #endif #ifdef Q_OS_FREEBSD #if defined(__DragonFly__) || __FreeBSD_version < 500107 #define devstat_getdevs(fd, stats) getdevs(stats) #define devstat_selectdevs selectdevs #define bytes_read(dev) (dev).bytes_read #define bytes_written(dev) (dev).bytes_written #else #define bytes_read(dev) (dev).bytes[DEVSTAT_READ] #define bytes_written(dev) (dev).bytes[DEVSTAT_WRITE] #endif statinfo diskStats; bzero(&diskStats, sizeof(diskStats)); diskStats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); bzero(diskStats.dinfo, sizeof(struct devinfo)); int deviceAmount; int selected; int selections; long generation; device_selection *deviceSelect = 0; if (devstat_getdevs(NULL, &diskStats) < 0) return; deviceAmount = diskStats.dinfo->numdevs; if (devstat_selectdevs(&deviceSelect, &selected, &selections, &generation, diskStats.dinfo->generation, diskStats.dinfo->devices, deviceAmount, 0, 0, 0, 0, DS_SELECT_ONLY, 10, 1) < 0) return; unsigned long readBlocks = 0, writeBlocks = 0, blockSize; for (int i = 0; i < deviceAmount; ++i) { int disk; devstat device; disk = deviceSelect[i].position; device = diskStats.dinfo->devices[disk]; blockSize = (device.block_size <= 0 ? 512 : device.block_size); readBlocks = bytes_read(device) / blockSize; writeBlocks = bytes_written(device) / blockSize; DiskData diskData; diskData.name = device.device_name + TQString::number(device.unit_number); diskData.major = device.device_number; diskData.minor = 0; diskData.total = readBlocks + writeBlocks; diskData.readIO = 0; diskData.readBlocks = readBlocks; diskData.writeIO = 0; diskData.writeBlocks = writeBlocks; disks.append(diskData); } free(deviceSelect); free(diskStats.dinfo); #endif } TQString DiskView::diskName( int major, int minor ) const { #ifdef Q_OS_LINUX TQString returnValue; switch ( major ) { case IDE0_MAJOR: returnValue.prepend(TQString::fromLatin1("hda")); break; case IDE1_MAJOR: returnValue.prepend(TQString::fromLatin1("hdc")); break; case IDE3_MAJOR: returnValue.prepend(TQString::fromLatin1("hde")); break; case SCSI_DISK0_MAJOR: returnValue.prepend(TQString::fromLatin1("sda")); break; case SCSI_GENERIC_MAJOR: returnValue.prepend(TQString::fromLatin1("sg0")); break; } returnValue.tqat(2) = returnValue.tqat(2).latin1() + minor; return returnValue; #else Q_UNUSED(major); Q_UNUSED(minor); return 0; #endif } DiskView::DiskPair *DiskView::addDisk() { KSim::Chart *chart = new KSim::Chart(false, 0, this); chart->show(); m_layout->addWidget(chart); KSim::Progress *progress = new KSim::Progress(0, KSim::Types::None, KSim::Progress::Panel, this); progress->show(); m_layout->addWidget(progress); return new DiskPair(chart, progress); } void DiskView::init() { m_data.resize(m_list.size()); TQStringList::ConstIterator it; for (it = m_list.begin(); it != m_list.end(); ++it) { if ((*it) == "complete") m_addAll = true; m_diskList.append(addDisk()); } } void DiskView::cleanup() { m_data.clear(); m_addAll = false; } DiskConfig::DiskConfig(KSim::PluginObject *parent, const char *name) : KSim::PluginPage(parent, name) { m_layout = new TQVBoxLayout(this); m_layout->setSpacing(6); m_listview = new KListView(this); m_listview->addColumn(i18n("Disks")); m_layout->addWidget(m_listview); TQHBoxLayout *tqlayout = new TQHBoxLayout; tqlayout->setSpacing(6); TQSpacerItem *spacer = new TQSpacerItem(20, 20, TQSizePolicy::Expanding, TQSizePolicy::Minimum); tqlayout->addItem(spacer); m_add = new TQPushButton(this); m_add->setText(i18n("Add...")); connect(m_add, TQT_SIGNAL(clicked()), TQT_SLOT(addItem())); tqlayout->addWidget(m_add); m_remove = new TQPushButton(this); m_remove->setText(i18n("Remove")); connect(m_remove, TQT_SIGNAL(clicked()), TQT_SLOT(removeItem())); tqlayout->addWidget(m_remove); m_layout->addLayout(tqlayout); m_buttonBox = new TQVButtonGroup(i18n("Disk Styles"), this); m_layout->addWidget(m_buttonBox); m_totalButton = new TQRadioButton(m_buttonBox); m_totalButton->setText(i18n("Display the read and write data as one")); m_bothButton = new TQRadioButton(m_buttonBox); m_bothButton->setText(i18n("Display the read and write data" "\nseparately as in/out data")); TQSpacerItem *vSpacer = new TQSpacerItem(20, 20, TQSizePolicy::Minimum, TQSizePolicy::Expanding); m_layout->addItem(vSpacer); } DiskConfig::~DiskConfig() { } void DiskConfig::readConfig() { config()->setGroup("DiskPlugin"); m_buttonBox->setButton(config()->readBoolEntry("UseSeperatly", true)); TQStringList list = config()->readListEntry("Disks"); TQStringList::ConstIterator it; for (it = list.begin(); it != list.end(); ++it) { TQString text = ((*it) == "complete" ? i18n("All Disks") : (*it)); if (!m_listview->findItem(text, 0)) new TQListViewItem(m_listview, text); } } void DiskConfig::saveConfig() { TQStringList list; for (TQListViewItemIterator it(m_listview); it.current(); ++it) { if (it.current()->text(0) == i18n("All Disks")) list.append("complete"); else list.append(it.current()->text(0)); } config()->setGroup("DiskPlugin"); config()->writeEntry("UseSeperatly", (bool)m_buttonBox->id(m_buttonBox->selected())); config()->writeEntry("Disks", list); } void DiskConfig::addItem() { bool ok = false; TQString text = KInputDialog::getText(i18n("Add Disk Device"), i18n("Disk name:"), TQString(), &ok, this); if (text.startsWith("/dev/")) text = text.mid(5); if (ok) new TQListViewItem(m_listview, text); } void DiskConfig::removeItem() { if (!m_listview->selectedItem()) return; TQListViewItem *item = m_listview->selectedItem(); delete item; }