| // Copyright 2017 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // memgraph prints system-wide task and memory information as JSON. | |
| // See memgraph-schema.json for the schema. | |
| #include <getopt.h> | |
| #include <inttypes.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <time.h> | |
| #include <zircon/process.h> | |
| #include <zircon/status.h> | |
| #include <zircon/syscalls.h> | |
| #include <zircon/syscalls/exception.h> | |
| #include <zircon/syscalls/object.h> | |
| #include <new> | |
| #include <task-utils/walker.h> | |
| #include "object-utils.h" | |
| #include "resources.h" | |
| #include "vmo-utils.h" | |
| // Defines kMemgraphSchema containing the contents of memgraph-schema.json | |
| #include "memgraph-schema.h" | |
| namespace { | |
| // Prints info about VMOs and their relationship to a process. | |
| // Assumes we're in the middle of dumping a process. | |
| // TODO(dbort): Insert some special entry if count < avail? | |
| void print_vmos(const zx_info_vmo_t* vmos, size_t count) { | |
| if (count == 0) { | |
| // Should never happen, but don't print anything in this case. | |
| return; | |
| } | |
| // List of VMOs that this task points to. Should only contain fields that | |
| // are fundamental parts of the VMO and do not change based on how the VMO | |
| // is used or referred to. | |
| printf(",\n \"vmos\": [\n"); | |
| for (size_t i = 0; i < count; i++) { | |
| const zx_info_vmo_t* vmo = vmos + i; | |
| char delim = i < (count - 1) ? ',' : ' '; | |
| printf( | |
| " {" | |
| "\"koid\": %" PRIu64 | |
| ", " | |
| "\"name\": \"%s\", " // TODO(dbort): Escape quotes | |
| "\"size_bytes\": %" PRIu64 | |
| ", " | |
| "\"parent_koid\": %" PRIu64 | |
| ", " | |
| "\"num_children\": %zu, " | |
| "\"num_mappings\": %zu, " | |
| "\"share_count\": %zu, " | |
| // TODO(dbort): copy-on-write? phys/paged? | |
| "\"committed_bytes\": %" PRIu64 "}%c\n", | |
| vmo->koid, vmo->name, vmo->size_bytes, vmo->parent_koid, vmo->num_children, | |
| vmo->num_mappings, vmo->share_count, vmo->committed_bytes, delim); | |
| } | |
| printf(" ],\n"); | |
| // List of references from this task to the VMOs listed above. May include | |
| // information specific to this particular use of a given VMO. | |
| printf(" \"vmo_refs\": [\n"); | |
| for (size_t i = 0; i < count; i++) { | |
| const zx_info_vmo_t* vmo = vmos + i; | |
| char delim = i < (count - 1) ? ',' : ' '; | |
| printf( | |
| " {" | |
| "\"vmo_koid\": %" PRIu64 | |
| ", " | |
| "\"via\": [", | |
| vmo->koid); | |
| bool need_comma = false; | |
| if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) { | |
| printf("\"HANDLE\""); | |
| need_comma = true; | |
| } | |
| if (vmo->flags & ZX_INFO_VMO_VIA_MAPPING) { | |
| printf("%s\"MAPPING\"", need_comma ? ", " : ""); | |
| // Future improvement: Could use ZX_INFO_PROCESS_MAPS to include | |
| // specifics of how this VMO is mapped. | |
| } | |
| printf("]"); | |
| if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) { | |
| need_comma = false; | |
| printf(", \"handle_rights\": ["); | |
| #define PRINT_RIGHT(r) \ | |
| do { \ | |
| if (vmo->handle_rights & ZX_RIGHT_##r) { \ | |
| printf("%s\"" #r "\"", need_comma ? ", " : ""); \ | |
| need_comma = true; \ | |
| } \ | |
| } while (false) | |
| PRINT_RIGHT(READ); | |
| PRINT_RIGHT(WRITE); | |
| PRINT_RIGHT(EXECUTE); | |
| PRINT_RIGHT(MAP); | |
| PRINT_RIGHT(DUPLICATE); | |
| PRINT_RIGHT(TRANSFER); | |
| #undef PRINT_RIGHT | |
| printf("]"); | |
| } | |
| printf("}%c\n", delim); | |
| } | |
| printf(" ]"); | |
| } | |
| class JsonTaskEnumerator final : public TaskEnumerator { | |
| public: | |
| // |self_koid| is the koid of this memgraph process, so we can | |
| // 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) | |
| : self_koid_(self_koid), | |
| show_threads_(show_threads), | |
| show_vmos_(show_vmos), | |
| show_handle_stats_(show_handle_stats) {} | |
| zx_status_t partial_failure() const { return partial_failure_; } | |
| private: | |
| static void GetTaskName(zx_handle_t task, zx_koid_t koid, char out_name[ZX_MAX_NAME_LEN]) { | |
| zx_status_t s = zx_object_get_property(task, ZX_PROP_NAME, out_name, ZX_MAX_NAME_LEN); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "WARNING: failed to get name of task %" PRIu64 ": %s (%d)\n", koid, | |
| zx_status_get_string(s), s); | |
| snprintf(out_name, ZX_MAX_NAME_LEN, "<UNKNOWN>"); | |
| // This is unfortunate, but not worth a partial failure | |
| // since the overall structure of the output is still intact. | |
| } | |
| // TODO(dbort): Escape quotes in name | |
| } | |
| zx_status_t OnJob(int depth, zx_handle_t job, zx_koid_t koid, zx_koid_t parent_koid) override { | |
| char name[ZX_MAX_NAME_LEN]; | |
| GetTaskName(job, koid, name); | |
| char parent_id[ZX_MAX_NAME_LEN + 16]; | |
| if (parent_koid == 0) { | |
| // This is the root job, which we treat as a child of the | |
| // system VMO arena node. | |
| snprintf(parent_id, sizeof(parent_id), "kernel/vmo"); | |
| } else { | |
| snprintf(parent_id, sizeof(parent_id), "j/%" PRIu64, parent_koid); | |
| } | |
| printf( | |
| " {" | |
| "\"id\": \"j/%" PRIu64 | |
| "\", " | |
| "\"type\": \"j\", " | |
| "\"koid\": %" PRIu64 | |
| ", " | |
| "\"parent\": \"%s\", " | |
| "\"name\": \"%s\"" | |
| "},\n", | |
| koid, koid, parent_id, name); | |
| return ZX_OK; | |
| } | |
| zx_status_t OnProcess(int depth, zx_handle_t process, zx_koid_t koid, | |
| zx_koid_t parent_koid) override { | |
| char name[ZX_MAX_NAME_LEN]; | |
| GetTaskName(process, koid, name); | |
| // Print basic info. | |
| printf( | |
| " {" | |
| "\"id\": \"p/%" PRIu64 | |
| "\", " | |
| "\"type\": \"p\", " | |
| "\"koid\": %" PRIu64 | |
| ", " | |
| "\"parent\": \"j/%" PRIu64 | |
| "\", " | |
| "\"name\": \"%s\"", | |
| koid, koid, parent_koid, name); | |
| // Print memory usage summaries. | |
| zx_info_task_stats_t info; | |
| zx_status_t s = | |
| zx_object_get_info(process, ZX_INFO_TASK_STATS, &info, sizeof(info), nullptr, nullptr); | |
| if (s == ZX_ERR_BAD_STATE) { | |
| // Process has exited, but has not been destroyed. | |
| // Default to zero for all sizes. | |
| info = {}; | |
| s = ZX_OK; | |
| } | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "WARNING: failed to get mem stats for process %" PRIu64 ": %s (%d)\n", koid, | |
| zx_status_get_string(s), s); | |
| set_partial_failure(s); | |
| } else { | |
| printf( | |
| ", " | |
| "\"private_bytes\": %zu, " | |
| "\"shared_bytes\": %zu, " | |
| "\"pss_bytes\": %zu", | |
| info.mem_private_bytes, info.mem_shared_bytes, | |
| info.mem_private_bytes + info.mem_scaled_shared_bytes); | |
| } | |
| // Print the process's VMOs. The same VMO may appear several | |
| // times in this list; it's up to the consumer of this output | |
| // to de-duplicate. | |
| if (show_vmos_ && koid != self_koid_) { | |
| zx_info_vmo_t* vmos; | |
| size_t count = 0; | |
| size_t avail = 0; | |
| s = get_vmos(process, &vmos, &count, &avail); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "WARNING: failed to read VMOs for process %" PRIu64 ": %s (%d)\n", koid, | |
| zx_status_get_string(s), s); | |
| set_partial_failure(s); | |
| } else { | |
| if (count < avail) { | |
| fprintf(stderr, | |
| "WARNING: failed to read all VMOs for process " | |
| "%" PRIu64 ": count %zu < avail %zu\n", | |
| koid, count, avail); | |
| set_partial_failure(ZX_ERR_BUFFER_TOO_SMALL); | |
| // Keep going with the truncated list. | |
| } | |
| print_vmos(vmos, count); | |
| free(vmos); | |
| } | |
| } | |
| if (show_handle_stats_) { | |
| zx_info_process_handle_stats_t info = {}; | |
| s = zx_object_get_info(process, ZX_INFO_PROCESS_HANDLE_STATS, &info, sizeof(info), nullptr, | |
| nullptr); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, | |
| "WARNING: failed to read handle stats for process " | |
| "%" PRIu64 ": %s (%d)\n", | |
| koid, zx_status_get_string(s), s); | |
| set_partial_failure(s); | |
| } else { | |
| printf(",\n \"handle_stats\": {"); | |
| size_t count = 0; | |
| for (zx_obj_type_t i = 0; i < ZX_OBJ_TYPE_UPPER_BOUND; i++) { | |
| if (!info.handle_count[i]) { | |
| continue; | |
| } | |
| if (count++ > 0) { | |
| printf(","); | |
| } | |
| printf("\n \"%s\": %u", obj_type_get_name(i), info.handle_count[i]); | |
| } | |
| printf("\n }"); | |
| } | |
| } | |
| printf("},\n"); | |
| return ZX_OK; | |
| } | |
| zx_status_t OnThread(int depth, zx_handle_t thread, zx_koid_t koid, | |
| zx_koid_t parent_koid) override { | |
| char name[ZX_MAX_NAME_LEN]; | |
| GetTaskName(thread, koid, name); | |
| // Print basic info. | |
| printf( | |
| " {" | |
| "\"id\": \"t/%" PRIu64 | |
| "\", " | |
| "\"type\": \"t\", " | |
| "\"koid\": %" PRIu64 | |
| ", " | |
| "\"parent\": \"p/%" PRIu64 | |
| "\", " | |
| "\"name\": \"%s\"", | |
| koid, koid, parent_koid, name); | |
| // Print state. | |
| zx_info_thread_t info; | |
| zx_status_t s = | |
| zx_object_get_info(thread, ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "WARNING: failed to get info for thread %" PRIu64 ": %s (%d)\n", koid, | |
| zx_status_get_string(s), s); | |
| set_partial_failure(s); | |
| } else { | |
| const char* state = "<UNKNOWN>"; | |
| if (info.wait_exception_channel_type != ZX_EXCEPTION_CHANNEL_TYPE_NONE) { | |
| state = "EXCEPTION"; | |
| } else { | |
| switch (ZX_THREAD_STATE_BASIC(info.state)) { | |
| case ZX_THREAD_STATE_NEW: | |
| state = "NEW"; | |
| break; | |
| case ZX_THREAD_STATE_RUNNING: | |
| state = "RUNNING"; | |
| break; | |
| case ZX_THREAD_STATE_SUSPENDED: | |
| state = "SUSPENDED"; | |
| break; | |
| case ZX_THREAD_STATE_BLOCKED: | |
| state = "BLOCKED"; | |
| break; | |
| case ZX_THREAD_STATE_DYING: | |
| state = "DYING"; | |
| break; | |
| case ZX_THREAD_STATE_DEAD: | |
| state = "DEAD"; | |
| break; | |
| } | |
| } | |
| printf(", \"state\": \"%s\"", state); | |
| } | |
| printf("},\n"); | |
| return ZX_OK; | |
| } | |
| const zx_koid_t self_koid_; | |
| const bool show_threads_; | |
| const bool show_vmos_; | |
| const bool show_handle_stats_; | |
| // We try to keep going despite failures, but for scripting | |
| // purposes it's good to indicate failure at the end. | |
| void set_partial_failure(zx_status_t status) { | |
| if (partial_failure_ == ZX_OK) { | |
| partial_failure_ = status; | |
| } | |
| } | |
| zx_status_t partial_failure_ = ZX_OK; | |
| bool has_on_job() const final { return true; } | |
| bool has_on_process() const final { return true; } | |
| bool has_on_thread() const final { return show_threads_; } | |
| }; | |
| static void print_kernel_json(const char* name, const char* parent, uint64_t size_bytes) { | |
| printf( | |
| " {" | |
| "\"id\": \"kernel/%s\", " | |
| "\"type\": \"kernel\", " | |
| "\"parent\": \"%s\", " | |
| "\"name\": \"%s\", " | |
| "\"size_bytes\": %zu" | |
| "},\n", | |
| name, parent, name, size_bytes); | |
| } | |
| zx_status_t dump_kernel_memory() { | |
| zx_handle_t root_resource; | |
| zx_status_t s = get_root_resource(&root_resource); | |
| if (s != ZX_OK) { | |
| return s; | |
| } | |
| zx_info_kmem_stats_t stats; | |
| s = zx_object_get_info(root_resource, ZX_INFO_KMEM_STATS, &stats, sizeof(stats), nullptr, | |
| nullptr); | |
| zx_handle_close(root_resource); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "WARNING: failed to get kernel memory stats: %s (%d)\n", | |
| zx_status_get_string(s), s); | |
| return s; | |
| } | |
| print_kernel_json("physmem", "", stats.total_bytes); | |
| print_kernel_json("free", "kernel/physmem", stats.free_bytes); | |
| print_kernel_json("vmo", "kernel/physmem", stats.vmo_bytes); | |
| print_kernel_json("heap", "kernel/physmem", stats.total_heap_bytes); | |
| print_kernel_json("heap/allocated", "kernel/heap", | |
| stats.total_heap_bytes - stats.free_heap_bytes); | |
| print_kernel_json("heap/free", "kernel/heap", stats.free_heap_bytes); | |
| print_kernel_json("wired", "kernel/physmem", stats.wired_bytes); | |
| print_kernel_json("mmu", "kernel/physmem", stats.mmu_overhead_bytes); | |
| print_kernel_json("other", "kernel/physmem", stats.other_bytes); | |
| return ZX_OK; | |
| } | |
| void print_help(FILE* f) { | |
| fprintf(f, "Usage: memgraph [options]\n"); | |
| fprintf(f, " Prints system-wide task and memory info as JSON.\n"); | |
| fprintf(f, "Options:\n"); | |
| fprintf(f, " -t|--threads Include threads in the output\n"); | |
| fprintf(f, " -v|--vmos Include VMOs in the output\n"); | |
| fprintf(f, " -H|--handles Include handle stats in the output\n"); | |
| fprintf(f, " -S|--schema Print the schema for the JSON output format\n"); | |
| fprintf(f, " -h|--help Display this message\n"); | |
| } | |
| } // namespace | |
| int main(int argc, char** argv) { | |
| bool show_threads = false; | |
| bool show_vmos = false; | |
| bool show_handle_stats = false; | |
| while (true) { | |
| static option options[] = { | |
| {"threads", no_argument, nullptr, 't'}, {"vmos", no_argument, nullptr, 'v'}, | |
| {"handles", no_argument, nullptr, 'H'}, {"schema", no_argument, nullptr, 'S'}, | |
| {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}, | |
| }; | |
| int option_index = 0; | |
| int c = getopt_long(argc, argv, "tvHSh", options, &option_index); | |
| if (c < 0) { | |
| break; | |
| } | |
| switch (c) { | |
| case 't': | |
| show_threads = true; | |
| break; | |
| case 'v': | |
| show_vmos = true; | |
| break; | |
| case 'H': | |
| show_handle_stats = true; | |
| break; | |
| case 'S': | |
| printf(kMemgraphSchema); | |
| return 0; | |
| case 'h': | |
| default: | |
| print_help(c == 'h' ? stdout : stderr); | |
| return c == 'h' ? 0 : 1; | |
| } | |
| } | |
| if (optind < argc) { | |
| fprintf(stderr, "%s: unrecognized extra arguments:", argv[0]); | |
| while (optind < argc) { | |
| fprintf(stderr, " %s", argv[optind++]); | |
| } | |
| fprintf(stderr, "\n"); | |
| print_help(stderr); | |
| return 1; | |
| } | |
| // Get our own koid so we can avoid (illegally) reading this process's VMOs. | |
| zx_info_handle_basic_t info; | |
| zx_status_t s = zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info), | |
| nullptr, nullptr); | |
| if (s != ZX_OK) { | |
| // This will probably result in a partial failure when we try to read | |
| // our own VMOs, but keep going. | |
| fprintf(stderr, "WARNING: could not find our own koid: %s (%d)\n", zx_status_get_string(s), s); | |
| info = {}; | |
| info.koid = 0; | |
| } | |
| // Grab the time when we start. | |
| struct timespec now; | |
| timespec_get(&now, TIME_UTC); | |
| printf("[\n"); | |
| zx_status_t ks = dump_kernel_memory(); | |
| JsonTaskEnumerator jte(info.koid, show_threads, show_vmos, show_handle_stats); | |
| s = jte.WalkRootJobTree(); | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "ERROR: %s (%d)\n", zx_status_get_string(s), s); | |
| return 1; | |
| } | |
| // Add a final entry with metadata. Also lets us avoid tracking commas | |
| // above. | |
| // Print the time as an ISO 8601 string. | |
| struct tm nowtm; | |
| gmtime_r(&now.tv_sec, &nowtm); | |
| char tbuf[40]; | |
| strftime(tbuf, sizeof(tbuf), "%FT%T", &nowtm); | |
| printf( | |
| " {" | |
| "\"type\": \"__META\", " | |
| "\"timestamp\": \"%s.%03ldZ\"}\n", | |
| tbuf, now.tv_nsec / (1000 * 1000)); | |
| printf("]\n"); | |
| // Exit with an error status if we hit any partial failures. | |
| s = jte.partial_failure(); | |
| if (s == ZX_OK) { | |
| s = ks; | |
| } | |
| if (s != ZX_OK) { | |
| fprintf(stderr, "ERROR: delayed exit after partial failure: %s (%d)\n", zx_status_get_string(s), | |
| s); | |
| return 1; | |
| } | |
| return 0; | |
| } |