Skip to content

Commit 002849c

Browse files
committedMay 10, 2024·
feat: add cgroup_setter node
Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com>
1 parent f36005b commit 002849c

File tree

7 files changed

+391
-0
lines changed

7 files changed

+391
-0
lines changed
 

‎system/cgroup_setter/CMakeLists.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(cgroup_setter)
3+
4+
find_package(autoware_cmake REQUIRED)
5+
find_package(yaml-cpp REQUIRED)
6+
find_package(Boost REQUIRED COMPONENTS filesystem system)
7+
include_directories(${Boost_INCLUDE_DIRS})
8+
9+
autoware_package()
10+
11+
ament_auto_add_executable(${PROJECT_NAME}
12+
src/cgroup_setter.cpp
13+
)
14+
15+
target_link_libraries(cgroup_setter yaml-cpp)
16+
target_link_libraries(cgroup_setter ${Boost_LIBRARIES})
17+
18+
ament_auto_package(INSTALL_TO_SHARE
19+
launch
20+
config
21+
)

‎system/cgroup_setter/README.md

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# cgroup_setter
2+
3+
## Purpose
4+
5+
This package set a PID to a custom cgroup.
6+
The PID is found by `pgrep -f`.
7+
8+
## Inputs / Outputs
9+
10+
### Outputs
11+
12+
| Name | Type | Description |
13+
| -------------- | ---------------------------------------- | ------------------- |
14+
| `/diagnostics` | `diagnostic_msgs::msgs::DiagnosticArray` | Diagnostics outputs |
15+
16+
## Parameters
17+
18+
### Node Parameters
19+
20+
| Name | Type | Default Value | Explanation | Reconfigurable |
21+
| --------------------------- | ------ | --------------------------------------------------- | --------------------------------------- | -------------- |
22+
| `cgroup_setting_config_path`| string | `$(find-pkg-share cgroup_setter)/config/cgroup.yaml`| yaml file path | |
23+
24+
### YAML format for cgroup_setter
25+
26+
format
27+
```yaml
28+
base_path: "/sys/fs/cgroup"
29+
settings:
30+
- directory: "xxx/xxx"
31+
search_word:
32+
- "xxxxx"
33+
```
34+
The following is an example of joining the PID from running `pgrep -f`
35+
with the keyword `__node:=system_monitor_container` to a cgroup named `/sys/fs/cgrpi/autoware/system_monitor`.
36+
37+
example
38+
```yaml
39+
base_path: "/sys/fs/cgroup"
40+
settings:
41+
- directory: "autoware/system_monitor"
42+
search_word:
43+
- "__node:=system_monitor_container"
44+
```
45+
#### Rules
46+
- The value of settings must be a sequence.
47+
48+
example
49+
```yaml
50+
# NG
51+
base_path: "/sys/fs/cgroup"
52+
settings:
53+
directory: "autoware/system_monitor" # - directory
54+
search_word:
55+
- "__node:=system_monitor_container"
56+
```
57+
- The value of search_word must be a sequence.
58+
59+
example
60+
```yaml
61+
# NG
62+
base_path: "/sys/fs/cgroup"
63+
settings:
64+
- directory: "autoware/system_monitor"
65+
search_word: "__node:=system_monitor_container" # ["__node:=system_monitor_container"] or - "__node:=system_monitor_container"
66+
```
67+
68+
69+
## Assumptions / Known limits
70+
71+
TBD.
72+
73+
## Usage
74+
75+
### launch
76+
77+
```sh
78+
ros2 launch cgroup_setter cgroup_setter.launch.xml
79+
```
80+
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
base_path: "/sys/fs/cgroup"
2+
settings:
3+
- directory: "autoware/system_monitor"
4+
search_word:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<launch>
2+
<arg name="cgroup_setting_config_path" default="$(find-pkg-share cgroup_setter)/config/cgroup.yaml"/>
3+
<node pkg="cgroup_setter" exec="cgroup_setter" name="cgroup_setter">
4+
<param name="cgroup_setting_config_path" value="$(var cgroup_setting_config_path)"/>
5+
</node>
6+
</launch>

