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> |
16 | 12 |
17 #include "file_utils.h" | 13 #include "atrace_process_dump.h" |
18 #include "logging.h" | 14 #include "logging.h" |
19 #include "process_info.h" | |
20 | 15 |
21 namespace { | 16 std::unique_ptr<AtraceProcessDump> g_prog; |
22 | |
23 using ProcessMap = std::map<int, std::unique_ptr<ProcessInfo>>; | |
24 | |
25 int g_timer; | |
26 | |
27 std::unique_ptr<ProcessMap> CollectStatsForAllProcs(bool full_mem_stats, | |
28 bool gpu_mem_stats) { | |
29 std::unique_ptr<ProcessMap> procs(new ProcessMap()); | |
30 file_utils::ForEachPidInProcPath("/proc", | |
31 [&procs, full_mem_stats, gpu_mem_stats](int pid) { | |
32 | |
33 if (!ProcessInfo::IsProcess(pid)) | |
34 return; | |
35 CHECK(procs->count(pid) == 0); | |
36 std::unique_ptr<ProcessInfo> pinfo(new ProcessInfo(pid)); | |
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 } | |
114 fprintf(stream, " ]\n"); | |
115 fprintf(stream, "}\n"); | |
116 } | |
117 | |
118 } // namespace | |
119 | 17 |
120 int main(int argc, char** argv) { | 18 int main(int argc, char** argv) { |
121 bool background = false; | 19 bool background = false; |
122 int dump_interval_ms = 5000; | 20 int dump_interval_ms = 5000; |
123 char out_file[PATH_MAX] = {}; | 21 char out_file[PATH_MAX] = {}; |
124 bool dump_to_file = false; | 22 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(); | 23 int count = std::numeric_limits<int>::max(); |
| 24 |
| 25 AtraceProcessDump* prog = new AtraceProcessDump(); |
| 26 g_prog = std::unique_ptr<AtraceProcessDump>(prog); |
| 27 |
| 28 if (geteuid()) { |
| 29 fprintf(stderr, "Must run as root\n"); |
| 30 exit(EXIT_FAILURE); |
| 31 } |
| 32 |
128 int opt; | 33 int opt; |
129 while ((opt = getopt(argc, argv, "bmgt:o:c:")) != -1) { | 34 while ((opt = getopt(argc, argv, "bm:gst:o:c:")) != -1) { |
130 switch (opt) { | 35 switch (opt) { |
131 case 'b': | 36 case 'b': |
132 background = true; | 37 background = true; |
133 break; | 38 break; |
134 case 'm': | 39 case 'm': |
135 full_mem_stats = true; | 40 prog->SetFullDumpConfig(optarg); |
136 break; | 41 break; |
137 case 'g': | 42 case 'g': |
138 gpu_mem_stats = true; | 43 prog->EnableGraphicsStats(); |
| 44 break; |
| 45 case 's': |
| 46 prog->EnablePrintSmaps(); |
139 break; | 47 break; |
140 case 't': | 48 case 't': |
141 dump_interval_ms = atoi(optarg); | 49 dump_interval_ms = atoi(optarg); |
142 CHECK(dump_interval_ms > 0); | 50 CHECK(dump_interval_ms > 0); |
143 break; | 51 break; |
144 case 'c': | 52 case 'c': |
145 count = atoi(optarg); | 53 count = atoi(optarg); |
146 CHECK(count > 0); | 54 CHECK(count > 0); |
147 break; | 55 break; |
148 case 'o': | 56 case 'o': |
149 strncpy(out_file, optarg, sizeof(out_file)); | 57 strncpy(out_file, optarg, sizeof(out_file)); |
150 dump_to_file = true; | 58 dump_to_file = true; |
151 break; | 59 break; |
152 default: | 60 default: |
153 fprintf(stderr, | 61 fprintf(stderr, |
154 "Usage: %s [-b] [-m] [-g] [-t dump_interval_ms] " | 62 "Usage: %s [-b] [-m full_dump_filter] [-g] [-s] " |
| 63 "[-t dump_interval_ms] " |
155 "[-c dumps_count] [-o out.json]\n", | 64 "[-c dumps_count] [-o out.json]\n", |
156 argv[0]); | 65 argv[0]); |
157 exit(EXIT_FAILURE); | 66 exit(EXIT_FAILURE); |
158 } | 67 } |
159 } | 68 } |
160 | 69 |
161 if (geteuid()) { | 70 prog->SetDumpCount(count); |
162 fprintf(stderr, "Must run as root\n"); | 71 prog->SetDumpFrequency(dump_interval_ms); |
163 exit(EXIT_FAILURE); | |
164 } | |
165 | 72 |
166 FILE* out_stream = stdout; | 73 FILE* out_stream = stdout; |
167 char tmp_file[PATH_MAX]; | 74 char tmp_file[PATH_MAX]; |
168 if (dump_to_file) { | 75 if (dump_to_file) { |
169 unlink(out_file); | 76 unlink(out_file); |
170 sprintf(tmp_file, "%s.tmp", out_file); | 77 sprintf(tmp_file, "%s.tmp", out_file); |
171 out_stream = fopen(tmp_file, "w"); | 78 out_stream = fopen(tmp_file, "w"); |
172 CHECK(out_stream); | 79 CHECK(out_stream); |
173 } | 80 } |
174 | 81 |
175 if (background) { | 82 if (background) { |
176 if (!dump_to_file) { | 83 if (!dump_to_file) { |
177 fprintf(stderr, "-b requires -o for output dump path\n"); | 84 fprintf(stderr, "-b requires -o for output dump path.\n"); |
178 exit(EXIT_FAILURE); | 85 exit(EXIT_FAILURE); |
179 } | 86 } |
180 printf("Continuing in background. kill -TERM to terminate the daemon.\n"); | 87 printf("Continuing in background. kill -TERM to terminate the daemon.\n"); |
181 CHECK(daemon(0 /* nochdir */, 0 /* noclose */) == 0); | 88 CHECK(daemon(0 /* nochdir */, 0 /* noclose */) == 0); |
182 } | 89 } |
183 | 90 |
184 g_timer = timerfd_create(CLOCK_MONOTONIC, 0); | 91 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); | 92 signal(SIGINT, on_exit); |
197 signal(SIGTERM, on_exit); | 93 signal(SIGTERM, on_exit); |
198 | 94 |
199 fprintf(out_stream, "{\"snapshots\": [\n"); | 95 prog->RunAndPrintJson(out_stream); |
200 bool is_first_snapshot = true; | |
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 | |
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) | 96 if (dump_to_file) |
219 rename(tmp_file, out_file); | 97 rename(tmp_file, out_file); |
220 } | 98 } |
OLD | NEW |