Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frame/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set(PUBLIC_HEADERS
pluginmetadata.h
pluginloader.h
pluginfactory.h
startuptimingrecorder.h
applet.h
appletdata.h
containment.h
Expand Down Expand Up @@ -55,6 +56,7 @@ add_library(dde-shell-frame SHARED
pluginmetadata.cpp
pluginloader.cpp
pluginfactory.cpp
startuptimingrecorder.cpp
applet.cpp
containment.cpp
panel.cpp
Expand Down
28 changes: 28 additions & 0 deletions frame/pluginloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include "pluginloader.h"
#include "startuptimingrecorder.h"

#include "applet.h"
#include "containment.h"
Expand All @@ -17,9 +18,11 @@
#include <QLoggingCategory>
#include <QPluginLoader>
#include <QStandardPaths>
#include <QThread>

Check warning on line 21 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QThread> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 21 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QThread> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFuture>

Check warning on line 22 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFuture> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 22 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFuture> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QtConcurrent>

Check warning on line 23 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QtConcurrent> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 23 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QtConcurrent> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <fcntl.h>

Check warning on line 24 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <fcntl.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 24 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <fcntl.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <unistd.h>

Check warning on line 25 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <unistd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 25 in frame/pluginloader.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <unistd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

DS_BEGIN_NAMESPACE;

Expand Down Expand Up @@ -61,8 +64,31 @@
m_loadMetaDatas = QtConcurrent::run(std::bind(&DPluginLoaderPrivate::initPlugins, this));
}

void preloadPluginLibraries()
{
D_Q(DPluginLoader);
for (const auto &pluginId : m_plugins.keys()) {
for (const auto &dir : q->pluginDirs()) {
const QString soPath = QDir(dir).absoluteFilePath(pluginId + PluginSuffix);
if (!QFile::exists(soPath))
continue;
const int fd = open(soPath.toUtf8().constData(), O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) != 0) {
qCDebug(dsLog) << "posix_fadvise failed for" << soPath;
}
close(fd);
} else {
qCDebug(dsLog) << "Failed to open plugin library for preloading:" << soPath;
}
break;
}
}
}

void initPlugins()
{
StartupTimingScope timingScope(QStringLiteral("plugin_discovery"));
for (const auto &item : m_pluginDirs) {
const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories | QDirIterator::FollowSymlinks;
const QStringList nameFilters = {MetaDataFileName};
Expand Down Expand Up @@ -94,6 +120,7 @@
m_plugins[info.pluginId()] = info;
}
}
preloadPluginLibraries();
}

QStringList builtinPackagePaths()
Expand Down Expand Up @@ -151,6 +178,7 @@
if (!existPlugin(data))
return nullptr;

StartupTimingScope timingScope(QStringLiteral("so_load"), data.pluginId());
DAppletFactory *factory = nullptr;
const QString fileName = data.pluginId();
QPluginLoader loader(fileName);
Expand Down
111 changes: 111 additions & 0 deletions frame/startuptimingrecorder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "startuptimingrecorder.h"

#include <dobject_p.h>

Check warning on line 7 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dobject_p.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 7 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dobject_p.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QApplicationStatic>

Check warning on line 8 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QApplicationStatic> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 8 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QApplicationStatic> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <QCoreApplication>

Check warning on line 10 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCoreApplication> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 10 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCoreApplication> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 11 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 11 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFile>

Check warning on line 12 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFile> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 12 in frame/startuptimingrecorder.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFile> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QMutexLocker>

DS_BEGIN_NAMESPACE

Q_LOGGING_CATEGORY(dsBenchmarkLog, "org.deepin.dde.shell.benchmark")

Q_APPLICATION_STATIC(StartupTimingRecorder, g_instance)

StartupTimingRecorder::StartupTimingRecorder()
: QObject()
{
}

StartupTimingRecorder::~StartupTimingRecorder() = default;

StartupTimingRecorder *StartupTimingRecorder::instance()
{
return g_instance;
}

void StartupTimingRecorder::record(const QString &phase, qint64 elapsedMs,
const QString &pluginId, const QVariantMap &metadata)
{
QMutexLocker locker(&m_mutex);
m_samples.append({phase, pluginId, elapsedMs, metadata});
}

QJsonArray StartupTimingRecorder::toJson() const
{
QMutexLocker locker(&m_mutex);
QJsonArray array;
for (const auto &sample : m_samples) {
QJsonObject obj;
obj["phase"] = sample.phase;
if (!sample.pluginId.isEmpty())
obj["plugin_id"] = sample.pluginId;
obj["elapsed_ms"] = sample.elapsedMs;
if (!sample.metadata.isEmpty())
obj["metadata"] = QJsonObject::fromVariantMap(sample.metadata);
array.append(obj);
}
return array;
}

bool StartupTimingRecorder::isEnabled() const
{
return qgetenv("DDE_SHELL_BENCHMARK") == "1";
}

void StartupTimingRecorder::dumpToFile(const QString &path) const
{
QJsonObject root;
root["timestamp"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
root["version"] = "1.0";
root["pid"] = QCoreApplication::applicationPid();
root["samples"] = toJson();

QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
file.close();
} else {
qCWarning(dsBenchmarkLog) << "Cannot open benchmark output:" << path;
}
}

StartupTimingScope::StartupTimingScope(const QString &phase, const QString &pluginId,
const QVariantMap &metadata, int timeoutMs)
: m_phase(phase)
, m_pluginId(pluginId)
, m_metadata(metadata)
, m_timeoutMs(timeoutMs)
{
if (StartupTimingRecorder::instance()->isEnabled()) {
m_enabled = true;
m_timer.start();
}
}

