From b33a35c4d1a77efe504630d44f06eb873b27342a Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 18 Dec 2025 20:04:01 +0800 Subject: [PATCH] feat: add OOM score adjustment tool for critical DDE services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Implement dde-oom-score-adj tool to monitor and adjust OOM scores for critical DDE services 2. Add DConfig configuration for customizable service monitoring list and target score 3. Set up systemd signal monitoring to detect service startup and adjust OOM scores in real-time 4. Include automatic capability management via postinst script for CAP_SYS_RESOURCE 5. Add autostart desktop file for automatic execution during system startup 6. The tool automatically exits after 3 minutes to avoid long-running processes Log: Added OOM score adjustment feature to protect critical DDE services from being killed by the system OOM killer Influence: 1. Test that dde-oom-score-adj starts automatically during system boot 2. Verify critical DDE services (dde-session-manager, dde-session@x11) have adjusted OOM scores 3. Check that the tool exits after 3 minutes as expected 4. Test configuration changes through DConfig interface 5. Verify capability assignment works correctly during package installation 6. Monitor system logs for any OOM adjustment related errors feat: 为关键 DDE 服务添加 OOM 分数调整工具 1. 实现 dde-oom-score-adj 工具来监控和调整关键 DDE 服务的 OOM 分数 2. 添加 DConfig 配置用于可自定义的服务监控列表和目标分数 3. 设置 systemd 信号监控以实时检测服务启动并调整 OOM 分数 4. 包含通过 postinst 脚本自动管理 CAP_SYS_RESOURCE 能力 5. 添加自动启动桌面文件用于系统启动时自动执行 6. 工具在 3 分钟后自动退出,避免长时间运行的进程 Log: 新增 OOM 分数调整功能,保护关键 DDE 服务不被系统 OOM killer 终止 Influence: 1. 测试 dde-oom-score-adj 在系统启动时是否自动启动 2. 验证关键 DDE 服务(dde-session-manager, dde-session@x11)是否具有调整 后的 OOM 分数 3. 检查工具是否在 3 分钟后按预期退出 4. 通过 DConfig 接口测试配置更改 5. 验证包安装期间的能力分配是否正确工作 6. 监控系统日志中是否有 OOM 调整相关的错误 --- REUSE.toml | 2 +- debian/control | 1 + debian/postinst | 13 + misc/CMakeLists.txt | 3 + .../org.deepin.dde.session.oom-score-adj.json | 16 + src/CMakeLists.txt | 2 +- tools/CMakeLists.txt | 1 + tools/dde-oom-score-adj/CMakeLists.txt | 30 ++ .../dde-oom-score-adj.desktop | 7 + tools/dde-oom-score-adj/main.cpp | 32 ++ tools/dde-oom-score-adj/oomscoreadjuster.cpp | 360 ++++++++++++++++++ tools/dde-oom-score-adj/oomscoreadjuster.h | 58 +++ 12 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 debian/postinst create mode 100644 misc/dconf/org.deepin.dde.session.oom-score-adj.json create mode 100644 tools/dde-oom-score-adj/CMakeLists.txt create mode 100644 tools/dde-oom-score-adj/dde-oom-score-adj.desktop create mode 100644 tools/dde-oom-score-adj/main.cpp create mode 100644 tools/dde-oom-score-adj/oomscoreadjuster.cpp create mode 100644 tools/dde-oom-score-adj/oomscoreadjuster.h 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