#include "info.h" #include "error.h" #include "fs.h" #include #include #if defined(_linux_) || defined(_cygwin_) #include #include #endif #if defined(_win_) #include "winint.h" #include #else #include #endif #if defined(_bionic_) // TODO #elif defined(_cygwin_) static int getloadavg(double* loadavg, int nelem) { for (int i = 0; i < nelem; ++i) { loadavg[i] = 0.0; } return nelem; } #elif defined(_unix_) || defined(_darwin_) #include #endif #if defined(_freebsd_) || defined(_darwin_) #include #endif #include #include #include #include #include #include #if defined(_linux_) /* This function olny works properly if you apply correct setting to your nanny/deploy project In nanny - Runtime -> Instance spec -> Advanced settings -> Cgroupfs settings: Mount mode = Read only In deploy - Stage - Edit stage - Box - Cgroupfs settings: Mount mode = Read only */ static inline double CgroupV1Cpus(const TString& cpuCfsQuotaUsPath, const TString& cfsPeriodUsPath) { try { double q = FromString(StripString(TFileInput(cpuCfsQuotaUsPath).ReadAll())); if (q <= 0) { return 0; } double p = FromString(StripString(TFileInput(cfsPeriodUsPath).ReadAll())); if (p <= 0) { return 0; } return q / p; } catch (...) { return 0; } } /* In cgroups v2 there isn't a dedicated "cpu" directory under /sys/fs/cgroup, so the approximation of the number of CPUs may use the cpu.max file. The format is the following: $MAX $PERIOD Which indicates that the group may consume up to $MAX in each $PERIOD duration. The "max" value could be either the string "max" or a number. In the first case our approximation doesn't work so we can bail out earlier. */ static inline double CgroupV2Cpus(const TString& cpuMaxPath) { try { TVector cgroupCpuMax = StringSplitter(TFileInput(cpuMaxPath).ReadAll()).Split(' ').Take(2); double max = FromString(StripString(cgroupCpuMax[0])); double period = FromString(StripString(cgroupCpuMax[1])); if (max <= 0 || period <= 0) { return 0; } return max / period; } catch (...) { return 0; } } static inline double CgroupCpus() { static const TString cpuMaxPath("/sys/fs/cgroup/cpu.max"); static const TString cpuCfsQuotaUsPath("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"); static const TString cfsPeriodUsPath("/sys/fs/cgroup/cpu/cpu.cfs_period_us"); if (NFs::Exists(cpuMaxPath)) { auto cgroup2Cpus = CgroupV2Cpus(cpuMaxPath); if (cgroup2Cpus > 0) { return cgroup2Cpus; } } if (NFs::Exists(cpuCfsQuotaUsPath) && NFs::Exists(cfsPeriodUsPath)) { auto cgroups1Cpus = CgroupV1Cpus(cpuCfsQuotaUsPath, cfsPeriodUsPath); if (cgroups1Cpus > 0) { return cgroups1Cpus; } } return 0; } #endif size_t NSystemInfo::NumberOfMillicores() { #if defined(_linux_) return CgroupCpus() * 1000; #else // fallback behaviour if cgroupfs is not available // returns number of millicores which is a multiple of an integer number of cpus return NSystemInfo::NumberOfCpus() * 1000; #endif } size_t NSystemInfo::NumberOfCpus() { #if defined(_linux_) if (auto res = CgroupCpus(); res) { return Max(1, std::llround(res)); } #endif #if defined(_win_) SYSTEM_INFO info; GetSystemInfo(&info); return info.dwNumberOfProcessors; #elif defined(_SC_NPROCESSORS_ONLN) return sysconf(_SC_NPROCESSORS_ONLN); #elif defined(_linux_) unsigned ret; int fd, nread, column; char buf[512]; static const char matchstr[] = "processor\t:"; fd = open("/proc/cpuinfo", O_RDONLY); if (fd == -1) { abort(); } column = 0; ret = 0; while (true) { nread = read(fd, buf, sizeof(buf)); if (nread <= 0) { break; } for (int i = 0; i < nread; ++i) { const char ch = buf[i]; if (ch == '\n') { column = 0; } else if (column != -1) { if (AsciiToLower(ch) == matchstr[column]) { ++column; if (column == sizeof(matchstr) - 1) { column = -1; ++ret; } } else { column = -1; } } } } if (ret == 0) { abort(); } close(fd); return ret; #elif defined(_freebsd_) || defined(_darwin_) int mib[2]; size_t len; unsigned ncpus = 1; mib[0] = CTL_HW; mib[1] = HW_NCPU; len = sizeof(ncpus); if (sysctl(mib, 2, &ncpus, &len, nullptr, 0) == -1) { abort(); } return ncpus; #else #error todo #endif } size_t NSystemInfo::LoadAverage(double* la, size_t len) { #if defined(_win_) || defined(_musl_) || defined(_bionic_) int ret = -1; #else for (size_t i = 0; i < len; ++i) { la[i] = 0; } int ret = getloadavg(la, len); #endif if (ret < 0) { for (size_t i = 0; i < len; ++i) { la[i] = 0; } ret = len; } return (size_t)ret; } static size_t NCpus; static size_t NMillicores; size_t NSystemInfo::CachedNumberOfCpus() { if (!NCpus) { NCpus = NumberOfCpus(); } return NCpus; } size_t NSystemInfo::CachedNumberOfMillicores() { if (!NMillicores) { NMillicores = NumberOfMillicores(); } return NMillicores; } size_t NSystemInfo::GetPageSize() noexcept { #if defined(_win_) SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); return sysInfo.dwPageSize; #else return sysconf(_SC_PAGESIZE); #endif } size_t NSystemInfo::TotalMemorySize() { #if defined(_linux_) && defined(_64_) try { auto q = FromString(StripString(TFileInput("/sys/fs/cgroup/memory/memory.limit_in_bytes").ReadAll())); if (q < (((size_t)1) << 60)) { return q; } } catch (...) { } #endif #if defined(_linux_) || defined(_cygwin_) struct sysinfo info; sysinfo(&info); return info.totalram; #elif defined(_darwin_) int mib[2]; int64_t memSize; size_t length; // Get the Physical memory size mib[0] = CTL_HW; mib[1] = HW_MEMSIZE; length = sizeof(int64_t); if (sysctl(mib, 2, &memSize, &length, NULL, 0) != 0) { ythrow yexception() << "sysctl failed: " << LastSystemErrorText(); } return (size_t)memSize; #elif defined(_win_) MEMORYSTATUSEX memoryStatusEx; memoryStatusEx.dwLength = sizeof(memoryStatusEx); if (!GlobalMemoryStatusEx(&memoryStatusEx)) { ythrow yexception() << "GlobalMemoryStatusEx failed: " << LastSystemErrorText(); } return (size_t)memoryStatusEx.ullTotalPhys; #else return 0; #endif } size_t NSystemInfo::MaxOpenFiles() { #if defined(ANDROID) || defined(__ANDROID__) return sysconf(_SC_OPEN_MAX); #elif defined(_win_) return _getmaxstdio(); #else return getdtablesize(); #endif }