be/src/common/cgroup_memory_ctl.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // This file is copied from |
18 | | // https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/CgroupsMemoryUsageObserver.cpp |
19 | | // and modified by Doris |
20 | | |
21 | | #include "common/cgroup_memory_ctl.h" |
22 | | |
23 | | #include <filesystem> |
24 | | #include <fstream> |
25 | | #include <memory> |
26 | | #include <utility> |
27 | | |
28 | | #include "common/status.h" |
29 | | #include "util/cgroup_util.h" |
30 | | #include "util/error_util.h" |
31 | | |
32 | | namespace doris { |
33 | | |
34 | | // Is the memory controller of cgroups v2 enabled on the system? |
35 | | // Assumes that cgroupsv2_enable() is enabled. |
36 | 88.8k | Status cgroupsv2_memory_controller_enabled(bool* ret) { |
37 | 88.8k | #if defined(OS_LINUX) |
38 | 88.8k | if (!CGroupUtil::cgroupsv2_enable()) { |
39 | 88.8k | return Status::CgroupError("cgroupsv2_enable is false"); |
40 | 88.8k | } |
41 | | // According to https://docs.kernel.org/admin-guide/cgroup-v2.html, file "cgroup.controllers" defines which controllers are available |
42 | | // for the current + child cgroups. The set of available controllers can be restricted from level to level using file |
43 | | // "cgroups.subtree_control". It is therefore sufficient to check the bottom-most nested "cgroup.controllers" file. |
44 | 0 | std::string cgroup = CGroupUtil::cgroupv2_of_process(); |
45 | 0 | auto cgroup_dir = cgroup.empty() ? default_cgroups_mount : (default_cgroups_mount / cgroup); |
46 | 0 | std::ifstream controllers_file(cgroup_dir / "cgroup.controllers"); |
47 | 0 | if (!controllers_file.is_open()) { |
48 | 0 | *ret = false; |
49 | 0 | return Status::CgroupError("open cgroup.controllers failed"); |
50 | 0 | } |
51 | 0 | std::string controllers; |
52 | 0 | std::getline(controllers_file, controllers); |
53 | 0 | *ret = controllers.find("memory") != std::string::npos; |
54 | 0 | return Status::OK(); |
55 | | #else |
56 | | *ret = false; |
57 | | return Status::CgroupError("cgroupsv2 only support Linux"); |
58 | | #endif |
59 | 0 | } |
60 | | |
61 | | struct CgroupsV1Reader : CGroupMemoryCtl::ICgroupsReader { |
62 | | explicit CgroupsV1Reader(std::filesystem::path mount_file_dir) |
63 | 88.8k | : _mount_file_dir(std::move(mount_file_dir)) {} |
64 | | |
65 | 885 | Status read_memory_limit(int64_t* value) override { |
66 | 885 | RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file( |
67 | 885 | (_mount_file_dir / "memory.limit_in_bytes"), value)); |
68 | 885 | return Status::OK(); |
69 | 885 | } |
70 | | |
71 | 87.9k | Status read_memory_usage(int64_t* value) override { |
72 | 87.9k | std::unordered_map<std::string, int64_t> metrics_map; |
73 | 87.9k | CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"), |
74 | 87.9k | metrics_map); |
75 | 87.9k | *value = metrics_map["rss"]; |
76 | 87.9k | return Status::OK(); |
77 | 87.9k | } |
78 | | |
79 | | private: |
80 | | std::filesystem::path _mount_file_dir; |
81 | | }; |
82 | | |
83 | | struct CgroupsV2Reader : CGroupMemoryCtl::ICgroupsReader { |
84 | | explicit CgroupsV2Reader(std::filesystem::path mount_file_dir) |
85 | 0 | : _mount_file_dir(std::move(mount_file_dir)) {} |
86 | | |
87 | 0 | Status read_memory_limit(int64_t* value) override { |
88 | 0 | std::filesystem::path file_path = _mount_file_dir / "memory.max"; |
89 | 0 | std::string line; |
90 | 0 | std::ifstream file_stream(file_path, std::ios::in); |
91 | 0 | getline(file_stream, line); |
92 | 0 | if (file_stream.fail() || file_stream.bad()) { |
93 | 0 | return Status::CgroupError("Error reading {}: {}", file_path.string(), |
94 | 0 | get_str_err_msg()); |
95 | 0 | } |
96 | 0 | if (line == "max") { |
97 | 0 | *value = std::numeric_limits<int64_t>::max(); |
98 | 0 | return Status::OK(); |
99 | 0 | } |
100 | 0 | RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file(file_path, value)); |
101 | 0 | return Status::OK(); |
102 | 0 | } |
103 | | |
104 | 0 | Status read_memory_usage(int64_t* value) override { |
105 | 0 | RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file( |
106 | 0 | (_mount_file_dir / "memory.current"), value)); |
107 | 0 | std::unordered_map<std::string, int64_t> metrics_map; |
108 | 0 | CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"), |
109 | 0 | metrics_map); |
110 | 0 | if (*value < metrics_map["inactive_file"]) { |
111 | 0 | return Status::CgroupError("CgroupsV2Reader read_memory_usage negative memory usage"); |
112 | 0 | } |
113 | | // the reason why we subtract inactive_file described here: |
114 | | // https://github.com/ClickHouse/ClickHouse/issues/64652#issuecomment-2149630667 |
115 | 0 | *value -= metrics_map["inactive_file"]; |
116 | | // Part of "slab" that might be reclaimed, such as dentries and inodes. |
117 | | // https://arthurchiao.art/blog/cgroupv2-zh/ |
118 | 0 | *value -= metrics_map["slab_reclaimable"]; |
119 | 0 | return Status::OK(); |
120 | 0 | } |
121 | | |
122 | | private: |
123 | | std::filesystem::path _mount_file_dir; |
124 | | }; |
125 | | |
126 | 88.8k | std::pair<std::string, CGroupUtil::CgroupsVersion> get_cgroups_path() { |
127 | 88.8k | bool enable_controller; |
128 | 88.8k | auto cgroupsv2_memory_controller_st = cgroupsv2_memory_controller_enabled(&enable_controller); |
129 | 88.8k | if (CGroupUtil::cgroupsv2_enable() && cgroupsv2_memory_controller_st.ok() && |
130 | 88.8k | enable_controller) { |
131 | 0 | auto v2_memory_stat_path = CGroupUtil::get_cgroupsv2_path("memory.stat"); |
132 | 0 | auto v2_memory_current_path = CGroupUtil::get_cgroupsv2_path("memory.current"); |
133 | 0 | auto v2_memory_max_path = CGroupUtil::get_cgroupsv2_path("memory.max"); |
134 | 0 | if (v2_memory_stat_path.has_value() && v2_memory_current_path.has_value() && |
135 | 0 | v2_memory_max_path.has_value() && v2_memory_stat_path == v2_memory_current_path && |
136 | 0 | v2_memory_current_path == v2_memory_max_path) { |
137 | 0 | return {*v2_memory_stat_path, CGroupUtil::CgroupsVersion::V2}; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | 88.8k | std::string cgroup_path; |
142 | 88.8k | auto st = CGroupUtil::find_abs_cgroupv1_path("memory", &cgroup_path); |
143 | 88.8k | if (st.ok()) { |
144 | 88.8k | return {cgroup_path, CGroupUtil::CgroupsVersion::V1}; |
145 | 88.8k | } |
146 | | |
147 | 0 | return {"", CGroupUtil::CgroupsVersion::V1}; |
148 | 88.8k | } |
149 | | |
150 | 88.8k | Status get_cgroups_reader(std::shared_ptr<CGroupMemoryCtl::ICgroupsReader>& reader) { |
151 | 88.8k | const auto [cgroup_path, version] = get_cgroups_path(); |
152 | 88.8k | if (cgroup_path.empty()) { |
153 | 0 | bool enable_controller; |
154 | 0 | auto st = cgroupsv2_memory_controller_enabled(&enable_controller); |
155 | 0 | return Status::CgroupError( |
156 | 0 | "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, " |
157 | 0 | "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}", |
158 | 0 | CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(), |
159 | 0 | CGroupUtil::cgroupsv1_enable()); |
160 | 0 | } |
161 | | |
162 | 88.8k | if (version == CGroupUtil::CgroupsVersion::V2) { |
163 | 0 | reader = std::make_shared<CgroupsV2Reader>(cgroup_path); |
164 | 88.8k | } else { |
165 | 88.8k | reader = std::make_shared<CgroupsV1Reader>(cgroup_path); |
166 | 88.8k | } |
167 | 88.8k | return Status::OK(); |
168 | 88.8k | } |
169 | | |
170 | 885 | Status CGroupMemoryCtl::find_cgroup_mem_limit(int64_t* bytes) { |
171 | 885 | std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader; |
172 | 885 | RETURN_IF_ERROR(get_cgroups_reader(reader)); |
173 | 885 | RETURN_IF_ERROR(reader->read_memory_limit(bytes)); |
174 | 885 | return Status::OK(); |
175 | 885 | } |
176 | | |
177 | 87.9k | Status CGroupMemoryCtl::find_cgroup_mem_usage(int64_t* bytes) { |
178 | 87.9k | std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader; |
179 | 87.9k | RETURN_IF_ERROR(get_cgroups_reader(reader)); |
180 | 87.9k | RETURN_IF_ERROR(reader->read_memory_usage(bytes)); |
181 | 87.9k | return Status::OK(); |
182 | 87.9k | } |
183 | | |
184 | 8 | std::string CGroupMemoryCtl::debug_string() { |
185 | 8 | const auto [cgroup_path, version] = get_cgroups_path(); |
186 | 8 | if (cgroup_path.empty()) { |
187 | 0 | bool enable_controller; |
188 | 0 | auto st = cgroupsv2_memory_controller_enabled(&enable_controller); |
189 | 0 | return fmt::format( |
190 | 0 | "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, " |
191 | 0 | "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}", |
192 | 0 | CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(), |
193 | 0 | CGroupUtil::cgroupsv1_enable()); |
194 | 0 | } |
195 | | |
196 | 8 | int64_t mem_limit; |
197 | 8 | auto mem_limit_st = find_cgroup_mem_limit(&mem_limit); |
198 | | |
199 | 8 | int64_t mem_usage; |
200 | 8 | auto mem_usage_st = find_cgroup_mem_usage(&mem_usage); |
201 | | |
202 | 8 | return fmt::format( |
203 | 8 | "Process CGroup Memory Info (cgroups path: {}, cgroup version: {}): memory limit: " |
204 | 8 | "{}, " |
205 | 8 | "memory usage: {}", |
206 | 8 | cgroup_path, (version == CGroupUtil::CgroupsVersion::V1) ? "v1" : "v2", |
207 | 8 | mem_limit_st.ok() ? std::to_string(mem_limit) : mem_limit_st.to_string(), |
208 | 8 | mem_usage_st.ok() ? std::to_string(mem_usage) : mem_usage_st.to_string()); |
209 | 8 | } |
210 | | |
211 | | } // namespace doris |