Coverage Report

Created: 2026-06-12 09:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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
226k
Status cgroupsv2_memory_controller_enabled(bool* ret) {
37
226k
#if defined(OS_LINUX)
38
226k
    if (!CGroupUtil::cgroupsv2_enable()) {
39
226k
        return Status::CgroupError("cgroupsv2_enable is false");
40
226k
    }
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
226k
            : _mount_file_dir(std::move(mount_file_dir)) {}
64
65
2.22k
    Status read_memory_limit(int64_t* value) override {
66
2.22k
        RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file(
67
2.22k
                (_mount_file_dir / "memory.limit_in_bytes"), value));
68
2.22k
        return Status::OK();
69
2.22k
    }
70
71
223k
    Status read_memory_usage(int64_t* value) override {
72
223k
        std::unordered_map<std::string, int64_t> metrics_map;
73
223k
        CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"),
74
223k
                                                     metrics_map);
75
223k
        *value = metrics_map["rss"];
76
223k
        return Status::OK();
77
223k
    }
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
        // This means no limit, for example, all process in linux will belong to a cgroup, and
97
        // the default value of the memory limit in memory.max file is  "max", which means no limit.
98
0
        if (line == "max") {
99
0
            *value = std::numeric_limits<int64_t>::max();
100
0
            return Status::OK();
101
0
        }
102
0
        RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file(file_path, value));
103
0
        return Status::OK();
104
0
    }
105
106
0
    Status read_memory_usage(int64_t* value) override {
107
0
        RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file(
108
0
                (_mount_file_dir / "memory.current"), value));
109
0
        std::unordered_map<std::string, int64_t> metrics_map;
110
0
        CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"),
111
0
                                                     metrics_map);
112
0
        int64_t inactive_file =
113
0
                metrics_map.contains("inactive_file") ? metrics_map["inactive_file"] : 0;
114
0
        int64_t active_file = metrics_map.contains("active_file") ? metrics_map["active_file"] : 0;
115
0
        int64_t slab_reclaimable =
116
0
                metrics_map.contains("slab_reclaimable") ? metrics_map["slab_reclaimable"] : 0;
117
0
        if (inactive_file < 0 || active_file < 0 || slab_reclaimable < 0) {
118
            // In this scenario, not return error, ignore it and print log.
119
0
            LOG(WARNING) << "CgroupsV2Reader read_memory_usage missing expected metrics in "
120
0
                            "memory.stat, inactive_file: "
121
0
                         << inactive_file << ", active_file: " << active_file
122
0
                         << ", slab_reclaimable: " << slab_reclaimable;
123
0
            return Status::OK();
124
0
        }
125
126
0
        const int64_t reclaimable_usage = inactive_file + active_file + slab_reclaimable;
127
0
        if (*value < reclaimable_usage) {
128
0
            LOG(WARNING)
129
0
                    << "CgroupsV2Reader read_memory_usage negative memory usage, not - reclaimable "
130
0
                       "usage any more, just return memory.current: "
131
0
                    << *value << ", inactive_file: " << inactive_file
132
0
                    << ", active_file: " << active_file
133
0
                    << ", slab_reclaimable: " << slab_reclaimable;
134
            // In this case, do not return an error, just ignore the negative usage and continue.
135
            // If return error, the upper system will use os available memory instead of cgroup available memory, which may cause OOM more easily.
136
0
            return Status::OK();
137
0
        }
138
        // The reclaimable file cache described here should not be counted as used memory:
139
        // https://github.com/ClickHouse/ClickHouse/issues/64652#issuecomment-2149630667
140
        // Part of "slab" that might be reclaimed, such as dentries and inodes.
141
        // https://arthurchiao.art/blog/cgroupv2-zh/
142
0
        *value -= reclaimable_usage;
143
0
        return Status::OK();
144
0
    }
145
146
private:
147
    std::filesystem::path _mount_file_dir;
