Coverage Report

Created: 2026-04-10 04:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/cpu_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/cpu-info.cpp
19
// and modified by Doris
20
21
#include "util/cpu_info.h"
22
23
#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
24
#elif defined(__GNUC__) && defined(__ARM_NEON__)
25
/* GCC-compatible compiler, targeting ARM with NEON */
26
#include <arm_neon.h>
27
#elif defined(__GNUC__) && defined(__IWMMXT__)
28
/* GCC-compatible compiler, targeting ARM with WMMX */
29
#include <mmintrin.h>
30
#elif (defined(__GNUC__) || defined(__xlC__)) && (defined(__VEC__) || defined(__ALTIVEC__))
31
/* XLC or GCC-compatible compiler, targeting PowerPC with VMX/VSX */
32
#include <altivec.h>
33
#elif defined(__GNUC__) && defined(__SPE__)
34
/* GCC-compatible compiler, targeting PowerPC with SPE */
35
#include <spe.h>
36
#endif
37
38
#ifndef __APPLE__
39
#include <sys/sysinfo.h>
40
#else
41
#include <sys/sysctl.h>
42
#endif
43
44
#include <gen_cpp/Metrics_types.h>
45
#include <sched.h>
46
#include <stdlib.h>
47
#include <unistd.h>
48
49
#include <algorithm>
50
#include <boost/algorithm/string/predicate.hpp>
51
#include <boost/algorithm/string/trim.hpp>
52
// IWYU pragma: no_include <bits/chrono.h>
53
#include <chrono> // IWYU pragma: keep
54
#include <filesystem>
55
#include <fstream>
56
57
#include "absl/strings/substitute.h"
58
#include "common/config.h"
59
#include "common/env_config.h"
60
#include "gflags/gflags.h"
61
#include "util/cgroup_util.h"
62
#include "util/pretty_printer.h"
63
64
using boost::algorithm::contains;
65
using boost::algorithm::trim;
66
namespace fs = std::filesystem;
67
#include "common/compile_check_avoid_begin.h"
68
using std::max;
69
70
DECLARE_bool(abort_on_config_error);
71
DEFINE_int32(num_cores, 0,
72
             "(Advanced) If > 0, it sets the number of cores available to"
73
             " Impala. Setting it to 0 means Impala will use all available cores on the machine"
74
             " according to /proc/cpuinfo.");
