Improve plugin caching

Summary:
Extend the plasmoid startup cache to DataEngines and ContaimentActions.
Include a cache for script engines. In practice it's always the same, we don't
need to check the file system every time.

Reviewers: #plasma, davidedmundson, mart

Reviewed By: #plasma, davidedmundson, mart

Subscribers: davidedmundson, mart, broulik, kde-frameworks-devel

Tags: #frameworks

Differential Revision: https://phabricator.kde.org/D22258
This commit is contained in:
Aleix Pol 2019-07-08 13:47:27 +02:00
parent 6d53dfad57
commit 21227e0f98
2 changed files with 107 additions and 90 deletions

View File

@ -69,15 +69,23 @@ public:
static QString s_servicesPluginDir;
static QString s_containmentActionsPluginDir;
// We only use this cache during start of the process to speed up many consecutive calls
// After that, we're too afraid to produce race conditions and it's not that time-critical anyway
// the 20 seconds here means that the cache is only used within 20sec during startup, after that,
// complexity goes up and we'd have to update the cache in order to avoid subtle bugs
// just not using the cache is way easier then, since it doesn't make *that* much of a difference,
// anyway
int maxCacheAge = 20;
qint64 pluginCacheAge = 0;
QHash<QString, QVector<KPluginMetaData>> pluginCache;
class Cache {
// We only use this cache during start of the process to speed up many consecutive calls
// After that, we're too afraid to produce race conditions and it's not that time-critical anyway
// the 20 seconds here means that the cache is only used within 20sec during startup, after that,
// complexity goes up and we'd have to update the cache in order to avoid subtle bugs
// just not using the cache is way easier then, since it doesn't make *that* much of a difference,
// anyway
int maxCacheAge = 20;
qint64 pluginCacheAge = 0;
QHash<QString, QVector<KPluginMetaData>> plugins;
public:
QVector<KPluginMetaData> findPluginsById(const QString& name, const QStringList &dirs);
};
Cache plasmoidCache;
Cache dataengineCache;
Cache containmentactionCache;
};
QSet<QString> PluginLoaderPrivate::s_customCategories;
@ -171,49 +179,8 @@ Applet *PluginLoader::loadApplet(const QString &name, uint appletId, const QVari
appletId = ++AppletPrivate::s_maxAppletId;
}
const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
bool useRuntimeCache = true;
if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
// cache is old and we're not within a few seconds of startup anymore
useRuntimeCache = false;
d->pluginCache.clear();
}
if (d->pluginCacheAge == 0) {
// Find all the plugins now, but only once
d->pluginCacheAge = now;
auto insertIntoCache = [this](const QString &pluginPath) {
KPluginMetaData metadata(pluginPath);
if (!metadata.isValid()) {
return;
}
d->pluginCache[metadata.pluginId()].append(metadata);
};
KPluginLoader::forEachPlugin(PluginLoaderPrivate::s_plasmoidsPluginDir, insertIntoCache);
// COMPAT CODE for applets installed into the toplevel plugins dir by mistake.
KPluginLoader::forEachPlugin(QString(), insertIntoCache);
}
//if name wasn't a path, pluginName == name
const QString pluginName = name.section(QLatin1Char('/'), -1);
QVector<KPluginMetaData> plugins;
if (useRuntimeCache) {
auto it = d->pluginCache.constFind(pluginName);
if (it != d->pluginCache.constEnd()) {
plugins = *it;
}
} else {
plugins = KPluginLoader::findPluginsById(PluginLoaderPrivate::s_plasmoidsPluginDir, pluginName);
// COMPAT CODE for applets installed into the toplevel plugins dir by mistake.
if (plugins.isEmpty()) {
plugins = KPluginLoader::findPluginsById(QString(), pluginName);
}
}
// Need to pass the empty directory because it's where plasmoids used to be
const auto plugins = d->plasmoidCache.findPluginsById(name, { PluginLoaderPrivate::s_plasmoidsPluginDir, {} });
const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), name);
@ -263,19 +230,12 @@ DataEngine *PluginLoader::loadDataEngine(const QString &name)
}
// Look for C++ plugins first
auto filter = [&name](const KPluginMetaData &md) -> bool
{
return md.pluginId() == name;
};
QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filter);
const QVector<KPluginMetaData> plugins = d->dataengineCache.findPluginsById(name, {PluginLoaderPrivate::s_dataEnginePluginDir});
if (!plugins.isEmpty()) {
KPluginLoader loader(plugins.first().fileName());
KPluginLoader loader(plugins.constFirst().fileName());
const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap();
KPluginFactory *factory = loader.factory();
if (factory) {
engine = factory->create<Plasma::DataEngine>(nullptr, argsWithMetaData);
}
return factory ? factory->create<Plasma::DataEngine>(nullptr, argsWithMetaData) : nullptr;
}
if (engine) {
return engine;
@ -406,13 +366,7 @@ ContainmentActions *PluginLoader::loadContainmentActions(Containment *parent, co
return actions;
}
// Look for C++ plugins first
auto filter = [&name](const KPluginMetaData &md) -> bool
{
return md.pluginId() == name;
};
QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_containmentActionsPluginDir, filter);
const QVector<KPluginMetaData> plugins = d->containmentactionCache.findPluginsById(name, {PluginLoaderPrivate::s_containmentActionsPluginDir});
if (!plugins.isEmpty()) {
KPluginLoader loader(plugins.first().fileName());
@ -891,5 +845,53 @@ bool PluginLoader::isPluginVersionCompatible(KPluginLoader &loader)
return true;
}
} // Plasma Namespace
QVector<KPluginMetaData> PluginLoaderPrivate::Cache::findPluginsById(const QString& name, const QStringList &dirs)
{
const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
bool useRuntimeCache = true;
if (pluginCacheAge == 0) {
// Find all the plugins now, but only once
pluginCacheAge = now;
auto insertIntoCache = [this](const QString &pluginPath) {
KPluginMetaData metadata(pluginPath);
if (!metadata.isValid()) {
qWarning() << "invalid metadata" << pluginPath;
return;
}
plugins[metadata.pluginId()].append(metadata);
};
for (const QString &dir : dirs)
KPluginLoader::forEachPlugin(dir, insertIntoCache);
} else if (now - pluginCacheAge > maxCacheAge) {
// cache is old and we're not within a few seconds of startup anymore
useRuntimeCache = false;
plugins.clear();
}
//if name wasn't a path, pluginName == name
const QString pluginName = name.section(QLatin1Char('/'), -1);
QVector<KPluginMetaData> ret;
if (useRuntimeCache) {
auto it = plugins.constFind(pluginName);
if (it != plugins.constEnd()) {
ret = *it;
}
qCDebug(LOG_PLASMA) << "loading applet by name" << name << useRuntimeCache << ret.size();
} else {
for (const auto& dir : dirs) {
ret = KPluginLoader::findPluginsById(dir, pluginName);
if (!ret.isEmpty())
break;
}
}
return ret;
}
} // Plasma Namespace

