Coverage Report

Created: 2026-06-11 19:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/mem_info.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/apache/impala/blob/branch-2.9.0/be/src/util/mem-info.cc
19
// and modified by Doris
20
21
#include "util/mem_info.h"
22
23
#include "runtime/memory/cache_manager.h"
24
25
#ifdef __APPLE__
26
#include <sys/sysctl.h>
27
#endif
28
29
#include <absl/strings/str_split.h>
30
#include <bvar/bvar.h>
31
#include <fmt/format.h>
32
#include <gen_cpp/Metrics_types.h>
33
#include <gen_cpp/segment_v2.pb.h>
34
#include <jemalloc/jemalloc.h>
35
36
#include <algorithm>
37
#include <boost/algorithm/string/trim.hpp>
38
#include <fstream>
39
#include <unordered_map>
40
41
#include "common/cgroup_memory_ctl.h"
42
#include "common/config.h"
43
#include "common/status.h"
44
#include "runtime/memory/global_memory_arbitrator.h"
45
#include "util/cgroup_util.h"
46
#include "util/parse_util.h"
47
#include "util/pretty_printer.h"
48
#include "util/string_parser.hpp"
49
50
namespace doris {
51
52
bool MemInfo::_s_initialized = false;
53
std::atomic<int64_t> MemInfo::_s_physical_mem = std::numeric_limits<int64_t>::max();
54
std::atomic<int64_t> MemInfo::_s_mem_limit = std::numeric_limits<int64_t>::max();
55
std::atomic<int64_t> MemInfo::_s_soft_mem_limit = std::numeric_limits<int64_t>::max();
56
57
std::atomic<int64_t> MemInfo::_s_cgroup_mem_limit = std::numeric_limits<int64_t>::max();
58
std::atomic<int64_t> MemInfo::_s_cgroup_mem_usage = std::numeric_limits<int64_t>::min();
59
std::atomic<bool> MemInfo::_s_cgroup_mem_refresh_state = false;
60
int64_t MemInfo::_s_cgroup_mem_refresh_wait_times = 0;
61
62
static std::unordered_map<std::string, int64_t> _mem_info_bytes;
63
std::atomic<int64_t> MemInfo::_s_sys_mem_available = -1;
64
int64_t MemInfo::_s_sys_mem_available_low_water_mark = std::numeric_limits<int64_t>::min();
65
int64_t MemInfo::_s_sys_mem_available_warning_water_mark = std::numeric_limits<int64_t>::min();
66
std::atomic<int64_t> MemInfo::_s_process_minor_gc_size = -1;
67
std::atomic<int64_t> MemInfo::_s_process_full_gc_size = -1;
68
69
#ifndef __APPLE__
70
35.2k
void MemInfo::refresh_proc_meminfo() {
71
35.2k
    std::ifstream meminfo("/proc/meminfo", std::ios::in);
72
35.2k
    std::string line;
73
74
1.94M
    while (meminfo.good() && !meminfo.eof()) {
75
1.90M
        getline(meminfo, line);
76
1.90M
        std::vector<std::string> fields = absl::StrSplit(line, " ", absl::SkipWhitespace());
77
1.90M
        if (fields.size() < 2) {
78
35.2k
            continue;
79
35.2k
        }
80
1.86M
        std::string key = fields[0].substr(0, fields[0].size() - 1);
81
82
1.86M
        StringParser::ParseResult result;
83
1.86M
        auto mem_value =
84
1.86M
                StringParser::string_to_int<int64_t>(fields[1].data(), fields[1].size(), &result);
85
86
1.86M
        if (result == StringParser::PARSE_SUCCESS) {
87
1.86M
            if (fields.size() == 2) {
88
141k
                _mem_info_bytes[key] = mem_value;
89
1.72M
            } else if (fields[2] == "kB") {
90
1.72M
                _mem_info_bytes[key] = mem_value * 1024L;
91
1.72M
            }
92
1.86M
        }
93
1.86M
    }
94
35.2k
    if (meminfo.is_open()) {
95
35.2k
        meminfo.close();
96
35.2k
    }
97
35.2k
    _s_cgroup_mem_refresh_state = false;
98
    // refresh cgroup memory
99
35.2k
    if (config::enable_use_cgroup_memory_info) {
100
35.2k
        if (_s_cgroup_mem_refresh_wait_times >= 0) {
101
352
            int64_t cgroup_mem_limit;
102
352
            auto status = CGroupMemoryCtl::find_cgroup_mem_limit(&cgroup_mem_limit);
103
352
            if (!status.ok()) {
104
0
                _s_cgroup_mem_limit = std::numeric_limits<int64_t>::max();
105
                // find cgroup limit failed, wait 300s, 1000 * 100ms.
106
0
                _s_cgroup_mem_refresh_wait_times = -3000;
107
0
                LOG(WARNING)
108
0
                        << "Refresh cgroup memory limit failed, refresh again after 300s, cgroup "
109
0
                           "mem limit: "
110
0
                        << _s_cgroup_mem_limit << ", " << status;
111
352
            } else {
112
352
                _s_cgroup_mem_limit = cgroup_mem_limit;
113
                // wait 10s, 100 * 100ms, avoid too frequently.
114
352
                _s_cgroup_mem_refresh_wait_times = -100;
115
352
            }
116
34.9k
        } else {
117
34.9k
            _s_cgroup_mem_refresh_wait_times++;
118
34.9k
        }
119
120
        // cgroup mem limit is refreshed every 10 seconds,
121
        // cgroup mem usage is refreshed together with memInfo every time, which is very frequent.
122
        // If _s_cgroup_mem_limit == max, it means get cgroup mem limit failed OR the cgroup has no memory limit for example
123
        // there is just "max" in memory.max file.
124
35.2k
        if (_s_cgroup_mem_limit != std::numeric_limits<int64_t>::max()) {
125
35.2k
            int64_t cgroup_mem_usage;
126
35.2k
            auto status = CGroupMemoryCtl::find_cgroup_mem_usage(&cgroup_mem_usage);
127
35.2k
            if (!status.ok()) {
128
0
                _s_cgroup_mem_usage = std::numeric_limits<int64_t>::min();
129
0
                LOG_EVERY_N(WARNING, 500)
130
0
                        << "Refresh cgroup memory usage failed, cgroup mem limit: "
131
0
                        << _s_cgroup_mem_limit << ", " << status;
132
35.2k
            } else {
133
35.2k
                _s_cgroup_mem_usage = cgroup_mem_usage;
134
35.2k
                _s_cgroup_mem_refresh_state = true;
135
35.2k
            }
136
35.2k
        }
137
35.2k
    }
138
139
    // 1. calculate physical_mem
140
35.2k
    int64_t physical_mem = -1;
141
35.2k
    if (_mem_info_bytes.find("MemTotal") != _mem_info_bytes.end()) {
142
35.2k
        physical_mem = _mem_info_bytes["MemTotal"];
143
35.2k
    }
144
35.2k
    if (_s_cgroup_mem_refresh_state) {
145
        // In theory, always cgroup_mem_limit < physical_mem
146
35.2k
        if (physical_mem < 0) {
147
0
            physical_mem = _s_cgroup_mem_limit;
148
35.2k
        } else {
149
35.2k
            physical_mem =
150
35.2k
                    std::min(physical_mem, _s_cgroup_mem_limit.load(std::memory_order_relaxed));
151
35.2k
        }
152
35.2k
    }
153
154
35.2k
    if (physical_mem <= 0) {
155
0
        LOG(WARNING)
156
0
                << "Could not determine amount of physical memory on this machine, physical_mem: "
157
0
                << physical_mem;
158
0
    }
159
160
    // 2. if physical_mem changed, refresh mem limit and gc size.
161
35.2k
    if (physical_mem > 0 && _s_physical_mem.load(std::memory_order_relaxed) != physical_mem) {
162
7
        if (_s_physical_mem != std::numeric_limits<int64_t>::max()) {
163
            // After MemInfo is initialized, if physical memory changed, reset initial capacity of all caches.
164
0
            CacheManager::instance()->for_each_cache_reset_initial_capacity(
165
0
                    physical_mem / (_s_physical_mem * 1.0));
166
0
        }
167
168
7
        _s_physical_mem.store(physical_mem);
169
170
7
        bool is_percent = true;
171
7
        _s_mem_limit.store(
172
7
                ParseUtil::parse_mem_spec(config::mem_limit, -1, _s_physical_mem, &is_percent));
173
7
        if (_s_mem_limit <= 0) {
174
0
            LOG(WARNING) << "Failed to parse mem limit from '" + config::mem_limit + "'.";
175
0
        }
176
7
        if (_s_mem_limit > _s_physical_mem) {
177
0
            LOG(WARNING) << "Memory limit " << PrettyPrinter::print(_s_mem_limit, TUnit::BYTES)
178
0
                         << " exceeds physical memory of "
179
0
                         << PrettyPrinter::print(_s_physical_mem, TUnit::BYTES)
180
0
                         << ". Using physical memory instead";
181
0
            _s_mem_limit.store(_s_physical_mem);
182
0
        }
183
7
        _s_soft_mem_limit.store(int64_t(_s_mem_limit * config::soft_mem_limit_frac));
184
185
7
        _s_process_minor_gc_size.store(ParseUtil::parse_mem_spec(config::process_minor_gc_size, -1,
186
7
                                                                 _s_mem_limit, &is_percent));
187
7
        _s_process_full_gc_size.store(ParseUtil::parse_mem_spec(config::process_full_gc_size, -1,
188
7
                                                                _s_mem_limit, &is_percent));
189
7
    }
190
191
    // 3. refresh process available memory
192
35.2k
    int64_t mem_available = -1;
193
35.2k
    if (_mem_info_bytes.find("MemAvailable") != _mem_info_bytes.end()) {
194
35.2k
        mem_available = _mem_info_bytes["MemAvailable"];
195
35.2k
    }
196
35.2k
    if (_s_cgroup_mem_refresh_state) {
197
        // Note, CgroupV2 MemAvailable is usually a little smaller than Process MemAvailable.
198
        // Process `MemAvailable = MemFree - LowWaterMark + (PageCache - min(PageCache / 2, LowWaterMark))`,
199
        // from `MemAvailable` in `/proc/meminfo`, calculated by OS.
200
        // CgroupV2 `MemAvailable = cgroup_mem_limit - cgroup_mem_usage`,
201
        // `cgroup_mem_usage = memory.current - inactive_file - active_file - slab_reclaimable`, in fact,
202
        // there seems to be some memory that can be reused in `cgroup_mem_usage`.
203
35.2k
        if (mem_available < 0) {
204
0
            mem_available = _s_cgroup_mem_limit - _s_cgroup_mem_usage;
205
35.2k
        } else {
206
35.2k
            mem_available = std::min(mem_available, _s_cgroup_mem_limit - _s_cgroup_mem_usage);
207
35.2k
        }
208
35.2k
    }
209
35.2k
    if (mem_available < 0) {
210
0
        LOG(WARNING) << "Failed to get available memory, set MAX_INT.";
211
0
        mem_available = std::numeric_limits<int64_t>::max();
212
0
    }
213
35.2k
    if (_s_sys_mem_available.load(std::memory_order_relaxed) != mem_available) {
214
25.0k
        _s_sys_mem_available.store(mem_available);
215
25.0k
    }
216
35.2k
}
217
218
7
void MemInfo::init() {
219
7
    refresh_proc_meminfo();
220
221
7
    std::string line;
222
7
    int64_t _s_vm_min_free_kbytes = 0;
223
7
    std::ifstream vminfo("/proc/sys/vm/min_free_kbytes", std::ios::in);
224
7
    if (vminfo.good() && !vminfo.eof()) {
225
7
        getline(vminfo, line);
226
7
        boost::algorithm::trim(line);
227
7
        StringParser::ParseResult result;
228
7
        auto mem_value = StringParser::string_to_int<int64_t>(line.data(), line.size(), &result);
229
230
7
        if (result == StringParser::PARSE_SUCCESS) {
231
7
            _s_vm_min_free_kbytes = mem_value * 1024L;
232
7
        }
233
7
    }
234
7
    if (vminfo.is_open()) {
235
7
        vminfo.close();
236
7
    }
237
238
    // Redhat 4.x OS, `/proc/meminfo` has no `MemAvailable`.
239
7
    if (_mem_info_bytes.find("MemAvailable") != _mem_info_bytes.end()) {
240
        // MemAvailable = MemFree - LowWaterMark + (PageCache - min(PageCache / 2, LowWaterMark))
241
        // LowWaterMark = /proc/sys/vm/min_free_kbytes
242
        // Ref:
243
        // https://serverfault.com/questions/940196/why-is-memavailable-a-lot-less-than-memfreebufferscached
244
        // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
245
        //
246
        // smaller sys_mem_available_low_water_mark can avoid wasting too much memory.
247
7
        _s_sys_mem_available_low_water_mark =
248
7
                config::max_sys_mem_available_low_water_mark_bytes != -1
249
7
                        ? config::max_sys_mem_available_low_water_mark_bytes
250
7
                        : std::min<int64_t>(_s_physical_mem - _s_mem_limit,
251
1
                                            int64_t(_s_physical_mem * 0.05));
252
7
        _s_sys_mem_available_warning_water_mark = _s_sys_mem_available_low_water_mark * 2;
253
7
    }
254
255
7
    std::ifstream sys_transparent_hugepage("/sys/kernel/mm/transparent_hugepage/enabled",
256
7
                                           std::ios::in);
257
7
    std::string hugepage_enable;
258
    // If file not exist, getline returns an empty string.
259
7
    getline(sys_transparent_hugepage, hugepage_enable);
260
7
    if (sys_transparent_hugepage.is_open()) {
261
7
        sys_transparent_hugepage.close();
262
7
    }
263
7
    if (hugepage_enable == "[always] madvise never") {
264
0
        std::cout << "[WARNING!] /sys/kernel/mm/transparent_hugepage/enabled: " << hugepage_enable
265
0
                  << ", Doris not recommend turning on THP, which may cause the BE process to use "
266
0
                     "more memory and cannot be freed in time. Turn off THP: `echo madvise | sudo "
267
0
                     "tee /sys/kernel/mm/transparent_hugepage/enabled`"
268
0
                  << std::endl;
269
0
    }
270
271
    // Expect vm overcommit memory value to be 1, system will no longer throw bad_alloc, memory alloc are always accepted,
272
    // memory limit check is handed over to Doris Allocator, make sure throw exception position is controllable,
273
    // otherwise bad_alloc can be thrown anywhere and it will be difficult to achieve exception safety.
274
7
    std::ifstream sys_vm("/proc/sys/vm/overcommit_memory", std::ios::in);
275
7
    std::string vm_overcommit;
276
7
    getline(sys_vm, vm_overcommit);
277
7
    if (sys_vm.is_open()) {
278
7
        sys_vm.close();
279
7
    }
280
7
    if (!vm_overcommit.empty() && std::stoi(vm_overcommit) == 2) {
281
0
        std::cout << "[WARNING!] /proc/sys/vm/overcommit_memory: " << vm_overcommit
282
0
                  << ", expect is 1, memory limit check is handed over to Doris Allocator, "
283
0
                     "otherwise BE may crash even with remaining memory"
284
0
                  << std::endl;
285
0
    }
286
287
7
    LOG(INFO) << "Physical Memory: " << _mem_info_bytes["MemTotal"]
288
7
              << ", BE Available Physical Memory(consider cgroup): "
289
7
              << PrettyPrinter::print(_s_physical_mem, TUnit::BYTES) << ", Mem Limit: "
290
7
              << PrettyPrinter::print(_s_mem_limit.load(std::memory_order_relaxed), TUnit::BYTES)
291
7
              << ", origin config value: " << config::mem_limit
292
7
              << ", System Mem Available Min Reserve: "
293
7
              << PrettyPrinter::print(_s_sys_mem_available_low_water_mark, TUnit::BYTES)
294
7
              << ", Vm Min Free KBytes: "
295
7
              << PrettyPrinter::print(_s_vm_min_free_kbytes, TUnit::BYTES)
296
7
              << ", Vm Overcommit Memory: " << vm_overcommit;
297
7
    _s_initialized = true;
298
7
}
299
#else
300
void MemInfo::refresh_proc_meminfo() {}
301
302
void MemInfo::init() {
303
    size_t size = sizeof(_s_physical_mem);
304
    if (sysctlbyname("hw.memsize", &_s_physical_mem, &size, nullptr, 0) != 0) {
305
        LOG(WARNING) << "Could not determine amount of physical memory on this machine.";
306
        _s_physical_mem = -1;
307
    }
308
309
    bool is_percent = true;
310
    _s_mem_limit = ParseUtil::parse_mem_spec(config::mem_limit, -1, _s_physical_mem, &is_percent);
311
    _s_soft_mem_limit = static_cast<int64_t>(_s_mem_limit * config::soft_mem_limit_frac);
312
313
    LOG(INFO) << "Physical Memory: " << PrettyPrinter::print(_s_physical_mem, TUnit::BYTES);
314
    _s_initialized = true;
315
}
316
#endif
317
318
6
std::string MemInfo::debug_string() {
319
    DCHECK(_s_initialized);
320
6
    std::stringstream stream;
321
6
    stream << "Physical Memory: " << PrettyPrinter::print(_s_physical_mem, TUnit::BYTES)
322
6
           << std::endl;
323
6
    stream << "Memory Limt: " << PrettyPrinter::print(_s_mem_limit, TUnit::BYTES) << std::endl;
324
6
    stream << "CGroup Info: " << doris::CGroupMemoryCtl::debug_string() << std::endl;
325
6
    return stream.str();
326
6
}
327
328
} // namespace doris