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