[memory] Added support for capturing and printing memory statistics.
A new command line program was created for this purpose,
called "mem". The supporting classes have been broken out
into a library to be used by this and memory_monitor.
In addition tests have been created for these functions.
CF-775
Change-Id: Ie10d74f65a067717f7317a2343810a739e481b24
Test: Included
diff --git a/garnet/bin/memory_monitor/BUILD.gn b/garnet/bin/memory_monitor/BUILD.gn
index 2883782b..12e110e 100644
--- a/garnet/bin/memory_monitor/BUILD.gn
+++ b/garnet/bin/memory_monitor/BUILD.gn
@@ -5,18 +5,59 @@
import("//build/config.gni")
import("//build/package.gni")
-source_set("lib") {
+source_set("mem_lib") {
+ sources = [
+ "capture.cc",
+ "capture.h",
+ "printer.cc",
+ "printer.h",
+ "summary.cc",
+ "summary.h",
+ ]
+
+ deps = [
+ "//garnet/public/lib/fsl",
+ "//src/lib/fxl",
+ "//zircon/public/fidl/fuchsia-sysinfo:fuchsia-sysinfo_c",
+ "//zircon/public/lib/task-utils",
+ ]
+}
+
+executable("mem_bin") {
+ output_name = "mem"
+ sources = [
+ "mem_main.cc",
+ ]
+ deps = [
+ ":mem_lib",
+ "//src/lib/fxl",
+ "//sdk/lib/sys/cpp",
+ ]
+}
+
+package("mem") {
+ deps = [
+ ":mem_bin",
+ ]
+ binaries = [
+ {
+ name = "mem"
+ shell = true
+ },
+ ]
+}
+
+source_set("mon_lib") {
sources = [
"monitor.cc",
"monitor.h",
]
-
public_deps = [
+ ":mem_lib",
"//garnet/public/lib/inspect",
"//sdk/fidl/fuchsia.memory",
"//sdk/lib/sys/cpp",
]
-
deps = [
"//garnet/public/lib/fsl",
"//src/lib/fxl",
@@ -26,14 +67,13 @@
]
}
-executable("bin") {
+executable("mon_bin") {
output_name = "memory_monitor"
-
sources = [
- "main.cc",
+ "mon_main.cc",
]
deps = [
- ":lib",
+ ":mon_lib",
"//sdk/lib/sys/cpp",
"//src/lib/fxl",
"//zircon/public/lib/async-default",
@@ -44,11 +84,9 @@
package("memory_monitor") {
deps = [
- ":bin",
+ ":mon_bin",
]
-
binary = "memory_monitor"
-
meta = [
{
path = rebase_path("meta/memory_monitor.cmx")
diff --git a/garnet/bin/memory_monitor/capture.cc b/garnet/bin/memory_monitor/capture.cc
new file mode 100644
index 0000000..9266c4d
--- /dev/null
+++ b/garnet/bin/memory_monitor/capture.cc
@@ -0,0 +1,158 @@
+// Copyright 2019 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.
+
+#include "garnet/bin/memory_monitor/capture.h"
+
+#include <fcntl.h>
+#include <fuchsia/sysinfo/c/fidl.h>
+#include <lib/zx/channel.h>
+#include <lib/fdio/fdio.h>
+#include <memory>
+#include <src/lib/fxl/logging.h>
+#include <task-utils/walker.h>
+#include <zircon/process.h>
+#include <zircon/status.h>
+
+namespace memory {
+namespace {
+
+zx_status_t get_root_resource(zx_handle_t* root_resource) {
+ const char* sysinfo = "/dev/misc/sysinfo";
+ int fd = open(sysinfo, O_RDWR);
+ if (fd < 0) {
+ FXL_LOG(ERROR) << "Cannot open sysinfo: " << strerror(errno);
+ return ZX_ERR_NOT_FOUND;
+ }
+
+ zx::channel channel;
+ zx_status_t status =
+ fdio_get_service_handle(fd, channel.reset_and_get_address());
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Cannot obtain sysinfo channel: "
+ << zx_status_get_string(status);
+ return status;
+ }
+
+ zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(
+ channel.get(), &status, root_resource);
+ if (fidl_status != ZX_OK) {
+ FXL_LOG(ERROR) << "Cannot obtain root resource: "
+ << zx_status_get_string(fidl_status);
+ return fidl_status;
+ } else if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Cannot obtain root resource: "
+ << zx_status_get_string(status);
+ return status;
+ }
+ return ZX_OK;
+}
+} // namespace
+
+class Capture::ProcessGetter final : public TaskEnumerator {
+ public:
+ ProcessGetter(CaptureLevel level, zx_koid_t self_koid, Capture& capture)
+ : level_(level), self_koid_(self_koid), capture_(capture) {}
+
+ zx_status_t OnProcess(
+ int depth,
+ zx_handle_t handle,
+ zx_koid_t koid,
+ zx_koid_t parent_koid) override {
+ Process process = { .koid = koid };
+ zx_status_t s = zx_object_get_property(
+ handle, ZX_PROP_NAME, process.name, ZX_MAX_NAME_LEN);
+ if (s != ZX_OK) {
+ return s;
+ }
+ s = zx_object_get_info(handle, ZX_INFO_TASK_STATS,
+ &process.stats, sizeof(process.stats),
+ nullptr, nullptr);
+ if (s != ZX_OK) {
+ return s;
+ }
+
+ if (level_ == PROCESS) {
+ return ZX_OK;
+ }
+
+ if (koid == self_koid_) {
+ return ZX_OK;
+ }
+
+ size_t num_vmos;
+ s = zx_object_get_info(handle, ZX_INFO_PROCESS_VMOS,
+ nullptr, 0, nullptr, &num_vmos);
+ if (s != ZX_OK) {
+ return s;
+ }
+ zx_info_vmo_t* vmos = new zx_info_vmo_t[num_vmos];
+ s = zx_object_get_info(handle, ZX_INFO_PROCESS_VMOS,
+ vmos, num_vmos * sizeof(zx_info_vmo_t),
+ &num_vmos, nullptr);
+ if (s != ZX_OK) {
+ delete[] vmos;
+ return s;
+ }
+ process.vmos.reserve(num_vmos);
+ for (size_t i = 0; i < num_vmos; i++) {
+ zx_koid_t vmo_koid = vmos[i].koid;
+ capture_.koid_to_vmo_.emplace(vmo_koid, vmos[i]);
+ process.vmos.push_back(vmo_koid);
+ }
+ delete[] vmos;
+ capture_.koid_to_process_.emplace(koid, process);
+ return ZX_OK;
+ }
+
+ private:
+ CaptureLevel level_;
+ zx_koid_t self_koid_;
+ Capture& capture_;
+
+ bool has_on_process() const final { return true; }
+};
+
+// static.
+zx_status_t Capture::GetCaptureState(CaptureState& state) {
+ zx_status_t err = get_root_resource(&state.root);
+ if (err != ZX_OK) {
+ return err;
+ }
+
+ zx_info_handle_basic_t info;
+ err = zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC,
+ &info, sizeof(info), nullptr, nullptr);
+ if (err != ZX_OK) {
+ return err;
+ }
+
+ state.self_koid = info.koid;
+ return ZX_OK;
+}
+
+// static.
+zx_status_t Capture::GetCapture(
+ Capture& capture, const CaptureState& state, CaptureLevel level) {
+ capture.time_ = zx_clock_get_monotonic();
+ zx_status_t err = zx_object_get_info(
+ state.root, ZX_INFO_KMEM_STATS,
+ &capture.kmem_, sizeof(capture.kmem_),
+ NULL, NULL);
+ if (err != ZX_OK) {
+ return err;
+ }
+
+ if (level == KMEM) {
+ return ZX_OK;
+ }
+
+ ProcessGetter getter(level, state.self_koid, capture);
+ err = getter.WalkRootJobTree();
+ if (err != ZX_OK) {
+ return err;
+ }
+ return ZX_OK;
+}
+
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/capture.h b/garnet/bin/memory_monitor/capture.h
new file mode 100644
index 0000000..bda9fbf
--- /dev/null
+++ b/garnet/bin/memory_monitor/capture.h
@@ -0,0 +1,69 @@
+// Copyright 2019 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.
+
+#ifndef GARNET_BIN_MEMORY_MONITOR_CAPTURE_H_
+#define GARNET_BIN_MEMORY_MONITOR_CAPTURE_H_
+
+#include <src/lib/fxl/macros.h>
+#include <lib/zx/time.h>
+#include <unordered_map>
+#include <vector>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+namespace memory {
+
+struct Process {
+ zx_koid_t koid;
+ char name[ZX_MAX_NAME_LEN];
+ zx_info_task_stats_t stats;
+ std::vector<zx_koid_t> vmos;
+};
+
+typedef enum {KMEM, PROCESS, VMO} CaptureLevel;
+
+struct CaptureState {
+ zx_handle_t root;
+ zx_koid_t self_koid;
+};
+
+class Capture {
+ public:
+ static zx_status_t GetCaptureState(CaptureState& state);
+ static zx_status_t GetCapture(
+ Capture& capture, const CaptureState& state, CaptureLevel level);
+
+ zx_time_t time() const { return time_; };
+ const zx_info_kmem_stats_t& kmem() const { return kmem_; } ;
+
+ const std::unordered_map<zx_koid_t, const Process>& koid_to_process() const {
+ return koid_to_process_;
+ }
+
+ const std::unordered_map<zx_koid_t, const zx_info_vmo_t>&
+ koid_to_vmo() const {
+ return koid_to_vmo_;
+ }
+
+ const Process& process_for_koid(zx_koid_t koid) const {
+ return koid_to_process_.at(koid);
+ }
+
+ const zx_info_vmo_t& vmo_for_koid(zx_koid_t koid) const {
+ return koid_to_vmo_.at(koid);
+ }
+
+ private:
+ zx_time_t time_;
+ zx_info_kmem_stats_t kmem_;
+ std::unordered_map<zx_koid_t, const Process> koid_to_process_;
+ std::unordered_map<zx_koid_t, const zx_info_vmo_t> koid_to_vmo_;
+
+ class ProcessGetter;
+ friend class TestUtils;
+};
+
+} // namespace memory
+
+#endif // GARNET_BIN_MEMORY_MONITOR_CAPTURE_H_
diff --git a/garnet/bin/memory_monitor/mem_main.cc b/garnet/bin/memory_monitor/mem_main.cc
new file mode 100644
index 0000000..b7d230b
--- /dev/null
+++ b/garnet/bin/memory_monitor/mem_main.cc
@@ -0,0 +1,102 @@
+// Copyright 2019 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.
+
+#include <iostream>
+#include <src/lib/fxl/command_line.h>
+#include <src/lib/fxl/strings/string_number_conversions.h>
+#include <zircon/status.h>
+
+#include "garnet/bin/memory_monitor/capture.h"
+#include "garnet/bin/memory_monitor/summary.h"
+#include "garnet/bin/memory_monitor/printer.h"
+
+using namespace memory;
+
+int main(int argc, const char** argv) {
+ auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
+
+ CaptureState capture_state;
+ auto s = Capture::GetCaptureState(capture_state);
+ Printer printer(std::cout);
+
+ if (s != ZX_OK) {
+ std::cerr << "Error getting capture state: "
+ << zx_status_get_string(s);
+ return EXIT_FAILURE;
+ }
+ if (command_line.HasOption("summarize")) {
+ Capture capture;
+ s = Capture::GetCapture(capture, capture_state, VMO);
+ if (s != ZX_OK) {
+ std::cerr << "Error getting capture: " << zx_status_get_string(s);
+ return EXIT_FAILURE;
+ }
+ Summary summary(capture);
+ printer.PrintSummary(summary, VMO, SORTED);
+ return EXIT_SUCCESS;
+ }
+
+ if (command_line.HasOption("print")) {
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state, VMO);
+ if (s != ZX_OK) {
+ std::cerr << "Error getting capture: " << zx_status_get_string(s);
+ return EXIT_FAILURE;
+ }
+ printer.PrintCapture(capture, VMO, SORTED);
+ return EXIT_SUCCESS;
+ }
+
+ if (command_line.HasOption("output")) {
+ zx_koid_t pid = 0;
+ if (command_line.HasOption("pid")) {
+ std::string pid_value;
+ command_line.GetOptionValue("pid", &pid_value);
+ if (!fxl::StringToNumberWithError(pid_value, &pid)) {
+ std::cerr << "Invalid value for --pid: " << pid_value;
+ return EXIT_FAILURE;
+ }
+ }
+ if (!command_line.HasOption("repeat")) {
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state, VMO);
+ if (s != ZX_OK) {
+ std::cerr << "Error getting capture: " << zx_status_get_string(s);
+ return EXIT_FAILURE;
+ }
+ Summary summary(capture);
+ printer.OutputSummary(summary, UNSORTED, pid);
+ return EXIT_SUCCESS;
+ }
+ zx::time start = zx::clock::get_monotonic();
+ uint64_t repeat = 0;
+ std::string repeat_value;
+ command_line.GetOptionValue("repeat", &repeat_value);
+ if (!fxl::StringToNumberWithError(repeat_value, &repeat)) {
+ std::cerr << "Invalid value for --repeat: " << repeat_value;
+ return EXIT_FAILURE;
+ }
+ zx::duration repeat_secs = zx::sec(repeat);
+ int64_t i = 1;
+ do {
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state, VMO);
+ if (s != ZX_OK) {
+ std::cerr << "Error getting capture: " << zx_status_get_string(s);
+ return EXIT_FAILURE;
+ }
+ Summary summary(capture);
+ printer.OutputSummary(summary, UNSORTED, pid);
+ zx::time next = start + (repeat_secs * i);
+ if (next <= zx::clock::get_monotonic()) {
+ next = zx::clock::get_monotonic() + repeat_secs;
+ }
+ zx::nanosleep(next);
+ i++;
+ } while (true);
+ return EXIT_SUCCESS;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/garnet/bin/memory_monitor/main.cc b/garnet/bin/memory_monitor/mon_main.cc
similarity index 81%
rename from garnet/bin/memory_monitor/main.cc
rename to garnet/bin/memory_monitor/mon_main.cc
index f7a2655..5583438 100644
--- a/garnet/bin/memory_monitor/main.cc
+++ b/garnet/bin/memory_monitor/mon_main.cc
@@ -1,14 +1,14 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Copyright 2019 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.
#include <lib/async-loop/cpp/loop.h>
+#include <src/lib/fxl/command_line.h>
+#include <src/lib/fxl/log_settings_command_line.h>
+#include <src/lib/fxl/logging.h>
#include <trace-provider/provider.h>
#include "garnet/bin/memory_monitor/monitor.h"
-#include "src/lib/fxl/command_line.h"
-#include "src/lib/fxl/log_settings_command_line.h"
-#include "src/lib/fxl/logging.h"
int main(int argc, const char** argv) {
auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
diff --git a/garnet/bin/memory_monitor/monitor.cc b/garnet/bin/memory_monitor/monitor.cc
index 75c8ce8..34c587b 100644
--- a/garnet/bin/memory_monitor/monitor.cc
+++ b/garnet/bin/memory_monitor/monitor.cc
@@ -7,21 +7,20 @@
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/sysinfo/c/fidl.h>
+#include <iostream>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/time.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
+#include <src/lib/fxl/command_line.h>
+#include <src/lib/fxl/logging.h>
+#include <src/lib/fxl/strings/string_number_conversions.h>
#include <string.h>
#include <trace/event.h>
#include <zircon/status.h>
-#include <iostream>
-
-#include "src/lib/fxl/command_line.h"
-#include "src/lib/fxl/logging.h"
-#include "src/lib/fxl/strings/string_number_conversions.h"
namespace memory {
@@ -29,37 +28,6 @@
constexpr char kKstatsPathComponent[] = "kstats";
-zx_status_t get_root_resource(zx_handle_t* root_resource) {
- const char* sysinfo = "/dev/misc/sysinfo";
- int fd = open(sysinfo, O_RDWR);
- if (fd < 0) {
- FXL_LOG(ERROR) << "Cannot open sysinfo: " << strerror(errno);
- return ZX_ERR_NOT_FOUND;
- }
-
- zx::channel channel;
- zx_status_t status =
- fdio_get_service_handle(fd, channel.reset_and_get_address());
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Cannot obtain sysinfo channel: "
- << zx_status_get_string(status);
- return status;
- }
-
- zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(
- channel.get(), &status, root_resource);
- if (fidl_status != ZX_OK) {
- FXL_LOG(ERROR) << "Cannot obtain root resource: "
- << zx_status_get_string(fidl_status);
- return fidl_status;
- } else if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Cannot obtain root resource: "
- << zx_status_get_string(status);
- return status;
- }
- return ZX_OK;
-}
-
} // namespace
const char Monitor::kTraceName[] = "memory_monitor";
@@ -74,6 +42,13 @@
dispatcher_(dispatcher),
component_context_(std::move(context)),
root_object_(component::ObjectDir::Make("root")) {
+ auto s = Capture::GetCaptureState(capture_state_);
+ if (s != ZX_OK) {
+ FXL_LOG(ERROR) << "Error getting capture state: "
+ << zx_status_get_string(s);
+ exit(EXIT_FAILURE);
+ }
+
component_context_->outgoing()->AddPublicService(bindings_.GetHandler(this));
root_object_.set_children_callback(
{kKstatsPathComponent},
@@ -85,11 +60,6 @@
std::make_unique<vfs::Service>(
inspect_bindings_.GetHandler(root_object_.object().get())));
- auto status = get_root_resource(&root_);
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Error getting root_resource: " << status;
- exit(-1);
- }
if (command_line.HasOption("help")) {
PrintHelp();
exit(EXIT_SUCCESS);
@@ -102,7 +72,6 @@
FXL_LOG(ERROR) << "Invalid value for delay: " << delay_as_string;
exit(-1);
}
- printf("setting delay to %d\n", delay_as_int);
delay_ = zx::msec(delay_as_int);
}
std::string prealloc_as_string;
@@ -141,17 +110,16 @@
trace_observer_.Start(dispatcher_, [this] { UpdateState(); });
if (logging_) {
- zx_info_kmem_stats_t stats;
- zx_status_t err = zx_object_get_info(root_, ZX_INFO_KMEM_STATS, &stats,
- sizeof(stats), NULL, NULL);
- if (err != ZX_OK) {
- FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS returns "
- << zx_status_get_string(err);
- exit(-1);
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state_, KMEM);
+ if (s != ZX_OK) {
+ FXL_LOG(ERROR) << "Error getting capture: " << zx_status_get_string(s);
+ exit(EXIT_FAILURE);
}
- FXL_LOG(INFO) << "Total: " << stats.total_bytes
- << " Wired: " << stats.wired_bytes
- << " Total Heap: " << stats.total_heap_bytes;
+ auto const& kmem = capture.kmem();
+ FXL_LOG(INFO) << "Total: " << kmem.total_bytes
+ << " Wired: " << kmem.wired_bytes
+ << " Total Heap: " << kmem.total_heap_bytes;
}
SampleAndPost();
}
@@ -179,7 +147,7 @@
std::remove_if(watchers_.begin(), watchers_.end(), predicate));
}
-void Monitor::NotifyWatchers(zx_info_kmem_stats_t kmem_stats) {
+void Monitor::NotifyWatchers(const zx_info_kmem_stats_t& kmem_stats) {
fuchsia::memory::Stats stats{
.total_bytes = kmem_stats.total_bytes,
.free_bytes = kmem_stats.free_bytes,
@@ -207,52 +175,50 @@
void Monitor::Inspect(component::Object::ObjectVector* out_children) {
auto kstats = component::ObjectDir::Make(kKstatsPathComponent);
- zx_info_kmem_stats_t stats;
- zx_status_t err = zx_object_get_info(root_, ZX_INFO_KMEM_STATS, &stats,
- sizeof(stats), NULL, NULL);
- if (err != ZX_OK) {
- FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS returns "
- << zx_status_get_string(err);
- return;
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state_, KMEM);
+ if (s != ZX_OK) {
+ FXL_LOG(ERROR) << "Error getting capture: " << zx_status_get_string(s);
}
- kstats.set_metric("total_bytes", component::UIntMetric(stats.total_bytes));
- kstats.set_metric("free_bytes", component::UIntMetric(stats.free_bytes));
- kstats.set_metric("wired_bytes", component::UIntMetric(stats.wired_bytes));
- kstats.set_metric("total_heap_bytes",
- component::UIntMetric(stats.total_heap_bytes));
- kstats.set_metric("vmo_bytes", component::UIntMetric(stats.vmo_bytes));
- kstats.set_metric("mmu_overhead_bytes",
- component::UIntMetric(stats.mmu_overhead_bytes));
- kstats.set_metric("ipc_bytes", component::UIntMetric(stats.ipc_bytes));
- kstats.set_metric("other_bytes", component::UIntMetric(stats.other_bytes));
+ auto const& kmem = capture.kmem();
+
+ kstats.set_metric("total_bytes", component::UIntMetric(kmem.total_bytes));
+ kstats.set_metric("free_bytes", component::UIntMetric(kmem.free_bytes));
+ kstats.set_metric("wired_bytes", component::UIntMetric(kmem.wired_bytes));
+ kstats.set_metric(
+ "total_heap_bytes",component::UIntMetric(kmem.total_heap_bytes));
+ kstats.set_metric("vmo_bytes", component::UIntMetric(kmem.vmo_bytes));
+ kstats.set_metric(
+ "mmu_overhead_bytes", component::UIntMetric(kmem.mmu_overhead_bytes));
+ kstats.set_metric("ipc_bytes", component::UIntMetric(kmem.ipc_bytes));
+ kstats.set_metric("other_bytes", component::UIntMetric(kmem.other_bytes));
out_children->push_back(kstats.object());
}
void Monitor::SampleAndPost() {
if (logging_ || tracing_ || watchers_.size() > 0) {
- zx_info_kmem_stats_t stats;
- zx_status_t err = zx_object_get_info(root_, ZX_INFO_KMEM_STATS, &stats,
- sizeof(stats), NULL, NULL);
- if (err != ZX_OK) {
- FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS returns "
- << zx_status_get_string(err);
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state_, KMEM);
+ if (s != ZX_OK) {
+ FXL_LOG(ERROR) << "Error getting capture: " << zx_status_get_string(s);
return;
}
+ auto const& kmem = capture.kmem();
if (logging_) {
- FXL_LOG(INFO) << "Free: " << stats.free_bytes
- << " Free Heap: " << stats.free_heap_bytes
- << " VMO: " << stats.vmo_bytes
- << " MMU: " << stats.mmu_overhead_bytes
- << " IPC: " << stats.ipc_bytes;
+ FXL_LOG(INFO) << "Free: " << kmem.free_bytes
+ << " Free Heap: " << kmem.free_heap_bytes
+ << " VMO: " << kmem.vmo_bytes
+ << " MMU: " << kmem.mmu_overhead_bytes
+ << " IPC: " << kmem.ipc_bytes;
}
if (tracing_) {
- TRACE_COUNTER(kTraceName, "allocated", 0, "vmo", stats.vmo_bytes,
- "mmu_overhead", stats.mmu_overhead_bytes, "ipc",
- stats.ipc_bytes);
- TRACE_COUNTER(kTraceName, "free", 0, "free", stats.free_bytes,
- "free_heap", stats.free_heap_bytes);
+ TRACE_COUNTER(kTraceName, "allocated", 0, "vmo", kmem.vmo_bytes,
+ "mmu_overhead", kmem.mmu_overhead_bytes, "ipc",
+ kmem.ipc_bytes);
+ TRACE_COUNTER(kTraceName, "free", 0, "free", kmem.free_bytes,
+ "free_heap", kmem.free_heap_bytes);
}
- NotifyWatchers(stats);
+ NotifyWatchers(kmem);
async::PostDelayedTask(
dispatcher_, [this] { SampleAndPost(); }, delay_);
}
@@ -263,17 +229,19 @@
if (trace_is_category_enabled(kTraceName)) {
FXL_LOG(INFO) << "Tracing started";
if (!tracing_) {
- zx_info_kmem_stats_t stats;
- zx_status_t err = zx_object_get_info(root_, ZX_INFO_KMEM_STATS, &stats,
- sizeof(stats), NULL, NULL);
- if (err != ZX_OK) {
- FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS returns "
- << zx_status_get_string(err);
+ Capture capture;
+ auto s = Capture::GetCapture(capture, capture_state_, KMEM);
+ if (s != ZX_OK) {
+ FXL_LOG(ERROR) << "Error getting capture: "
+ << zx_status_get_string(s);
return;
}
- TRACE_COUNTER(kTraceName, "fixed", 0, "total", stats.total_bytes,
- "wired", stats.wired_bytes, "total_heap",
- stats.total_heap_bytes);
+ auto const& kmem = capture.kmem();
+ TRACE_COUNTER(kTraceName,
+ "fixed", 0,
+ "total", kmem.total_bytes,
+ "wired", kmem.wired_bytes,
+ "total_heap", kmem.total_heap_bytes);
tracing_ = true;
if (!logging_) {
SampleAndPost();
diff --git a/garnet/bin/memory_monitor/monitor.h b/garnet/bin/memory_monitor/monitor.h
index 55e39b7..da2d28f 100644
--- a/garnet/bin/memory_monitor/monitor.h
+++ b/garnet/bin/memory_monitor/monitor.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Copyright 2019 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.
@@ -11,13 +11,12 @@
#include <lib/inspect/deprecated/object_dir.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/zx/vmo.h>
+#include <memory>
+#include <src/lib/fxl/command_line.h>
#include <trace/observer.h>
#include <zircon/types.h>
-#include <memory>
-
-#include "src/lib/fxl/command_line.h"
-#include "src/lib/fxl/macros.h"
+#include "garnet/bin/memory_monitor/capture.h"
namespace memory {
@@ -43,8 +42,9 @@
// Destroys a watcher proxy (called upon a connection error).
void ReleaseWatcher(fuchsia::memory::Watcher* watcher);
// Alerts all watchers when an update has occurred.
- void NotifyWatchers(zx_info_kmem_stats_t stats);
+ void NotifyWatchers(const zx_info_kmem_stats_t& stats);
+ CaptureState capture_state_;
uint64_t prealloc_size_;
zx::vmo prealloc_vmo_;
bool logging_;
diff --git a/garnet/bin/memory_monitor/printer.cc b/garnet/bin/memory_monitor/printer.cc
new file mode 100644
index 0000000..638435e
--- /dev/null
+++ b/garnet/bin/memory_monitor/printer.cc
@@ -0,0 +1,203 @@
+// Copyright 2019 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.
+
+#include "garnet/bin/memory_monitor/printer.h"
+
+namespace memory {
+
+void Printer::PrintCapture(
+ const Capture& capture, CaptureLevel level, Sorted sorted) {
+ auto const& kmem = capture.kmem();
+ os_ << "K,"
+ << capture.time() << ","
+ << kmem.total_bytes << ","
+ << kmem.free_bytes << ","
+ << kmem.wired_bytes << ","
+ << kmem.total_heap_bytes << ","
+ << kmem.free_heap_bytes << ","
+ << kmem.vmo_bytes << ","
+ << kmem.mmu_overhead_bytes << ","
+ << kmem.ipc_bytes << ","
+ << kmem.other_bytes << "\n";
+ if (level == KMEM) {
+ return;
+ }
+
+ auto const& koid_to_process = capture.koid_to_process();
+ std::vector<zx_koid_t> process_koids;
+ for (auto const& pair : koid_to_process) {
+ process_koids.push_back(pair.first);
+ }
+ if (sorted == SORTED) {
+ sort(process_koids.begin(), process_koids.end(),
+ [&koid_to_process](zx_koid_t a, zx_koid_t b) {
+ auto const& sa = koid_to_process.at(a).stats;
+ auto const& sb = koid_to_process.at(b).stats;
+ return sa.mem_private_bytes == sb.mem_private_bytes
+ ? sa.mem_scaled_shared_bytes > sb.mem_scaled_shared_bytes
+ : sa.mem_private_bytes > sb.mem_private_bytes;
+ });
+ }
+ for (auto const& koid : process_koids) {
+ auto const& p = koid_to_process.at(koid);
+ os_ << "P,"
+ << p.koid << ","
+ << p.name << ","
+ << p.stats.mem_mapped_bytes << ","
+ << p.stats.mem_private_bytes << ","
+ << p.stats.mem_shared_bytes << ","
+ << p.stats.mem_scaled_shared_bytes;
+ for (auto const& v: p.vmos) {
+ os_ << "," << v;
+ }
+ os_ << "\n";
+ }
+ if (level == PROCESS) {
+ return;
+ }
+
+ auto const& koid_to_vmo = capture.koid_to_vmo();
+ std::vector<zx_koid_t> vmo_koids;
+ for (auto const& pair : koid_to_vmo) {
+ vmo_koids.push_back(pair.first);
+ }
+ if (sorted == SORTED) {
+ sort(vmo_koids.begin(), vmo_koids.end(),
+ [&koid_to_vmo](zx_koid_t a, zx_koid_t b) {
+ auto const& sa = koid_to_vmo.at(a);
+ auto const& sb = koid_to_vmo.at(b);
+ return sa.committed_bytes > sb.committed_bytes;
+ });
+ }
+ for (auto const& koid : vmo_koids) {
+ auto const& v = koid_to_vmo.at(koid);
+ os_ << "V,"
+ << v.koid << ","
+ << v.name << ","
+ << v.size_bytes << ","
+ << v.parent_koid << ","
+ << v.committed_bytes << "\n";
+ }
+ os_ << std::flush;
+}
+
+void Printer::PrintSummary(
+ const Summary& summary, CaptureLevel level, Sorted sorted) {
+ auto& kstats = summary.kstats();
+ os_ << "Time: " << summary.time()
+ << " VMO: " << kstats.vmo_bytes
+ << " Free: " << kstats.free_bytes
+ << " Wired: " << kstats.wired_bytes
+ << " Heap: " << kstats.total_heap_bytes
+ << " Heap Free: " << kstats.free_heap_bytes
+ << " Overhead: " << kstats.mmu_overhead_bytes
+ << " Other: " << kstats.other_bytes << "\n";
+
+ if (level == KMEM) {
+ return;
+ }
+
+ auto const& summaries = summary.process_summaries();
+ std::vector<ProcessSummary> sorted_summaries;
+ if (sorted == SORTED) {
+ sorted_summaries = summaries;
+ sort(sorted_summaries.begin(), sorted_summaries.end(),
+ [](ProcessSummary a, ProcessSummary b) {
+ return a.sizes().private_bytes > b.sizes().private_bytes;
+ });
+ }
+ for (auto const& s : sorted == SORTED ? sorted_summaries : summaries) {
+ os_ << s.name() << "<" << s.koid() << "> "
+ << s.sizes().private_bytes << " "
+ << s.sizes().scaled_bytes << " "
+ << s.sizes().total_bytes << "\n";
+ if (level == PROCESS) {
+ continue;
+ }
+ auto const& name_to_sizes = s.name_to_sizes();
+ std::vector<std::string> names;
+ for (auto const& pair : name_to_sizes) {
+ names.push_back(pair.first);
+ }
+ if (sorted == SORTED) {
+ sort(names.begin(), names.end(),
+ [&name_to_sizes](std::string a, std::string b) {
+ auto const& sa = name_to_sizes.at(a);
+ auto const& sb = name_to_sizes.at(b);
+ return sa.private_bytes == sb.private_bytes
+ ? sa.scaled_bytes > sb.scaled_bytes
+ : sa.private_bytes > sb.private_bytes;
+ });
+ }
+ for (auto const& name : names) {
+ auto const& sizes = name_to_sizes.at(name);
+ if (sizes.total_bytes == 0) {
+ continue;
+ }
+ os_ << " " << name
+ << " " << sizes.private_bytes
+ << " " << sizes.scaled_bytes
+ << " " << sizes.total_bytes << "\n";
+ }
+ }
+ os_ << std::flush;
+}
+
+void Printer::OutputSummary(
+ const Summary& summary, Sorted sorted, zx_koid_t pid) {
+ auto const& summaries = summary.process_summaries();
+ std::vector<ProcessSummary> sorted_summaries;
+ if (sorted == SORTED) {
+ sorted_summaries = summaries;
+ sort(sorted_summaries.begin(), sorted_summaries.end(),
+ [](ProcessSummary a, ProcessSummary b) {
+ return a.sizes().private_bytes > b.sizes().private_bytes;
+ });
+ }
+ for (auto const& s : sorted == SORTED ? sorted_summaries : summaries) {
+ if (pid != ZX_KOID_INVALID) {
+ if (s.koid() != pid) {
+ continue;
+ }
+ auto const& name_to_sizes = s.name_to_sizes();
+ std::vector<std::string> names;
+ for (auto const& pair : name_to_sizes) {
+ names.push_back(pair.first);
+ }
+ if (sorted == SORTED) {
+ sort(names.begin(), names.end(),
+ [&name_to_sizes](std::string a, std::string b) {
+ auto const& sa = name_to_sizes.at(a);
+ auto const& sb = name_to_sizes.at(b);
+ return sa.private_bytes == sb.private_bytes
+ ? sa.scaled_bytes > sb.scaled_bytes
+ : sa.private_bytes > sb.private_bytes;
+ });
+ }
+ for (auto const& name : names) {
+ auto const& sizes = name_to_sizes.at(name);
+ if (sizes.total_bytes == 0) {
+ continue;
+ }
+ os_ << summary.time() / 1000000000 << ","
+ << s.koid() << ","
+ << name << ","
+ << sizes.private_bytes << ","
+ << sizes.scaled_bytes << ","
+ << sizes.total_bytes << "\n";
+ }
+ continue;
+ }
+ auto sizes = s.sizes();
+ os_ << summary.time() / 1000000000 << ","
+ << s.koid() << ","
+ << s.name() << ","
+ << sizes.private_bytes << ","
+ << sizes.scaled_bytes << ","
+ << sizes.total_bytes << "\n";
+ }
+ os_ << std::flush;
+}
+
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/printer.h b/garnet/bin/memory_monitor/printer.h
new file mode 100644
index 0000000..d05a93d
--- /dev/null
+++ b/garnet/bin/memory_monitor/printer.h
@@ -0,0 +1,34 @@
+// Copyright 2019 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.
+
+#ifndef GARNET_BIN_MEMORY_MONITOR_PRINTER_H_
+#define GARNET_BIN_MEMORY_MONITOR_PRINTER_H_
+
+#include <iostream>
+#include <src/lib/fxl/macros.h>
+#include <string>
+#include <sstream>
+
+#include "garnet/bin/memory_monitor/capture.h"
+#include "garnet/bin/memory_monitor/summary.h"
+
+namespace memory {
+
+enum Sorted { UNSORTED, SORTED };
+
+class Printer {
+ public:
+ Printer(std::ostream& os) : os_(os) {}
+ void PrintCapture(const Capture& capture, CaptureLevel level, Sorted sorted);
+ void PrintSummary(const Summary& summary, CaptureLevel level, Sorted sorted);
+ void OutputSummary(const Summary& summary, Sorted sorted, zx_koid_t pid);
+
+ private:
+ std::ostream& os_;
+ FXL_DISALLOW_COPY_AND_ASSIGN(Printer);
+};
+
+} // namespace memory
+
+#endif // GARNET_BIN_MEMORY_MONITOR_PRINTER_H_
diff --git a/garnet/bin/memory_monitor/summary.cc b/garnet/bin/memory_monitor/summary.cc
new file mode 100644
index 0000000..746aa3e
--- /dev/null
+++ b/garnet/bin/memory_monitor/summary.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 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.
+
+#include "garnet/bin/memory_monitor/summary.h"
+
+namespace memory {
+
+Summary::Summary(const Capture& capture)
+ : time_(capture.time()), kstats_(capture.kmem()) {
+ std::unordered_map<zx_koid_t, std::unordered_set<zx_koid_t>>
+ vmo_to_processes;
+ for (auto const& pair : capture.koid_to_process()) {
+ auto process_koid = pair.first;
+ auto& process = pair.second;
+ ProcessSummary s(process_koid, process.name);
+ for (auto vmo_koid: process.vmos) {
+ do {
+ vmo_to_processes[vmo_koid].insert(process_koid);
+ s.vmos_.insert(vmo_koid);
+ auto const& vmo = capture.vmo_for_koid(vmo_koid);
+ vmo_koid = vmo.parent_koid;
+ } while (vmo_koid);
+ }
+ process_summaries_.push_back(s);
+ }
+
+ for (auto& s: process_summaries_) {
+ for (auto const& v : s.vmos_) {
+ auto const& vmo = capture.vmo_for_koid(v);
+ auto share_count = vmo_to_processes[v].size();
+ auto& name_sizes = s.name_to_sizes_[vmo.name];
+ name_sizes.total_bytes += vmo.committed_bytes;
+ s.sizes_.total_bytes += vmo.committed_bytes;
+ if (share_count == 1) {
+ name_sizes.private_bytes += vmo.committed_bytes;
+ s.sizes_.private_bytes += vmo.committed_bytes;
+ name_sizes.scaled_bytes += vmo.committed_bytes;
+ s.sizes_.scaled_bytes += vmo.committed_bytes;
+ } else {
+ auto scaled_bytes = vmo.committed_bytes / share_count;
+ name_sizes.scaled_bytes += scaled_bytes;
+ s.sizes_.scaled_bytes += scaled_bytes;
+ }
+ }
+ }
+}
+
+const Sizes& ProcessSummary::GetSizes(std::string name) const {
+ return name_to_sizes_.at(name);
+}
+
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/summary.h b/garnet/bin/memory_monitor/summary.h
new file mode 100644
index 0000000..3f20067
--- /dev/null
+++ b/garnet/bin/memory_monitor/summary.h
@@ -0,0 +1,66 @@
+// Copyright 2019 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.
+
+#ifndef GARNET_BIN_MEMORY_MONITOR_SUMMARY_H_
+#define GARNET_BIN_MEMORY_MONITOR_SUMMARY_H_
+
+#include <string>
+#include <vector>
+#include <unordered_map>
+#include <unordered_set>
+#include <zircon/types.h>
+
+#include "garnet/bin/memory_monitor/capture.h"
+
+namespace memory {
+
+struct Sizes {
+ Sizes() : private_bytes(0), scaled_bytes(0), total_bytes(0) {}
+ uint64_t private_bytes;
+ uint64_t scaled_bytes;
+ uint64_t total_bytes;
+};
+
+class ProcessSummary {
+ public:
+ zx_koid_t koid() const { return koid_; }
+ std::string name() const { return name_; }
+ Sizes sizes() const { return sizes_; }
+ const std::unordered_map<std::string, Sizes>& name_to_sizes() const {
+ return name_to_sizes_;
+ }
+ const Sizes& GetSizes(std::string name) const;
+
+ private:
+ ProcessSummary(zx_koid_t koid, std::string name) : koid_(koid), name_(name) {}
+
+ zx_koid_t koid_;
+ std::string name_;
+ Sizes sizes_;
+ std::unordered_set<zx_koid_t> vmos_;
+ std::unordered_map<std::string, Sizes> name_to_sizes_;
+
+ friend class Summary;
+};
+
+class Summary {
+ public:
+ Summary(const Capture& capture);
+
+ void SortProcessSummaries();
+ zx_time_t time() const { return time_; }
+ const zx_info_kmem_stats_t& kstats() const { return kstats_; }
+ const std::vector<ProcessSummary>& process_summaries() const {
+ return process_summaries_;
+ }
+
+ private:
+ zx_time_t time_;
+ zx_info_kmem_stats_t kstats_;
+ std::vector<ProcessSummary> process_summaries_;
+};
+
+} // namespace memory
+
+#endif // GARNET_BIN_MEMORY_MONITOR_MEMORY_SUMMARY_H_
diff --git a/garnet/bin/memory_monitor/test/BUILD.gn b/garnet/bin/memory_monitor/test/BUILD.gn
index e7379a9..ee5a602 100644
--- a/garnet/bin/memory_monitor/test/BUILD.gn
+++ b/garnet/bin/memory_monitor/test/BUILD.gn
@@ -7,23 +7,22 @@
group("test") {
testonly = true
- deps = [
- ":memory_monitor_tests",
- ":memory_monitor_unittests",
- ]
+ deps = [":memory_monitor_tests"]
}
executable("memory_monitor_unittests") {
testonly = true
-
output_name = "memory_monitor_unittests"
-
sources = [
"monitor_unittest.cc",
+ "printer_unittest.cc",
+ "summary_unittest.cc",
+ "test_utils.cc",
+ "test_utils.h",
]
deps = [
- "//garnet/bin/memory_monitor:lib",
+ "//garnet/bin/memory_monitor:mon_lib",
"//garnet/public/lib/gtest",
"//sdk/lib/sys/cpp/testing:unit",
"//src/lib/fxl/test:gtest_main",
@@ -31,9 +30,7 @@
}
test_package("memory_monitor_tests") {
- deps = [
- ":memory_monitor_unittests",
- ]
+ deps = [":memory_monitor_unittests"]
tests = [
{
diff --git a/garnet/bin/memory_monitor/test/monitor_unittest.cc b/garnet/bin/memory_monitor/test/monitor_unittest.cc
index a127fd3..6b1bea0 100644
--- a/garnet/bin/memory_monitor/test/monitor_unittest.cc
+++ b/garnet/bin/memory_monitor/test/monitor_unittest.cc
@@ -1,14 +1,13 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Copyright 2019 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.
#include "garnet/bin/memory_monitor/monitor.h"
+#include <gtest/gtest.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
-#include "gtest/gtest.h"
-
namespace memory {
namespace test {
diff --git a/garnet/bin/memory_monitor/test/printer_unittest.cc b/garnet/bin/memory_monitor/test/printer_unittest.cc
new file mode 100644
index 0000000..437e632
--- /dev/null
+++ b/garnet/bin/memory_monitor/test/printer_unittest.cc
@@ -0,0 +1,245 @@
+// Copyright 2019 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.
+
+#include <gtest/gtest.h>
+#include <lib/gtest/test_loop_fixture.h>
+#include <src/lib/fxl/strings/string_view.h>
+#include <src/lib/fxl/strings/split_string.h>
+
+#include "garnet/bin/memory_monitor/capture.h"
+#include "garnet/bin/memory_monitor/printer.h"
+#include "garnet/bin/memory_monitor/summary.h"
+#include "garnet/bin/memory_monitor/test/test_utils.h"
+
+namespace memory {
+namespace test {
+
+using PrinterUnitTest = gtest::TestLoopFixture;
+
+void ConfirmLines(std::ostringstream& oss,
+ std::vector<std::string> expected_lines) {
+ SCOPED_TRACE("");
+ fxl::StringView lines_view(oss.str());
+ auto lines = fxl::SplitStringCopy(
+ lines_view, "\n", fxl::kKeepWhitespace, fxl::kSplitWantNonEmpty);
+ ASSERT_EQ(expected_lines.size(), lines.size());
+ for (size_t li = 0; li < expected_lines.size(); li++) {
+ SCOPED_TRACE(li);
+ std::string expected_line = expected_lines.at(li);
+ fxl::StringView line = lines.at(li);
+ EXPECT_STREQ(expected_line.c_str(), line.data());
+ }
+}
+
+TEST_F(PrinterUnitTest, PrintCaptureKMEM) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234,
+ .kmem = {
+ .total_bytes = 300,
+ .free_bytes = 100,
+ .wired_bytes = 10,
+ .total_heap_bytes = 20,
+ .free_heap_bytes = 30,
+ .vmo_bytes = 40,
+ .mmu_overhead_bytes = 50,
+ .ipc_bytes = 60,
+ .other_bytes = 70
+ },
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1}},
+ },
+ });
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.PrintCapture(c, KMEM, SORTED);
+ ConfirmLines(oss, {
+ "K,1234,300,100,10,20,30,40,50,60,70"
+ });
+}
+
+TEST_F(PrinterUnitTest, PrintCapturePROCESS) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234,
+ .kmem = {
+ .total_bytes = 300,
+ .free_bytes = 100,
+ .wired_bytes = 10,
+ .total_heap_bytes = 20,
+ .free_heap_bytes = 30,
+ .vmo_bytes = 40,
+ .mmu_overhead_bytes = 50,
+ .ipc_bytes = 60,
+ .other_bytes = 70
+ },
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1}, .stats = {10, 20, 30, 40}},
+ },
+ });
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.PrintCapture(c, PROCESS, SORTED);
+ ConfirmLines(oss, {
+ "K,1234,300,100,10,20,30,40,50,60,70",
+ "P,100,p1,10,20,30,40,1"
+ });
+}
+
+TEST_F(PrinterUnitTest, PrintCaptureVMO) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234,
+ .kmem = {
+ .total_bytes = 300,
+ .free_bytes = 100,
+ .wired_bytes = 10,
+ .total_heap_bytes = 20,
+ .free_heap_bytes = 30,
+ .vmo_bytes = 40,
+ .mmu_overhead_bytes = 50,
+ .ipc_bytes = 60,
+ .other_bytes = 70,
+ },
+ .vmos = {
+ {
+ .koid = 1,
+ .name = "v1",
+ .size_bytes = 100,
+ .parent_koid = 200,
+ .committed_bytes = 300,
+ },
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1}, .stats = {10, 20, 30, 40}},
+ },
+ });
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.PrintCapture(c, VMO, SORTED);
+ ConfirmLines(oss, {
+ "K,1234,300,100,10,20,30,40,50,60,70",
+ "P,100,p1,10,20,30,40,1",
+ "V,1,v1,100,200,300",
+ });
+}
+
+TEST_F(PrinterUnitTest, OutputSummarySingle) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234L * 1000000000L,
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1}},
+ },
+ });
+ Summary s(c);
+
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.OutputSummary(s, SORTED, ZX_KOID_INVALID);
+ ConfirmLines(oss, {
+ "1234,100,p1,100,100,100",
+ });
+
+ oss.str("");
+ p.OutputSummary(s, SORTED, 100);
+ ConfirmLines(oss, {
+ "1234,100,v1,100,100,100",
+ });
+}
+
+TEST_F(PrinterUnitTest, OutputSummaryDouble) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234L * 1000000000L,
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v2", .committed_bytes = 200},
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1}},
+ {.koid = 200, .name = "p2", .vmos = {2}},
+ },
+ });
+ Summary s(c);
+
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.OutputSummary(s, SORTED, ZX_KOID_INVALID);
+ ConfirmLines(oss, {
+ "1234,200,p2,200,200,200",
+ "1234,100,p1,100,100,100",
+ });
+
+ oss.str("");
+ p.OutputSummary(s, SORTED, 100);
+ ConfirmLines(oss, {
+ "1234,100,v1,100,100,100",
+ });
+
+ oss.str("");
+ p.OutputSummary(s, SORTED, 200);
+ ConfirmLines(oss, {
+ "1234,200,v2,200,200,200",
+ });
+}
+
+TEST_F(PrinterUnitTest, OutputSummaryShared) {
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .time = 1234L * 1000000000L,
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v1", .committed_bytes = 100},
+ {.koid = 3, .name = "v1", .committed_bytes = 100},
+ {.koid = 4, .name = "v2", .committed_bytes = 100},
+ {.koid = 5, .name = "v3", .committed_bytes = 200},
+ },
+ .processes = {
+ {.koid = 100, .name = "p1", .vmos = {1,2,4}},
+ {.koid = 200, .name = "p2", .vmos = {2,3,5}},
+ },
+ });
+ Summary s(c);
+
+ std::ostringstream oss;
+ Printer p(oss);
+
+ p.OutputSummary(s, SORTED, ZX_KOID_INVALID);
+ ConfirmLines(oss, {
+ "1234,200,p2,300,350,400",
+ "1234,100,p1,200,250,300",
+ });
+
+ oss.str("");
+ p.OutputSummary(s, SORTED, 100);
+ ConfirmLines(oss, {
+ "1234,100,v1,100,150,200",
+ "1234,100,v2,100,100,100",
+ });
+
+ oss.str("");
+ p.OutputSummary(s, SORTED, 200);
+ ConfirmLines(oss, {
+ "1234,200,v3,200,200,200",
+ "1234,200,v1,100,150,200",
+ });
+}
+
+} // namespace test
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/test/summary_unittest.cc b/garnet/bin/memory_monitor/test/summary_unittest.cc
new file mode 100644
index 0000000..555c9fc
--- /dev/null
+++ b/garnet/bin/memory_monitor/test/summary_unittest.cc
@@ -0,0 +1,286 @@
+// Copyright 2019 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.
+
+#include <gtest/gtest.h>
+#include <lib/gtest/test_loop_fixture.h>
+
+#include "garnet/bin/memory_monitor/capture.h"
+#include "garnet/bin/memory_monitor/summary.h"
+#include "garnet/bin/memory_monitor/test/test_utils.h"
+
+namespace memory {
+namespace test {
+
+using SummaryUnitTest = gtest::TestLoopFixture;
+
+TEST_F(SummaryUnitTest, Single) {
+ // One process, one vmo.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1}},
+ },
+ });
+ Summary s(c);
+ ASSERT_EQ(1U, s.process_summaries().size());
+
+ ProcessSummary ps = s.process_summaries().at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, TwoVmos) {
+ // One process, two vmos with same name.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1, 2}},
+ },
+ });
+ Summary s(c);
+ ASSERT_EQ(1U, s.process_summaries().size());
+
+ ProcessSummary ps = s.process_summaries().at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(200U, sizes.private_bytes);
+ EXPECT_EQ(200U, sizes.scaled_bytes);
+ EXPECT_EQ(200U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(200U, sizes.private_bytes);
+ EXPECT_EQ(200U, sizes.scaled_bytes);
+ EXPECT_EQ(200U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, TwoVmoNames) {
+ // One process, two vmos with different names.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v2", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1, 2}}
+ },
+ });
+ Summary s(c);
+ ASSERT_EQ(1U, s.process_summaries().size());
+
+ ProcessSummary ps = s.process_summaries().at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(200U, sizes.private_bytes);
+ EXPECT_EQ(200U, sizes.scaled_bytes);
+ EXPECT_EQ(200U, sizes.total_bytes);
+
+ EXPECT_EQ(2U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+ sizes = ps.GetSizes("v2");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, Parent) {
+ // One process, two vmos with different names, one is child.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v2", .committed_bytes = 100, .parent_koid = 1},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {2}}
+ },
+ });
+ Summary s(c);
+ ASSERT_EQ(1U, s.process_summaries().size());
+
+ ProcessSummary ps = s.process_summaries().at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(200U, sizes.private_bytes);
+ EXPECT_EQ(200U, sizes.scaled_bytes);
+ EXPECT_EQ(200U, sizes.total_bytes);
+
+ EXPECT_EQ(2U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+ sizes = ps.GetSizes("v2");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, TwoProcesses) {
+ // Two processes, with different vmos.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v2", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1}},
+ {.koid = 2, .name = "p2", .vmos = {2}},
+ }
+ });
+ Summary s(c);
+ auto process_summaries = TestUtils::GetProcessSummaries(s);
+ ASSERT_EQ(2U, process_summaries.size());
+
+ ProcessSummary ps = process_summaries.at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ ps = process_summaries.at(1);
+ EXPECT_EQ(2U, ps.koid());
+ EXPECT_STREQ("p2", ps.name().c_str());
+ sizes = ps.sizes();
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v2");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, TwoProcessesShared) {
+ // Two processes, with same vmos.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1}},
+ {.koid = 2, .name = "p2", .vmos = {1}},
+ }
+ });
+ Summary s(c);
+ auto process_summaries = TestUtils::GetProcessSummaries(s);
+ ASSERT_EQ(2U, process_summaries.size());
+
+ ProcessSummary ps = process_summaries.at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ ps = process_summaries.at(1);
+ EXPECT_EQ(2U, ps.koid());
+ EXPECT_STREQ("p2", ps.name().c_str());
+ sizes = ps.sizes();
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+TEST_F(SummaryUnitTest, TwoProcessesChild) {
+ // Two processes, with one vmo shared through parantage.
+ Capture c;
+ TestUtils::CreateCapture(c, {
+ .vmos = {
+ {.koid = 1, .name = "v1", .committed_bytes = 100},
+ {.koid = 2, .name = "v2", .committed_bytes = 100, .parent_koid = 1},
+ },
+ .processes = {
+ {.koid = 1, .name = "p1", .vmos = {1}},
+ {.koid = 2, .name = "p2", .vmos = {2}},
+ }
+ });
+ Summary s(c);
+ auto process_summaries = TestUtils::GetProcessSummaries(s);
+ ASSERT_EQ(2U, process_summaries.size());
+
+ ProcessSummary ps = process_summaries.at(0);
+ EXPECT_EQ(1U, ps.koid());
+ EXPECT_STREQ("p1", ps.name().c_str());
+ Sizes sizes = ps.sizes();
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ EXPECT_EQ(1U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+
+ ps = process_summaries.at(1);
+ EXPECT_EQ(2U, ps.koid());
+ EXPECT_STREQ("p2", ps.name().c_str());
+ sizes = ps.sizes();
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(150U, sizes.scaled_bytes);
+ EXPECT_EQ(200U, sizes.total_bytes);
+
+ EXPECT_EQ(2U, ps.name_to_sizes().size());
+ sizes = ps.GetSizes("v1");
+ EXPECT_EQ(0U, sizes.private_bytes);
+ EXPECT_EQ(50U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+ sizes = ps.GetSizes("v2");
+ EXPECT_EQ(100U, sizes.private_bytes);
+ EXPECT_EQ(100U, sizes.scaled_bytes);
+ EXPECT_EQ(100U, sizes.total_bytes);
+}
+
+} // namespace test
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/test/test_utils.cc b/garnet/bin/memory_monitor/test/test_utils.cc
new file mode 100644
index 0000000..2bff0f6
--- /dev/null
+++ b/garnet/bin/memory_monitor/test/test_utils.cc
@@ -0,0 +1,32 @@
+// Copyright 2019 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.
+
+#include "garnet/bin/memory_monitor/test/test_utils.h"
+
+namespace memory {
+
+// static.
+void TestUtils::CreateCapture(memory::Capture& capture,
+ const CaptureTemplate& t) {
+ capture.time_ = t.time;
+ capture.kmem_= t.kmem;
+ for (auto vmo : t.vmos) {
+ capture.koid_to_vmo_.emplace(vmo.koid, vmo);
+ }
+ for (auto process : t.processes) {
+ capture.koid_to_process_.emplace(process.koid, process);
+ }
+}
+
+// static.
+std::vector<ProcessSummary> TestUtils::GetProcessSummaries(
+ const Summary& summary) {
+ std::vector<ProcessSummary> summaries = summary.process_summaries();
+ sort(summaries.begin(), summaries.end(),
+ [](ProcessSummary a, ProcessSummary b) {
+ return a.koid() < b.koid();
+ });
+ return summaries;
+}
+} // namespace memory
diff --git a/garnet/bin/memory_monitor/test/test_utils.h b/garnet/bin/memory_monitor/test/test_utils.h
new file mode 100644
index 0000000..d17d15d
--- /dev/null
+++ b/garnet/bin/memory_monitor/test/test_utils.h
@@ -0,0 +1,28 @@
+// Copyright 2019 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.
+
+#include <vector>
+
+#include "garnet/bin/memory_monitor/capture.h"
+#include "garnet/bin/memory_monitor/summary.h"
+
+namespace memory {
+
+struct CaptureTemplate {
+ zx_time_t time;
+ zx_info_kmem_stats_t kmem;
+ std::vector<zx_info_vmo_t> vmos;
+ std::vector<Process> processes;
+};
+
+class TestUtils {
+ public:
+ static void CreateCapture(memory::Capture& capture, const CaptureTemplate& t);
+
+ // Sorted by koid.
+ static std::vector<ProcessSummary> GetProcessSummaries(
+ const Summary& summary);
+};
+
+} // namespace memory
diff --git a/garnet/packages/prod/BUILD.gn b/garnet/packages/prod/BUILD.gn
index a4b08c7..00fa99e 100644
--- a/garnet/packages/prod/BUILD.gn
+++ b/garnet/packages/prod/BUILD.gn
@@ -360,6 +360,7 @@
testonly = true
public_deps = [
"//garnet/bin/memory_monitor",
+ "//garnet/bin/memory_monitor:mem",
"//garnet/bin/memory_monitor:config",
"//garnet/packages/config:services",
]