2 +----------------------------------------------------------------------+
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>
23 #include <sys/fcntl.h>
24 #include <sys/utsname.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>
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"
46 const std::string kCpu
= "cpu";
47 const std::string kIo
= "io";
48 const std::string kMemory
= "memory";
52 ///////////////////////////////////////////////////////////////////////////////
56 static void readString(FILE *f
, string
&out
) {
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
;
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
);
92 auto const f
= fopen(name
.c_str(), "r");
94 readString(f
, cmdline
);
98 std::string converted
;
99 for (auto ch
: cmdline
) {
100 converted
+= ch
? ch
: ' ';
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;
126 if (!(fp
= fopen("/proc/schedstat", "r"))) {
130 // Refresh the proc info.
134 int64_t totalCpuDelay
= 0;
135 // Supposedly this should be enough to hold th important lines of the
138 while (fgets(buf
, sizeof(buf
), fp
) != nullptr) {
139 if (strncmp(buf
, "cpu", 3) == 0) {
142 "%*s %*u %*u %*u %*u %*u %*u %*u %lu %*u",
146 totalCpuDelay
+= cpuDelay
;
149 // The kernel reports the information in nanoseconds. Convert it
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
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]);
185 std::ifstream
file(path
);
186 if (file
.is_open()) {
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
);
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 /////////////////////////////////////////////////////////////////////////
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")) {
234 if (fscanf(f
, "%ld", &size
) != 1) 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) {
271 auto n
= sscanf(line
, "%*s %" SCNd64
" %7s", &result
, tail
);
273 if (n
< 2) return -1;
274 if (tail
[0] != 'k' || tail
[1] != 'B') return -1;
281 /////////////////////////////////////////////////////////////////////////
283 bool Process::GetMemoryInfo(MemInfo
& info
, bool checkCgroup2
) {
285 FILE* f
= fopen("/proc/meminfo", "r");
287 SCOPE_EXIT
{ fclose(f
); };
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;
304 if (checkCgroup2
) updateMemInfoWithCgroup2Info(info
);
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
;
319 ///////////////////////////////////////////////////////////////////////////////
321 std::string
Process::GetAppName() {
322 const char* progname
= getenv("_");
323 if (!progname
|| !*progname
) {
324 progname
= "unknown program";
329 std::string
Process::GetCurrentUser() {
330 const char *name
= getenv("LOGNAME");
335 auto buf
= PasswdBuffer
{};
337 if (!getpwuid_r(geteuid(), &buf
.ent
, buf
.data
.get(), buf
.size
, &pwd
) &&
338 pwd
&& pwd
->pw_name
) {
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
) {
352 // Read cwd symlink directly if it leads to the deleted path.
353 int r
= readlink("/proc/self/cwd", buf
, sizeof(buf
));
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;
365 std::string
Process::GetHomeDirectory() {
368 const char *home
= getenv("HOME");
372 passwd
*pwd
= getpwent();
373 if (pwd
&& pwd
->pw_dir
) {
378 if (ret
.empty() || ret
[ret
.size() - 1] != '/') {
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+")) {
402 if (fscanf(f
, "%x", &mask
)) {
403 constexpr unsigned hugetlbMask
= 0x60;
404 if ((mask
& hugetlbMask
) != hugetlbMask
) {
407 fprintf(f
, "0x%x", mask
);
414 void ProcStatus::update() {
415 if (FILE* f
= fopen("/proc/self/status", "r")) {
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
);
436 lastUpdate
.store(0, std::memory_order_release
);
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
);
447 #if USE_JEMALLOC_EXTENT_HOOKS
449 // Various arenas where range of hugetlb pages can be reserved but only
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
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
);
477 int Process::Relaunch() {
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()) {
499 // don't conflict with STDIO
500 next_fd
= dup(current
);
501 if (next_fd
<= STDERR_FILENO
) {
505 // don't conflict with targets
507 for (auto [target
, _current
] : fds
) {
508 if (next_fd
== target
) {
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.
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()) {
546 fds_to_close
.emplace(fd
);
548 for (const auto& fd
: fds_to_close
) {
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
;
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
) {
569 close(dups
.at(orig
));
575 char **build_cstrarr(const std::vector
<std::string
> &vec
) {
576 char **cstrarr
= nullptr;
577 int size
= vec
.size();
579 cstrarr
= (char **)malloc((size
+ 1) * sizeof(char *));
581 for (unsigned int i
= 0; i
< vec
.size(); i
++, j
++) {
582 *(cstrarr
+ j
) = (char *)vec
[i
].c_str();
584 *(cstrarr
+ j
) = nullptr;
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
,
599 // Distinguish execve failure: if the write side of the pipe
600 // is closed with no data, it succeeded.
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();
614 mprotect_1g_pages(PROT_READ
);
615 Process::OOMScoreAdj(1000);
620 std::map
<int, int> fds(orig_fds
);
622 const auto remapped
= Process::RemapFDsPreExec(fds
);
623 fork_w
= remapped
.at(-1);
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
);
653 execve(path
.c_str(), argv_arr
, envp_arr
);
655 dprintf(fork_w
, "%s %d", "execve", errno
);
656 _Exit(HPHP_EXIT_FAILURE
);
663 pfd
[0].events
= POLLIN
;
666 ret
= poll(pfd
, /* number of fds = */ 1, /* timeout = no timeout*/ -1);
667 } while (ret
== -1 && errno
== EINTR
);
670 auto len
= read(fork_r
, &buf
, sizeof(buf
));
673 // Closed without write, which means close-on-exec
678 char failed_call_buf
[17]; // 16 + trailing null
680 if (sscanf(buf
, "%16s %d", failed_call_buf
, &saved_errno
) != 2) {
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") {
692 if (failed_call
== "setsid") {
695 if (failed_call
== "setpgid") {
698 if (failed_call
== "execve") {
701 if (failed_call
== "putenv") {
707 ///////////////////////////////////////////////////////////////////////////////