StartupTimingScope::~StartupTimingScope()
{
if (!m_enabled)
return;

const qint64 elapsed = m_timer.elapsed();
StartupTimingRecorder::instance()->record(m_phase, elapsed, m_pluginId, m_metadata);

if (m_timeoutMs > 0 && elapsed >= m_timeoutMs) {
if (m_pluginId.isEmpty())
qCWarning(dsBenchmarkLog) << m_phase << ": elapsed time [" << elapsed << "ms ].";
else
qCWarning(dsBenchmarkLog) << m_phase << ":" << m_pluginId << ": elapsed time [" << elapsed << "ms ].";
}
}

DS_END_NAMESPACE
64 changes: 64 additions & 0 deletions frame/startuptimingrecorder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "dsglobal.h"

#include <QObject>
#include <QElapsedTimer>
#include <QJsonArray>
#include <QMutex>
#include <QVariantMap>
#include <QVector>
#include <QString>

DS_BEGIN_NAMESPACE

struct TimingSample
{
QString phase;
QString pluginId;
qint64 elapsedMs = 0;
QVariantMap metadata;
};

class DS_SHARE StartupTimingRecorder : public QObject
{
Q_OBJECT
public:
StartupTimingRecorder();
~StartupTimingRecorder() override;
static StartupTimingRecorder *instance();

void record(const QString &phase, qint64 elapsedMs,
const QString &pluginId = {}, const QVariantMap &metadata = {});
QJsonArray toJson() const;
bool isEnabled() const;
void dumpToFile(const QString &path) const;

private:
Q_DISABLE_COPY(StartupTimingRecorder)

QVector<TimingSample> m_samples;
mutable QMutex m_mutex;
};

class StartupTimingScope
{
public:
explicit StartupTimingScope(const QString &phase, const QString &pluginId = {},
const QVariantMap &metadata = {}, int timeoutMs = 0);
~StartupTimingScope();

private:
QString m_phase;
QString m_pluginId;
QVariantMap m_metadata;
int m_timeoutMs = 0;
bool m_enabled = false;
QElapsedTimer m_timer;
};

DS_END_NAMESPACE
51 changes: 22 additions & 29 deletions shell/appletconfigmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
#include "appletconfigmanager.h"

#include "pluginloader.h"
#include "startuptimingrecorder.h"

#include <DConfig>
#include <QLoggingCategory>
#include <QSet>

DS_BEGIN_NAMESPACE

Expand All @@ -30,8 +30,9 @@ QStringList AppletConfigManager::disabledPlugins() const
return m_disabledPlugins;
}

bool AppletConfigManager::isAppletEnabled(const QString &pluginId) const
bool AppletConfigManager::isAppletEnabled(const QString &pluginId)
{
ensureAppletConfig(pluginId);
return !m_disabledPlugins.contains(pluginId);
}

Expand All @@ -48,36 +49,28 @@ void AppletConfigManager::setAppletEnabled(const QString &pluginId, bool enabled
Q_EMIT appletEnabledChanged(pluginId, enabled);
}

void AppletConfigManager::ensureAppletConfigs()
void AppletConfigManager::ensureAppletConfig(const QString &pluginId)
{
auto loader = DPluginLoader::instance();
const auto plugins = loader->plugins();
QSet<QString> pluginIds;
for (const auto &plugin : plugins) {
pluginIds.insert(plugin.pluginId());
}
if (m_appletConfigs.contains(pluginId))
return;

for (const auto &pluginId : pluginIds) {
if (m_appletConfigs.contains(pluginId))
continue;

auto config = DConfig::create(QLatin1String(AppId),
QLatin1String(ConfigName),
QLatin1Char('/') + pluginId,
this);
m_appletConfigs.insert(pluginId, config);
if (!config || !config->isValid()) {
qCWarning(dsLoaderLog) << "Unable to create applet DConfig; applet remains enabled:" << pluginId;
setAppletEnabled(pluginId, true);
continue;
}

setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool());
QObject::connect(config, &DConfig::valueChanged, this, [this, pluginId, config](const QString &key) {
if (key == QLatin1String(EnableKey))
setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool());
});
StartupTimingScope pluginScope(QStringLiteral("dconfig_create"), pluginId);
auto config = DConfig::create(QLatin1String(AppId),
QLatin1String(ConfigName),
QLatin1Char('/') + pluginId,
this);
m_appletConfigs.insert(pluginId, config);
if (!config || !config->isValid()) {
qCWarning(dsLoaderLog) << "Unable to create applet DConfig; applet remains enabled:" << pluginId;
setAppletEnabled(pluginId, true);
return;
}

setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool());
QObject::connect(config, &DConfig::valueChanged, this, [this, pluginId, config](const QString &key) {
if (key == QLatin1String(EnableKey))
setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool());
});
}

DS_END_NAMESPACE
4 changes: 2 additions & 2 deletions shell/appletconfigmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class AppletConfigManager : public QObject
explicit AppletConfigManager(QObject *parent = nullptr);

QStringList disabledPlugins() const;
bool isAppletEnabled(const QString &pluginId) const;
void ensureAppletConfigs();
bool isAppletEnabled(const QString &pluginId);
void ensureAppletConfig(const QString &pluginId);

Q_SIGNALS:
void appletEnabledChanged(const QString &pluginId, bool enabled);
Expand Down
Loading
Loading