|
8 | 8 | #include <mach/mach.h> |
9 | 9 | #include <mach/processor_info.h> |
10 | 10 | #include <mach/vm_statistics.h> |
| 11 | +#include <fcntl.h> |
11 | 12 | #include <pwd.h> |
| 13 | +#include <spawn.h> |
12 | 14 | #include <sys/proc.h> |
13 | 15 | #include <sys/sysctl.h> |
14 | 16 | #include <sys/time.h> |
| 17 | +#include <sys/wait.h> |
15 | 18 | #include <unistd.h> |
16 | 19 |
|
17 | 20 | #include <algorithm> |
@@ -73,17 +76,113 @@ std::uint64_t current_uptime_seconds() { |
73 | 76 | return now > boot_time.tv_sec ? static_cast<std::uint64_t>(now - boot_time.tv_sec) : 0; |
74 | 77 | } |
75 | 78 |
|
76 | | -int gpu_core_count() { |
77 | | - FILE* pipe = popen("system_profiler -json SPDisplaysDataType 2>/dev/null", "r"); |
78 | | - if (!pipe) { |
79 | | - return 0; |
| 79 | +std::string run_command_capture(const std::vector<std::string>& args, bool discard_stderr) { |
| 80 | + if (args.empty()) { |
| 81 | + return ""; |
| 82 | + } |
| 83 | + |
| 84 | + int pipe_fds[2] = {-1, -1}; |
| 85 | + if (pipe(pipe_fds) != 0) { |
| 86 | + return ""; |
80 | 87 | } |
| 88 | + |
| 89 | + posix_spawn_file_actions_t actions; |
| 90 | + posix_spawn_file_actions_init(&actions); |
| 91 | + posix_spawn_file_actions_addclose(&actions, pipe_fds[0]); |
| 92 | + posix_spawn_file_actions_adddup2(&actions, pipe_fds[1], STDOUT_FILENO); |
| 93 | + posix_spawn_file_actions_addclose(&actions, pipe_fds[1]); |
| 94 | + |
| 95 | + int devnull_fd = -1; |
| 96 | + if (discard_stderr) { |
| 97 | + devnull_fd = open("/dev/null", O_WRONLY); |
| 98 | + if (devnull_fd >= 0) { |
| 99 | + posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDERR_FILENO); |
| 100 | + posix_spawn_file_actions_addclose(&actions, devnull_fd); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + std::vector<char*> argv; |
| 105 | + argv.reserve(args.size() + 1); |
| 106 | + for (const auto& arg : args) { |
| 107 | + argv.push_back(const_cast<char*>(arg.c_str())); |
| 108 | + } |
| 109 | + argv.push_back(nullptr); |
| 110 | + |
| 111 | + static char path_env[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin"; |
| 112 | + static char lang_env[] = "LANG=C"; |
| 113 | + static char lc_all_env[] = "LC_ALL=C"; |
| 114 | + char* const safe_env[] = {path_env, lang_env, lc_all_env, nullptr}; |
| 115 | + |
| 116 | + pid_t pid = 0; |
| 117 | + const int spawn_rc = posix_spawn(&pid, args[0].c_str(), &actions, nullptr, argv.data(), safe_env); |
| 118 | + posix_spawn_file_actions_destroy(&actions); |
| 119 | + if (devnull_fd >= 0) close(devnull_fd); |
| 120 | + close(pipe_fds[1]); |
| 121 | + if (spawn_rc != 0) { |
| 122 | + close(pipe_fds[0]); |
| 123 | + return ""; |
| 124 | + } |
| 125 | + |
81 | 126 | std::string output; |
82 | 127 | std::array<char, 4096> buffer{}; |
83 | | - while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) { |
84 | | - output += buffer.data(); |
| 128 | + ssize_t bytes = 0; |
| 129 | + while ((bytes = read(pipe_fds[0], buffer.data(), buffer.size())) > 0) { |
| 130 | + output.append(buffer.data(), static_cast<std::size_t>(bytes)); |
| 131 | + } |
| 132 | + close(pipe_fds[0]); |
| 133 | + int status = 0; |
| 134 | + waitpid(pid, &status, 0); |
| 135 | + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { |
| 136 | + return ""; |
| 137 | + } |
| 138 | + return output; |
| 139 | +} |
| 140 | + |
| 141 | +bool run_command_quiet(const std::vector<std::string>& args) { |
| 142 | + if (args.empty()) { |
| 143 | + return false; |
| 144 | + } |
| 145 | + |
| 146 | + posix_spawn_file_actions_t actions; |
| 147 | + posix_spawn_file_actions_init(&actions); |
| 148 | + const int devnull_fd = open("/dev/null", O_WRONLY); |
| 149 | + if (devnull_fd >= 0) { |
| 150 | + posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDOUT_FILENO); |
| 151 | + posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDERR_FILENO); |
| 152 | + posix_spawn_file_actions_addclose(&actions, devnull_fd); |
| 153 | + } |
| 154 | + |
| 155 | + std::vector<char*> argv; |
| 156 | + argv.reserve(args.size() + 1); |
| 157 | + for (const auto& arg : args) { |
| 158 | + argv.push_back(const_cast<char*>(arg.c_str())); |
| 159 | + } |
| 160 | + argv.push_back(nullptr); |
| 161 | + |
| 162 | + static char path_env[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin"; |
| 163 | + static char lang_env[] = "LANG=C"; |
| 164 | + static char lc_all_env[] = "LC_ALL=C"; |
| 165 | + char* const safe_env[] = {path_env, lang_env, lc_all_env, nullptr}; |
| 166 | + |
| 167 | + pid_t pid = 0; |
| 168 | + const int spawn_rc = posix_spawn(&pid, args[0].c_str(), &actions, nullptr, argv.data(), safe_env); |
| 169 | + posix_spawn_file_actions_destroy(&actions); |
| 170 | + if (devnull_fd >= 0) close(devnull_fd); |
| 171 | + if (spawn_rc != 0) { |
| 172 | + return false; |
| 173 | + } |
| 174 | + int status = 0; |
| 175 | + waitpid(pid, &status, 0); |
| 176 | + return WIFEXITED(status) && WEXITSTATUS(status) == 0; |
| 177 | +} |
| 178 | + |
| 179 | +int gpu_core_count() { |
| 180 | + const std::string output = run_command_capture( |
| 181 | + {"/usr/sbin/system_profiler", "-json", "SPDisplaysDataType"}, |
| 182 | + true); |
| 183 | + if (output.empty()) { |
| 184 | + return 0; |
85 | 185 | } |
86 | | - pclose(pipe); |
87 | 186 | const std::string token = "\"sppci_cores\" : \""; |
88 | 187 | const std::size_t pos = output.find(token); |
89 | 188 | if (pos == std::string::npos) { |
@@ -181,20 +280,6 @@ std::string battery_description() { |
181 | 280 | return result; |
182 | 281 | } |
183 | 282 |
|
184 | | -std::string read_command_output(const std::string& command) { |
185 | | - FILE* pipe = popen(command.c_str(), "r"); |
186 | | - if (!pipe) { |
187 | | - return ""; |
188 | | - } |
189 | | - std::string output; |
190 | | - std::array<char, 4096> buffer{}; |
191 | | - while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) { |
192 | | - output += buffer.data(); |
193 | | - } |
194 | | - pclose(pipe); |
195 | | - return output; |
196 | | -} |
197 | | - |
198 | 283 | std::string username_for_uid(uid_t uid) { |
199 | 284 | if (const passwd* entry = getpwuid(uid)) { |
200 | 285 | return entry->pw_name; |
@@ -618,11 +703,15 @@ class DarwinSampler final : public Sampler { |
618 | 703 | int fd = mkstemp(plist_template); |
619 | 704 | if (fd >= 0) { |
620 | 705 | close(fd); |
621 | | - std::string plist_command = |
622 | | - "powermetrics --samplers cpu_power,gpu_power,thermal,ane_power -n 1 -i 1000 -f plist -o " + |
623 | | - std::string(plist_template) + " </dev/null >/dev/null 2>&1"; |
624 | | - const int rc = std::system(plist_command.c_str()); |
625 | | - if (rc == 0) { |
| 706 | + const bool rc = run_command_quiet({ |
| 707 | + "/usr/bin/powermetrics", |
| 708 | + "--samplers", "cpu_power,gpu_power,thermal,ane_power", |
| 709 | + "-n", "1", |
| 710 | + "-i", "1000", |
| 711 | + "-f", "plist", |
| 712 | + "-o", plist_template, |
| 713 | + }); |
| 714 | + if (rc) { |
626 | 715 | std::ifstream input(plist_template, std::ios::binary); |
627 | 716 | std::vector<char> bytes((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); |
628 | 717 | if (!bytes.empty()) { |
@@ -713,8 +802,14 @@ class DarwinSampler final : public Sampler { |
713 | 802 | std::remove(plist_template); |
714 | 803 | } |
715 | 804 |
|
716 | | - const std::string amp_text = |
717 | | - read_command_output("powermetrics --samplers tasks,cpu_power --show-process-amp --show-process-ipc -n 1 -i 1000 </dev/null 2>/dev/null"); |
| 805 | + const std::string amp_text = run_command_capture({ |
| 806 | + "/usr/bin/powermetrics", |
| 807 | + "--samplers", "tasks,cpu_power", |
| 808 | + "--show-process-amp", |
| 809 | + "--show-process-ipc", |
| 810 | + "-n", "1", |
| 811 | + "-i", "1000", |
| 812 | + }, true); |
718 | 813 | metrics.process_core_mix = parse_amp_core_mix(amp_text); |
719 | 814 | return metrics; |
720 | 815 | } |
|
0 commit comments