| //===------------- Linux sysinfo support -------------------------------------// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H |
| #define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H |
| |
| #include "hdr/errno_macros.h" |
| #include "src/__support/CPP/array.h" |
| #include "src/__support/CPP/bit.h" |
| #include "src/__support/CPP/optional.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/close.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/open.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/read.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h" |
| #include "src/__support/ctype_utils.h" |
| #include "src/__support/macros/config.h" |
| |
| namespace LIBC_NAMESPACE_DECL { |
| namespace sysinfo { |
| |
| LIBC_INLINE_VAR constexpr char POSSIBLE_NPROC_PATH[] = |
| "/sys/devices/system/cpu/possible"; |
| LIBC_INLINE_VAR constexpr char ONLINE_NPROC_PATH[] = |
| "/sys/devices/system/cpu/online"; |
| |
| // Parses Linux CPU-list syntax: |
| // list := item (',' item)* |
| // item := number | number '-' number |
| // number := [0-9]+ |
| class ProcParser { |
| enum class ProcParserState { |
| ParseUnstarted, |
| ParseNumber, |
| ParseRangeSeparator, |
| ParseRangeEnd |
| }; |
| |
| ProcParserState state; |
| cpp::array<char, 128> buffer; |
| int fd; |
| size_t cursor; |
| size_t buffer_end; |
| size_t cpu_count; |
| size_t current_number; |
| size_t range_start; |
| bool has_error; |
| |
| LIBC_INLINE static int open_path(const char *path) { |
| ErrorOr<int> open_result = |
| linux_syscalls::open(path, O_RDONLY | O_CLOEXEC, 0); |
| return open_result ? *open_result : -1; |
| } |
| |
| LIBC_INLINE cpp::optional<char> next_char() { |
| if (fd < 0) |
| return cpp::nullopt; |
| |
| while (cursor == buffer_end) { |
| ErrorOr<ssize_t> bytes_read = |
| linux_syscalls::read(fd, buffer.data(), buffer.size()); |
| if (!bytes_read) { |
| if (bytes_read.error() == EINTR) |
| continue; |
| has_error = true; |
| return cpp::nullopt; |
| } |
| |
| if (*bytes_read == 0) |
| return cpp::nullopt; |
| |
| cursor = 0; |
| buffer_end = static_cast<size_t>(*bytes_read); |
| } |
| |
| return buffer[cursor++]; |
| } |
| |
| LIBC_INLINE bool finish_group() { |
| if (state == ProcParserState::ParseUnstarted) |
| return true; |
| if (state == ProcParserState::ParseRangeSeparator) |
| return false; |
| |
| if (state == ProcParserState::ParseRangeEnd) { |
| if (current_number < range_start) |
| return false; |
| cpu_count += current_number - range_start + 1; |
| } else { |
| ++cpu_count; |
| } |
| |
| current_number = 0; |
| range_start = 0; |
| state = ProcParserState::ParseUnstarted; |
| return true; |
| } |
| |
| LIBC_INLINE bool consume(char ch) { |
| if (internal::isdigit(ch)) { |
| // Not using internal::strtointeger here because a number can be across |
| // two reads in rare cases. |
| current_number = current_number * 10 + static_cast<size_t>(ch - '0'); |
| if (state == ProcParserState::ParseUnstarted) |
| state = ProcParserState::ParseNumber; |
| else if (state == ProcParserState::ParseRangeSeparator) |
| state = ProcParserState::ParseRangeEnd; |
| return true; |
| } |
| |
| if (ch == '-') { |
| if (state != ProcParserState::ParseNumber) |
| return false; |
| range_start = current_number; |
| current_number = 0; |
| state = ProcParserState::ParseRangeSeparator; |
| return true; |
| } |
| |
| if (ch == ',' || ch == '\n') |
| return finish_group(); |
| |
| if (ch == ' ' || ch == '\t' || ch == '\r') |
| return state == ProcParserState::ParseUnstarted ? true : finish_group(); |
| |
| return false; |
| } |
| |
| public: |
| // Using string view isn't exactly correct because we demands null-terminated |
| // strings. |
| LIBC_INLINE explicit ProcParser(const char *path) |
| : state(ProcParserState::ParseUnstarted), buffer{}, fd(open_path(path)), |
| cursor(buffer.size()), buffer_end(buffer.size()), cpu_count(0), |
| current_number(0), range_start(0), has_error(fd < 0) {} |
| |
| LIBC_INLINE ~ProcParser() { |
| if (fd >= 0) |
| linux_syscalls::close(fd); |
| } |
| |
| LIBC_INLINE cpp::optional<size_t> parse() { |
| if (fd < 0) |
| return cpp::nullopt; |
| |
| while (cpp::optional<char> ch = next_char()) |
| if (!consume(*ch)) |
| return cpp::nullopt; |
| |
| if (has_error) |
| return cpp::nullopt; |
| |
| if (!finish_group()) |
| return cpp::nullopt; |
| |
| if (cpu_count == 0) |
| return cpp::nullopt; |
| return cpu_count; |
| } |
| }; |
| |
| LIBC_INLINE cpp::optional<size_t> parse_nproc_from(const char *path) { |
| return ProcParser(path).parse(); |
| } |
| |
| LIBC_INLINE size_t parse_nproc_with_fallback_from(const char *path) { |
| if (cpp::optional<size_t> cpu_count = parse_nproc_from(path)) |
| return *cpu_count; |
| |
| cpp::array<unsigned char, 128> mask_buffer = {}; |
| |
| ErrorOr<int> affinity_result = |
| linux_syscalls::sched_getaffinity(0, mask_buffer); |
| if (!affinity_result) |
| return 1; |
| |
| size_t cpu_count = 0; |
| for (unsigned char byte : mask_buffer) |
| cpu_count += static_cast<size_t>(cpp::popcount(byte)); |
| |
| return cpu_count > 0 ? cpu_count : 1; |
| } |
| |
| } // namespace sysinfo |
| } // namespace LIBC_NAMESPACE_DECL |
| |
| #endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H |