diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,8 @@ add_subdirectory( tests ) endif() +add_subdirectory( processplugins) + install(DIRECTORY scripts/ DESTINATION ${KDE_INSTALL_DATADIR}/ksysguard/scripts) set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/KF5SysGuard) diff --git a/processplugins/CMakeLists.txt b/processplugins/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/processplugins/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(xres) diff --git a/processplugins/xres/CMakeLists.txt b/processplugins/xres/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/processplugins/xres/CMakeLists.txt @@ -0,0 +1,29 @@ +find_package(X11) +set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" + URL "http://www.x.org" + TYPE OPTIONAL + PURPOSE "Required for building the X11 based workspace" + ) +if(X11_FOUND) + find_package(Qt5X11Extras REQUIRED) + find_library(X11_XRes_LIB XRes ${X11_LIB_SEARCH_PATH}) + find_path(X11_XRes_INCLUDE_PATH X11/extensions/XRes.h ${X11_INC_SEARCH_PATH}) + + if(X11_XRes_LIB AND X11_XRes_INCLUDE_PATH) + set(X11_XRes_FOUND TRUE) + endif() +endif() + +set(HAVE_X11 ${X11_FOUND}) +set(HAVE_XRES ${X11_XRes_FOUND}) + + + +if(X11_XRes_FOUND) + add_library(ksysguard_plugin_xres MODULE xres.cpp) + include_directories(${X11_XRes_INCLUDE_PATH}) + + target_link_libraries(ksysguard_plugin_xres Qt5::Core Qt5::DBus Qt5::Gui KF5::CoreAddons KF5::I18n Qt5::X11Extras KF5::WindowSystem ${X11_XRes_LIB} ${X11_LIBRARIES} KF5::ProcessCore) + + install(TARGETS ksysguard_plugin_xres DESTINATION ${KDE_INSTALL_PLUGINDIR}/ksysguard/process) +endif() diff --git a/processplugins/xres/xres.h b/processplugins/xres/xres.h new file mode 100644 --- /dev/null +++ b/processplugins/xres/xres.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + + Copyright (C) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +class XResPlugin : public KSysGuard::ProcessDataProvider +{ + Q_OBJECT +public: + XResPlugin(QObject *parent, const QVariantList &args); + void update() override; +private: + bool updateXResClientData(); + KSysGuard::ProcessAttribute *m_xresAttribute = nullptr; + bool mIsX11 = false; + QMap mXResClientResources; +}; diff --git a/processplugins/xres/xres.cpp b/processplugins/xres/xres.cpp new file mode 100644 --- /dev/null +++ b/processplugins/xres/xres.cpp @@ -0,0 +1,123 @@ +/* This file is part of the KDE project + + Copyright (C) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#include "xres.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using namespace KSysGuard; + +XResPlugin::XResPlugin(QObject *parent, const QVariantList &args) + : ProcessDataProvider(parent, args) +{ + if (!QGuiApplication::instance()) { + return; + } + if (!QX11Info::QX11Info::isPlatformX11()) { + return; + } + mIsX11 = true; + m_xresAttribute = new ProcessAttribute(QStringLiteral("xres"), i18n("X11 Memory"), this); + m_xresAttribute->setUnit(KSysGuard::UnitByte); + addProcessAttribute(m_xresAttribute); +} + +bool XResPlugin::updateXResClientData() { + if (!mIsX11) { + return false; + } + XResClient *clients; + int count; + + XResQueryClients(QX11Info::display(), &count, &clients); + + mXResClientResources.clear(); + for (int i=0; i < count; i++) + mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); + + if(clients) + XFree(clients); + return true; +} + +void XResPlugin::update() { + if (!mIsX11) { + return; + } + updateXResClientData(); + Window *children, dummy; + unsigned int count; + Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); + if(!result) + return; + if(!updateXResClientData()) + return; + for (uint i=0; i < count; ++i) { + WId wid = children[i]; + QMap::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); + if(iter == mXResClientResources.end()) + continue; //We couldn't find it this time :-/ + + if(-iter.key() != (qlonglong)(wid & ~iter.value())) + continue; //Already added this window + + //Get the PID for this window if we do not know it + NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()); + + qlonglong pid = info.pid(); + if(!pid) + continue; + //We found a window with this client + mXResClientResources.erase(iter); + + KSysGuard::Process *process = getProcess(pid); + if(!process) { + continue; //can in race condition etc + } + + //Now update the pixmap bytes for this window + unsigned long memUsage; + bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &memUsage); + + if(!success) { + m_xresAttribute->clearData(process); + } else { + qulonglong memUsageCast = memUsage; + m_xresAttribute->setData(process, memUsageCast); + } + + } + if(children) + XFree((char*)children); +} + +K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "xres.json", registerPlugin();) + +#include "xres.moc" diff --git a/processplugins/xres/xres.json b/processplugins/xres/xres.json new file mode 100644 --- /dev/null +++ b/processplugins/xres/xres.json @@ -0,0 +1,5 @@ +{ + "KPlugin": { + "Description": "Per-process X resource usage" + } +} diff --git a/processui/ProcessModel.h b/processui/ProcessModel.h --- a/processui/ProcessModel.h +++ b/processui/ProcessModel.h @@ -154,7 +154,7 @@ HeadingStartTime, HeadingNoNewPrivileges, HeadingCommand, - HeadingXMemory, + HeadingXMemory, ///@Deprecated. See plugins HeadingXTitle, HeadingCGroup, HeadingMACContext diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -58,10 +58,6 @@ #include #endif -#ifdef HAVE_XRES -#include -#endif - extern QApplication* Qapp; static QString formatByteSize(qlonglong amountInKB, int units) { @@ -122,9 +118,6 @@ mShowingTooltips = true; mNormalizeCPUUsage = true; mIoInformation = ProcessModel::ActualBytes; -#ifdef HAVE_XRES - mHaveXRes = false; -#endif mHaveTimer = false, mTimerId = -1, mMovingRow = false; @@ -150,12 +143,6 @@ : QAbstractItemModel(parent), d(new ProcessModelPrivate) { d->q=this; -#ifdef HAVE_XRES - if (d->mIsX11) { - int event, error, major, minor; - d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor); - } -#endif if(host.isEmpty() || host == QLatin1String("localhost")) { d->mHostName = QString(); @@ -267,8 +254,6 @@ } case HeadingNoNewPrivileges: return processLeft->noNewPrivileges() > processRight->noNewPrivileges(); - case HeadingXMemory: - return processLeft->pixmapBytes() > processRight->pixmapBytes(); case HeadingVmSize: return processLeft->vmSize() > processRight->vmSize(); case HeadingSharedMemory: { @@ -414,77 +399,6 @@ } #endif -#ifdef HAVE_XRES -bool ProcessModelPrivate::updateXResClientData() { - if (!mIsX11) { - return false; - } - XResClient *clients; - int count; - - XResQueryClients(QX11Info::display(), &count, &clients); - - mXResClientResources.clear(); - for (int i=0; i < count; i++) - mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); - - if(clients) - XFree(clients); - return true; -} - -void ProcessModelPrivate::queryForAndUpdateAllXWindows() { - if (!mIsX11) { - return; - } - updateXResClientData(); - Window *children, dummy; - unsigned int count; - Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); - if(!result) - return; - if(!updateXResClientData()) - return; - for (uint i=0; i < count; ++i) { - WId wid = children[i]; - QMap::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); - if(iter == mXResClientResources.end()) - continue; //We couldn't find it this time :-/ - - if(-iter.key() != (qlonglong)(wid & ~iter.value())) - continue; //Already added this window - - //Get the PID for this window if we do not know it - NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()); - - qlonglong pid = info.pid(); - if(!pid) - continue; - //We found a window with this client - mXResClientResources.erase(iter); - KSysGuard::Process *process = mProcesses->getProcess(pid); - if(!process) return; //shouldn't really happen.. maybe race condition etc - unsigned long previousPixmapBytes = process->pixmapBytes(); - //Now update the pixmap bytes for this window - bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes()); - if(!success) - process->pixmapBytes() = 0; - - if(previousPixmapBytes != process->pixmapBytes()) { - int row; - if(mSimple) - row = process->index(); - else - row = process->parent()->children().indexOf(process); - QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process); - emit q->dataChanged(index, index); - } - } - if(children) - XFree((char*)children); -} -#endif - void ProcessModelPrivate::setupProcesses() { if(mProcesses) { #ifdef Q_WS_X11_DISABLE @@ -607,12 +521,6 @@ if(d->mMemTotal <= 0) d->mMemTotal = d->mProcesses->totalPhysicalMemory(); } - -#ifdef HAVE_XRES - //Add all the rest of the windows - if(d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory)) - d->queryForAndUpdateAllXWindows(); -#endif } QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const @@ -996,7 +904,6 @@ case HeadingPid: case HeadingTty: case HeadingMemory: - case HeadingXMemory: case HeadingSharedMemory: case HeadingStartTime: case HeadingNoNewPrivileges: @@ -1050,8 +957,6 @@ return i18n("Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc."); case HeadingCommand: return i18n("The command with which this process was launched."); - case HeadingXMemory: - return i18n("The amount of pixmap memory that this process is using."); case HeadingXTitle: return i18n("The title of any windows that this process is showing."); case HeadingPid: @@ -1099,8 +1004,6 @@ return i18n("Technical information: The flag is retrieved from /proc/[pid]/status"); case HeadingCommand: return i18n("Technical information: This is from /proc/*/cmdline"); - case HeadingXMemory: - return i18n("Technical information: This is the amount of memory used by the Xorg process for images for this process. This is memory used in addition to Memory and Shared Memory.
Technical information: This only counts the pixmap memory, and does not include resource memory used by fonts, cursors, glyphsets etc. See the xrestop program for a more detailed breakdown."); case HeadingXTitle: return i18n("Technical information: For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID. If a process' windows are not shown, then that application incorrectly is not setting _NET_WM_PID."); case HeadingPid: @@ -1457,8 +1360,6 @@ return QVariant(); } #if HAVE_X11 - case HeadingXMemory: - return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true); case HeadingXTitle: { if(!process->hasManagedGuiWindow()) @@ -1746,7 +1647,6 @@ case HeadingNoNewPrivileges: case HeadingPid: case HeadingMemory: - case HeadingXMemory: case HeadingSharedMemory: case HeadingVmSize: case HeadingIoWrite: @@ -1843,8 +1743,6 @@ } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through - case HeadingXMemory: - return (qulonglong)process->pixmapBytes(); #if HAVE_X11 case HeadingXTitle: { @@ -2037,7 +1935,6 @@ headings << i18nc("process heading", "Command"); #if HAVE_X11 if (d->mIsX11) { - headings << i18nc("process heading", "X11 Memory"); headings << i18nc("process heading", "Window Title"); } #endif @@ -2107,8 +2004,6 @@ row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingMemory, process); emit dataChanged(index, index); - index = createIndex(row, HeadingXMemory, process); - emit dataChanged(index, index); index = createIndex(row, HeadingSharedMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingVmSize, process); diff --git a/processui/ProcessModel_p.h b/processui/ProcessModel_p.h --- a/processui/ProcessModel_p.h +++ b/processui/ProcessModel_p.h @@ -141,10 +141,6 @@ void updateWindowInfo(WId wid, unsigned int properties, bool newWindow); QMultiHash< long long, WindowInfo *> mPidToWindowInfo; ///< Map a process pid to X window info if available QHash< WId, WindowInfo *> mWIdToWindowInfo; ///< Map an X window id to window info -#ifdef HAVE_XRES - bool updateXResClientData(); - void queryForAndUpdateAllXWindows(); -#endif #endif void timerEvent ( QTimerEvent * event ) override; ///< Call dataChanged() for all the processes in mPidsToUpdate /** @see setIsLocalhost */ @@ -210,11 +206,6 @@ QVector mExtraAttributes; -#ifdef HAVE_XRES - bool mHaveXRes; ///< True if the XRes extension is available at run time - QMap mXResClientResources; -#endif - bool mMovingRow; bool mRemovingRow; bool mInsertingRow;