let UsingUninitializedTry not allocate
[hiphop-php.git] / hphp / util / process.cpp
blob199cdc9c306808be5ff7d90a8ba65e15debb29e0
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/util/process.h"
17 #include "hphp/util/process-cpu.h"
18 #include "hphp/util/process-host.h"
20 #include <sys/types.h>
21 #include <stdlib.h>
23 #include <sys/fcntl.h>
24 #include <sys/utsname.h>
25 #include <sys/wait.h>
26 #include <pwd.h>
28 #include <folly/portability/Sockets.h>
29 #include <folly/portability/SysMman.h>
30 #include <folly/portability/Unistd.h>
31 #include <folly/Conv.h>
32 #include <folly/Format.h>
33 #include <folly/ScopeGuard.h>
34 #include <folly/String.h>
36 #include <filesystem>
37 #include <set>
38 #include <fstream>
40 #include "hphp/util/hugetlb.h"
41 #include "hphp/util/managed-arena.h"
42 #include "hphp/util/text-color.h"
43 #include "hphp/util/user-info.h"
45 namespace {
46 const std::string kCpu = "cpu";
47 const std::string kIo = "io";
48 const std::string kMemory = "memory";
51 namespace HPHP {
52 ///////////////////////////////////////////////////////////////////////////////
54 using std::string;
56 static void readString(FILE *f, string &out) {
57 size_t nread = 0;
58 constexpr unsigned int BUFFER_SIZE = 1024;
59 char buf[BUFFER_SIZE];
60 while ((nread = fread(buf, 1, BUFFER_SIZE, f)) != 0) {
61 out.append(buf, nread);
65 ///////////////////////////////////////////////////////////////////////////////
67 // Cached process statics
68 std::string Process::HostName;
69 std::string Process::CurrentWorkingDirectory;
70 char** Process::Argv;
71 std::atomic_int64_t ProcStatus::VmSizeKb;
72 std::atomic_int64_t ProcStatus::VmRSSKb;
73 std::atomic_int64_t ProcStatus::VmHWMKb;
74 std::atomic_int64_t ProcStatus::VmSwapKb;
75 std::atomic_int64_t ProcStatus::HugetlbPagesKb;
76 std::atomic_int64_t ProcStatus::UnusedKb;
77 std::atomic_int ProcStatus::threads;
78 std::atomic_uint ProcStatus::lastUpdate;
80 void Process::InitProcessStatics() {
81 HostName = GetHostName();
82 CurrentWorkingDirectory = GetCurrentDirectory();
85 ///////////////////////////////////////////////////////////////////////////////
86 // /proc/* parsing functions
88 std::string Process::GetCommandLine(pid_t pid) {
89 auto const name = folly::sformat("/proc/{}/cmdline", pid);
91 std::string cmdline;
92 auto const f = fopen(name.c_str(), "r");
93 if (f) {
94 readString(f, cmdline);
95 fclose(f);
98 std::string converted;
99 for (auto ch : cmdline) {
100 converted += ch ? ch : ' ';
102 return converted;
105 bool Process::IsUnderGDB() {
106 auto const cmdStr = GetCommandLine(getppid());
107 auto const cmdPiece = folly::StringPiece{cmdStr};
109 if (cmdPiece.empty()) return false;
111 auto const spaceIdx = std::min(cmdPiece.find(' '), cmdPiece.size() - 1);
112 auto const binaryPiece = cmdPiece.subpiece(0, spaceIdx + 1);
114 std::filesystem::path binaryPath(binaryPiece.begin(), binaryPiece.end());
115 return binaryPath.filename() == "gdb ";
118 int64_t Process::GetMemUsageMb() {
119 ProcStatus::update();
120 return ProcStatus::valid() ? ProcStatus::adjustedRssKb() / 1024 : 0;
123 int64_t Process::GetSystemCPUDelayMS() {
124 static FILE* fp = nullptr;
125 if (!fp) {
126 if (!(fp = fopen("/proc/schedstat", "r"))) {
127 return -1;
130 // Refresh the proc info.
131 rewind(fp);
132 fflush(fp);
134 int64_t totalCpuDelay = 0;
135 // Supposedly this should be enough to hold th important lines of the
136 // schedstat file.
137 char buf[320];
138 while (fgets(buf, sizeof(buf), fp) != nullptr) {
139 if (strncmp(buf, "cpu", 3) == 0) {
140 uint64_t cpuDelay;
141 if (sscanf(buf,
142 "%*s %*u %*u %*u %*u %*u %*u %*u %lu %*u",
143 &cpuDelay) != 1) {
144 return -1;
146 totalCpuDelay += cpuDelay;
149 // The kernel reports the information in nanoseconds. Convert it
150 // to milliseconds.
151 return totalCpuDelay / 1000000;
154 ProcPressure getProcPressure(const std::filesystem::path &path) {
155 ProcPressure procPressure;
157 // Helper function to parse a single line from the pressure file. Expected to
158 // be called with the entire line as input, for example:
159 // some avg10=0.01 avg60=0.01 avg300=0.01 total=12345
160 const auto parseLine = [](const std::string_view &line) -> Pressure {
161 // Drop the 'some|full' + space prefix and load into parts
162 std::vector<std::string> parts;
163 folly::split(' ', line.substr(5), parts);
165 assertx(parts.size() == 4); // Expect 4 metrics
167 Pressure pressure;
168 for (const auto &part : parts) {
169 std::vector<std::string> subparts;
170 folly::split('=', part, subparts);
171 assertx(subparts.size() == 2); // Expect 2 values
172 if (subparts[0] == "avg10") {
173 pressure.avg10 = folly::to<double>(subparts[1]);
174 } else if (subparts[0] == "avg60") {
175 pressure.avg60 = folly::to<double>(subparts[1]);
176 } else if (subparts[0] == "avg300") {
177 pressure.avg300 = folly::to<double>(subparts[1]);
178 } else if (subparts[0] == "total") {
179 pressure.total = folly::to<uint64_t>(subparts[1]);
182 return pressure;
185 std::ifstream file(path);
186 if (file.is_open()) {
187 std::string line;
188 while (std::getline(file, line)) {
189 if (line.starts_with("some")) {
190 procPressure.some = parseLine(line);
191 } else if (line.starts_with("full")) {
192 procPressure.full = parseLine(line);
197 return procPressure;
200 ProcPressure Process::GetCPUPressure(const std::filesystem::path &path) {
201 return getProcPressure(path);
204 ProcPressure Process::GetIOPressure(const std::filesystem::path &path) {
205 return getProcPressure(path);
208 ProcPressure Process::GetMemoryPressure(const std::filesystem::path &path) {
209 return getProcPressure(path);
212 int Process::GetNumThreads() {
213 ProcStatus::update();
214 return ProcStatus::valid() ? ProcStatus::nThreads() : 1;
217 /////////////////////////////////////////////////////////////////////////
219 namespace {
222 * Try to read the memory information from the given /cgroup2/memory.<fileName>
223 * file if available. For reference, see the "Memory Interface Files" section in
224 * https://www.kernel.org/doc/Documentation/cgroup-v2.txt.
226 * In case this function fails to read from the given file, it returns -1.
227 * Otherwise, it returns the size read converted to MBs.
229 int64_t readCgroup2FileMb(const char* fileName) {
230 std::string fullFileName = std::string("/cgroup2/memory.") + fileName;
232 if (FILE* f = fopen(fullFileName.c_str(), "r")) {
233 int64_t size;
234 if (fscanf(f, "%ld", &size) != 1) return -1;
235 fclose(f);
236 return size >> 20;
239 return -1;
243 * If cgroup2 is enabled, update the MemInfo in `info' based on cgroup2 limits.
245 void updateMemInfoWithCgroup2Info(MemInfo& info) {
246 if (!ProcStatus::valid()) return;
247 const int64_t cgroup2TotalMb = readCgroup2FileMb("max");
248 const int64_t currUsageMb = ProcStatus::totalRssKb() / 1024;
249 if (cgroup2TotalMb >= 0) {
250 auto const availableMb_approx =
251 std::max(cgroup2TotalMb - currUsageMb, int64_t{});
252 if (availableMb_approx < info.availableMb) {
253 info.availableMb = availableMb_approx;
255 if (cgroup2TotalMb < info.totalMb) {
256 info.totalMb = cgroup2TotalMb;
261 // Files such as /proc/meminfo and /proc/self/status contain many lines
262 // formatted as one of the following:
263 // <fieldName>: <number>
264 // <fieldName>: <number> kB
265 // This function parses the line and return the number in it. -1 is returned
266 // when the line isn't formatted as expected (until one day we need to read a
267 // line where -1 is a legit value).
268 int64_t readSize(const char* line, bool expectKB = false) {
269 int64_t result = -1;
270 char tail[8];
271 auto n = sscanf(line, "%*s %" SCNd64 " %7s", &result, tail);
272 if (expectKB) {
273 if (n < 2) return -1;
274 if (tail[0] != 'k' || tail[1] != 'B') return -1;
276 return result;
281 /////////////////////////////////////////////////////////////////////////
283 bool Process::GetMemoryInfo(MemInfo& info, bool checkCgroup2) {
284 info = MemInfo{};
285 FILE* f = fopen("/proc/meminfo", "r");
286 if (f) {
287 SCOPE_EXIT{ fclose(f); };
289 char line[128];
290 while (fgets(line, sizeof(line), f)) {
291 auto const kb = readSize(line, true);
292 if (!strncmp(line, "MemTotal:", 9)) {
293 if (kb >= 0) info.totalMb = kb / 1024;
294 } else if (!strncmp(line, "MemFree:", 8)) {
295 if (kb >= 0) info.freeMb = kb / 1024;
296 } else if (!strncmp(line, "Buffers:", 8)) {
297 if (kb >= 0) info.buffersMb = kb / 1024;
298 } else if (!strncmp(line, "Cached:", 7)) {
299 if (kb >= 0) info.cachedMb = kb / 1024;
300 } else if (!strncmp(line, "MemAvailable:", 13)) {
301 if (kb >= 0) info.availableMb = kb / 1024;
303 if (info.valid()) {
304 if (checkCgroup2) updateMemInfoWithCgroup2Info(info);
305 return true;
308 // If MemAvailable isn't available, which shouldn't be the case for kernel
309 // versions later than 3.14, we get a rough estimation.
310 if (info.availableMb < 0 && info.freeMb >= 0 &&
311 info.cachedMb >= 0 && info.buffersMb >= 0) {
312 info.availableMb = info.freeMb + info.cachedMb;
313 return true;
316 return false;
319 ///////////////////////////////////////////////////////////////////////////////
321 std::string Process::GetAppName() {
322 const char* progname = getenv("_");
323 if (!progname || !*progname) {
324 progname = "unknown program";
326 return progname;
329 std::string Process::GetCurrentUser() {
330 const char *name = getenv("LOGNAME");
331 if (name && *name) {
332 return name;
335 auto buf = PasswdBuffer{};
336 passwd *pwd;
337 if (!getpwuid_r(geteuid(), &buf.ent, buf.data.get(), buf.size, &pwd) &&
338 pwd && pwd->pw_name) {
339 return pwd->pw_name;
341 return "";
344 std::string Process::GetCurrentDirectory() {
345 char buf[PATH_MAX + 64]; // additional space for suffixes like " (deleted)";
346 memset(buf, 0, sizeof(buf));
347 if (char* cwd = getcwd(buf, PATH_MAX)) return cwd;
349 if (errno != ENOENT) {
350 return "";
352 // Read cwd symlink directly if it leads to the deleted path.
353 int r = readlink("/proc/self/cwd", buf, sizeof(buf));
354 if (r == -1) {
355 return "";
357 auto const kDeleted = " (deleted)";
358 auto const kDeletedLen = strlen(kDeleted);
359 if (r >= kDeletedLen && !strcmp(buf + r - kDeletedLen, kDeleted)) {
360 buf[r - kDeletedLen] = 0;
362 return &(buf[0]);
365 std::string Process::GetHomeDirectory() {
366 string ret;
368 const char *home = getenv("HOME");
369 if (home && *home) {
370 ret = home;
371 } else {
372 passwd *pwd = getpwent();
373 if (pwd && pwd->pw_dir) {
374 ret = pwd->pw_dir;
378 if (ret.empty() || ret[ret.size() - 1] != '/') {
379 ret += '/';
381 return ret;
384 void Process::SetCoreDumpHugePages() {
386 * From documentation athttp://man7.org/linux/man-pages/man5/core.5.html
388 * The bits in coredump_filter have the following meanings:
390 * bit 0 Dump anonymous private mappings.
391 * bit 1 Dump anonymous shared mappings.
392 * bit 2 Dump file-backed private mappings.
393 * bit 3 Dump file-backed shared mappings.
394 * bit 4 (since Linux 2.6.24) Dump ELF headers.
395 * bit 5 (since Linux 2.6.28) Dump private huge pages.
396 * bit 6 (since Linux 2.6.28) Dump shared huge pages.
397 * bit 7 (since Linux 4.4) Dump private DAX pages.
398 * bit 8 (since Linux 4.4) Dump shared DAX pages.
400 if (FILE* f = fopen("/proc/self/coredump_filter", "r+")) {
401 unsigned mask = 0;
402 if (fscanf(f, "%x", &mask)) {
403 constexpr unsigned hugetlbMask = 0x60;
404 if ((mask & hugetlbMask) != hugetlbMask) {
405 mask |= hugetlbMask;
406 rewind(f);
407 fprintf(f, "0x%x", mask);
410 fclose(f);
414 void ProcStatus::update() {
415 if (FILE* f = fopen("/proc/self/status", "r")) {
416 char line[128];
417 int64_t vmsize = 0, vmrss = 0, vmhwm = 0, vmswap = 0, hugetlb = 0;
418 while (fgets(line, sizeof(line), f)) {
419 if (!strncmp(line, "VmSize:", 7)) {
420 vmsize = readSize(line, true);
421 } else if (!strncmp(line, "VmRSS:", 6)) {
422 vmrss = readSize(line, true);
423 } else if (!strncmp(line, "VmHWM:", 6)) {
424 vmhwm = readSize(line, true);
425 } else if (!strncmp(line, "VmSwap:", 7)) {
426 vmswap = readSize(line, true);
427 } else if (!strncmp(line, "HugetlbPages:", 13)) {
428 hugetlb = readSize(line, true);
429 } else if (!strncmp(line, "Threads:", 8)) {
430 threads.store(readSize(line, false), std::memory_order_release);
433 fclose(f);
434 if (vmrss <= 0) {
435 // Invalid
436 lastUpdate.store(0, std::memory_order_release);
437 } else {
438 VmSizeKb.store(vmsize, std::memory_order_release);
439 VmRSSKb.store(vmrss, std::memory_order_release);
440 VmSwapKb.store(vmswap, std::memory_order_release);
441 VmHWMKb.store(vmhwm + hugetlb, std::memory_order_release);
442 HugetlbPagesKb.store(hugetlb, std::memory_order_release);
443 lastUpdate.store(time(), std::memory_order_release);
445 #ifdef USE_JEMALLOC
446 mallctl_epoch();
447 #if USE_JEMALLOC_EXTENT_HOOKS
448 size_t unused = 0;
449 // Various arenas where range of hugetlb pages can be reserved but only
450 // partially used.
451 unused += alloc::getRange(alloc::AddrRangeClass::VeryLow).retained();
452 unused += alloc::getRange(alloc::AddrRangeClass::Low).retained();
453 unused += alloc::getRange(alloc::AddrRangeClass::Uncounted).retained();
454 if (alloc::g_arena0) {
455 unused += alloc::g_arena0->retained();
457 for (auto const arena : alloc::g_local_arenas) {
458 if (arena) unused += arena->retained();
460 updateUnused(unused >> 10); // convert to kB
461 #endif
462 #endif
466 bool Process::OOMScoreAdj(int adj) {
467 if (adj >= -1000 && adj < 1000) {
468 if (auto f = fopen("/proc/self/oom_score_adj", "r+")) {
469 fprintf(f, "%d", adj);
470 fclose(f);
471 return true;
474 return false;
477 int Process::Relaunch() {
478 if (!Argv) {
479 errno = EINVAL;
480 return -1;
482 return execvp(Argv[0], Argv);
485 std::map<int, int> Process::RemapFDsPreExec(const std::map<int, int>& fds) {
486 std::map<int, int> unspecified;
487 // 1. copy all to FDs outside of STDIO range
488 std::map<int, int> dups;
489 std::set<int> preserve_set;
490 for (auto& [_target, current] : fds) {
491 if (dups.find(current) != dups.end()) {
492 continue;
495 int next_fd;
496 bool conflict;
497 do {
498 conflict = false;
499 // don't conflict with STDIO
500 next_fd = dup(current);
501 if (next_fd <= STDERR_FILENO) {
502 conflict = true;
503 continue;
505 // don't conflict with targets
506 conflict = false;
507 for (auto [target, _current] : fds) {
508 if (next_fd == target) {
509 conflict = true;
510 break;
513 } while (conflict);
514 dups[current] = next_fd;
515 preserve_set.emplace(next_fd);
518 // 2. clean up libc STDIO
520 // Don't want to swap the FD underlying these FILE*...
522 // If they are closed already, these are silent no-ops.
523 fclose(stdin);
524 fclose(stdout);
525 fclose(stderr);
527 // 3. close all FDs except our dups
529 // This includes the STDIO FDs as it's possible that:
530 // - the FILE* were previously closed (so the fclose above were no-ops)
531 // - the FDs were then re-used
532 const char* fd_dir = "/proc/self/fd";
533 // If you close FDs while in this loop, they get removed from /proc/self/fd
534 // and the iterator gets sad ("Bad file descriptor: /proc/self/fd")
535 std::set<int> fds_to_close;
536 for (const auto& entry : std::filesystem::directory_iterator(fd_dir)) {
537 char* endptr = nullptr;
538 auto filename = entry.path().filename();
539 const char* filename_c = filename.c_str();
540 const int fd = strtol(filename_c, &endptr, 10);
541 assert(endptr != filename_c); // no matching characters
542 assert(*endptr == '\0'); // entire string
543 if (preserve_set.find(fd) != preserve_set.end()) {
544 continue;
546 fds_to_close.emplace(fd);
548 for (const auto& fd: fds_to_close) {
549 close(fd);
552 // 4. Move the dups into place.
553 for (const auto& [target, orig] : fds) {
554 int tmp = dups[orig];
556 if (target < 0 /* don't care what the FD is */) {
557 unspecified[target] = tmp;
558 } else {
559 dup2(tmp, target);
563 // 5. Close the dups; do this separately to above in case
564 // the same orig was used for multiple targets
565 for (const auto& [target, orig] : fds) {
566 if (target < 0) {
567 continue;
569 close(dups.at(orig));
571 return unspecified;
574 namespace {
575 char **build_cstrarr(const std::vector<std::string> &vec) {
576 char **cstrarr = nullptr;
577 int size = vec.size();
578 if (size) {
579 cstrarr = (char **)malloc((size + 1) * sizeof(char *));
580 int j = 0;
581 for (unsigned int i = 0; i < vec.size(); i++, j++) {
582 *(cstrarr + j) = (char *)vec[i].c_str();
584 *(cstrarr + j) = nullptr;
586 return cstrarr;
588 } // namespace {
590 pid_t Process::ForkAndExecve(
591 const std::string& path,
592 const std::vector<std::string>& argv,
593 const std::vector<std::string>& envp,
594 const std::string& cwd,
595 const std::map<int, int>& orig_fds,
596 int flags,
597 pid_t pgid
599 // Distinguish execve failure: if the write side of the pipe
600 // is closed with no data, it succeeded.
601 int fork_fds[2];
602 pipe(fork_fds);
603 int fork_r = fork_fds[0];
604 int fork_w = fork_fds[1];
605 fcntl(fork_w, F_SETFD, fcntl(fork_w, F_GETFD) | O_CLOEXEC);
607 pid_t child = fork();
609 if (child == -1) {
610 return -1;
613 if (child == 0) {
614 mprotect_1g_pages(PROT_READ);
615 Process::OOMScoreAdj(1000);
617 close(fork_r);
619 // Need mutable copy
620 std::map<int, int> fds(orig_fds);
621 fds[-1] = fork_w;
622 const auto remapped = Process::RemapFDsPreExec(fds);
623 fork_w = remapped.at(-1);
625 if (!cwd.empty()) {
626 if (cwd != Process::GetCurrentDirectory()) {
627 if (chdir(cwd.c_str()) == -1) {
628 dprintf(fork_w, "%s %d", "chdir", errno);
629 _Exit(HPHP_EXIT_FAILURE);
634 if (flags & Process::FORK_AND_EXECVE_FLAG_SETSID) {
635 if (setsid() == -1) {
636 dprintf(fork_w, "%s\n%d\n", "setsid", errno);
637 _Exit(HPHP_EXIT_FAILURE);
639 } else if (flags & Process::FORK_AND_EXECVE_FLAG_SETPGID) {
640 if (setpgid(0, pgid) == -1) {
641 dprintf(fork_w, "%s %d", "setpgid", errno);
642 _Exit(HPHP_EXIT_FAILURE);
646 char** argv_arr = build_cstrarr(argv);
647 char** envp_arr = build_cstrarr(envp);
648 SCOPE_EXIT { free(argv_arr); free(envp_arr); };
650 if (flags & Process::FORK_AND_EXECVE_FLAG_EXECVPE) {
651 execvpe(path.c_str(), argv_arr, envp_arr);
652 } else {
653 execve(path.c_str(), argv_arr, envp_arr);
655 dprintf(fork_w, "%s %d", "execve", errno);
656 _Exit(HPHP_EXIT_FAILURE);
659 close(fork_w);
661 pollfd pfd[1];
662 pfd[0].fd = fork_w;
663 pfd[0].events = POLLIN;
664 int ret;
665 do {
666 ret = poll(pfd, /* number of fds = */ 1, /* timeout = no timeout*/ -1);
667 } while (ret == -1 && errno == EINTR);
669 char buf[16];
670 auto len = read(fork_r, &buf, sizeof(buf));
671 close(fork_r);
673 // Closed without write, which means close-on-exec
674 if (len < 1) {
675 return child;
678 char failed_call_buf[17]; // 16 + trailing null
679 int saved_errno;
680 if (sscanf(buf, "%16s %d", failed_call_buf, &saved_errno) != 2) {
681 return -999;
684 // Doing the call => return value here instead of sending return values over
685 // the pipe so that it's all in one place, and we're less likely to introduce
686 // bugs when/if we add additional features.
687 const std::string failed_call(failed_call_buf);
688 SCOPE_EXIT { errno = saved_errno; };
689 if (failed_call == "chdir") {
690 return -2;
692 if (failed_call == "setsid") {
693 return -3;
695 if (failed_call == "setpgid") {
696 return -4;
698 if (failed_call == "execve") {
699 return -5;
701 if (failed_call == "putenv") {
702 return -6;
704 return -9999;
707 ///////////////////////////////////////////////////////////////////////////////