Coverage Report

Created: 2026-03-14 06:50

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
7
Status cgroupsv2_memory_controller_enabled(bool* ret) {
37
7
#if defined(OS_LINUX)
38
7
    if (!CGroupUtil::cgroupsv2_enable()) {
39
7
        return Status::CgroupError("cgroupsv2_enable is false");
40
7
    }
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
6
            : _mount_file_dir(std::move(mount_file_dir)) {}
64
65
3
    Status read_memory_limit(int64_t* value) override {
66
3
        RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file(
67
3
                (_mount_file_dir / "memory.limit_in_bytes"), value));
68
3
        return Status::OK();
69
3
    }
70
71
3
    Status read_memory_usage(int64_t* value) override {
72
3
        std::unordered_map<std::string, int64_t> metrics_map;
73
3
        CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"),
74
3
                                                     metrics_map);
75
3
        *value = metrics_map["rss"];
76
3
        return Status::OK();
77
3
    }
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
7
std::pair<std::string, CGroupUtil::CgroupsVersion> get_cgroups_path() {
127
7
    bool enable_controller;
128
7
    auto cgroupsv2_memory_controller_st = cgroupsv2_memory_controller_enabled(&enable_controller);
129
7
    if (CGroupUtil::cgroupsv2_enable() && cgroupsv2_memory_controller_st.ok() &&
130
7
        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
7
    std::string cgroup_path;
142
7
    auto st = CGroupUtil::find_abs_cgroupv1_path("memory", &cgroup_path);
143
7
    if (st.ok()) {
144
7
        return {cgroup_path, CGroupUtil::CgroupsVersion::V1};
145
7
    }
146
147
0
    return {"", CGroupUtil::CgroupsVersion::V1};
148
7
}
149
150
6
Status get_cgroups_reader(std::shared_ptr<CGroupMemoryCtl::ICgroupsReader>& reader) {
151
6
    const auto [cgroup_path, version] = get_cgroups_path();
152
6
    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
6
    if (version == CGroupUtil::CgroupsVersion::V2) {
163
0
        reader = std::make_shared<CgroupsV2Reader>(cgroup_path);
164
6
    } else {
165
6
        reader = std::make_shared<CgroupsV1Reader>(cgroup_path);
166
6
    }
167
6
    return Status::OK();
168
6
}
169
170
3
Status CGroupMemoryCtl::find_cgroup_mem_limit(int64_t* bytes) {
171
3
    std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader;
172
3
    RETURN_IF_ERROR(get_cgroups_reader(reader));
173
3
    RETURN_IF_ERROR(reader->read_memory_limit(bytes));
174
3
    return Status::OK();
175
3
}
176
177
3
Status CGroupMemoryCtl::find_cgroup_mem_usage(int64_t* bytes) {
178
3
    std::shared_ptr<CGroupMemoryCtl::ICgroupsReader> reader;
179
3
    RETURN_IF_ERROR(get_cgroups_reader(reader));
180
3
    RETURN_IF_ERROR(reader->read_memory_usage(bytes));
181
3
    return Status::OK();
182
3
}
183
184
1
std::string CGroupMemoryCtl::debug_string() {
185
1
    const auto [cgroup_path, version] = get_cgroups_path();
186
1
    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
1
    int64_t mem_limit;
197
1
    auto mem_limit_st = find_cgroup_mem_limit(&mem_limit);
198
199
1
    int64_t mem_usage;
200
1
    auto mem_usage_st = find_cgroup_mem_usage(&mem_usage);
201
202
1
    return fmt::format(
203
1
            "Process CGroup Memory Info (cgroups path: {}, cgroup version: {}): memory limit: "
204
1
            "{}, "
205
1
            "memory usage: {}",
206
1
            cgroup_path, (version == CGroupUtil::CgroupsVersion::V1) ? "v1" : "v2",
207
1
            mem_limit_st.ok() ? std::to_string(mem_limit) : mem_limit_st.to_string(),
208
1
            mem_usage_st.ok() ? std::to_string(mem_usage) : mem_usage_st.to_string());
209
1
}
210
211
} // namespace doris