blob: bb99b1c60abebb9dbe40ba408d8b6148cb65a791 [file] [log] [blame]
// 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;
}