Index: kdevplatform/project/CMakeLists.txt =================================================================== --- kdevplatform/project/CMakeLists.txt +++ kdevplatform/project/CMakeLists.txt @@ -15,6 +15,7 @@ abstractfilemanagerplugin.cpp filemanagerlistjob.cpp projectfiltermanager.cpp + projectwatcher.cpp interfaces/iprojectbuilder.cpp interfaces/iprojectfilemanager.cpp interfaces/ibuildsystemmanager.cpp Index: kdevplatform/project/abstractfilemanagerplugin.h =================================================================== --- kdevplatform/project/abstractfilemanagerplugin.h +++ kdevplatform/project/abstractfilemanagerplugin.h @@ -34,6 +34,7 @@ namespace KDevelop { class AbstractFileManagerPluginPrivate; +class AFMPBenchmark; /** * This class can be used as a common base for file managers. @@ -118,6 +119,8 @@ private: const QScopedPointer d; friend class AbstractFileManagerPluginPrivate; +public: + friend class AFMPBenchmark; }; } Index: kdevplatform/project/abstractfilemanagerplugin.cpp =================================================================== --- kdevplatform/project/abstractfilemanagerplugin.cpp +++ kdevplatform/project/abstractfilemanagerplugin.cpp @@ -41,6 +41,7 @@ #include #include "projectfiltermanager.h" +#include "projectwatcher.h" #include "debug.h" #define ifDebug(x) @@ -103,7 +104,7 @@ void removeFolder(ProjectFolderItem* folder); - QHash m_watchers; + QHash m_watchers; QHash > m_projectJobs; QVector m_stoppedFolders; ProjectFilterManager m_filters; @@ -271,7 +272,7 @@ const IndexedString indexedPath(path.pathOrUrl()); const IndexedString indexedParent(path.parent().pathOrUrl()); - QHashIterator it(m_watchers); + QHashIterator it(m_watchers); while (it.hasNext()) { const auto p = it.next().key(); if ( !p->projectItem()->model() ) { @@ -334,7 +335,7 @@ const Path path(QUrl::fromLocalFile(path_)); const IndexedString indexed(path.pathOrUrl()); - QHashIterator it(m_watchers); + QHashIterator it(m_watchers); while (it.hasNext()) { const auto p = it.next().key(); if (path == p->path()) { @@ -491,14 +492,15 @@ ///TODO: check if this works for remote files when something gets changed through another KDE app if ( project->path().isLocalFile() ) { - d->m_watchers[project] = new KDirWatch( project ); + auto watcher = new ProjectWatcher(project, &d->m_filters); - connect(d->m_watchers[project], &KDirWatch::created, + // set up the signal handling + connect(watcher, &KDirWatch::created, this, [&] (const QString& path_) { d->created(path_); }); - connect(d->m_watchers[project], &KDirWatch::deleted, + connect(watcher, &KDirWatch::deleted, this, [&] (const QString& path_) { d->deleted(path_); }); - - d->m_watchers[project]->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); + watcher->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); + d->m_watchers[project] = watcher; } d->m_filters.add(project); Index: kdevplatform/project/projectwatcher.h =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * This file is part of KDevelop * + * Copyright 2017 René Bertin * + * * + * This program 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 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. * + ***************************************************************************/ + +#ifndef KDEVPLATFORM_PROJECTWATCHER_H +#define KDEVPLATFORM_PROJECTWATCHER_H + +#include "projectexport.h" + +#include + +namespace KDevelop { + +class IProject; +class ProjectFilterManager; + +class KDEVPLATFORMPROJECT_EXPORT ProjectWatcher : public KDirWatch +{ + Q_OBJECT +public: + /** + * Create a dirwatcher for @p project based on KDirWatch and + * enhanced with support for the project @p filter. + */ + explicit ProjectWatcher(IProject* project, ProjectFilterManager* filter); + + /** + * Add directory @p path to the project dirwatcher if it is not + * rejected by the project filter. + */ + void addDir(const QString& path, WatchModes watchModes = WatchDirOnly); + void removeDir(const QString& path); + + /** + * return the current number of directories being watched. + */ + int size() const; + +private: + IProject* m_project; + ProjectFilterManager* m_filter; + int m_watchedCount; +}; + +} +#endif //KDEVPLATFORM_PROJECTWATCHER_H Index: kdevplatform/project/projectwatcher.cpp =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.cpp @@ -0,0 +1,57 @@ +/*************************************************************************** + * This file is part of KDevelop * + * Copyright 2017 René Bertin * + * * + * This program 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 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 "projectwatcher.h" +#include "iproject.h" +#include "projectfiltermanager.h" +#include "path.h" + +#include + +using namespace KDevelop; + +KDevelop::ProjectWatcher::ProjectWatcher(IProject* project, ProjectFilterManager* filter) + : KDirWatch(project) + , m_project(project) + , m_filter(filter) + , m_watchedCount(0) +{} + +void KDevelop::ProjectWatcher::addDir(const QString& path, WatchModes watchModes) +{ + if (m_filter->isValid(Path(path), true, m_project) && !contains(path)) { + KDirWatch::addDir(path, watchModes); + m_watchedCount += 1; + } +} + +void KDevelop::ProjectWatcher::removeDir(const QString& path) +{ + if (contains(path)) { + KDirWatch::removeDir(path); + m_watchedCount -= 1; + } +} + +int KDevelop::ProjectWatcher::size() const +{ + return m_watchedCount; +} + Index: kdevplatform/project/tests/CMakeLists.txt =================================================================== --- kdevplatform/project/tests/CMakeLists.txt +++ kdevplatform/project/tests/CMakeLists.txt @@ -21,3 +21,12 @@ KDev::Tests Qt5::QuickWidgets ) + +add_executable(abstractfilemanagerpluginimportbenchmark + abstractfilemanagerpluginimportbenchmark.cpp +) +ecm_mark_nongui_executable(abstractfilemanagerpluginimportbenchmark) +target_link_libraries(abstractfilemanagerpluginimportbenchmark + KDev::Project + KDev::Tests +) Index: kdevplatform/project/tests/abstractfilemanagerpluginimportbenchmark.cpp =================================================================== --- /dev/null +++ kdevplatform/project/tests/abstractfilemanagerpluginimportbenchmark.cpp @@ -0,0 +1,160 @@ +/* This file is part of KDevelop + Copyright 2017 René J.V. Bertin + + 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 +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +namespace KDevelop { + +class AFMPBenchmark : public QObject +{ + Q_OBJECT +public: + AFMPBenchmark(AbstractFileManagerPlugin* manager, const QString& path, QObject* parent) + : QObject(parent) + { + m_manager = manager; + m_project = new TestProject(Path(path)); + } + + void start() + { + m_projectNumber = s_numBenchmarksRunning++; + qInfo() << "Starting import of project" << m_project->path(); + m_timer.start(); + auto root = m_manager->import(m_project); + int elapsed = m_timer.elapsed(); + qInfo() << "\tcreating dirwatcher took" + << elapsed / 1000.0 << "seconds"; + auto import = m_manager->createImportJob(root); + QObject::connect(import, &KJob::finished, this, &AFMPBenchmark::projectImportDone); + m_timer.restart(); + import->start(); + } + + AbstractFileManagerPlugin* m_manager; + TestProject* m_project; + QElapsedTimer m_timer; + int m_projectNumber; + + static int s_numBenchmarksRunning; + +Q_SIGNALS: + void finished(); + +private Q_SLOTS: + void projectImportDone(KJob* job) + { + Q_UNUSED(job); + int elapsed = m_timer.elapsed(); + // The number of items in the dirwatcher can be obtained as follows, + // provided dirs are added one by one and not with a recursive call + // to ProjectWatcher::addDir(): + ProjectWatcher* watcher = qobject_cast(m_manager->projectWatcher(m_project)); + int watched = watcher ? watcher->size() : -1; + qInfo() << "imported project" << m_projectNumber << "with" + << m_project->fileSet().size() << "elements (files & folders) and" + << watched << "calls to KDirWatch::addDir() took" + << elapsed / 1000.0 << "seconds"; + + s_numBenchmarksRunning -= 1; + if (s_numBenchmarksRunning <= 0) { + emit finished(); + } + } + +}; + +int AFMPBenchmark::s_numBenchmarksRunning = 0; +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + qWarning() << "Usage:" << argv[0] << "projectDir1 [...projectDirN]"; + return 1; + } + QCoreApplication app(argc, argv); + // measure the total test time, this provides an indication + // of overhead and how well multiple projects are imported in parallel + // (= how different is the total time from the import time of the largest + // project). When testing a single project the difference between this + // value and total runtime will provide an estimate of the time required + // to destroy the dirwatcher. + QElapsedTimer runTimer; + runTimer.start(); + + AutoTestShell::init({"no plugins"}); + auto core = TestCore::initialize(Core::NoUi); + auto manager = new AbstractFileManagerPlugin({}, core); + + const char *kdwMethod[] = {"FAM", "Inotify", "Stat", "QFSWatch"}; + qInfo() << "KDirWatch backend:" << kdwMethod[KDirWatch().internalMethod()]; + + QList benchmarks; + + for (int i = 1 ; i < argc ; ++i) { + const QString path = QString::fromUtf8(argv[i]); + if (QFileInfo(path).isDir()) { + const auto benchmark = new AFMPBenchmark(manager, path, core); + benchmarks << benchmark; + QObject::connect(benchmark, &AFMPBenchmark::finished, + &app, [&runTimer] { + qInfo() << "Done in" << runTimer.elapsed() / 1000.0 + << "seconds total (excluding dirwatcher destruction)"; + QCoreApplication::instance()->quit(); + }); + } + } + + if (benchmarks.isEmpty()) { + qWarning() << "no projects to import (arguments must be directories)"; + return 1; + } + + qInfo() << "Benchmark set-up took" << runTimer.elapsed() / 1000.0 << "seconds"; + for (auto benchmark : benchmarks) { + benchmark->start(); + } + + return app.exec(); +} + +#include "abstractfilemanagerpluginimportbenchmark.moc" + +