75
76
#include "common/compile_check_avoid_end.h"
77
78
namespace doris {
79
// Helper function to warn if a given file does not contain an expected string as its
80
// first line. If the file cannot be opened, no error is reported.
81
void WarnIfFileNotEqual(const std::string& filename, const std::string& expected,
82
0
                        const std::string& warning_text) {
83
0
    std::ifstream file(filename);
84
0
    if (!file) {
85
0
        return;
86
0
    }
87
0
    std::string line;
88
0
    getline(file, line);
89
0
    if (line != expected) {
90
0
        LOG(ERROR) << "Expected " << expected << ", actual " << line << std::endl << warning_text;
91
0
    }
92
0
}
93
94
bool CpuInfo::initialized_ = false;
95
int64_t CpuInfo::hardware_flags_ = 0;
96
int64_t CpuInfo::original_hardware_flags_;
97
int64_t CpuInfo::cycles_per_ms_;
98
int CpuInfo::num_cores_ = 1;
99
int CpuInfo::max_num_cores_ = 1;
100
std::string CpuInfo::model_name_ = "unknown";
101
int CpuInfo::max_num_numa_nodes_;
102
std::unique_ptr<int[]> CpuInfo::core_to_numa_node_;
103
std::vector<std::vector<int>> CpuInfo::numa_node_to_cores_;
104
std::vector<int> CpuInfo::numa_node_core_idx_;
105
106
static struct {
107
    std::string name;
108
    int64_t flag;
109
} flag_mappings[] = {
110
        {.name = "ssse3", .flag = CpuInfo::SSSE3},   {.name = "sse4_1", .flag = CpuInfo::SSE4_1},
111
        {.name = "sse4_2", .flag = CpuInfo::SSE4_2}, {.name = "popcnt", .flag = CpuInfo::POPCNT},
112
        {.name = "avx", .flag = CpuInfo::AVX},       {.name = "avx2", .flag = CpuInfo::AVX2},
113
};
114
115
// Helper function to parse for hardware flags.
116
// values contains a list of space-separated flags.  check to see if the flags we
117
// care about are present.
118
// Returns a bitmap of flags.
119
156
int64_t ParseCPUFlags(const std::string& values) {
120
156
    int64_t flags = 0;
121
936
    for (auto& flag_mapping : flag_mappings) {
122
936
        if (contains(values, flag_mapping.name)) {
123
936
            flags |= flag_mapping.flag;
124
936
        }
125
936
    }
126
156
    return flags;
127
156
}
128
129
18
void CpuInfo::init() {
130
18
    if (initialized_) {
131
10
        return;
132
10
    }
133
8
    std::string line;
134
8
    std::string name;
135
8
    std::string value;
136
137
8
    float max_mhz = 0;
138
8
    int physical_num_cores = 0;
139
140
    // maybe use std::thread::hardware_concurrency()?
141
    // Read from /proc/cpuinfo
142
8
    std::ifstream cpuinfo("/proc/cpuinfo");
143
4.22k
    while (cpuinfo) {
144
4.22k
        getline(cpuinfo, line);
145
4.22k
        size_t colon = line.find(':');
146
4.22k
        if (colon != std::string::npos) {
147
4.05k
            name = line.substr(0, colon - 1);
148
4.05k
            value = line.substr(colon + 1, std::string::npos);
149
4.05k
            trim(name);
150
4.05k
            trim(value);
151
4.05k
            if (name == "flags") {
152
156
                hardware_flags_ |= ParseCPUFlags(value);
153
3.90k
            } else if (name == "cpu MHz") {
154
                // Every core will report a different speed.  We'll take the max, assuming
155
                // that when impala is running, the core will not be in a lower power state.
156
                // TODO: is there a more robust way to do this, such as
157
                // Window's QueryPerformanceFrequency()
158
156
                float mhz = std::stof(value);
159
156
                max_mhz = max(mhz, max_mhz);
160
3.74k
            } else if (name == "processor") {
161
156
                ++physical_num_cores;
162
3.58k
            } else if (name == "model name") {
163
156
                model_name_ = value;
164
156
            }
165
4.05k
        }
166
4.22k
    }
167
168
#ifdef __APPLE__
169
    size_t len = sizeof(max_num_cores_);
170
    sysctlbyname("hw.physicalcpu", &physical_num_cores, &len, nullptr, 0);
171
#endif
172
173
8
    int num_cores = CGroupUtil::get_cgroup_limited_cpu_number(physical_num_cores);
174
8
    if (max_mhz != 0) {
175
8
        cycles_per_ms_ = int64_t(max_mhz) * 1000;
176
8
    } else {
177
0
        cycles_per_ms_ = 1000000;
178
0
    }
179
8
    original_hardware_flags_ = hardware_flags_;
180
181
8
    if (num_cores > 0) {
182
8
        num_cores_ = num_cores;
183
8
    } else {
184
0
        num_cores_ = 1;
185
0
    }
186
8
    if (config::num_cores > 0) {
187
0
        num_cores_ = config::num_cores;
188
0
    }
189
190
#ifdef __APPLE__
191
    sysctlbyname("hw.logicalcpu", &max_num_cores_, &len, nullptr, 0);
192
#else
193
8
    max_num_cores_ = get_nprocs_conf();
194
8
#endif
195
196
    // Print a warning if something is wrong with sched_getcpu().
197
8
#ifdef HAVE_SCHED_GETCPU
198
8
    if (sched_getcpu() == -1) {
199
0
        LOG(WARNING) << "Kernel does not support sched_getcpu(). Performance may be impacted.";
200
0
    }
201
#else
202
    LOG(WARNING) << "Built on a system without sched_getcpu() support. Performance may"
203
                 << " be impacted.";
204
#endif
205
206
8
    _init_numa();
207
8
    initialized_ = true;
208
8
}
209
210
8
void CpuInfo::_init_numa() {
211
    // Use the NUMA info in the /sys filesystem. which is part of the Linux ABI:
212
    // see https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node and
213
    // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
214
    // The filesystem entries are only present if the kernel was compiled with NUMA support.
215
8
    core_to_numa_node_.reset(new int[max_num_cores_]);
216
217
8
    if (!fs::is_directory("/sys/devices/system/node")) {
218
0
        LOG(WARNING) << "/sys/devices/system/node is not present - no NUMA support";
219
        // Assume a single NUMA node.
220
0
        max_num_numa_nodes_ = 1;
221
0
        std::fill_n(core_to_numa_node_.get(), max_num_cores_, 0);
222
0
        _init_numa_node_to_cores();
223
0
        return;
224
0
    }
225
226
    // Search for node subdirectories - node0, node1, node2, etc to determine possible
227
    // NUMA nodes.
228
8
    fs::directory_iterator dir_it("/sys/devices/system/node");
229
8
    max_num_numa_nodes_ = 0;
230
72
    for (; dir_it != fs::directory_iterator(); ++dir_it) {
231
64
        const std::string filename = dir_it->path().filename().string();
232
64
        if (filename.starts_with("node")) {
233
8
            ++max_num_numa_nodes_;
234
8
        }
235
64
    }
236
8
    if (max_num_numa_nodes_ == 0) {
237
0
        LOG(WARNING) << "Could not find nodes in /sys/devices/system/node";
238
0
        max_num_numa_nodes_ = 1;
239
0
    }
240
241
    // Check which NUMA node each core belongs to based on the existence of a symlink
242
    // to the node subdirectory.
243
164
    for (int core = 0; core < max_num_cores_; ++core) {
244
156
        bool found_numa_node = false;
245
156
        for (int node = 0; node < max_num_numa_nodes_; ++node) {
246
156
            if (fs::exists(absl::Substitute("/sys/devices/system/cpu/cpu$0/node$1", core, node))) {
247
156
                core_to_numa_node_[core] = node;
248
156
                found_numa_node = true;
249
156
                break;
250
156
            }
251
156
        }
252
156
        if (!found_numa_node) {
253
0
            LOG(WARNING) << "Could not determine NUMA node for core " << core
254
0
                         << " from /sys/devices/system/cpu/";
255
0
            core_to_numa_node_[core] = 0;
256
0
        }
257
156
    }
258
8
    _init_numa_node_to_cores();
259
8
}
260
261
void CpuInfo::_init_fake_numa_for_test(int max_num_numa_nodes,
262
0
                                       const std::vector<int>& core_to_numa_node) {
263
0
    DCHECK_EQ(max_num_cores_, core_to_numa_node.size());
264
0
    max_num_numa_nodes_ = max_num_numa_nodes;
265
0
    for (int i = 0; i < max_num_cores_; ++i) {
266
0
        core_to_numa_node_[i] = core_to_numa_node[i];
267
0
    }
268
0
    numa_node_to_cores_.clear();
269
0
    _init_numa_node_to_cores();
270
0
}
271
272
8
void CpuInfo::_init_numa_node_to_cores() {
273
8
    DCHECK(numa_node_to_cores_.empty());
274
8
    numa_node_to_cores_.resize(max_num_numa_nodes_);
275
8
    numa_node_core_idx_.resize(max_num_cores_);
276
164
    for (int core = 0; core < max_num_cores_; ++core) {
277
156
        std::vector<int>* cores_of_node = &numa_node_to_cores_[core_to_numa_node_[core]];
278
156
        numa_node_core_idx_[core] = static_cast<int>(cores_of_node->size());
279
156
        cores_of_node->push_back(core);
280
156
    }
281
8
}
282
283
0
void CpuInfo::verify_cpu_requirements() {
284
0
    if (!CpuInfo::is_supported(CpuInfo::SSSE3)) {
285
0
        LOG(ERROR) << "CPU does not support the Supplemental SSE3 (SSSE3) instruction set. "
286
0
                   << "This setup is generally unsupported and Impala might be unstable.";
287
0
    }
288
0
}
289
290
0
void CpuInfo::verify_performance_governor() {
291
0
    for (int cpu_id = 0; cpu_id < CpuInfo::num_cores(); ++cpu_id) {
292
0
        const std::string governor_file =
293
0
                absl::Substitute("/sys/devices/system/cpu/cpu$0/cpufreq/scaling_governor", cpu_id);
294
0
        const std::string warning_text = absl::Substitute(
295
0
                "WARNING: CPU $0 is not using 'performance' governor. Note that changing the "
296
0
                "governor to 'performance' will reset the no_turbo setting to 0.",
297
0
                cpu_id);
298
0
        WarnIfFileNotEqual(governor_file, "performance", warning_text);
299
0
    }
300
0
}
301
302
0
void CpuInfo::verify_turbo_disabled() {
303
0
    WarnIfFileNotEqual(
304
0
            "/sys/devices/system/cpu/intel_pstate/no_turbo", "1",
305
0
            "WARNING: CPU turbo is enabled. This setting can change the clock frequency of CPU "
306
0
            "cores during the benchmark run, which can lead to inaccurate results. You can "
307
0
            "disable CPU turbo by writing a 1 to "
308
0
            "/sys/devices/system/cpu/intel_pstate/no_turbo. Note that changing the governor to "
309
0
            "'performance' will reset this to 0.");
310
0
}
311
312
0
void CpuInfo::enable_feature(long flag, bool enable) {
313
0
    DCHECK(initialized_);
314
0
    if (!enable) {
315
0
        hardware_flags_ &= ~flag;
316
0
    } else {
317
        // Can't turn something on that can't be supported
318
0
        DCHECK((original_hardware_flags_ & flag) != 0);
319
0
        hardware_flags_ |= flag;
320
0
    }
321
0
}
322
323
0
int CpuInfo::get_current_core() {
324
    // sched_getcpu() is not supported on some old kernels/glibcs (like the versions that
325
    // shipped with CentOS 5). In that case just pretend we're always running on CPU 0
326
    // so that we can build and run with degraded perf.
327
0
#ifdef HAVE_SCHED_GETCPU
328
0
    int cpu = sched_getcpu();
329
0
    if (cpu < 0) {
330
0
        return 0;
331
0
    }
332
0
    if (cpu >= max_num_cores_) {
333
0
        LOG_FIRST_N(WARNING, 5) << "sched_getcpu() return value " << cpu
334
0
                                << ", which is greater than get_nprocs_conf() retrun value "
335
0
                                << max_num_cores_ << ", now is " << get_nprocs_conf();
336
0
        cpu %= max_num_cores_;
337
0
    }
338
0
    return cpu;
339
#else
340
    return 0;
341
#endif
342
0
}
343
344
void CpuInfo::_get_cache_info(long cache_sizes[NUM_CACHE_LEVELS],
345
8
                              long cache_line_sizes[NUM_CACHE_LEVELS]) {
346
#ifdef __APPLE__
347
    // On Mac OS X use sysctl() to get the cache sizes
348
    size_t len = 0;
349
    sysctlbyname("hw.cachesize", nullptr, &len, nullptr, 0);
350
    uint64_t* data = static_cast<uint64_t*>(malloc(len));
351
    sysctlbyname("hw.cachesize", data, &len, nullptr, 0);
352
#ifndef __arm64__
353
    DCHECK(len / sizeof(uint64_t) >= 3);
354
    for (size_t i = 0; i < NUM_CACHE_LEVELS; ++i) {
355
        cache_sizes[i] = data[i];
356
    }
357
#else
358
    for (size_t i = 0; i < NUM_CACHE_LEVELS; ++i) {
359
        cache_sizes[i] = data[i + 1];
360
    }
361
#endif
362
    size_t linesize;
363
    size_t sizeof_linesize = sizeof(linesize);
364
    sysctlbyname("hw.cachelinesize", &linesize, &sizeof_linesize, nullptr, 0);
365
    for (size_t i = 0; i < NUM_CACHE_LEVELS; ++i) cache_line_sizes[i] = linesize;
366
#else
367
    // Call sysconf to query for the cache sizes
368
    // Note: on some systems (e.g. RHEL 5 on AWS EC2), this returns 0 instead of the
369
    // actual cache line size.
370
8
    cache_sizes[L1_CACHE] = sysconf(_SC_LEVEL1_DCACHE_SIZE);
371
8
    cache_sizes[L2_CACHE] = sysconf(_SC_LEVEL2_CACHE_SIZE);
372
8
    cache_sizes[L3_CACHE] = sysconf(_SC_LEVEL3_CACHE_SIZE);
373
374
8
    cache_line_sizes[L1_CACHE] = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
375
8
    cache_line_sizes[L2_CACHE] = sysconf(_SC_LEVEL2_CACHE_LINESIZE);
376
8
    cache_line_sizes[L3_CACHE] = sysconf(_SC_LEVEL3_CACHE_LINESIZE);
377
8
#endif
378
8
}
379
380
8
std::string CpuInfo::debug_string() {
381
8
    DCHECK(initialized_);
382
8
    std::stringstream stream;
383
8
    long cache_sizes[NUM_CACHE_LEVELS];
384
8
    long cache_line_sizes[NUM_CACHE_LEVELS];
385
8
    _get_cache_info(cache_sizes, cache_line_sizes);
386
387
8
    std::string L1 = absl::Substitute(
388
8
            "L1 Cache: $0 (Line: $1)",
389
8
            PrettyPrinter::print(static_cast<int64_t>(cache_sizes[L1_CACHE]), TUnit::BYTES),
390
8
            PrettyPrinter::print(static_cast<int64_t>(cache_line_sizes[L1_CACHE]), TUnit::BYTES));
391
8
    std::string L2 = absl::Substitute(
392
8
            "L2 Cache: $0 (Line: $1)",
393
8
            PrettyPrinter::print(static_cast<int64_t>(cache_sizes[L2_CACHE]), TUnit::BYTES),
394
8
            PrettyPrinter::print(static_cast<int64_t>(cache_line_sizes[L2_CACHE]), TUnit::BYTES));
395
8
    std::string L3 =
396
8
            cache_sizes[L3_CACHE]
397
8
                    ? absl::Substitute(
398
8
                              "L3 Cache: $0 (Line: $1)",
399
8
                              PrettyPrinter::print(static_cast<int64_t>(cache_sizes[L3_CACHE]),
400
8
                                                   TUnit::BYTES),
401
8
                              PrettyPrinter::print(static_cast<int64_t>(cache_line_sizes[L3_CACHE]),
402
8
                                                   TUnit::BYTES))
403
8
                    : "";
404
8
    stream << "Cpu Info:" << std::endl
405
8
           << "  Model: " << model_name_ << std::endl
406
8
           << "  Cores: " << num_cores_ << std::endl
407
8
           << "  Max Possible Cores: " << max_num_cores_ << std::endl
408
8
           << "  " << L1 << std::endl
409
8
           << "  " << L2 << std::endl
410
8
           << "  " << L3 << std::endl
411
8
           << "  Hardware Supports:" << std::endl;
412
48
    for (auto& flag_mapping : flag_mappings) {
413
48
        if (is_supported(flag_mapping.flag)) {
414
48
            stream << "    " << flag_mapping.name << std::endl;
415
48
        }
416
48
    }
417
8
    stream << "  Numa Nodes: " << max_num_numa_nodes_ << std::endl;
418
8
    stream << "  Numa Nodes of Cores:";
419
184
    for (int core = 0; core < max_num_cores_; ++core) {
420
176
        stream << " " << core << "->" << core_to_numa_node_[core] << " |";
421
176
    }
422
8
    stream << std::endl;
423
8
    return stream.str();
424
8
}
425
} // namespace doris