diff --git a/include/pfs/parsers.hpp b/include/pfs/parsers.hpp index 2006770..5a751a7 100644 --- a/include/pfs/parsers.hpp +++ b/include/pfs/parsers.hpp @@ -67,6 +67,7 @@ std::pair parse_filesystems_line(const std::string& line); std::pair parse_meminfo_line(const std::string& line); zone parse_buddyinfo_line(const std::string& line); +cgroup parse_cgroup_line(const std::string& line); cgroup_controller parse_cgroup_controller_line(const std::string& line); load_average parse_loadavg_line(const std::string& line); uptime parse_uptime_line(const std::string& line); diff --git a/include/pfs/task.hpp b/include/pfs/task.hpp index 8e1d5dd..d0cc9db 100644 --- a/include/pfs/task.hpp +++ b/include/pfs/task.hpp @@ -17,8 +17,8 @@ #ifndef PFS_TASK_HPP #define PFS_TASK_HPP -#include #include +#include #include #include @@ -48,6 +48,8 @@ class task final const std::string& dir() const; public: // Getters + std::vector get_cgroups() const; + std::vector get_cmdline(size_t max_size = 65536) const; std::string get_comm() const; diff --git a/include/pfs/types.hpp b/include/pfs/types.hpp index 9bdc2a8..aa7d84a 100644 --- a/include/pfs/types.hpp +++ b/include/pfs/types.hpp @@ -559,6 +559,13 @@ struct cgroup_controller bool enabled; }; +struct cgroup +{ + unsigned hierarchy; + std::vector controllers; + std::string pathname; +}; + } // namespace pfs #endif // PFS_TYPES_HPP diff --git a/sample/enum_task.cpp b/sample/enum_task.cpp index 0975672..01c0849 100644 --- a/sample/enum_task.cpp +++ b/sample/enum_task.cpp @@ -71,6 +71,9 @@ static void enum_task(const pfs::task& task) auto mountinfo = task.get_mountinfo(); print(mountinfo); + auto cgroups = task.get_cgroups(); + print(cgroups); + auto ns = task.get_ns(); print(ns); diff --git a/sample/format.hpp b/sample/format.hpp index 96ae0db..80b8885 100644 --- a/sample/format.hpp +++ b/sample/format.hpp @@ -663,3 +663,11 @@ inline std::ostream& operator<<(std::ostream& out, out << "enabled[" << controller.enabled << "] "; return out; } + +inline std::ostream& operator<<(std::ostream& out, const pfs::cgroup& cg) +{ + out << "hierarchy[" << cg.hierarchy << "] "; + out << "controllers[" << join(cg.controllers) << "] "; + out << "pathname[" << cg.pathname << "] "; + return out; +} diff --git a/src/parsers/cgroup.cpp b/src/parsers/cgroup.cpp new file mode 100644 index 0000000..880b1da --- /dev/null +++ b/src/parsers/cgroup.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2020-present Daniel Trugman + * + * 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. + */ + +#include "pfs/parsers.hpp" +#include "pfs/utils.hpp" + +namespace pfs { +namespace impl { +namespace parsers { + +cgroup parse_cgroup_line(const std::string& line) +{ + // Some examples: + // clang-format off + // 12:devices:/user.slice + // 11:perf_event:/ + // 10:hugetlb:/ + // 9:rdma:/ + // 8:memory:/user.slice + // 7:blkio:/user.slice + // 6:cpuset:/ + // 5:cpu,cpuacct:/user.slice + // 4:pids:/user.slice/user-1000.slice/session-174.scope + // 3:freezer:/ + // 2:net_cls,net_prio:/ + // 1:name=systemd:/user.slice/user-1000.slice/session-174.scope + // 0::/user.slice/user-1000.slice/session-174.scope + // clang-format on + + enum token + { + HIERARCHY = 0, + CONTROLLERS = 1, + PATHNAME = 2, + COUNT + }; + + static const char DELIM = ':'; + static const char CONTROLLERS_DELIM = ','; + + auto tokens = utils::split(line, DELIM, /* keep_empty = */ true); + if (tokens.size() != COUNT) + { + throw parser_error("Corrupted cgroup line - Unexpected tokens count", + line); + } + + try + { + cgroup cg; + + utils::stot(tokens[HIERARCHY], cg.hierarchy); + + cg.controllers = utils::split(tokens[CONTROLLERS], CONTROLLERS_DELIM); + + cg.pathname = tokens[PATHNAME]; + + return cg; + } + catch (const std::invalid_argument& ex) + { + throw parser_error("Corrupted cgroup - Invalid argument", line); + } + catch (const std::out_of_range& ex) + { + throw parser_error("Corrupted cgroup - Out of range", line); + } +} + +} // namespace parsers +} // namespace impl +} // namespace pfs diff --git a/src/task.cpp b/src/task.cpp index 748656c..12c7116 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -68,6 +68,17 @@ const std::string& task::dir() const return _task_root; } +std::vector task::get_cgroups() const +{ + static const std::string CGROUP_FILE("cgroup"); + auto path = _task_root + CGROUP_FILE; + + std::vector output; + parsers::parse_lines(path, std::back_inserter(output), + parsers::parse_cgroup_line); + return output; +} + std::string task::get_exe() const { static const std::string EXE_FILE("exe"); diff --git a/test/cgroup.cpp b/test/cgroup.cpp new file mode 100644 index 0000000..4bf7f69 --- /dev/null +++ b/test/cgroup.cpp @@ -0,0 +1,59 @@ +#include + +#include "catch.hpp" +#include "test_utils.hpp" + +#include "pfs/parsers.hpp" + +using namespace pfs::impl::parsers; + +TEST_CASE("Parse corrupted cgroup", "[procfs][cgroup]") +{ + // Missing last token (enabled) + std::string line = "1:name=systemd"; + + REQUIRE_THROWS_AS(parse_cgroup_line(line), pfs::parser_error); +} + +TEST_CASE("Parse cgroup", "[procfs][cgroup]") +{ + pfs::cgroup expected; + + SECTION("Root pathname") + { + expected.hierarchy = 6; + expected.controllers = {std::string("cpuset")}; + expected.pathname = "/"; + } + + SECTION("Long pathname") + { + expected.hierarchy = 4; + expected.controllers = {std::string("pids")}; + expected.pathname = "/user.slice/user-1000.slice/session-174.scope"; + } + + SECTION("Key value controller") + { + expected.hierarchy = 1; + expected.controllers = {std::string("name=systemd")}; + expected.pathname = "/user.slice/user-1000.slice/session-174.scope"; + } + + SECTION("V2 controller") + { + expected.hierarchy = 0; + expected.controllers = {}; + expected.pathname = "/user.slice/user-1000.slice/session-174.scope"; + } + + std::ostringstream line; + line << expected.hierarchy << ':'; + line << join(expected.controllers) << ':'; + line << expected.pathname; + + auto cg = parse_cgroup_line(line.str()); + REQUIRE(cg.hierarchy == expected.hierarchy); + REQUIRE(cg.controllers == expected.controllers); + REQUIRE(cg.pathname == expected.pathname); +} diff --git a/test/test_utils.hpp b/test/test_utils.hpp index 65dcf8b..6db420a 100644 --- a/test/test_utils.hpp +++ b/test/test_utils.hpp @@ -66,4 +66,22 @@ inline std::string create_temp_file(const std::vector& lines) return std::string(temp); } +template +inline std::string join(const T& container) +{ + if (container.empty()) + { + return ""; + } + + std::ostringstream out; + for (const auto& v : container) + { + out << v << ','; + } + auto out_str = out.str(); + out_str.pop_back(); + return out_str; +} + #endif // PFS_TEST_UTILS_HPP