148
};
149
150
226k
std::pair<std::string, CGroupUtil::CgroupsVersion> get_cgroups_path() {
151
226k
    bool enable_controller;
152
226k
    auto cgroupsv2_memory_controller_st = cgroupsv2_memory_controller_enabled(&enable_controller);
153
226k
    if (CGroupUtil::cgroupsv2_enable() && cgroupsv2_memory_controller_st.ok() &&
154
226k
        enable_controller) {
155
0
        auto v2_memory_stat_path = CGroupUtil::get_cgroupsv2_path("memory.stat");
156
0
        auto v2_memory_current_path = CGroupUtil::get_cgroupsv2_path("memory.current");
157
0
        auto v2_memory_max_path = CGroupUtil::get_cgroupsv2_path("memory.max");
158
0
        if (v2_memory_stat_path.has_value() && v2_memory_current_path.has_value() &&
159
0
            v2_memory_max_path.has_value() && v2_memory_stat_path == v2_memory_current_path &&
160
0
            v2_memory_current_path == v2_memory_max_path) {
161
0
            return {*v2_memory_stat_path, CGroupUtil::CgroupsVersion::V2};
162
0
        }
163
0
    }
164
165
226k
    std::string cgroup_path;
166
226k
    auto st = CGroupUtil::find_abs_cgroupv1_path("memory", &cgroup_path);
167
226k
    if (st.ok()) {
168
226k
        return {cgroup_path, CGroupUtil::CgroupsVersion::V1};
169
226k
    }
170
171
0
    return {"", CGroupUtil::CgroupsVersion::V1};
172
226k
}
173
174
226k
Status get_cgroups_reader(std::shared_ptr<CGroupMemoryCtl::ICgroupsReader>& reader) {
175
226k
    const auto [cgroup_path, version] = get_cgroups_path();
176
226k
    if (cgroup_path.empty()) {
177
0
        bool enable_controller;
178
0
        auto st = cgroupsv2_memory_controller_enabled(&enable_controller);
179
0
        return Status::CgroupError(
180
0
                "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, "
181
0
                "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}",
182
0
                CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(),
183
0
                CGroupUtil::cgroupsv1_enable());
184
0
    }
185
186
226k
    if (version == CGroupUtil::CgroupsVersion::V2) {
187
0
        reader = std::make_shared<CgroupsV2Reader>(cgroup_path);
188
226k
    } else {
189
226k
        reader = std::make_shared<CgroupsV1Reader>(cgroup_path);
190
226k
    }
191
226k
    return Status::OK();
192
226k
}
193
194
2.22k
Status CGroupMemoryCtl::find_cgroup_mem_limit(int64_t* bytes) {
195
2.22k
    std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader;
196
2.22k
    RETURN_IF_ERROR(get_cgroups_reader(reader));
197
2.22k
    RETURN_IF_ERROR(reader->read_memory_limit(bytes));
198
2.22k
    return Status::OK();
199
2.22k
}
200
201
223k
Status CGroupMemoryCtl::find_cgroup_mem_usage(int64_t* bytes) {
202
223k
    std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader;
203
223k
    RETURN_IF_ERROR(get_cgroups_reader(reader));
204
223k
    RETURN_IF_ERROR(reader->read_memory_usage(bytes));
205
223k
    return Status::OK();
206
223k
}
207
208
8
std::string CGroupMemoryCtl::debug_string() {
209
8
    const auto [cgroup_path, version] = get_cgroups_path();
210
8
    if (cgroup_path.empty()) {
211
0
        bool enable_controller;
212
0
        auto st = cgroupsv2_memory_controller_enabled(&enable_controller);
213
0
        return fmt::format(
214
0
                "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, "
215
0
                "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}",
216
0
                CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(),
217
0
                CGroupUtil::cgroupsv1_enable());
218
0
    }
219
220
8
    int64_t mem_limit;
221
8
    auto mem_limit_st = find_cgroup_mem_limit(&mem_limit);
222
223
8
    int64_t mem_usage;
224
8
    auto mem_usage_st = find_cgroup_mem_usage(&mem_usage);
225
226
8
    return fmt::format(
227
8
            "Process CGroup Memory Info (cgroups path: {}, cgroup version: {}): memory limit: "
228
8
            "{}, "
229
8
            "memory usage: {}",
230
8
            cgroup_path, (version == CGroupUtil::CgroupsVersion::V1) ? "v1" : "v2",
231
8
            mem_limit_st.ok() ? std::to_string(mem_limit) : mem_limit_st.to_string(),
232
8
            mem_usage_st.ok() ? std::to_string(mem_usage) : mem_usage_st.to_string());
233
8
}
234
235
} // namespace doris