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