‎system/cgroup_setter/package.xml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>cgroup_setter</name>
5+
<version>0.1.0</version>
6+
<description>set pid to a cgroup</description>
7+
<maintainer email="tetsuhiro.kawaguchi@tier4.jp">TetsuKawa</maintainer>
8+
<license>Apache License 2.0</license>
9+
10+
<buildtool_depend>ament_cmake_auto</buildtool_depend>
11+
<buildtool_depend>autoware_cmake</buildtool_depend>
12+
13+
<depend>diagnostic_msgs</depend>
14+
<depend>diagnostic_updater</depend>
15+
<depend>yaml-cpp</depend>
16+
<depend>libboost-filesystem-dev</depend>
17+
<depend>rclcpp</depend>
18+
<depend>rclcpp_components</depend>
19+
20+
<test_depend>ament_lint_auto</test_depend>
21+
<test_depend>autoware_lint_common</test_depend>
22+
23+
<export>
24+
<build_type>ament_cmake</build_type>
25+
</export>
26+
</package>
+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Copyright 2024 Autoware Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file cgroup_setter.cpp
17+
* @brief Cgroup setter class
18+
*/
19+
20+
#include "cgroup_setter.hpp"
21+
#include <iostream>
22+
#include <fstream>
23+
#include <sstream>
24+
#include <cstring>
25+
#include <unistd.h>
26+
#include <boost/process.hpp>
27+
#include <yaml-cpp/yaml.h>
28+
29+
namespace bp = boost::process;
30+
31+
CgroupSetter::CgroupSetter(const rclcpp::NodeOptions & options)
32+
: Node("cgroup_setter_node", options),
33+
updater_(this)
34+
{
35+
try {
36+
std::string yaml_path = this->declare_parameter<std::string>("cgroup_setting_config_path");
37+
YAML::Node config = YAML::LoadFile(yaml_path);
38+
if (config["base_path"]) {
39+
base_path_ = config["base_path"].as<std::string>();
40+
} else {
41+
RCLCPP_ERROR(this->get_logger(), "base_path is not set in the config file.");
42+
return;
43+
}
44+
45+
if (!config["settings"]) {
46+
RCLCPP_ERROR(this->get_logger(), "settings is not set in the config file.");
47+
return;
48+
}
49+
50+
for (auto setting : config["settings"]) {
51+
if (!setting["directory"] || !setting["search_word"]) {
52+
RCLCPP_ERROR(this->get_logger(), "directory or search_word is not set in the config file.");
53+
return;
54+
}
55+
56+
for (auto word : setting["search_word"]) {
57+
std::pair<std::string, std::string> tmp_pair = std::make_pair(setting["directory"].as<std::string>(), word.as<std::string>());
58+
cgroup_map_[tmp_pair] = false;
59+
}
60+
}
61+
} catch (const std::exception& e) {
62+
RCLCPP_ERROR(this->get_logger(), "Failed to load the config file.");
63+
return;
64+
}
65+
66+
gethostname(hostname_, sizeof(hostname_));
67+
updater_.setHardwareID(hostname_);
68+
updater_.add("Cgroup Setting", this, &CgroupSetter::checkCgroup);
69+
70+
// Timer
71+
using namespace std::chrono_literals;
72+
timer_ = rclcpp::create_timer(
73+
this, this->get_clock(), 1s, std::bind(&CgroupSetter::checkProcessAndAddToCgroup, this));
74+
}
75+
76+
void CgroupSetter::checkCgroup(diagnostic_updater::DiagnosticStatusWrapper & stat) {
77+
bool allOK = true;
78+
for (auto& entry : cgroup_map_) {
79+
if (entry.second) {
80+
stat.add(entry.first.first + " " + entry.first.second, "OK");
81+
} else {
82+
allOK = false;
83+
stat.add(entry.first.first + " " + entry.first.second, "NG");
84+
}
85+
}
86+
if (allOK) {
87+
if (timer_->is_active()) timer->cancel();
88+
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "All processes are added to cgroup.");
89+
} else {
90+
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN, "Some processes are not added to cgroup.");
91+
}
92+
}
93+
94+
void CgroupSetter::checkProcessAndAddToCgroup() {
95+
for (auto& entry : cgroup_map_) {
96+
if (entry.second) {
97+
continue;
98+
}
99+
std::string word = entry.first.second;
100+
std::string result = executeCommand(word);
101+
if (!result.empty()) {
102+
std::istringstream iss(result);
103+
std::string pid;
104+
bool allAdded = true;
105+
while (std::getline(iss, pid, '\n')) {
106+
if (!pid.empty() && addToCgroup(entry.first.first, pid)) {
107+
if (checkPIDExists(base_path_ + "/" + entry.first.first + "/cgroup.procs", pid)) {
108+
RCLCPP_INFO(this->get_logger(), "Added all PIDs to cgroup. %s %s", entry.first.second.c_str(), pid.c_str());
109+
} else {
110+
allAdded = false;
111+
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());
112+
}
113+
} else {
114+
allAdded = false;
115+
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());
116+
}
117+
}
118+
if (allAdded) {
119+
entry.second = true;
120+
}
121+
} else {
122+
RCLCPP_ERROR(this->get_logger(), "Failed to get PID. %s %s", entry.first.second.c_str(), result.c_str());
123+
}
124+
}
125+
}
126+
127+
std::string CgroupSetter::executeCommand(const std::string& search_word) {
128+
int out_fd[2];
129+
if (pipe2(out_fd, O_CLOEXEC) != 0) {
130+
RCLCPP_ERROR(this->get_logger(), "pipe2 error");
131+
return "";
132+
}
133+
bp::pipe out_pipe{out_fd[0], out_fd[1]};
134+
bp::ipstream is_out{std::move(out_pipe)};
135+
136+
int err_fd[2];
137+
if (pipe2(err_fd, O_CLOEXEC) != 0) {
138+
RCLCPP_ERROR(this->get_logger(), "pipe2 error");
139+
return "";
140+
}
141+
bp::pipe err_pipe{err_fd[0], err_fd[1]};
142+
bp::ipstream is_err{std::move(err_pipe)};
143+
auto cmd=bp::search_path("pgrep");
144+
std::vector<std::string> args;
145+
args.push_back("-f");
146+
args.push_back(search_word);
147+
bp::child c(cmd, bp::args=args, bp::std_out > is_out, bp::std_err > is_err);
148+
c.wait();
149+
if (c.exit_code() != 0) {
150+
std::ostringstream os;
151+
is_err >> os.rdbuf();
152+
RCLCPP_ERROR(this->get_logger(), os.str().c_str());
153+
return "";
154+
} else {
155+
std::ostringstream os;
156+
os << is_out.rdbuf();
157+
std::string output = os.str();
158+
return output;
159+
}
160+
}
161+
162+
bool CgroupSetter::checkPIDExists(const std::string& filePath, const std::string & pid) {
163+
std::ifstream file(filePath);
164+
if (!file.is_open()) {
165+
RCLCPP_ERROR(this->get_logger(), "Failed to open %s", filePath.c_str());
166+
return false;
167+
}
168+
169+
std::string line;
170+
while (std::getline(file, line)) {
171+
if (line == pid) {
172+
return true;
173+
}
174+
}
175+
return false;
176+
}
177+
178+
bool CgroupSetter::addToCgroup(const std::string& cgroupPath, const std::string& pid) {
179+
std::string cgroupProcFile = base_path_ + "/" + cgroupPath + "/cgroup.procs";
180+
std::ofstream ofs(cgroupProcFile, std::ofstream::app);
181+
if (!ofs) {
182+
std::cerr << "Failed to open " << cgroupProcFile << std::endl;
183+
ofs.close();
184+
return false;
185+
}
186+
ofs << pid;
187+
if (!ofs) {
188+
std::cerr << "Failed to write to " << cgroupProcFile << std::endl;
189+
ofs.close();
190+
return false;
191+
}
192+
ofs.close();
193+
return true;
194+
}
195+
196+
int main(int argc, char ** argv) {
197+
rclcpp::init(argc, argv);
198+
rclcpp::executors::SingleThreadedExecutor executor;
199+
auto options = rclcpp::NodeOptions();
200+
auto node = std::make_shared<CgroupSetter>(options);
201+
executor.add_node(node);
202+
executor.spin();
203+
executor.remove_node(node);
204+
rclcpp::shutdown();
205+
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Tier IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file cgroup_setter.hpp
17+
* @brief Cgroup setter class
18+
*/
19+
20+
#ifndef CGROUP_SETTER_HPP
21+
#define CGROUP_SETTER_HPP
22+
23+
#include <rclcpp/rclcpp.hpp>
24+
#include <diagnostic_updater/diagnostic_updater.hpp>
25+
#include <map>
26+
#include <string>
27+
#include <utility>
28+
29+
30+
class CgroupSetter : public rclcpp::Node
31+
{
32+
public:
33+
explicit CgroupSetter(const rclcpp::NodeOptions& options);
34+
35+
private:
36+
void checkCgroup(diagnostic_updater::DiagnosticStatusWrapper & stat);
37+
void checkProcessAndAddToCgroup();
38+
std::string executeCommand(const std::string& cmd);
39+
bool addToCgroup(const std::string& cgroupPath, const std::string& pid);
40+
bool checkPIDExists(const std::string& filePath, const std::string & pid);
41+
42+
std::map<std::pair<std::string, std::string>, bool> cgroup_map_;
43+
char hostname_[256];
44+
diagnostic_updater::Updater updater_;
45+
rclcpp::TimerBase::SharedPtr timer_;
46+
std::string base_path_;
47+
};
48+
49+
#endif // CGROUP_SETTER_HPP

0 commit comments

Comments
 (0)
Please sign in to comment.