diff --git a/system/cgroup_setter/CMakeLists.txt b/system/cgroup_setter/CMakeLists.txt new file mode 100644 index 0000000000000..3882ea9781e9e --- /dev/null +++ b/system/cgroup_setter/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.14) +project(cgroup_setter) + +find_package(autoware_cmake REQUIRED) +find_package(yaml-cpp REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem system) +include_directories(${Boost_INCLUDE_DIRS}) + +autoware_package() + +ament_auto_add_executable(${PROJECT_NAME} + src/cgroup_setter.cpp +) + +target_link_libraries(cgroup_setter yaml-cpp) +target_link_libraries(cgroup_setter ${Boost_LIBRARIES}) + +ament_auto_package(INSTALL_TO_SHARE + launch + config +) diff --git a/system/cgroup_setter/README.md b/system/cgroup_setter/README.md new file mode 100644 index 0000000000000..6c510fcde0b94 --- /dev/null +++ b/system/cgroup_setter/README.md @@ -0,0 +1,95 @@ +# cgroup_setter + +## Purpose + +This package set a PID to a custom cgroup. +The PID is found by `pgrep -f`. + +## Inputs / Outputs + +### Outputs + +| Name | Type | Description | +| -------------- | ---------------------------------------- | ------------------- | +| `/diagnostics` | `diagnostic_msgs::msgs::DiagnosticArray` | Diagnostics outputs | + +## Parameters + +### Node Parameters + +| Name | Type | Default Value | Explanation | Reconfigurable | +| ---------------------------- | ------ | ---------------------------------------------------- | -------------- | -------------- | +| `cgroup_setting_config_path` | string | `$(find-pkg-share cgroup_setter)/config/cgroup.yaml` | yaml file path | | + +### YAML format for cgroup_setter + +format + +```yaml +base_path: /sys/fs/cgroup +settings: + - directory: xxx/xxx + search_word: + - xxxxx + - xxxxx + - directory: xxx/xxx + search_word: + - xxxxx +``` + +The following is an example of joining the PID from running `pgrep -f` +with the keyword `__node:=system_monitor_container` to a cgroup named `/sys/fs/cgroup/autoware/system_monitor`. + +example + +```yaml +base_path: /sys/fs/cgroup +settings: + - directory: autoware/system_monitor + search_word: + - __node:=system_monitor_container +``` + +#### Rules + +- The value of settings must be a sequence. + +example + +```yaml +# NG +base_path: /sys/fs/cgroup +settings: + directory: autoware/system_monitor # - directory + search_word: + - __node:=system_monitor_container +``` + +- The value of search_word must be a sequence. + +example + +```yaml +# NG +base_path: /sys/fs/cgroup +settings: + - directory: autoware/system_monitor + search_word: __node:=system_monitor_container # ["__node:=system_monitor_container"] or - "__node:=system_monitor_container" +``` + +#### Notes + +- Write permission is required for a custom cgroup, and /sys/fs/cgroup/cgroup.procs to attach a PID to the cgroup. +- A PID cannot be attached to a cgroup that has a small group.(only leaf cgroup) + +## Assumptions / Known limits + +TBD. + +## Usage + +### launch + +```sh +ros2 launch cgroup_setter cgroup_setter.launch.xml +``` diff --git a/system/cgroup_setter/config/cgroup.yaml b/system/cgroup_setter/config/cgroup.yaml new file mode 100644 index 0000000000000..34d7e4378055f --- /dev/null +++ b/system/cgroup_setter/config/cgroup.yaml @@ -0,0 +1,5 @@ +base_path: /sys/fs/cgroup +settings: + - directory: autoware/system_monitor + search_word: + - __node:=system_monitor_container diff --git a/system/cgroup_setter/launch/cgroup_setter.launch.xml b/system/cgroup_setter/launch/cgroup_setter.launch.xml new file mode 100644 index 0000000000000..017638670f2da --- /dev/null +++ b/system/cgroup_setter/launch/cgroup_setter.launch.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/system/cgroup_setter/package.xml b/system/cgroup_setter/package.xml new file mode 100644 index 0000000000000..214392a6c3465 --- /dev/null +++ b/system/cgroup_setter/package.xml @@ -0,0 +1,26 @@ + + + + cgroup_setter + 0.1.0 + set pid to a cgroup + TetsuKawa + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + diagnostic_msgs + diagnostic_updater + libboost-filesystem-dev + rclcpp + rclcpp_components + yaml-cpp + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/system/cgroup_setter/src/cgroup_setter.cpp b/system/cgroup_setter/src/cgroup_setter.cpp new file mode 100644 index 0000000000000..c8e5f65958a57 --- /dev/null +++ b/system/cgroup_setter/src/cgroup_setter.cpp @@ -0,0 +1,224 @@ +// Copyright 2024 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file cgroup_setter.cpp + * @brief Cgroup setter class + */ + +#include "cgroup_setter.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace bp = boost::process; + +CgroupSetter::CgroupSetter(const rclcpp::NodeOptions & options) +: Node("cgroup_setter_node", options), updater_(this) +{ + try { + std::string yaml_path = this->declare_parameter("cgroup_setting_config_path"); + YAML::Node config = YAML::LoadFile(yaml_path); + if (config["base_path"]) { + base_path_ = config["base_path"].as(); + } else { + RCLCPP_ERROR(this->get_logger(), "base_path is not set in the config file."); + return; + } + + if (!config["settings"]) { + RCLCPP_ERROR(this->get_logger(), "settings is not set in the config file."); + return; + } + + for (auto setting : config["settings"]) { + if (!setting["directory"] || !setting["search_word"]) { + RCLCPP_ERROR(this->get_logger(), "directory or search_word is not set in the config file."); + return; + } + + for (auto word : setting["search_word"]) { + std::pair tmp_pair = + std::make_pair(setting["directory"].as(), word.as()); + cgroup_map_[tmp_pair] = false; + } + } + } catch (const std::exception & e) { + RCLCPP_ERROR(this->get_logger(), "Failed to load the config file."); + return; + } + + gethostname(hostname_, sizeof(hostname_)); + updater_.setHardwareID(hostname_); + updater_.add("Cgroup Setting", this, &CgroupSetter::checkCgroup); + + // Timer + using namespace std::literals::chrono_literals; + timer_ = rclcpp::create_timer( + this, this->get_clock(), 1s, std::bind(&CgroupSetter::checkProcessAndAddToCgroup, this)); +} + +void CgroupSetter::checkCgroup(diagnostic_updater::DiagnosticStatusWrapper & stat) +{ + bool allOK = true; + for (auto & entry : cgroup_map_) { + if (entry.second) { + stat.add(entry.first.first + " " + entry.first.second, "OK"); + } else { + allOK = false; + stat.add(entry.first.first + " " + entry.first.second, "NG"); + } + } + if (allOK) { + timer_->cancel(); + stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "All processes are added to cgroup."); + } else { + stat.summary( + diagnostic_msgs::msg::DiagnosticStatus::WARN, "Some processes are not added to cgroup."); + } +} + +void CgroupSetter::checkProcessAndAddToCgroup() +{ + for (auto & entry : cgroup_map_) { + if (entry.second) { + continue; + } + std::string word = entry.first.second; + std::string result = executeCommand(word); + if (!result.empty()) { + std::istringstream iss(result); + std::string pid; + bool allAdded = true; + while (std::getline(iss, pid, '\n')) { + if (!pid.empty() && addToCgroup(entry.first.first, pid)) { + if (checkPIDExists(base_path_ + "/" + entry.first.first + "/cgroup.procs", pid)) { + RCLCPP_INFO( + this->get_logger(), "Added all PIDs to cgroup. %s %s", entry.first.second.c_str(), + pid.c_str()); + } else { + allAdded = false; + RCLCPP_ERROR( + this->get_logger(), "Failed to add PID %s to cgroup. %s %s", pid.c_str(), + entry.first.second.c_str(), result.c_str()); + } + } else { + allAdded = false; + RCLCPP_ERROR( + this->get_logger(), "Failed to add PID %s to cgroup. %s %s", pid.c_str(), + entry.first.second.c_str(), result.c_str()); + } + } + if (allAdded) { + entry.second = true; + } + } else { + RCLCPP_ERROR( + this->get_logger(), "Failed to get PID. %s %s", entry.first.second.c_str(), result.c_str()); + } + } +} + +std::string CgroupSetter::executeCommand(const std::string & search_word) +{ + int out_fd[2]; + if (pipe2(out_fd, O_CLOEXEC) != 0) { + RCLCPP_ERROR(this->get_logger(), "pipe2 error"); + return ""; + } + bp::pipe out_pipe{out_fd[0], out_fd[1]}; + bp::ipstream is_out{std::move(out_pipe)}; + + int err_fd[2]; + if (pipe2(err_fd, O_CLOEXEC) != 0) { + RCLCPP_ERROR(this->get_logger(), "pipe2 error"); + return ""; + } + bp::pipe err_pipe{err_fd[0], err_fd[1]}; + bp::ipstream is_err{std::move(err_pipe)}; + auto cmd = bp::search_path("pgrep"); + std::vector args; + args.push_back("-f"); + args.push_back(search_word); + bp::child c(cmd, bp::args = args, bp::std_out > is_out, bp::std_err > is_err); + c.wait(); + if (c.exit_code() != 0) { + std::ostringstream os; + is_err >> os.rdbuf(); + RCLCPP_ERROR(this->get_logger(), os.str().c_str()); + return ""; + } else { + std::ostringstream os; + os << is_out.rdbuf(); + std::string output = os.str(); + return output; + } +} + +bool CgroupSetter::checkPIDExists(const std::string & filePath, const std::string & pid) +{ + std::ifstream file(filePath); + if (!file.is_open()) { + RCLCPP_ERROR(this->get_logger(), "Failed to open %s", filePath.c_str()); + return false; + } + + std::string line; + while (std::getline(file, line)) { + if (line == pid) { + return true; + } + } + return false; +} + +bool CgroupSetter::addToCgroup(const std::string & cgroupPath, const std::string & pid) +{ + std::string cgroupProcFile = base_path_ + "/" + cgroupPath + "/cgroup.procs"; + std::ofstream ofs(cgroupProcFile, std::ofstream::app); + if (!ofs) { + std::cerr << "Failed to open " << cgroupProcFile << std::endl; + ofs.close(); + return false; + } + ofs << pid; + if (!ofs) { + std::cerr << "Failed to write to " << cgroupProcFile << std::endl; + ofs.close(); + return false; + } + ofs.close(); + return true; +} + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + rclcpp::executors::SingleThreadedExecutor executor; + auto options = rclcpp::NodeOptions(); + auto node = std::make_shared(options); + executor.add_node(node); + executor.spin(); + executor.remove_node(node); + rclcpp::shutdown(); +} diff --git a/system/cgroup_setter/src/cgroup_setter.hpp b/system/cgroup_setter/src/cgroup_setter.hpp new file mode 100644 index 0000000000000..81289c20e9730 --- /dev/null +++ b/system/cgroup_setter/src/cgroup_setter.hpp @@ -0,0 +1,49 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file cgroup_setter.hpp + * @brief Cgroup setter class + */ + +#ifndef CGROUP_SETTER_HPP_ +#define CGROUP_SETTER_HPP_ + +#include +#include + +#include +#include +#include + +class CgroupSetter : public rclcpp::Node +{ +public: + explicit CgroupSetter(const rclcpp::NodeOptions & options); + +private: + void checkCgroup(diagnostic_updater::DiagnosticStatusWrapper & stat); + void checkProcessAndAddToCgroup(); + std::string executeCommand(const std::string & cmd); + bool addToCgroup(const std::string & cgroupPath, const std::string & pid); + bool checkPIDExists(const std::string & filePath, const std::string & pid); + + std::map, bool> cgroup_map_; + char hostname_[256]; + diagnostic_updater::Updater updater_; + rclcpp::TimerBase::SharedPtr timer_; + std::string base_path_; +}; + +#endif // CGROUP_SETTER_HPP_