Coverage Report

Created: 2024-11-20 12:06

/root/doris/be/src/util/cgroup_util.cpp
Line
Count
Source (jump to first uncovered line)
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
18
#include "util/cgroup_util.h"
19
20
#include <algorithm>
21
#include <fstream>
22
#include <utility>
23
#include <vector>
24
25
#include "gutil/stringprintf.h"
26
#include "gutil/strings/escaping.h"
27
#include "gutil/strings/split.h"
28
#include "gutil/strings/substitute.h"
29
#include "io/fs/local_file_system.h"
30
#include "util/error_util.h"
31
#include "util/string_parser.hpp"
32
33
using strings::CUnescape;
34
using strings::Split;
35
using strings::SkipWhitespace;
36
using std::pair;
37
38
namespace doris {
39
40
8
bool CGroupUtil::cgroupsv1_enable() {
41
8
    bool exists = true;
42
8
    Status st = io::global_local_filesystem()->exists("/proc/cgroups", &exists);
43
8
    return st.ok() && exists;
44
8
}
45
46
14
bool CGroupUtil::cgroupsv2_enable() {
47
14
#if defined(OS_LINUX)
48
    // This file exists iff the host has cgroups v2 enabled.
49
14
    auto controllers_file = default_cgroups_mount / "cgroup.controllers";
50
14
    bool exists = true;
51
14
    Status st = io::global_local_filesystem()->exists(controllers_file, &exists);
52
14
    return st.ok() && exists;
53
#else
54
    return false;
55
#endif
56
14
}
57
58
7
Status CGroupUtil::find_global_cgroupv1(const string& subsystem, string* path) {
59
7
    std::ifstream proc_cgroups("/proc/self/cgroup", std::ios::in);
60
7
    string line;
61
63
    while (true) {
62
63
        if (proc_cgroups.fail()) {
63
0
            return Status::CgroupError("Error reading /proc/self/cgroup: {}", get_str_err_msg());
64
63
        } else if (proc_cgroups.peek() == std::ifstream::traits_type::eof()) {
65
0
            return Status::CgroupError("Could not find subsystem {} in /proc/self/cgroup",
66
0
                                       subsystem);
67
0
        }
68
        // The line format looks like this:
69
        // 4:memory:/user.slice
70
        // 9:cpu,cpuacct:/user.slice
71
        // so field size will be 3
72
63
        getline(proc_cgroups, line);
73
63
        if (!proc_cgroups.good()) {
74
0
            continue;
75
0
        }
76
63
        std::vector<string> fields = Split(line, ":");
77
        // ":" in the path does not appear to be escaped - bail in the unusual case that
78
        // we get too many tokens.
79
63
        if (fields.size() != 3) {
80
0
            return Status::InvalidArgument(
81
0
                    "Could not parse line from /proc/self/cgroup - had {} > 3 tokens: '{}'",
82
0
                    fields.size(), line);
83
0
        }
84
63
        std::vector<string> subsystems = Split(fields[1], ",");
85
63
        auto it = std::find(subsystems.begin(), subsystems.end(), subsystem);
86
63
        if (it != subsystems.end()) {
87
7
            *path = std::move(fields[2]);
88
7
            return Status::OK();
89
7
        }
90
63
    }
91
7
}
92
93
14
static Status unescape_path(const string& escaped, string* unescaped) {
94
14
    string err;
95
14
    if (!CUnescape(escaped, unescaped, &err)) {
96
0
        return Status::InvalidArgument("Could not unescape path '{}': {}", escaped, err);
97
0
    }
98
14
    return Status::OK();
99
14
}
100
101
7
Status CGroupUtil::find_cgroupv1_mounts(const string& subsystem, pair<string, string>* result) {
102
7
    std::ifstream mountinfo("/proc/self/mountinfo", std::ios::in);
103
7
    string line;
104
70
    while (true) {
105
70
        if (mountinfo.fail() || mountinfo.bad()) {
106
0
            return Status::CgroupError("Error reading /proc/self/mountinfo: {}", get_str_err_msg());
107
70
        } else if (mountinfo.eof()) {
108
0
            return Status::CgroupError("Could not find subsystem {} in /proc/self/mountinfo",
109
0
                                       subsystem);
110
0
        }
111
        // The relevant lines look like below (see proc manpage for full documentation). The
112
        // first example is running outside of a container, the second example is running
113
        // inside a docker container. Field 3 is the path relative to the root CGroup on
114
        // the host and Field 4 is the mount point from this process's point of view.
115
        // 34 29 0:28 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:15 -
116
        //    cgroup cgroup rw,memory
117
        // 275 271 0:28 /docker/f23eee6f88c2ba99fcce /sys/fs/cgroup/memory
118
        //    ro,nosuid,nodev,noexec,relatime master:15 - cgroup cgroup rw,memory
119
70
        getline(mountinfo, line);
120
70
        if (!mountinfo.good()) {
121
0
            continue;
122
0
        }
123
70
        std::vector<string> fields = Split(line, " ", SkipWhitespace());
124
70
        if (fields.size() < 7) {
125
0
            return Status::InvalidArgument(
126
0
                    "Could not parse line from /proc/self/mountinfo - had {} > 7 tokens: '{}'",
127
0
                    fields.size(), line);
128
0
        }
129
70
        if (fields[fields.size() - 3] != "cgroup") {
130
42
            continue;
131
42
        }
132
        // This is a cgroup mount. Check if it's the mount we're looking for.
133
28
        std::vector<string> cgroup_opts = Split(fields[fields.size() - 1], ",", SkipWhitespace());
134
28
        auto it = std::find(cgroup_opts.begin(), cgroup_opts.end(), subsystem);
135
28
        if (it == cgroup_opts.end()) {
136
21
            continue;
137
21
        }
138
        // This is the right mount.
139
7
        string mount_path, system_path;
140
7
        RETURN_IF_ERROR(unescape_path(fields[4], &mount_path));
141
7
        RETURN_IF_ERROR(unescape_path(fields[3], &system_path));
142
        // Strip trailing "/" so that both returned paths match in whether they have a
143
        // trailing "/".
144
7
        if (system_path[system_path.size() - 1] == '/') {
145
0
            system_path.pop_back();
146
0
        }
147
7
        *result = {mount_path, system_path};
148
7
        return Status::OK();
149
7
    }
150
7
}
151
152
7
Status CGroupUtil::find_abs_cgroupv1_path(const string& subsystem, string* path) {
153
7
    if (!cgroupsv1_enable()) {
154
0
        return Status::InvalidArgument("cgroup is not enabled!");
155
0
    }
156
7
    RETURN_IF_ERROR(find_global_cgroupv1(subsystem, path));
157
7
    pair<string, string> paths;
158
7
    RETURN_IF_ERROR(find_cgroupv1_mounts(subsystem, &paths));
159
7
    const string& mount_path = paths.first;
160
7
    const string& system_path = paths.second;
161
7
    if (path->compare(0, system_path.size(), system_path) != 0) {
162
0
        return Status::InvalidArgument("Expected CGroup path '{}' to start with '{}'", *path,
163
0
                                       system_path);
164
0
    }
165
7
    path->replace(0, system_path.size(), mount_path);
166
7
    return Status::OK();
167
7
}
168
169
0
std::string CGroupUtil::cgroupv2_of_process() {
170
0
#if defined(OS_LINUX)
171
0
    if (!cgroupsv2_enable()) {
172
0
        return "";
173
0
    }
174
    // All PIDs assigned to a cgroup are in /sys/fs/cgroups/{cgroup_name}/cgroup.procs
175
    // A simpler way to get the membership is:
176
0
    std::ifstream cgroup_name_file("/proc/self/cgroup");
177
0
    if (!cgroup_name_file.is_open()) {
178
0
        return "";
179
0
    }
180
    // With cgroups v2, there will be a *single* line with prefix "0::/"
181
    // (see https://docs.kernel.org/admin-guide/cgroup-v2.html)
182
0
    std::string cgroup;
183
0
    std::getline(cgroup_name_file, cgroup);
184
0
    static const std::string v2_prefix = "0::/";
185
0
    if (!cgroup.starts_with(v2_prefix)) {
186
0
        return "";
187
0
    }
188
0
    cgroup = cgroup.substr(v2_prefix.length());
189
0
    return cgroup;
190
#else
191
    return "";
192
#endif
193
0
}
194
195
0
std::optional<std::string> CGroupUtil::get_cgroupsv2_path(const std::string& subsystem) {
196
0
#if defined(OS_LINUX)
197
0
    if (!CGroupUtil::cgroupsv2_enable()) {
198
0
        return {};
199
0
    }
200
201
0
    std::string cgroup = CGroupUtil::cgroupv2_of_process();
202
0
    auto current_cgroup = cgroup.empty() ? default_cgroups_mount : (default_cgroups_mount / cgroup);
203
204
    // Return the bottom-most nested current memory file. If there is no such file at the current
205
    // level, try again at the parent level as memory settings are inherited.
206
0
    while (current_cgroup != default_cgroups_mount.parent_path()) {
207
0
        if (std::filesystem::exists(current_cgroup / subsystem)) {
208
0
            return {current_cgroup};
209
0
        }
210
0
        current_cgroup = current_cgroup.parent_path();
211
0
    }
212
0
    return {};
213
#else
214
    return {};
215
#endif
216
0
}
217
218
Status CGroupUtil::read_int_line_from_cgroup_file(const std::filesystem::path& file_path,
219
5
                                                  int64_t* val) {
220
5
    std::ifstream file_stream(file_path, std::ios::in);
221
5
    string line;
222
5
    getline(file_stream, line);
223
5
    if (file_stream.fail() || file_stream.bad()) {
224
1
        return Status::CgroupError("Error reading {}: {}", file_path.string(), get_str_err_msg());
225
1
    }
226
4
    StringParser::ParseResult pr;
227
    // Parse into an int64_t If it overflows, returning the max value of int64_t is ok because that
228
    // is effectively unlimited.
229
4
    *val = StringParser::string_to_int<int64_t>(line.c_str(), line.size(), &pr);
230
4
    if ((pr != StringParser::PARSE_SUCCESS && pr != StringParser::PARSE_OVERFLOW)) {
231
0
        return Status::InvalidArgument("Failed to parse {} as int64: '{}'", file_path.string(),
232
0
                                       line);
233
0
    }
234
4
    return Status::OK();
235
4
}
236
237
void CGroupUtil::read_int_metric_from_cgroup_file(
238
        const std::filesystem::path& file_path,
239
5
        std::unordered_map<std::string, int64_t>& metrics_map) {
240
5
    std::ifstream cgroup_file(file_path, std::ios::in);
241
5
    std::string line;
242
144
    while (cgroup_file.good() && !cgroup_file.eof()) {
243
139
        getline(cgroup_file, line);
244
139
        std::vector<std::string> fields = strings::Split(line, " ", strings::SkipWhitespace());
245
139
        if (fields.size() < 2) {
246
4
            continue;
247
4
        }
248
135
        std::string key = fields[0].substr(0, fields[0].size());
249
250
135
        StringParser::ParseResult result;
251
135
        auto value =
252
135
                StringParser::string_to_int<int64_t>(fields[1].data(), fields[1].size(), &result);
253
254
135
        if (result == StringParser::PARSE_SUCCESS) {
255
135
            if (fields.size() == 2) {
256
135
                metrics_map[key] = value;
257
135
            } else if (fields[2] == "kB") {
258
0
                metrics_map[key] = value * 1024L;
259
0
            }
260
135
        }
261
135
    }
262
5
    if (cgroup_file.is_open()) {
263
4
        cgroup_file.close();
264
4
    }
265
5
}
266
267
} // namespace doris