diff --git a/REUSE.toml b/REUSE.toml index 677feff..eaae1fb 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -22,7 +22,7 @@ SPDX-FileCopyrightText = "UnionTech Software Technology Co., Ltd." SPDX-License-Identifier = "LGPL-3.0-or-later" [[annotations]] -path = ["dbus/**.service", "systemd/**", "tools/**.service"] +path = ["dbus/**.service", "systemd/**", "tools/**.service", "tools/**.desktop"] precedence = "aggregate" SPDX-FileCopyrightText = "UnionTech Software Technology Co., Ltd." SPDX-License-Identifier = "LGPL-3.0-or-later" diff --git a/debian/control b/debian/control index 764d02a..7a4fc96 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Maintainer: Deepin Packages Builder Build-Depends: cmake, debhelper-compat (= 11), + libcap-ng-dev, libdtk6core-dev, libdtk6core-bin, libglib2.0-dev, diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..9a8b355 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,13 @@ +#!/bin/sh + +case "$1" in + configure) + # Set CAP_SYS_RESOURCE capability on dde-oom-score-adj + # This allows it to adjust oom_score_adj for monitored services + if [ -x /usr/bin/dde-oom-score-adj ]; then + setcap cap_sys_resource=ep /usr/bin/dde-oom-score-adj || true + fi + ;; +esac + +#DEBHELPER# \ No newline at end of file diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index 4eef91c..e7a3499 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -21,3 +21,6 @@ install( install(FILES ${XSESSION} DESTINATION /etc/X11/Xsession.d/) install(FILES ${PROFILE} DESTINATION /etc/profile.d) + +file(GLOB DCONFIG_FILES "misc/dconf/*.json") +dtk_add_config_meta_files(APPID org.deepin.dde.session BASE misc/dconf FILES ${DCONFIG_FILES}) diff --git a/misc/dconf/org.deepin.dde.session.oom-score-adj.json b/misc/dconf/org.deepin.dde.session.oom-score-adj.json new file mode 100644 index 0000000..88ecc8d --- /dev/null +++ b/misc/dconf/org.deepin.dde.session.oom-score-adj.json @@ -0,0 +1,16 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "monitorServices": { + "value": ["dde-session-manager.service","dde-session@x11.service"], + "serial": 0, + "flags": ["global"], + "name": "monitorServices", + "name[zh_CN]": "OOM 分数调整监控服务列表", + "description": "List of systemd services to monitor for OOM score adjustment", + "permissions": "readonly", + "visibility": "private" + } + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6cdf09c..a9def57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 3b3f3ef..7dd6ce5 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory("dde-keyring-checker") add_subdirectory("dde-version-checker") add_subdirectory("dde-xsettings-checker") add_subdirectory("dde-quick-login") +add_subdirectory("dde-oom-score-adj") diff --git a/tools/dde-oom-score-adj/CMakeLists.txt b/tools/dde-oom-score-adj/CMakeLists.txt new file mode 100644 index 0000000..526a577 --- /dev/null +++ b/tools/dde-oom-score-adj/CMakeLists.txt @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: CC0-1.0 + +set(BIN_NAME dde-oom-score-adj) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core DBus REQUIRED) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core) +find_package(PkgConfig REQUIRED) +pkg_check_modules(CAPNG REQUIRED IMPORTED_TARGET libcap-ng) + +add_executable(${BIN_NAME} + main.cpp + oomscoreadjuster.h + oomscoreadjuster.cpp +) + +target_link_libraries(${BIN_NAME} + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::DBus + Dtk${DTK_VERSION_MAJOR}::Core + PkgConfig::CAPNG +) + +install(TARGETS ${BIN_NAME} DESTINATION bin) +install(FILES dde-oom-score-adj.desktop DESTINATION /etc/xdg/autostart/) diff --git a/tools/dde-oom-score-adj/dde-oom-score-adj.desktop b/tools/dde-oom-score-adj/dde-oom-score-adj.desktop new file mode 100644 index 0000000..d20ecb8 --- /dev/null +++ b/tools/dde-oom-score-adj/dde-oom-score-adj.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=DDE OOM Score Adjuster +Comment=Adjust OOM score for critical DDE services +Exec=/usr/bin/dde-oom-score-adj +Terminal=false +NoDisplay=true diff --git a/tools/dde-oom-score-adj/main.cpp b/tools/dde-oom-score-adj/main.cpp new file mode 100644 index 0000000..33e49b9 --- /dev/null +++ b/tools/dde-oom-score-adj/main.cpp @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "oomscoreadjuster.h" +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + // 初始化 libcap-ng + capng_get_caps_process(); + + // 检查是否有 CAP_SYS_RESOURCE capability(仅用于调试信息) + if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_RESOURCE)) { + qWarning() << "Warning: Missing CAP_SYS_RESOURCE capability!"; + qWarning() << "The program may not be able to modify oom_score_adj."; + qWarning() << "When running as systemd service, capabilities are granted automatically."; + } + + OomScoreAdjuster adjuster; + + // 连接退出信号 + QObject::connect(&adjuster, &OomScoreAdjuster::handleExit, &app, &QCoreApplication::quit); + QMetaObject::invokeMethod(&adjuster, "start", Qt::QueuedConnection); + + return app.exec(); +} diff --git a/tools/dde-oom-score-adj/oomscoreadjuster.cpp b/tools/dde-oom-score-adj/oomscoreadjuster.cpp new file mode 100644 index 0000000..39f346e --- /dev/null +++ b/tools/dde-oom-score-adj/oomscoreadjuster.cpp @@ -0,0 +1,360 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "oomscoreadjuster.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DCORE_USE_NAMESPACE + +OomScoreAdjuster::OomScoreAdjuster(QObject *parent) + : QObject(parent) + , m_systemdBus(QDBusConnection::sessionBus()) + , m_exitTimer(new QTimer(this)) + , m_targetScore(-999) +{ + loadServiceList(); + + // 设置3分钟后自动退出 + m_exitTimer->setSingleShot(true); + m_exitTimer->setInterval(3 * 60 * 1000); // 3分钟 = 180000毫秒 + connect(m_exitTimer, &QTimer::timeout, this, &OomScoreAdjuster::onExitTimeout); +} + +OomScoreAdjuster::~OomScoreAdjuster() +{ +} + +void OomScoreAdjuster::loadServiceList() +{ + // 从 DConfig 读取服务列表 + // 注意:传入 this 作为 parent,Qt 会自动管理内存,不需要手动 delete + DConfig *config = DConfig::create("org.deepin.dde.session", "org.deepin.dde.session.oom-score-adj", QString(), this); + + if (config && config->isValid()) { + // 读取服务列表 + m_watchedServices = config->value("monitorServices").toStringList(); + } else { + qWarning() << "Failed to load configuration, using default services"; + m_watchedServices << "dde-session-manager.service" << "dde-session@x11.service"; + } + if (m_watchedServices.isEmpty()) { + qWarning() << "No services to monitor, exiting immediately"; + emit handleExit(); + return; + } +} + +void OomScoreAdjuster::start() +{ + // 检查 D-Bus 连接 + if (!m_systemdBus.isConnected()) { + qWarning() << "D-Bus connection failed, exiting immediately"; + emit handleExit(); + return; + } + + qInfo() << "Starting OOM score adjuster for services:" << m_watchedServices; + + // 订阅 systemd 信号 + subscribeToSystemd(); + + // 检查已经运行的服务 + checkExistingServices(); + + // 检查是否所有服务都已调整完成 + if (m_adjustedServices.size() == m_watchedServices.size()) { + qInfo() << "All services have been adjusted successfully, exiting immediately"; + qInfo() << "Adjusted services:" << m_adjustedServices; + emit handleExit(); + return; + } + + // 还有服务未调整,启动 3 分钟超时定时器 + m_exitTimer->start(); + qInfo() << "Waiting for remaining services. Adjusted:" << m_adjustedServices.size() + << "/ Total:" << m_watchedServices.size(); + qInfo() << "Timeout timer started, will exit in 3 minutes if not all services are adjusted"; +} + +void OomScoreAdjuster::subscribeToSystemd() +{ + // 连接到 systemd 的 UnitNew 信号 + m_systemdBus.connect( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitNew", + this, + SLOT(onUnitNew(QString, QDBusObjectPath)) + ); +} + +void OomScoreAdjuster::checkExistingServices() +{ + for (const QString &serviceName : m_watchedServices) { + checkAndAdjustService(serviceName); + } +} + +void OomScoreAdjuster::onUnitNew(const QString &unitName, const QDBusObjectPath &unitPath) +{ + if (m_watchedServices.contains(unitName)) { + m_servicePathMap[unitName] = unitPath.path(); + + // 检查服务当前状态 + QDBusInterface unit( + "org.freedesktop.systemd1", + unitPath.path(), + "org.freedesktop.systemd1.Unit", + m_systemdBus + ); + + if (unit.isValid()) { + QString activeState = unit.property("ActiveState").toString(); + + if (activeState == "active") { + // 服务已经是 active,直接调整,不需要监听信号 + checkAndAdjustService(unitName); + } else { + // 服务还未 active,监听状态变化 + m_systemdBus.connect( + "org.freedesktop.systemd1", + unitPath.path(), + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + this, + SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)) + ); + qInfo() << "Service" << unitName << "is not active yet, waiting for activation"; + } + } + } +} + +void OomScoreAdjuster::onPropertiesChanged(const QString &interface, + const QVariantMap &changedProperties, + const QStringList &invalidatedProperties) +{ + Q_UNUSED(invalidatedProperties) + + if (interface != "org.freedesktop.systemd1.Service" && interface != "org.freedesktop.systemd1.Unit") { + return; + } + + if (!changedProperties.contains("ActiveState")) { + return; + } + + QString activeState = changedProperties.value("ActiveState").toString(); + if (activeState != "active") { + return; + } + + for (const QString &serviceName : m_watchedServices) { + checkAndAdjustService(serviceName); + } +} + +void OomScoreAdjuster::checkAndAdjustService(const QString &serviceName) +{ + QList pids = getServiceProcesses(serviceName); + if (pids.isEmpty()) { + qDebug() << "Service" << serviceName << "has no processes yet, waiting for it to start"; + return; + } + + bool allAdjusted = true; + for (quint32 pid : pids) { + // 读取当前的 oom_score_adj + QString procPath = QString("/proc/%1/oom_score_adj").arg(pid); + QFile readFile(procPath); + if (readFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString currentScore = readFile.readAll().trimmed(); + readFile.close(); + + // 如果已经是目标值,跳过 + if (currentScore.toInt() == m_targetScore) { + continue; + } + } + + if (adjustOomScore(pid, m_targetScore)) { + qInfo() << "Successfully adjusted OOM score for" << serviceName << "PID:" << pid << "to" << m_targetScore; + } else { + qWarning() << "Failed to adjust OOM score for" << serviceName << "PID:" << pid; + allAdjusted = false; + } + } + + // 标记该服务已完成调整 + if (allAdjusted && !pids.isEmpty()) { + m_adjustedServices.insert(serviceName); + qInfo() << "Service" << serviceName << "adjustment completed"; + + // 检查是否所有服务都已调整完成 + checkIfAllServicesAdjusted(); + } +} + +QList OomScoreAdjuster::getServiceProcesses(const QString &serviceName) +{ + QList pids; + + // 获取 unit 的对象路径 + QString unitPath = getUnitObjectPath(serviceName); + if (unitPath.isEmpty()) { + return pids; + } + + // 调用 GetProcesses 方法 + QDBusInterface service( + "org.freedesktop.systemd1", + unitPath, + "org.freedesktop.systemd1.Service", + m_systemdBus + ); + + if (!service.isValid()) { + return pids; + } + + QDBusMessage reply = service.call("GetProcesses"); + if (reply.type() == QDBusMessage::ErrorMessage) { + return pids; + } + + if (reply.arguments().isEmpty()) { + return pids; + } + + // 解析返回值: a(sus) - array of (string, uint32, string) + const QDBusArgument arg = reply.arguments().at(0).value(); + + arg.beginArray(); + while (!arg.atEnd()) { + QString cgroupPath; + quint32 pid = 0; + QString command; + + arg.beginStructure(); + arg >> cgroupPath >> pid >> command; + arg.endStructure(); + + if (pid > 0) { + pids.append(pid); + } + } + arg.endArray(); + + return pids; +} + +QString OomScoreAdjuster::getUnitObjectPath(const QString &serviceName) +{ + // 如果已经缓存了路径,直接返回 + if (m_servicePathMap.contains(serviceName)) { + return m_servicePathMap[serviceName]; + } + + // 通过 systemd Manager 接口获取 unit 路径 + QDBusInterface manager( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + m_systemdBus + ); + + if (!manager.isValid()) { + return QString(); + } + + QDBusReply reply = manager.call("GetUnit", serviceName); + if (!reply.isValid()) { + return QString(); + } + + QString path = reply.value().path(); + m_servicePathMap[serviceName] = path; + + return path; +} + +bool OomScoreAdjuster::adjustOomScore(quint32 pid, int score) +{ + if (pid == 0) { + return false; + } + + QString procPath = QString("/proc/%1/oom_score_adj").arg(pid); + + QFile file(procPath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Unbuffered)) { + return false; + } + + QTextStream out(&file); + out << score; + out.flush(); + file.close(); + + if (file.error() != QFile::NoError) { + return false; + } + + // 验证写入 + QFile verifyFile(procPath); + if (verifyFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString actualValue = verifyFile.readAll().trimmed(); + verifyFile.close(); + return actualValue.toInt() == score; + } + + return false; +} + +void OomScoreAdjuster::checkIfAllServicesAdjusted() +{ + if (m_adjustedServices.size() == m_watchedServices.size()) { + qInfo() << "All services have been adjusted successfully, exiting immediately"; + qInfo() << "Adjusted services:" << m_adjustedServices; + + // 停止定时器 + m_exitTimer->stop(); + + // 立即退出 + emit handleExit(); + } else { + qInfo() << "Waiting for remaining services. Adjusted:" << m_adjustedServices.size() + << "/ Total:" << m_watchedServices.size(); + } +} + +void OomScoreAdjuster::onExitTimeout() +{ + qWarning() << "Exit timeout reached (3 minutes), exiting now"; + qWarning() << "Adjusted services:" << m_adjustedServices.size() << "/" << m_watchedServices.size(); + qWarning() << "Successfully adjusted:" << m_adjustedServices; + + QSet pending; + for (const QString &service : m_watchedServices) { + if (!m_adjustedServices.contains(service)) { + pending.insert(service); + } + } + if (!pending.isEmpty()) { + qWarning() << "Pending services:" << pending; + } + + emit handleExit(); +} diff --git a/tools/dde-oom-score-adj/oomscoreadjuster.h b/tools/dde-oom-score-adj/oomscoreadjuster.h new file mode 100644 index 0000000..137730f --- /dev/null +++ b/tools/dde-oom-score-adj/oomscoreadjuster.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef OOMSCOREADJUSTER_H +#define OOMSCOREADJUSTER_H + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief The OomScoreAdjuster class + * @brief 监听 systemd 服务启动并调整其 OOM score + */ +class OomScoreAdjuster : public QObject +{ + Q_OBJECT + +public: + explicit OomScoreAdjuster(QObject *parent = nullptr); + ~OomScoreAdjuster(); + +signals: + void handleExit(); + +public slots: + void start(); + +private slots: + void onExitTimeout(); + void onUnitNew(const QString &unitName, const QDBusObjectPath &unitPath); + void onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties); + +private: + void loadServiceList(); + void subscribeToSystemd(); + void checkExistingServices(); + void checkAndAdjustService(const QString &serviceName); + bool adjustOomScore(quint32 pid, int score); + QList getServiceProcesses(const QString &serviceName); + QString getUnitObjectPath(const QString &serviceName); + void checkIfAllServicesAdjusted(); + +private: + QDBusConnection m_systemdBus; + QStringList m_watchedServices; + QMap m_servicePathMap; // serviceName -> objectPath + QSet m_adjustedServices; // 已完成调整的服务 + int m_targetScore; + QTimer *m_exitTimer; +}; + +#endif // OOMSCOREADJUSTER_H