/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/port/Stat.h" #include "td/utils/port/FileFd.h" #if TD_PORT_POSIX #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/Clocks.h" #include "td/utils/port/detail/skip_eintr.h" #include "td/utils/ScopeGuard.h" #include #if TD_DARWIN #include #include #endif // We don't want warnings from system headers #if TD_GCC #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #if TD_GCC #pragma GCC diagnostic pop #endif #if TD_ANDROID || TD_TIZEN #include #endif #elif TD_PORT_WINDOWS #include "td/utils/port/thread.h" #ifndef PSAPI_VERSION #define PSAPI_VERSION 1 #endif #include #pragma comment(lib, "psapi.lib") #endif namespace td { #if TD_PORT_POSIX namespace detail { template struct voider { using type = void; }; template using void_t = typename voider::type; template struct TimeNsec { static std::pair get(const T &) { T().warning("Platform lacks support of precise access/modification file times, comment this line to continue"); return {0, 0}; } }; // remove libc compatibility hacks if any: we have our own hacks #ifdef st_atimespec #undef st_atimespec #endif #ifdef st_atimensec #undef st_atimensec #endif #ifdef st_atime_nsec #undef st_atime_nsec #endif template struct TimeNsec> { static std::pair get( const T &s) { return {s.st_atimespec.tv_nsec, s.st_mtimespec.tv_nsec}; } }; template struct TimeNsec> { static std::pair get(const T &s) { return {s.st_atimensec, s.st_mtimensec}; } }; template struct TimeNsec> { static std::pair get(const T &s) { return {s.st_atim.tv_nsec, s.st_mtim.tv_nsec}; } }; template struct TimeNsec> { static std::pair get(const T &s) { return {s.st_atime_nsec, s.st_mtime_nsec}; } }; Stat from_native_stat(const struct ::stat &buf) { auto time_nsec = TimeNsec::get(buf); Stat res; res.atime_nsec_ = static_cast(buf.st_atime) * 1000000000 + time_nsec.first; res.mtime_nsec_ = static_cast(buf.st_mtime) * 1000000000 + time_nsec.second / 1000 * 1000; res.size_ = buf.st_size; res.real_size_ = buf.st_blocks * 512; res.is_dir_ = (buf.st_mode & S_IFMT) == S_IFDIR; res.is_reg_ = (buf.st_mode & S_IFMT) == S_IFREG; return res; } Result fstat(int native_fd) { struct ::stat buf; if (detail::skip_eintr([&] { return ::fstat(native_fd, &buf); }) < 0) { return OS_ERROR(PSLICE() << "Stat for fd " << native_fd << " failed"); } return detail::from_native_stat(buf); } Status update_atime(int native_fd) { #if TD_LINUX timespec times[2]; // access time times[0].tv_nsec = UTIME_NOW; times[0].tv_sec = 0; // modify time times[1].tv_nsec = UTIME_OMIT; times[1].tv_sec = 0; if (futimens(native_fd, times) < 0) { auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd)); LOG(WARNING) << status; return status; } return Status::OK(); #elif TD_DARWIN TRY_RESULT(info, fstat(native_fd)); timeval upd[2]; auto now = Clocks::system(); // access time upd[0].tv_sec = static_cast(now); upd[0].tv_usec = static_cast((now - static_cast(upd[0].tv_sec)) * 1000000); // modify time upd[1].tv_sec = static_cast(info.mtime_nsec_ / 1000000000ll); upd[1].tv_usec = static_cast(info.mtime_nsec_ % 1000000000ll / 1000); if (futimes(native_fd, upd) < 0) { auto status = OS_ERROR(PSLICE() << "futimes " << tag("fd", native_fd)); LOG(WARNING) << status; return status; } return Status::OK(); #else return Status::Error("Not supported"); // timespec times[2]; //// access time // times[0].tv_nsec = UTIME_NOW; //// modify time // times[1].tv_nsec = UTIME_OMIT; //// int err = syscall(__NR_utimensat, native_fd, nullptr, times, 0); // if (futimens(native_fd, times) < 0) { // auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd)); // LOG(WARNING) << status; // return status; // } // return Status::OK(); #endif } } // namespace detail Status update_atime(CSlice path) { TRY_RESULT(file, FileFd::open(path, FileFd::Flags::Read)); SCOPE_EXIT { file.close(); }; return detail::update_atime(file.get_native_fd().fd()); } #endif Result stat(CSlice path) { #if TD_PORT_POSIX struct ::stat buf; int err = detail::skip_eintr([&] { return ::stat(path.c_str(), &buf); }); if (err < 0) { return OS_ERROR(PSLICE() << "Stat for file \"" << path << "\" failed"); } return detail::from_native_stat(buf); #elif TD_PORT_WINDOWS TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read | FileFd::PrivateFlags::WinStat)); return fd.stat(); #endif } Result mem_stat() { #if TD_DARWIN task_basic_info t_info; mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast(&t_info), &t_info_count)) { return Status::Error("Call to task_info failed"); } MemStat res; res.resident_size_ = t_info.resident_size; res.virtual_size_ = t_info.virtual_size; res.resident_size_peak_ = 0; res.virtual_size_peak_ = 0; return res; #elif TD_LINUX || TD_ANDROID || TD_TIZEN TRY_RESULT(fd, FileFd::open("/proc/self/status", FileFd::Read)); SCOPE_EXIT { fd.close(); }; constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); CHECK(size < TMEM_SIZE - 1); mem[size] = 0; const char *s = mem; MemStat res; while (*s) { const char *name_begin = s; while (*s != 0 && *s != '\n') { s++; } auto name_end = name_begin; while (is_alpha(*name_end)) { name_end++; } Slice name(name_begin, name_end); uint64 *x = nullptr; if (name == "VmPeak") { x = &res.virtual_size_peak_; } if (name == "VmSize") { x = &res.virtual_size_; } if (name == "VmHWM") { x = &res.resident_size_peak_; } if (name == "VmRSS") { x = &res.resident_size_; } if (x != nullptr) { Slice value(name_end, s); if (!value.empty() && value[0] == ':') { value.remove_prefix(1); } value = trim(value); value = split(value).first; auto r_mem = to_integer_safe(value); if (r_mem.is_error()) { LOG(ERROR) << "Failed to parse memory stats " << tag("name", name) << tag("value", value); *x = static_cast(-1); } else { *x = r_mem.ok() * 1024; // memory is in KB } } if (*s == 0) { break; } s++; } return res; #elif TD_WINDOWS PROCESS_MEMORY_COUNTERS_EX counters; if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters))) { return Status::Error("Call to GetProcessMemoryInfo failed"); } MemStat res; res.resident_size_ = counters.WorkingSetSize; res.resident_size_peak_ = counters.PeakWorkingSetSize; res.virtual_size_ = counters.PrivateUsage; res.virtual_size_peak_ = counters.PeakPagefileUsage; return res; #else return Status::Error("Not supported"); #endif } #if TD_LINUX Status cpu_stat_self(CpuStat &stat) { TRY_RESULT(fd, FileFd::open("/proc/self/stat", FileFd::Read)); SCOPE_EXIT { fd.close(); }; constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); if (size >= TMEM_SIZE - 1) { return Status::Error("Failed for read /proc/self/stat"); } mem[size] = 0; char *s = mem; char *t = mem + size; int pass_cnt = 0; while (pass_cnt < 15) { if (pass_cnt == 13) { stat.process_user_ticks_ = to_integer(Slice(s, t)); } if (pass_cnt == 14) { stat.process_system_ticks_ = to_integer(Slice(s, t)); } while (*s && *s != ' ') { s++; } if (*s == ' ') { s++; pass_cnt++; } else { return Status::Error("Unexpected end of proc file"); } } return Status::OK(); } Status cpu_stat_total(CpuStat &stat) { TRY_RESULT(fd, FileFd::open("/proc/stat", FileFd::Read)); SCOPE_EXIT { fd.close(); }; constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); if (size >= TMEM_SIZE - 1) { return Status::Error("Failed for read /proc/stat"); } mem[size] = 0; uint64 sum = 0; uint64 cur = 0; for (size_t i = 0; i < size; i++) { char c = mem[i]; if (c >= '0' && c <= '9') { cur = cur * 10 + static_cast(c) - '0'; } else { sum += cur; cur = 0; if (c == '\n') { break; } } } stat.total_ticks_ = sum; return Status::OK(); } #endif Result cpu_stat() { #if TD_LINUX CpuStat stat; TRY_STATUS(cpu_stat_self(stat)); TRY_STATUS(cpu_stat_total(stat)); return stat; #elif TD_WINDOWS CpuStat stat; stat.total_ticks_ = static_cast(GetTickCount64()) * 10000; auto hardware_concurrency = thread::hardware_concurrency(); if (hardware_concurrency != 0) { stat.total_ticks_ *= hardware_concurrency; } FILETIME ignored_time; FILETIME kernel_time; FILETIME user_time; if (!GetProcessTimes(GetCurrentProcess(), &ignored_time, &ignored_time, &kernel_time, &user_time)) { return Status::Error("Failed to call GetProcessTimes"); } stat.process_system_ticks_ = kernel_time.dwLowDateTime + (static_cast(kernel_time.dwHighDateTime) << 32); stat.process_user_ticks_ = user_time.dwLowDateTime + (static_cast(user_time.dwHighDateTime) << 32); return stat; #else return Status::Error("Not supported"); #endif } Result get_total_mem_stat() { #if TD_LINUX TRY_RESULT(fd, FileFd::open("/proc/meminfo", FileFd::Read)); SCOPE_EXIT { fd.close(); }; constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); if (size >= TMEM_SIZE - 1) { return Status::Error("Failed for read /proc/meminfo"); } TotalMemStat stat; mem[size] = 0; const char *s = mem; size_t got = 0; while (*s) { const char *name_begin = s; while (*s != 0 && *s != '\n') { s++; } auto name_end = name_begin; while (is_alpha(*name_end)) { name_end++; } Slice name(name_begin, name_end); td::uint64 *dest = nullptr; if (name == "MemTotal") { dest = &stat.total_ram; } else if (name == "MemAvailable") { dest = &stat.available_ram; } if (dest != nullptr) { Slice value(name_end, s); if (!value.empty() && value[0] == ':') { value.remove_prefix(1); } value = trim(value); value = split(value).first; TRY_RESULT_PREFIX(mem, to_integer_safe(value), PSLICE() << "Invalid value of " << name); if (mem >= 1ULL << (64 - 10)) { return Status::Error("Invalid value of MemTotal"); } *dest = mem * 1024; got++; if (got == 2) { return stat; } } if (*s == 0) { break; } s++; } return Status::Error("No MemTotal in /proc/meminfo"); #else return Status::Error("Not supported"); #endif } Result get_cpu_cores() { #if TD_LINUX uint32 result = 0; TRY_RESULT(fd, FileFd::open("/proc/cpuinfo", FileFd::Read)); SCOPE_EXIT { fd.close(); }; std::string data; char buf[10000]; while (true) { TRY_RESULT(size, fd.read(MutableSlice{buf, sizeof(buf) - 1})); if (size == 0) { break; } buf[size] = '\0'; data += buf; } size_t i = 0; while (i < data.size()) { const char *line_begin = data.data() + i; while (i < data.size() && data[i] != '\n') { ++i; } auto line_end = data.data() + i; ++i; Slice line{line_begin, line_end}; size_t j = 0; while (j < line.size() && line[j] != ' ' && line[j] != '\t' && line[j] != ':') { ++j; } Slice name = line.substr(0, j); if (name == "processor") { ++result; } } return result; #else return Status::Error("Not supported"); #endif } } // namespace td