1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/utils/port/Stat.h"
8
9 #include "td/utils/port/FileFd.h"
10
11 #if TD_PORT_POSIX
12
13 #include "td/utils/format.h"
14 #include "td/utils/logging.h"
15 #include "td/utils/misc.h"
16 #include "td/utils/port/Clocks.h"
17 #include "td/utils/port/detail/skip_eintr.h"
18 #include "td/utils/ScopeGuard.h"
19 #include "td/utils/SliceBuilder.h"
20
21 #include <utility>
22
23 #if TD_DARWIN
24 #include <mach/mach.h>
25 #include <sys/time.h>
26 #endif
27
28 // We don't want warnings from system headers
29 #if TD_GCC
30 #pragma GCC diagnostic push
31 #pragma GCC diagnostic ignored "-Wconversion"
32 #endif
33 #include <sys/stat.h>
34 #if TD_GCC
35 #pragma GCC diagnostic pop
36 #endif
37
38 #if TD_ANDROID || TD_TIZEN
39 #include <sys/syscall.h>
40 #endif
41
42 #elif TD_PORT_WINDOWS
43
44 #include "td/utils/port/thread.h"
45
46 #ifndef PSAPI_VERSION
47 #define PSAPI_VERSION 1
48 #endif
49 #ifdef __MINGW32__
50 #include <psapi.h>
51 #else
52 #include <Psapi.h>
53 #endif
54
55 #endif
56
57 namespace td {
58
59 #if TD_PORT_POSIX
60 namespace detail {
61
62 template <class...>
63 struct voider {
64 using type = void;
65 };
66 template <class... T>
67 using void_t = typename voider<T...>::type;
68
69 template <class T, class = void>
70 struct TimeNsec {
gettd::detail::TimeNsec71 static std::pair<int, int> get(const T &) {
72 T().warning("Platform lacks support of precise access/modification file times, comment this line to continue");
73 return {0, 0};
74 }
75 };
76
77 // remove libc compatibility hacks if any: we have our own hacks
78 #ifdef st_atimespec
79 #undef st_atimespec
80 #endif
81
82 #ifdef st_atimensec
83 #undef st_atimensec
84 #endif
85
86 #ifdef st_atime_nsec
87 #undef st_atime_nsec
88 #endif
89
90 template <class T>
91 struct TimeNsec<T, void_t<char, decltype(T::st_atimespec), decltype(T::st_mtimespec)>> {
gettd::detail::TimeNsec92 static std::pair<decltype(decltype(T::st_atimespec)::tv_nsec), decltype(decltype(T::st_mtimespec)::tv_nsec)> get(
93 const T &s) {
94 return {s.st_atimespec.tv_nsec, s.st_mtimespec.tv_nsec};
95 }
96 };
97
98 template <class T>
99 struct TimeNsec<T, void_t<short, decltype(T::st_atimensec), decltype(T::st_mtimensec)>> {
gettd::detail::TimeNsec100 static std::pair<decltype(T::st_atimensec), decltype(T::st_mtimensec)> get(const T &s) {
101 return {s.st_atimensec, s.st_mtimensec};
102 }
103 };
104
105 template <class T>
106 struct TimeNsec<T, void_t<int, decltype(T::st_atim), decltype(T::st_mtim)>> {
gettd::detail::TimeNsec107 static std::pair<decltype(decltype(T::st_atim)::tv_nsec), decltype(decltype(T::st_mtim)::tv_nsec)> get(const T &s) {
108 return {s.st_atim.tv_nsec, s.st_mtim.tv_nsec};
109 }
110 };
111
112 template <class T>
113 struct TimeNsec<T, void_t<long, decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)>> {
gettd::detail::TimeNsec114 static std::pair<decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)> get(const T &s) {
115 return {s.st_atime_nsec, s.st_mtime_nsec};
116 }
117 };
118
from_native_stat(const struct::stat & buf)119 Stat from_native_stat(const struct ::stat &buf) {
120 auto time_nsec = TimeNsec<struct ::stat>::get(buf);
121
122 Stat res;
123 res.atime_nsec_ = static_cast<uint64>(buf.st_atime) * 1000000000 + time_nsec.first;
124 res.mtime_nsec_ = static_cast<uint64>(buf.st_mtime) * 1000000000 + time_nsec.second / 1000 * 1000;
125 res.size_ = buf.st_size;
126 res.real_size_ = buf.st_blocks * 512;
127 res.is_dir_ = (buf.st_mode & S_IFMT) == S_IFDIR;
128 res.is_reg_ = (buf.st_mode & S_IFMT) == S_IFREG;
129 return res;
130 }
131
fstat(int native_fd)132 Result<Stat> fstat(int native_fd) {
133 struct ::stat buf;
134 if (detail::skip_eintr([&] { return ::fstat(native_fd, &buf); }) < 0) {
135 return OS_ERROR(PSLICE() << "Stat for fd " << native_fd << " failed");
136 }
137 return detail::from_native_stat(buf);
138 }
139
update_atime(int native_fd)140 Status update_atime(int native_fd) {
141 #if TD_LINUX
142 timespec times[2];
143 // access time
144 times[0].tv_nsec = UTIME_NOW;
145 times[0].tv_sec = 0;
146 // modify time
147 times[1].tv_nsec = UTIME_OMIT;
148 times[1].tv_sec = 0;
149 if (futimens(native_fd, times) < 0) {
150 auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd));
151 LOG(WARNING) << status;
152 return status;
153 }
154 return Status::OK();
155 #elif TD_DARWIN
156 TRY_RESULT(info, fstat(native_fd));
157 timeval upd[2];
158 auto now = Clocks::system();
159 // access time
160 upd[0].tv_sec = static_cast<decltype(upd[0].tv_sec)>(now);
161 upd[0].tv_usec = static_cast<decltype(upd[0].tv_usec)>((now - static_cast<double>(upd[0].tv_sec)) * 1000000);
162 // modify time
163 upd[1].tv_sec = static_cast<decltype(upd[1].tv_sec)>(info.mtime_nsec_ / 1000000000ll);
164 upd[1].tv_usec = static_cast<decltype(upd[1].tv_usec)>(info.mtime_nsec_ % 1000000000ll / 1000);
165 if (futimes(native_fd, upd) < 0) {
166 auto status = OS_ERROR(PSLICE() << "futimes " << tag("fd", native_fd));
167 LOG(WARNING) << status;
168 return status;
169 }
170 return Status::OK();
171 #else
172 return Status::Error("Not supported");
173 // timespec times[2];
174 //// access time
175 // times[0].tv_nsec = UTIME_NOW;
176 //// modify time
177 // times[1].tv_nsec = UTIME_OMIT;
178 //// int err = syscall(__NR_utimensat, native_fd, nullptr, times, 0);
179 // if (futimens(native_fd, times) < 0) {
180 // auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd));
181 // LOG(WARNING) << status;
182 // return status;
183 // }
184 // return Status::OK();
185 #endif
186 }
187 } // namespace detail
188
update_atime(CSlice path)189 Status update_atime(CSlice path) {
190 TRY_RESULT(file, FileFd::open(path, FileFd::Flags::Read));
191 SCOPE_EXIT {
192 file.close();
193 };
194 return detail::update_atime(file.get_native_fd().fd());
195 }
196 #endif
197
stat(CSlice path)198 Result<Stat> stat(CSlice path) {
199 #if TD_PORT_POSIX
200 struct ::stat buf;
201 int err = detail::skip_eintr([&] { return ::stat(path.c_str(), &buf); });
202 if (err < 0) {
203 return OS_ERROR(PSLICE() << "Stat for file \"" << path << "\" failed");
204 }
205 return detail::from_native_stat(buf);
206 #elif TD_PORT_WINDOWS
207 TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read | FileFd::PrivateFlags::WinStat));
208 return fd.stat();
209 #endif
210 }
211
mem_stat()212 Result<MemStat> mem_stat() {
213 #if TD_DARWIN
214 task_basic_info t_info;
215 mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
216
217 if (KERN_SUCCESS !=
218 task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&t_info), &t_info_count)) {
219 return Status::Error("Call to task_info failed");
220 }
221 MemStat res;
222 res.resident_size_ = t_info.resident_size;
223 res.virtual_size_ = t_info.virtual_size;
224 res.resident_size_peak_ = 0;
225 res.virtual_size_peak_ = 0;
226 return res;
227 #elif TD_LINUX || TD_ANDROID || TD_TIZEN
228 TRY_RESULT(fd, FileFd::open("/proc/self/status", FileFd::Read));
229 SCOPE_EXIT {
230 fd.close();
231 };
232
233 constexpr int TMEM_SIZE = 10000;
234 char mem[TMEM_SIZE];
235 TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1)));
236 CHECK(size < TMEM_SIZE - 1);
237 mem[size] = 0;
238
239 const char *s = mem;
240 MemStat res;
241 while (*s) {
242 const char *name_begin = s;
243 while (*s != 0 && *s != '\n') {
244 s++;
245 }
246 auto name_end = name_begin;
247 while (is_alpha(*name_end)) {
248 name_end++;
249 }
250 Slice name(name_begin, name_end);
251
252 uint64 *x = nullptr;
253 if (name == "VmPeak") {
254 x = &res.virtual_size_peak_;
255 }
256 if (name == "VmSize") {
257 x = &res.virtual_size_;
258 }
259 if (name == "VmHWM") {
260 x = &res.resident_size_peak_;
261 }
262 if (name == "VmRSS") {
263 x = &res.resident_size_;
264 }
265 if (x != nullptr) {
266 Slice value(name_end, s);
267 if (!value.empty() && value[0] == ':') {
268 value.remove_prefix(1);
269 }
270 value = trim(value);
271 value = split(value).first;
272 auto r_mem = to_integer_safe<uint64>(value);
273 if (r_mem.is_error()) {
274 LOG(ERROR) << "Failed to parse memory stats " << tag("name", name) << tag("value", value);
275 *x = static_cast<uint64>(-1);
276 } else {
277 *x = r_mem.ok() * 1024; // memory is in KB
278 }
279 }
280 if (*s == 0) {
281 break;
282 }
283 s++;
284 }
285
286 return res;
287 #elif TD_WINDOWS
288 PROCESS_MEMORY_COUNTERS_EX counters;
289 if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS *>(&counters),
290 sizeof(counters))) {
291 return Status::Error("Call to GetProcessMemoryInfo failed");
292 }
293
294 // Working set = all non-virtual memory in RAM, including memory-mapped files
295 // PrivateUsage = Commit charge = all non-virtual memory in RAM and swap file, but not in memory-mapped files
296 MemStat res;
297 res.resident_size_ = counters.WorkingSetSize;
298 res.resident_size_peak_ = counters.PeakWorkingSetSize;
299 res.virtual_size_ = counters.PrivateUsage;
300 res.virtual_size_peak_ = counters.PeakPagefileUsage;
301 return res;
302 #else
303 return Status::Error("Not supported");
304 #endif
305 }
306
307 #if TD_LINUX
cpu_stat_self(CpuStat & stat)308 Status cpu_stat_self(CpuStat &stat) {
309 TRY_RESULT(fd, FileFd::open("/proc/self/stat", FileFd::Read));
310 SCOPE_EXIT {
311 fd.close();
312 };
313
314 constexpr int TMEM_SIZE = 10000;
315 char mem[TMEM_SIZE];
316 TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1)));
317 if (size >= TMEM_SIZE - 1) {
318 return Status::Error("Failed for read /proc/self/stat");
319 }
320 mem[size] = 0;
321
322 char *s = mem;
323 char *t = mem + size;
324 int pass_cnt = 0;
325
326 while (pass_cnt < 15) {
327 if (pass_cnt == 13) {
328 stat.process_user_ticks_ = to_integer<uint64>(Slice(s, t));
329 }
330 if (pass_cnt == 14) {
331 stat.process_system_ticks_ = to_integer<uint64>(Slice(s, t));
332 }
333 while (*s && *s != ' ') {
334 s++;
335 }
336 if (*s == ' ') {
337 s++;
338 pass_cnt++;
339 } else {
340 return Status::Error("Unexpected end of proc file");
341 }
342 }
343 return Status::OK();
344 }
345
cpu_stat_total(CpuStat & stat)346 Status cpu_stat_total(CpuStat &stat) {
347 TRY_RESULT(fd, FileFd::open("/proc/stat", FileFd::Read));
348 SCOPE_EXIT {
349 fd.close();
350 };
351
352 constexpr int TMEM_SIZE = 10000;
353 char mem[TMEM_SIZE];
354 TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1)));
355 if (size >= TMEM_SIZE - 1) {
356 return Status::Error("Failed for read /proc/stat");
357 }
358 mem[size] = 0;
359
360 uint64 sum = 0;
361 uint64 cur = 0;
362 for (size_t i = 0; i < size; i++) {
363 char c = mem[i];
364 if (c >= '0' && c <= '9') {
365 cur = cur * 10 + static_cast<uint64>(c) - '0';
366 } else {
367 sum += cur;
368 cur = 0;
369 if (c == '\n') {
370 break;
371 }
372 }
373 }
374
375 stat.total_ticks_ = sum;
376 return Status::OK();
377 }
378 #endif
379
cpu_stat()380 Result<CpuStat> cpu_stat() {
381 #if TD_LINUX
382 CpuStat stat;
383 TRY_STATUS(cpu_stat_self(stat));
384 TRY_STATUS(cpu_stat_total(stat));
385 return stat;
386 #elif TD_WINDOWS
387 CpuStat stat;
388 stat.total_ticks_ = static_cast<uint64>(GetTickCount64()) * 10000;
389 auto hardware_concurrency = thread::hardware_concurrency();
390 if (hardware_concurrency != 0) {
391 stat.total_ticks_ *= hardware_concurrency;
392 }
393
394 FILETIME ignored_time;
395 FILETIME kernel_time;
396 FILETIME user_time;
397 if (!GetProcessTimes(GetCurrentProcess(), &ignored_time, &ignored_time, &kernel_time, &user_time)) {
398 return Status::Error("Failed to call GetProcessTimes");
399 }
400 stat.process_system_ticks_ = kernel_time.dwLowDateTime + (static_cast<uint64>(kernel_time.dwHighDateTime) << 32);
401 stat.process_user_ticks_ = user_time.dwLowDateTime + (static_cast<uint64>(user_time.dwHighDateTime) << 32);
402
403 return stat;
404 #else
405 return Status::Error("Not supported");
406 #endif
407 }
408
409 } // namespace td
410