1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // memgraph prints system-wide task and memory information as JSON.
6 // See memgraph-schema.json for the schema.
7
8 #include <getopt.h>
9 #include <inttypes.h>
10 #include <new>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <time.h>
14
15 #include <zircon/process.h>
16 #include <zircon/status.h>
17 #include <zircon/syscalls.h>
18 #include <zircon/syscalls/exception.h>
19 #include <zircon/syscalls/object.h>
20 #include <task-utils/walker.h>
21
22 #include "resources.h"
23 #include "vmo-utils.h"
24
25 // Defines kMemgraphSchema containing the contents of memgraph-schema.json
26 #include "memgraph-schema.h"
27
28 namespace {
29
obj_type_get_name(zx_obj_type_t type)30 const char* obj_type_get_name(zx_obj_type_t type) {
31 switch (type) {
32 case ZX_OBJ_TYPE_NONE:
33 return "none";
34 case ZX_OBJ_TYPE_PROCESS:
35 return "process";
36 case ZX_OBJ_TYPE_THREAD:
37 return "thread";
38 case ZX_OBJ_TYPE_VMO:
39 return "vmo";
40 case ZX_OBJ_TYPE_CHANNEL:
41 return "channel";
42 case ZX_OBJ_TYPE_EVENT:
43 return "event";
44 case ZX_OBJ_TYPE_PORT:
45 return "port";
46 case ZX_OBJ_TYPE_INTERRUPT:
47 return "interrupt";
48 case ZX_OBJ_TYPE_PCI_DEVICE:
49 return "pci_device";
50 case ZX_OBJ_TYPE_LOG:
51 return "log";
52 case ZX_OBJ_TYPE_SOCKET:
53 return "socket";
54 case ZX_OBJ_TYPE_RESOURCE:
55 return "resource";
56 case ZX_OBJ_TYPE_EVENTPAIR:
57 return "eventpair";
58 case ZX_OBJ_TYPE_JOB:
59 return "job";
60 case ZX_OBJ_TYPE_VMAR:
61 return "vmar";
62 case ZX_OBJ_TYPE_FIFO:
63 return "fifo";
64 case ZX_OBJ_TYPE_GUEST:
65 return "guest";
66 case ZX_OBJ_TYPE_VCPU:
67 return "vcpu";
68 case ZX_OBJ_TYPE_TIMER:
69 return "timer";
70 case ZX_OBJ_TYPE_IOMMU:
71 return "iommu";
72 case ZX_OBJ_TYPE_BTI:
73 return "bti";
74 case ZX_OBJ_TYPE_PROFILE:
75 return "profile";
76 default:
77 return "unknown";
78 }
79 }
80
81 // Prints info about VMOs and their relationship to a process.
82 // Assumes we're in the middle of dumping a process.
83 // TODO(dbort): Insert some special entry if count < avail?
print_vmos(const zx_info_vmo_t * vmos,size_t count)84 void print_vmos(const zx_info_vmo_t* vmos, size_t count) {
85 if (count == 0) {
86 // Should never happen, but don't print anything in this case.
87 return;
88 }
89
90 // List of VMOs that this task points to. Should only contain fields that
91 // are fundamental parts of the VMO and do not change based on how the VMO
92 // is used or referred to.
93 printf(",\n \"vmos\": [\n");
94 for (size_t i = 0; i < count; i++) {
95 const zx_info_vmo_t* vmo = vmos + i;
96 char delim = i < (count - 1) ? ',' : ' ';
97 printf(" {"
98 "\"koid\": %" PRIu64 ", "
99 "\"name\": \"%s\", " // TODO(dbort): Escape quotes
100 "\"size_bytes\": %" PRIu64 ", "
101 "\"parent_koid\": %" PRIu64 ", "
102 "\"num_children\": %zu, "
103 "\"num_mappings\": %zu, "
104 "\"share_count\": %zu, "
105 // TODO(dbort): copy-on-write? phys/paged?
106 "\"committed_bytes\": %" PRIu64
107 "}%c\n",
108 vmo->koid,
109 vmo->name,
110 vmo->size_bytes,
111 vmo->parent_koid,
112 vmo->num_children,
113 vmo->num_mappings,
114 vmo->share_count,
115 vmo->committed_bytes,
116 delim);
117 }
118 printf(" ],\n");
119 // List of references from this task to the VMOs listed above. May include
120 // information specific to this particular use of a given VMO.
121 printf(" \"vmo_refs\": [\n");
122 for (size_t i = 0; i < count; i++) {
123 const zx_info_vmo_t* vmo = vmos + i;
124 char delim = i < (count - 1) ? ',' : ' ';
125 printf(" {"
126 "\"vmo_koid\": %" PRIu64 ", "
127 "\"via\": [",
128 vmo->koid);
129 bool need_comma = false;
130 if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) {
131 printf("\"HANDLE\"");
132 need_comma = true;
133 }
134 if (vmo->flags & ZX_INFO_VMO_VIA_MAPPING) {
135 printf("%s\"MAPPING\"", need_comma ? ", " : "");
136 // Future improvement: Could use ZX_INFO_PROCESS_MAPS to include
137 // specifics of how this VMO is mapped.
138 }
139 printf("]");
140 if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) {
141 need_comma = false;
142 printf(", \"handle_rights\": [");
143
144 #define PRINT_RIGHT(r) \
145 do { \
146 if (vmo->handle_rights & ZX_RIGHT_##r) { \
147 printf("%s\"" #r "\"", need_comma ? ", " : ""); \
148 need_comma = true; \
149 } \
150 } while (false)
151
152 PRINT_RIGHT(READ);
153 PRINT_RIGHT(WRITE);
154 PRINT_RIGHT(EXECUTE);
155 PRINT_RIGHT(MAP);
156 PRINT_RIGHT(DUPLICATE);
157 PRINT_RIGHT(TRANSFER);
158
159 #undef PRINT_RIGHT
160
161 printf("]");
162 }
163 printf("}%c\n", delim);
164 }
165 printf(" ]");
166 }
167
168 class JsonTaskEnumerator final : public TaskEnumerator {
169 public:
170 // |self_koid| is the koid of this memgraph process, so we can
171 // avoid trying to read our own VMOs (which is illegal).
JsonTaskEnumerator(zx_koid_t self_koid,bool show_threads,bool show_vmos,bool show_handle_stats)172 JsonTaskEnumerator(zx_koid_t self_koid, bool show_threads, bool show_vmos,
173 bool show_handle_stats)
174 : self_koid_(self_koid),
175 show_threads_(show_threads),
176 show_vmos_(show_vmos),
177 show_handle_stats_(show_handle_stats) {}
178
partial_failure() const179 zx_status_t partial_failure() const { return partial_failure_; }
180
181 private:
GetTaskName(zx_handle_t task,zx_koid_t koid,char out_name[ZX_MAX_NAME_LEN])182 static void GetTaskName(zx_handle_t task, zx_koid_t koid,
183 char out_name[ZX_MAX_NAME_LEN]) {
184 zx_status_t s = zx_object_get_property(
185 task, ZX_PROP_NAME, out_name, ZX_MAX_NAME_LEN);
186 if (s != ZX_OK) {
187 fprintf(stderr,
188 "WARNING: failed to get name of task %" PRIu64
189 ": %s (%d)\n",
190 koid, zx_status_get_string(s), s);
191 snprintf(out_name, ZX_MAX_NAME_LEN, "<UNKNOWN>");
192 // This is unfortunate, but not worth a partial failure
193 // since the overall structure of the output is still intact.
194 }
195 // TODO(dbort): Escape quotes in name
196 }
197
OnJob(int depth,zx_handle_t job,zx_koid_t koid,zx_koid_t parent_koid)198 zx_status_t OnJob(int depth, zx_handle_t job,
199 zx_koid_t koid, zx_koid_t parent_koid) override {
200 char name[ZX_MAX_NAME_LEN];
201 GetTaskName(job, koid, name);
202
203 char parent_id[ZX_MAX_NAME_LEN + 16];
204 if (parent_koid == 0) {
205 // This is the root job, which we treat as a child of the
206 // system VMO arena node.
207 snprintf(parent_id, sizeof(parent_id), "kernel/vmo");
208 } else {
209 snprintf(parent_id, sizeof(parent_id), "j/%" PRIu64, parent_koid);
210 }
211
212 printf(" {"
213 "\"id\": \"j/%" PRIu64 "\", "
214 "\"type\": \"j\", "
215 "\"koid\": %" PRIu64 ", "
216 "\"parent\": \"%s\", "
217 "\"name\": \"%s\""
218 "},\n",
219 koid,
220 koid,
221 parent_id,
222 name);
223
224 return ZX_OK;
225 }
226
OnProcess(int depth,zx_handle_t process,zx_koid_t koid,zx_koid_t parent_koid)227 zx_status_t OnProcess(int depth, zx_handle_t process,
228 zx_koid_t koid, zx_koid_t parent_koid) override {
229 char name[ZX_MAX_NAME_LEN];
230 GetTaskName(process, koid, name);
231
232 // Print basic info.
233 printf(" {"
234 "\"id\": \"p/%" PRIu64 "\", "
235 "\"type\": \"p\", "
236 "\"koid\": %" PRIu64 ", "
237 "\"parent\": \"j/%" PRIu64 "\", "
238 "\"name\": \"%s\"",
239 koid,
240 koid,
241 parent_koid,
242 name);
243
244 // Print memory usage summaries.
245 zx_info_task_stats_t info;
246 zx_status_t s = zx_object_get_info(
247 process, ZX_INFO_TASK_STATS, &info, sizeof(info), nullptr, nullptr);
248 if (s == ZX_ERR_BAD_STATE) {
249 // Process has exited, but has not been destroyed.
250 // Default to zero for all sizes.
251 info = {};
252 s = ZX_OK;
253 }
254 if (s != ZX_OK) {
255 fprintf(stderr,
256 "WARNING: failed to get mem stats for process %" PRIu64
257 ": %s (%d)\n",
258 koid, zx_status_get_string(s), s);
259 set_partial_failure(s);
260 } else {
261 printf(", "
262 "\"private_bytes\": %zu, "
263 "\"shared_bytes\": %zu, "
264 "\"pss_bytes\": %zu",
265 info.mem_private_bytes,
266 info.mem_shared_bytes,
267 info.mem_private_bytes + info.mem_scaled_shared_bytes);
268 }
269
270 // Print the process's VMOs. The same VMO may appear several
271 // times in this list; it's up to the consumer of this output
272 // to de-duplicate.
273 if (show_vmos_ && koid != self_koid_) {
274 zx_info_vmo_t* vmos;
275 size_t count = 0;
276 size_t avail = 0;
277 s = get_vmos(process, &vmos, &count, &avail);
278 if (s != ZX_OK) {
279 fprintf(stderr,
280 "WARNING: failed to read VMOs for process %" PRIu64
281 ": %s (%d)\n",
282 koid, zx_status_get_string(s), s);
283 set_partial_failure(s);
284 } else {
285 if (count < avail) {
286 fprintf(stderr,
287 "WARNING: failed to read all VMOs for process "
288 "%" PRIu64 ": count %zu < avail %zu\n",
289 koid, count, avail);
290 set_partial_failure(ZX_ERR_BUFFER_TOO_SMALL);
291 // Keep going with the truncated list.
292 }
293 print_vmos(vmos, count);
294 free(vmos);
295 }
296 }
297
298 if (show_handle_stats_) {
299 zx_info_process_handle_stats_t info = {};
300 s = zx_object_get_info(process, ZX_INFO_PROCESS_HANDLE_STATS, &info,
301 sizeof(info), nullptr, nullptr);
302 if (s != ZX_OK) {
303 fprintf(stderr,
304 "WARNING: failed to read handle stats for process "
305 "%" PRIu64 ": %s (%d)\n",
306 koid, zx_status_get_string(s), s);
307 set_partial_failure(s);
308 } else {
309 printf(",\n \"handle_stats\": {");
310 size_t count = 0;
311 for (zx_obj_type_t i = 0; i < ZX_OBJ_TYPE_LAST; i++) {
312 if (!info.handle_count[i]) {
313 continue;
314 }
315 if (count++ > 0) {
316 printf(",");
317 }
318 printf("\n \"%s\": %u", obj_type_get_name(i),
319 info.handle_count[i]);
320 }
321 printf("\n }");
322 }
323 }
324
325 printf("},\n");
326
327 return ZX_OK;
328 }
329
OnThread(int depth,zx_handle_t thread,zx_koid_t koid,zx_koid_t parent_koid)330 zx_status_t OnThread(int depth, zx_handle_t thread,
331 zx_koid_t koid, zx_koid_t parent_koid) override {
332 char name[ZX_MAX_NAME_LEN];
333 GetTaskName(thread, koid, name);
334
335 // Print basic info.
336 printf(" {"
337 "\"id\": \"t/%" PRIu64 "\", "
338 "\"type\": \"t\", "
339 "\"koid\": %" PRIu64 ", "
340 "\"parent\": \"p/%" PRIu64 "\", "
341 "\"name\": \"%s\"",
342 koid,
343 koid,
344 parent_koid,
345 name);
346
347 // Print state.
348 zx_info_thread_t info;
349 zx_status_t s = zx_object_get_info(
350 thread, ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
351 if (s != ZX_OK) {
352 fprintf(stderr,
353 "WARNING: failed to get info for thread %" PRIu64
354 ": %s (%d)\n",
355 koid, zx_status_get_string(s), s);
356 set_partial_failure(s);
357 } else {
358 const char* state = "<UNKNOWN>";
359 if (info.wait_exception_port_type != ZX_EXCEPTION_PORT_TYPE_NONE) {
360 state = "EXCEPTION";
361 } else {
362 switch (ZX_THREAD_STATE_BASIC(info.state)) {
363 case ZX_THREAD_STATE_NEW:
364 state = "NEW";
365 break;
366 case ZX_THREAD_STATE_RUNNING:
367 state = "RUNNING";
368 break;
369 case ZX_THREAD_STATE_SUSPENDED:
370 state = "SUSPENDED";
371 break;
372 case ZX_THREAD_STATE_BLOCKED:
373 state = "BLOCKED";
374 break;
375 case ZX_THREAD_STATE_DYING:
376 state = "DYING";
377 break;
378 case ZX_THREAD_STATE_DEAD:
379 state = "DEAD";
380 break;
381 }
382 }
383 printf(", \"state\": \"%s\"", state);
384 }
385 printf("},\n");
386 return ZX_OK;
387 }
388
389 const zx_koid_t self_koid_;
390 const bool show_threads_;
391 const bool show_vmos_;
392 const bool show_handle_stats_;
393
394 // We try to keep going despite failures, but for scripting
395 // purposes it's good to indicate failure at the end.
set_partial_failure(zx_status_t status)396 void set_partial_failure(zx_status_t status) {
397 if (partial_failure_ == ZX_OK) {
398 partial_failure_ = status;
399 }
400 }
401 zx_status_t partial_failure_ = ZX_OK;
402
has_on_job() const403 bool has_on_job() const final { return true; }
has_on_process() const404 bool has_on_process() const final { return true; }
has_on_thread() const405 bool has_on_thread() const final { return show_threads_; }
406 };
407
print_kernel_json(const char * name,const char * parent,uint64_t size_bytes)408 static void print_kernel_json(const char* name, const char* parent,
409 uint64_t size_bytes) {
410 printf(" {"
411 "\"id\": \"kernel/%s\", "
412 "\"type\": \"kernel\", "
413 "\"parent\": \"%s\", "
414 "\"name\": \"%s\", "
415 "\"size_bytes\": %zu"
416 "},\n",
417 name,
418 parent,
419 name,
420 size_bytes);
421 }
422
dump_kernel_memory()423 zx_status_t dump_kernel_memory() {
424 zx_handle_t root_resource;
425 zx_status_t s = get_root_resource(&root_resource);
426 if (s != ZX_OK) {
427 return s;
428 }
429 zx_info_kmem_stats_t stats;
430 s = zx_object_get_info(root_resource, ZX_INFO_KMEM_STATS,
431 &stats, sizeof(stats), nullptr, nullptr);
432 zx_handle_close(root_resource);
433 if (s != ZX_OK) {
434 fprintf(stderr, "WARNING: failed to get kernel memory stats: %s (%d)\n",
435 zx_status_get_string(s), s);
436 return s;
437 }
438
439 print_kernel_json("physmem", "", stats.total_bytes);
440 print_kernel_json("free", "kernel/physmem", stats.free_bytes);
441 print_kernel_json("vmo", "kernel/physmem", stats.vmo_bytes);
442 print_kernel_json("heap", "kernel/physmem", stats.total_heap_bytes);
443 print_kernel_json("heap/allocated", "kernel/heap",
444 stats.total_heap_bytes - stats.free_heap_bytes);
445 print_kernel_json("heap/free", "kernel/heap", stats.free_heap_bytes);
446 print_kernel_json("wired", "kernel/physmem", stats.wired_bytes);
447 print_kernel_json("mmu", "kernel/physmem", stats.mmu_overhead_bytes);
448 print_kernel_json("other", "kernel/physmem", stats.other_bytes);
449
450 return ZX_OK;
451 }
452
print_help(FILE * f)453 void print_help(FILE* f) {
454 fprintf(f, "Usage: memgraph [options]\n");
455 fprintf(f, " Prints system-wide task and memory info as JSON.\n");
456 fprintf(f, "Options:\n");
457 fprintf(f, " -t|--threads Include threads in the output\n");
458 fprintf(f, " -v|--vmos Include VMOs in the output\n");
459 fprintf(f, " -H|--handles Include handle stats in the output\n");
460 fprintf(f, " -S|--schema Print the schema for the JSON output format\n");
461 fprintf(f, " -h|--help Display this message\n");
462 }
463
464 } // namespace
465
main(int argc,char ** argv)466 int main(int argc, char** argv) {
467 bool show_threads = false;
468 bool show_vmos = false;
469 bool show_handle_stats = false;
470 while (true) {
471 static option options[] = {
472 {"threads", no_argument, nullptr, 't'},
473 {"vmos", no_argument, nullptr, 'v'},
474 {"handles", no_argument, nullptr, 'H'},
475 {"schema", no_argument, nullptr, 'S'},
476 {"help", no_argument, nullptr, 'h'},
477 {nullptr, 0, nullptr, 0},
478 };
479 int option_index = 0;
480 int c = getopt_long(argc, argv, "tvHSh", options, &option_index);
481 if (c < 0) {
482 break;
483 }
484 switch (c) {
485 case 't':
486 show_threads = true;
487 break;
488 case 'v':
489 show_vmos = true;
490 break;
491 case 'H':
492 show_handle_stats = true;
493 break;
494 case 'S':
495 printf(kMemgraphSchema);
496 return 0;
497 case 'h':
498 default:
499 print_help(c == 'h' ? stdout : stderr);
500 return c == 'h' ? 0 : 1;
501 }
502 }
503 if (optind < argc) {
504 fprintf(stderr, "%s: unrecognized extra arguments:", argv[0]);
505 while (optind < argc) {
506 fprintf(stderr, " %s", argv[optind++]);
507 }
508 fprintf(stderr, "\n");
509 print_help(stderr);
510 return 1;
511 }
512
513 // Get our own koid so we can avoid (illegally) reading this process's VMOs.
514 zx_info_handle_basic_t info;
515 zx_status_t s = zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC,
516 &info, sizeof(info), nullptr, nullptr);
517 if (s != ZX_OK) {
518 // This will probably result in a partial failure when we try to read
519 // our own VMOs, but keep going.
520 fprintf(stderr, "WARNING: could not find our own koid: %s (%d)\n",
521 zx_status_get_string(s), s);
522 info = {};
523 info.koid = 0;
524 }
525
526 // Grab the time when we start.
527 struct timespec now;
528 timespec_get(&now, TIME_UTC);
529
530 printf("[\n");
531
532 zx_status_t ks = dump_kernel_memory();
533
534 JsonTaskEnumerator jte(info.koid, show_threads, show_vmos,
535 show_handle_stats);
536 s = jte.WalkRootJobTree();
537 if (s != ZX_OK) {
538 fprintf(stderr, "ERROR: %s (%d)\n", zx_status_get_string(s), s);
539 return 1;
540 }
541
542 // Add a final entry with metadata. Also lets us avoid tracking commas
543 // above.
544 // Print the time as an ISO 8601 string.
545 struct tm nowtm;
546 gmtime_r(&now.tv_sec, &nowtm);
547 char tbuf[40];
548 strftime(tbuf, sizeof(tbuf), "%FT%T", &nowtm);
549 printf(" {"
550 "\"type\": \"__META\", "
551 "\"timestamp\": \"%s.%03ldZ\"}\n",
552 tbuf, now.tv_nsec / (1000 * 1000));
553 printf("]\n");
554
555 // Exit with an error status if we hit any partial failures.
556 s = jte.partial_failure();
557 if (s == ZX_OK) {
558 s = ks;
559 }
560 if (s != ZX_OK) {
561 fprintf(stderr, "ERROR: delayed exit after partial failure: %s (%d)\n",
562 zx_status_get_string(s), s);
563 return 1;
564 }
565 return 0;
566 }
567