View File

@ -20,7 +20,7 @@
#include "scripting/scriptengine.h"
#include <QDebug>
#include <kservice.h>
#include <QGlobalStatic>
#include "applet.h"
#include "dataengine.h"
@ -32,6 +32,23 @@
namespace Plasma
{
static QVector<KPluginMetaData> listEngines(Types::ComponentTypes types, std::function<bool(const KPluginMetaData &)> filter)
{
QVector<KPluginMetaData> ret;
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("plasma/scriptengines"));
ret.reserve(plugins.size());
for (const auto &plugin : plugins) {
if (!filter(plugin))
continue;
const QStringList componentTypes = KPluginMetaData::readStringList(plugins.first().rawData(), QStringLiteral("X-Plasma-ComponentTypes"));
if (((types & Types::AppletComponent) && componentTypes.contains(QStringLiteral("Applet")))
||((types & Types::DataEngineComponent) && componentTypes.contains(QStringLiteral("DataEngine")))) {
ret << plugin;
}
}
return ret;
}
ScriptEngine::ScriptEngine(QObject *parent)
: QObject(parent),
d(nullptr)
@ -61,44 +78,42 @@ QString ScriptEngine::mainScript() const
QStringList knownLanguages(Types::ComponentTypes types)
{
QStringList languages;
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("plasma/scriptengines"));
const QVector<KPluginMetaData> plugins = listEngines(types, [] (const KPluginMetaData &) -> bool { return true;});
foreach (const auto &plugin, plugins) {
const QStringList componentTypes = KPluginMetaData::readStringList(plugins.first().rawData(), QStringLiteral("X-Plasma-ComponentTypes"));
if (((types & Types::AppletComponent) && componentTypes.contains(QStringLiteral("Applet")))
||((types & Types::DataEngineComponent) && componentTypes.contains(QStringLiteral("DataEngine")))) {
languages << plugin.value(QStringLiteral("X-Plasma-API"));
}
}
for (const auto &plugin : plugins)
languages << plugin.value(QStringLiteral("X-Plasma-API"));
return languages;
}
typedef QHash<QString, QSharedPointer<KPluginLoader>> EngineCache;
Q_GLOBAL_STATIC(EngineCache, engines)
ScriptEngine *loadEngine(const QString &language, Types::ComponentType type, QObject *parent,
const QVariantList &args = QVariantList())
{
Q_UNUSED(parent);
ScriptEngine *engine = nullptr;
{
auto it = engines->constFind(language);
if (it != engines->constEnd()) {
return (*it)->factory()->create<Plasma::ScriptEngine>(nullptr, args);
}
}
ScriptEngine *engine = nullptr;
auto filter = [&language](const KPluginMetaData &md) -> bool
{
return md.value(QStringLiteral("X-Plasma-API")) == language;
};
QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("plasma/scriptengines"), filter);
const QVector<KPluginMetaData> plugins = listEngines(type, filter);
if (!plugins.isEmpty()) {
const QStringList componentTypes = KPluginMetaData::readStringList(plugins.first().rawData(), QStringLiteral("X-Plasma-ComponentTypes"));
if (((type & Types::AppletComponent) && !componentTypes.contains(QStringLiteral("Applet")))
|| ((type & Types::DataEngineComponent) && !componentTypes.contains(QStringLiteral("DataEngine")))) {
qCWarning(LOG_PLASMA) << "ScriptEngine" << plugins.first().name() << "does not provide Applet or DataEngine components, returning empty.";
return nullptr;
}
KPluginLoader loader(plugins.first().fileName());
KPluginFactory *factory = loader.factory();
QSharedPointer<KPluginLoader> loader(new KPluginLoader(plugins.first().fileName()));
KPluginFactory *factory = loader->factory();
if (factory) {
engine = factory->create<Plasma::ScriptEngine>(nullptr, args);
engines->insert(language, loader);
} else {
qCWarning(LOG_PLASMA) << "Unable to load" << plugins.first().name() << "ScriptEngine";
}