| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <dirent.h> | |
| 6 #include <signal.h> | 5 #include <signal.h> |
| 7 #include <stdio.h> | 6 #include <stdio.h> |
| 8 #include <stdlib.h> | 7 #include <stdlib.h> |
| 9 #include <string.h> | 8 #include <string.h> |
| 10 #include <sys/time.h> | |
| 11 #include <sys/timerfd.h> | |
| 12 #include <sys/types.h> | |
| 13 | 9 |
| 14 #include <limits> | 10 #include <limits> |
| 15 #include <memory> | 11 #include <memory> |
| 12 #include <set> |
| 13 #include <string> |
| 14 #include <sstream> |
| 16 | 15 |
| 17 #include "file_utils.h" | 16 #include "atrace_process_dump.h" |
| 18 #include "logging.h" | 17 #include "logging.h" |
| 19 #include "process_info.h" | |
| 20 | 18 |
| 21 namespace { | 19 namespace { |
| 22 | 20 |
| 23 using ProcessMap = std::map<int, std::unique_ptr<ProcessInfo>>; | 21 std::unique_ptr<AtraceProcessDump> g_prog; |
| 24 | 22 |
| 25 int g_timer; | 23 void ParseFullDumpConfig(const std::string& config, AtraceProcessDump* prog) { |
| 26 | 24 using FullDumpMode = AtraceProcessDump::FullDumpMode; |
| 27 std::unique_ptr<ProcessMap> CollectStatsForAllProcs(bool full_mem_stats, | 25 if (config == "all") { |
| 28 bool gpu_mem_stats) { | 26 prog->set_full_dump_mode(FullDumpMode::kAllProcesses); |
| 29 std::unique_ptr<ProcessMap> procs(new ProcessMap()); | 27 } else if (config == "apps") { |
| 30 file_utils::ForEachPidInProcPath("/proc", | 28 prog->set_full_dump_mode(FullDumpMode::kAllJavaApps); |
| 31 [&procs, full_mem_stats, gpu_mem_stats](int pid) { | 29 } else { |
| 32 | 30 std::set<std::string> whitelist; |
| 33 if (!ProcessInfo::IsProcess(pid)) | 31 std::istringstream ss(config); |
| 32 std::string entry; |
| 33 while (std::getline(ss, entry, ',')) { |
| 34 whitelist.insert(entry); |
| 35 } |
| 36 if (whitelist.empty()) |
| 34 return; | 37 return; |
| 35 CHECK(procs->count(pid) == 0); | 38 prog->set_full_dump_mode(FullDumpMode::kOnlyWhitelisted); |
| 36 std::unique_ptr<ProcessInfo> pinfo(new ProcessInfo(pid)); | 39 prog->set_full_dump_whitelist(whitelist); |
| 37 if (!(pinfo->ReadProcessName() && pinfo->ReadThreadNames() && | |
| 38 pinfo->ReadOOMStats() && pinfo->ReadPageFaultsAndCPUTimeStats())) | |
| 39 return; | |
| 40 | |
| 41 if (full_mem_stats) { | |
| 42 if (!pinfo->memory()->ReadFullStats()) | |
| 43 return; | |
| 44 } else { | |
| 45 if (!pinfo->memory()->ReadLightStats()) | |
| 46 return; | |
| 47 } | |
| 48 if (gpu_mem_stats) { | |
| 49 // It might fail on some devices. | |
| 50 pinfo->memory()->ReadMemtrackStats(); | |
| 51 } | |
| 52 (*procs)[pid] = std::move(pinfo); | |
| 53 }); | |
| 54 return procs; | |
| 55 } | |
| 56 | |
| 57 void SerializeSnapshot(const ProcessMap& procs, | |
| 58 FILE* stream, | |
| 59 bool full_mem_stats, | |
| 60 bool gpu_mem_stats) { | |
| 61 struct timespec ts = {}; | |
| 62 CHECK(clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0); | |
| 63 fprintf(stream, "{\n"); | |
| 64 fprintf(stream, " \"ts\": %lu,\n", | |
| 65 (ts.tv_sec * 1000 + ts.tv_nsec / 1000000ul)); | |
| 66 fprintf(stream, " \"processes\": [\n"); | |
| 67 for (auto it = procs.begin(); it != procs.end();) { | |
| 68 int pid = it->first; | |
| 69 const ProcessInfo& pinfo = *it->second; | |
| 70 fprintf(stream, " {\"pid\": %d, \"name\": \"%s\", \"exe\": \"%s\"", pid, | |
| 71 pinfo.name(), pinfo.exe()); | |
| 72 fprintf(stream, ", \"threads\": ["); | |
| 73 for (auto t = pinfo.threads()->begin(); t != pinfo.threads()->end();) { | |
| 74 fprintf(stream, "{\"tid\": %d, \"name\":\"%s\"", t->first, | |
| 75 t->second->name); | |
| 76 t++; | |
| 77 fprintf(stream, t != pinfo.threads()->end() ? "}, " : "}"); | |
| 78 } | |
| 79 fprintf(stream, "]"); | |
| 80 | |
| 81 const ProcessMemoryStats* mem_info = pinfo.memory(); | |
| 82 fprintf(stream, ", \"mem\": {\"vm\": %llu, \"rss\": %llu", | |
| 83 mem_info->virt_kb(), mem_info->rss_kb()); | |
| 84 if (full_mem_stats) { | |
| 85 fprintf(stream, | |
| 86 ", \"pss\": %llu, \"swp\": %llu, \"pc\": %llu, \"pd\": %llu, " | |
| 87 "\"sc\": %llu, \"sd\": %llu", | |
| 88 mem_info->pss_kb(), mem_info->swapped_kb(), | |
| 89 mem_info->private_clean_kb(), mem_info->private_dirty_kb(), | |
| 90 mem_info->shared_clean_kb(), mem_info->shared_dirty_kb()); | |
| 91 } | |
| 92 if (gpu_mem_stats) { | |
| 93 fprintf(stream, | |
| 94 ", \"gpu\": %llu, \"gpu_pss\": %llu" | |
| 95 ", \"gpu_gl\": %llu, \"gpu_gl_pss\": %llu" | |
| 96 ", \"gpu_etc\": %llu, \"gpu_etc_pss\": %llu", | |
| 97 mem_info->gpu_graphics_kb(), mem_info->gpu_graphics_pss_kb(), | |
| 98 mem_info->gpu_gl_kb(), mem_info->gpu_gl_pss_kb(), | |
| 99 mem_info->gpu_other_kb(), mem_info->gpu_other_pss_kb()); | |
| 100 } | |
| 101 fprintf(stream, "}"); | |
| 102 | |
| 103 fprintf(stream, | |
| 104 ", \"oom\": {\"adj\": %d, \"score_adj\": %d, \"score\": %d}", | |
| 105 pinfo.oom_adj(), pinfo.oom_score_adj(), pinfo.oom_score()); | |
| 106 fprintf(stream, | |
| 107 ", \"stat\": {\"minflt\": %lu, \"majflt\": %lu, " | |
| 108 "\"utime\": %lu, \"stime\": %lu }", | |
| 109 pinfo.minflt(), pinfo.majflt(), pinfo.utime(), pinfo.stime()); | |
| 110 fprintf(stream, "}"); | |
| 111 it++; | |
| 112 fprintf(stream, it != procs.end() ? ",\n" : "\n"); | |
| 113 } | 40 } |
| 114 fprintf(stream, " ]\n"); | |
| 115 fprintf(stream, "}\n"); | |
| 116 } | 41 } |
| 117 | 42 |
| 118 } // namespace | 43 } // namespace |
| 119 | 44 |
| 120 int main(int argc, char** argv) { | 45 int main(int argc, char** argv) { |
| 121 bool background = false; | 46 bool background = false; |
| 122 int dump_interval_ms = 5000; | 47 int dump_interval_ms = 5000; |
| 123 char out_file[PATH_MAX] = {}; | 48 char out_file[PATH_MAX] = {}; |
| 124 bool dump_to_file = false; | 49 bool dump_to_file = false; |
| 125 bool full_mem_stats = false; | |
| 126 bool gpu_mem_stats = false; | |
| 127 int count = std::numeric_limits<int>::max(); | 50 int count = std::numeric_limits<int>::max(); |
| 51 |
| 52 AtraceProcessDump* prog = new AtraceProcessDump(); |
| 53 g_prog = std::unique_ptr<AtraceProcessDump>(prog); |
| 54 |
| 55 if (geteuid()) { |
| 56 fprintf(stderr, "Must run as root\n"); |
| 57 exit(EXIT_FAILURE); |
| 58 } |
| 59 |
| 128 int opt; | 60 int opt; |
| 129 while ((opt = getopt(argc, argv, "bmgt:o:c:")) != -1) { | 61 while ((opt = getopt(argc, argv, "bm:gst:o:c:")) != -1) { |
| 130 switch (opt) { | 62 switch (opt) { |
| 131 case 'b': | 63 case 'b': |
| 132 background = true; | 64 background = true; |
| 133 break; | 65 break; |
| 134 case 'm': | 66 case 'm': |
| 135 full_mem_stats = true; | 67 ParseFullDumpConfig(optarg, prog); |
| 136 break; | 68 break; |
| 137 case 'g': | 69 case 'g': |
| 138 gpu_mem_stats = true; | 70 prog->enable_graphics_stats(); |
| 71 break; |
| 72 case 's': |
| 73 prog->enable_print_smaps(); |
| 139 break; | 74 break; |
| 140 case 't': | 75 case 't': |
| 141 dump_interval_ms = atoi(optarg); | 76 dump_interval_ms = atoi(optarg); |
| 142 CHECK(dump_interval_ms > 0); | 77 CHECK(dump_interval_ms > 0); |
| 143 break; | 78 break; |
| 144 case 'c': | 79 case 'c': |
| 145 count = atoi(optarg); | 80 count = atoi(optarg); |
| 146 CHECK(count > 0); | 81 CHECK(count > 0); |
| 147 break; | 82 break; |
| 148 case 'o': | 83 case 'o': |
| 149 strncpy(out_file, optarg, sizeof(out_file)); | 84 strncpy(out_file, optarg, sizeof(out_file)); |
| 150 dump_to_file = true; | 85 dump_to_file = true; |
| 151 break; | 86 break; |
| 152 default: | 87 default: |
| 153 fprintf(stderr, | 88 fprintf(stderr, |
| 154 "Usage: %s [-b] [-m] [-g] [-t dump_interval_ms] " | 89 "Usage: %s [-b] [-m full_dump_filter] [-g] [-s] " |
| 90 "[-t dump_interval_ms] " |
| 155 "[-c dumps_count] [-o out.json]\n", | 91 "[-c dumps_count] [-o out.json]\n", |
| 156 argv[0]); | 92 argv[0]); |
| 157 exit(EXIT_FAILURE); | 93 exit(EXIT_FAILURE); |
| 158 } | 94 } |
| 159 } | 95 } |
| 160 | 96 |
| 161 if (geteuid()) { | 97 prog->set_dump_count(count); |
| 162 fprintf(stderr, "Must run as root\n"); | 98 prog->set_dump_interval(dump_interval_ms); |
| 163 exit(EXIT_FAILURE); | |
| 164 } | |
| 165 | 99 |
| 166 FILE* out_stream = stdout; | 100 FILE* out_stream = stdout; |
| 167 char tmp_file[PATH_MAX]; | 101 char tmp_file[PATH_MAX]; |
| 168 if (dump_to_file) { | 102 if (dump_to_file) { |
| 169 unlink(out_file); | 103 unlink(out_file); |
| 170 sprintf(tmp_file, "%s.tmp", out_file); | 104 sprintf(tmp_file, "%s.tmp", out_file); |
| 171 out_stream = fopen(tmp_file, "w"); | 105 out_stream = fopen(tmp_file, "w"); |
| 172 CHECK(out_stream); | 106 CHECK(out_stream); |
| 173 } | 107 } |
| 174 | 108 |
| 175 if (background) { | 109 if (background) { |
| 176 if (!dump_to_file) { | 110 if (!dump_to_file) { |
| 177 fprintf(stderr, "-b requires -o for output dump path\n"); | 111 fprintf(stderr, "-b requires -o for output dump path.\n"); |
| 178 exit(EXIT_FAILURE); | 112 exit(EXIT_FAILURE); |
| 179 } | 113 } |
| 180 printf("Continuing in background. kill -TERM to terminate the daemon.\n"); | 114 printf("Continuing in background. kill -TERM to terminate the daemon.\n"); |
| 181 CHECK(daemon(0 /* nochdir */, 0 /* noclose */) == 0); | 115 CHECK(daemon(0 /* nochdir */, 0 /* noclose */) == 0); |
| 182 } | 116 } |
| 183 | 117 |
| 184 g_timer = timerfd_create(CLOCK_MONOTONIC, 0); | 118 auto on_exit = [](int) { g_prog->Stop(); }; |
| 185 CHECK(g_timer >= 0); | |
| 186 struct itimerspec ts = {}; | |
| 187 ts.it_value.tv_nsec = 1; // Get the first snapshot immediately. | |
| 188 ts.it_interval.tv_nsec = (dump_interval_ms % 1000) * 1000000ul; | |
| 189 ts.it_interval.tv_sec = dump_interval_ms / 1000; | |
| 190 CHECK(timerfd_settime(g_timer, 0, &ts, nullptr) == 0); | |
| 191 | |
| 192 // Closing the g_timer fd on SIGINT/SIGTERM will cause the read() below to | |
| 193 // unblock and fail with EBADF, hence allowing the loop below to finalize | |
| 194 // the file and exit. | |
| 195 auto on_exit = [](int) { close(g_timer); }; | |
| 196 signal(SIGINT, on_exit); | 119 signal(SIGINT, on_exit); |
| 197 signal(SIGTERM, on_exit); | 120 signal(SIGTERM, on_exit); |
| 198 | 121 |
| 199 fprintf(out_stream, "{\"snapshots\": [\n"); | 122 prog->RunAndPrintJson(out_stream); |
| 200 bool is_first_snapshot = true; | 123 fclose(out_stream); |
| 201 for (; count > 0; count--) { | |
| 202 uint64_t missed = 0; | |
| 203 int res = read(g_timer, &missed, sizeof(missed)); | |
| 204 if (res < 0 && errno == EBADF) | |
| 205 break; // Received SIGINT/SIGTERM signal. | |
| 206 CHECK(res > 0); | |
| 207 if (!is_first_snapshot) | |
| 208 fprintf(out_stream, ","); | |
| 209 is_first_snapshot = false; | |
| 210 | 124 |
| 211 std::unique_ptr<ProcessMap> procs = | |
| 212 CollectStatsForAllProcs(full_mem_stats, gpu_mem_stats); | |
| 213 SerializeSnapshot(*procs, out_stream, full_mem_stats, gpu_mem_stats); | |
| 214 fflush(out_stream); | |
| 215 } | |
| 216 fprintf(out_stream, "]}\n"); | |
| 217 fclose(out_stream); | |
| 218 if (dump_to_file) | 125 if (dump_to_file) |
| 219 rename(tmp_file, out_file); | 126 rename(tmp_file, out_file); |
| 220 } | 127 } |
| OLD | NEW |