[ledger][benchmark] Add first memory usage benchmark.

Test: put_memory.tspec

This CL introduces LedgerMemoryEstimator, providing an API to capture
Ledger's memory usage. This API is used in this CL to measure the
memory usage after every insertion in the `put` benchmark.

Change-Id: I2822f649e1382875b21327f3d5dbf215eac94fd4
diff --git a/peridot/tests/benchmarks/benchmarks.cc b/peridot/tests/benchmarks/benchmarks.cc
index 53e3127..7b64fd0 100644
--- a/peridot/tests/benchmarks/benchmarks.cc
+++ b/peridot/tests/benchmarks/benchmarks.cc
@@ -45,6 +45,7 @@
   benchmarks_runner.AddTspecBenchmark("ledger.disk_space_updates", "/pkgfs/packages/ledger_benchmarks/0/data/disk_space_updates.tspec");
   benchmarks_runner.AddTspecBenchmark("ledger.disk_space_one_commit_per_entry", "/pkgfs/packages/ledger_benchmarks/0/data/disk_space_one_commit_per_entry.tspec");
   benchmarks_runner.AddTspecBenchmark("ledger.disk_space_cleared_page", "/pkgfs/packages/ledger_benchmarks/0/data/disk_space_cleared_page.tspec");
+  benchmarks_runner.AddTspecBenchmark("ledger.put_memory", "/pkgfs/packages/ledger_benchmarks/0/data/put_memory.tspec");
   benchmarks_runner.AddTspecBenchmark("modular.story_runner.json", "/pkgfs/packages/modular_benchmarks/0/data/modular_benchmark_story.tspec", "fuchsia.modular");
   // clang-format on
 
diff --git a/src/ledger/bin/testing/BUILD.gn b/src/ledger/bin/testing/BUILD.gn
index 822df1f..386d1bd 100644
--- a/src/ledger/bin/testing/BUILD.gn
+++ b/src/ledger/bin/testing/BUILD.gn
@@ -27,6 +27,8 @@
     "get_page_ensure_initialized.h",
     "ledger_matcher.cc",
     "ledger_matcher.h",
+    "ledger_memory_usage.cc",
+    "ledger_memory_usage.h",
     "page_data_generator.cc",
     "page_data_generator.h",
     "quit_on_error.cc",
@@ -61,7 +63,9 @@
     "//third_party/googletest:gtest",
     "//third_party/rapidjson",
     "//zircon/public/lib/fit",
+    "//zircon/public/lib/task-utils",
     "//zircon/public/lib/trace-provider",
+    "//zircon/public/lib/zx",
   ]
 
   deps = [
diff --git a/src/ledger/bin/testing/ledger_memory_usage.cc b/src/ledger/bin/testing/ledger_memory_usage.cc
new file mode 100644
index 0000000..a84ac04
--- /dev/null
+++ b/src/ledger/bin/testing/ledger_memory_usage.cc
@@ -0,0 +1,117 @@
+// 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 "src/ledger/bin/testing/ledger_memory_usage.h"
+
+#include <lib/zx/object.h>
+#include <lib/zx/process.h>
+#include <src/lib/fxl/logging.h>
+#include <src/lib/fxl/strings/string_view.h>
+#include <task-utils/walker.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+
+#include <string>
+
+namespace ledger {
+namespace {
+
+constexpr fxl::StringView kLedgerBinaryName = "ledger.cmx";
+
+// Retrieves the name of the task with the given handle. Returns true on
+// success, or false otherwise.
+bool GetTaskName(zx::unowned<zx::process>& task, std::string* name) {
+  char task_name[ZX_MAX_NAME_LEN];
+  zx_status_t status =
+      task->get_property(ZX_PROP_NAME, task_name, ZX_MAX_NAME_LEN);
+  if (status != ZX_OK) {
+    // Failed to get the name of task.
+    return false;
+  }
+  *name = std::string(task_name);
+  return true;
+}
+
+// Retrieves the private bytes used by the given task. Returns true on success,
+// or false otherwise.
+bool GetMemoryUsageForTask(zx::process& task, uint64_t* memory) {
+  zx_info_task_stats_t info;
+  zx_status_t status =
+      task.get_info(ZX_INFO_TASK_STATS, &info, sizeof(info), nullptr, nullptr);
+  if (status != ZX_OK) {
+    return false;
+  }
+  *memory = info.mem_private_bytes;
+  return true;
+}
+
+// |Walker| is a |TaskEnumerator| used to find the Ledger process and the
+// corresponding handle.
+class Walker final : public TaskEnumerator {
+ public:
+  Walker() = default;
+  ~Walker() = default;
+
+  // TaskEnumerator:
+  zx_status_t OnProcess(int depth, zx_handle_t task, zx_koid_t koid,
+                        zx_koid_t /*pkoid*/) override {
+    zx::unowned<zx::process> unowned_task(task);
+    std::string process_name;
+    FXL_CHECK(GetTaskName(unowned_task, &process_name));
+    if (process_name == kLedgerBinaryName) {
+      if (ledger_handle_.is_valid()) {
+        // This is the second Ledger process we find: interrupt the iteration by
+        // returning a status different than |ZK_OK|.
+        return ZX_ERR_ALREADY_EXISTS;
+      }
+      // This process corresponds to Ledger.
+      FXL_CHECK(unowned_task->duplicate(ZX_RIGHT_SAME_RIGHTS,
+                                        &ledger_handle_) == ZX_OK);
+    }
+    return ZX_OK;
+  }
+
+  // Returns the handle of the Ledger process, or an invalid handle if it was
+  // not found. This method should be called only after a successful termination
+  // of |Walker::WalkRootJobTree()|. The caller takes ownership of the returned
+  // handle, meaning that this method can only be called once.
+  zx::process TakeLedgerHandle() { return std::move(ledger_handle_); }
+
+ protected:
+  // TaskEnumerator:
+  bool has_on_process() const override { return true; }
+
+ private:
+  zx::process ledger_handle_;
+};
+
+}  // namespace
+
+LedgerMemoryEstimator::LedgerMemoryEstimator() = default;
+LedgerMemoryEstimator::~LedgerMemoryEstimator() = default;
+
+bool LedgerMemoryEstimator::Init() {
+  FXL_DCHECK(!ledger_task_.is_valid()) << "Init should only be called once";
+  Walker walker;
+  zx_status_t status = walker.WalkRootJobTree();
+  if (status == ZX_ERR_ALREADY_EXISTS) {
+    // TODO(nellyv): Update so that we know how which Ledger corresponds to the
+    // test being executed.
+    FXL_LOG(ERROR) << "More than one Ledger processes are running.";
+    return false;
+  }
+  ledger_task_ = walker.TakeLedgerHandle();
+  if (!ledger_task_.is_valid()) {
+    FXL_LOG(ERROR) << "Failed to find a Ledger process.";
+    return false;
+  }
+  return true;
+}
+
+bool LedgerMemoryEstimator::GetLedgerMemoryUsage(uint64_t* memory) {
+  FXL_CHECK(ledger_task_);
+  return GetMemoryUsageForTask(ledger_task_, memory);
+}
+
+}  // namespace ledger
diff --git a/src/ledger/bin/testing/ledger_memory_usage.h b/src/ledger/bin/testing/ledger_memory_usage.h
new file mode 100644
index 0000000..ddb5151
--- /dev/null
+++ b/src/ledger/bin/testing/ledger_memory_usage.h
@@ -0,0 +1,39 @@
+// 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 SRC_LEDGER_BIN_TESTING_LEDGER_MEMORY_USAGE_H_
+#define SRC_LEDGER_BIN_TESTING_LEDGER_MEMORY_USAGE_H_
+
+#include <lib/fit/function.h>
+#include <lib/zx/object.h>
+#include <lib/zx/process.h>
+
+namespace ledger {
+
+// Allows estimating Ledger's memory usage. Assumes there is a single ledger
+// process running throughout the lifetime of a |LedgerMemoryEstimator| object.
+class LedgerMemoryEstimator {
+ public:
+  LedgerMemoryEstimator();
+  ~LedgerMemoryEstimator();
+
+  // Initializes the |LedgerMemoryEstimator|. This must be called before any
+  // call to |GetLedgerMemoryUsage|. Ledger must already be running before
+  // |Init()| is called.
+  bool Init();
+
+  // Updates |memory| to store the memory usage, in bytes, of the Ledger binary.
+  // This only includes the private bytes, not the shared memory. Returns true
+  // on success; false otherwise.
+  // Note that a successfull call to |Init()| must be made before calling this
+  // method.
+  bool GetLedgerMemoryUsage(uint64_t* memory);
+
+ private:
+  zx::process ledger_task_;
+};
+
+}  // namespace ledger
+
+#endif  // SRC_LEDGER_BIN_TESTING_LEDGER_MEMORY_USAGE_H_
diff --git a/src/ledger/bin/tests/benchmark/BUILD.gn b/src/ledger/bin/tests/benchmark/BUILD.gn
index 327a8e4..c259be1 100644
--- a/src/ledger/bin/tests/benchmark/BUILD.gn
+++ b/src/ledger/bin/tests/benchmark/BUILD.gn
@@ -272,6 +272,11 @@
     },
 
     {
+      path = rebase_path("put/put_memory.tspec")
+      dest = "put_memory.tspec"
+    },
+
+    {
       path = rebase_path("put/put_as_reference.tspec")
       dest = "put_as_reference.tspec"
     },
diff --git a/src/ledger/bin/tests/benchmark/README.md b/src/ledger/bin/tests/benchmark/README.md
index 5c8af85..e31c0ac 100644
--- a/src/ledger/bin/tests/benchmark/README.md
+++ b/src/ledger/bin/tests/benchmark/README.md
@@ -100,6 +100,8 @@
     * `put_as_reference.tspec`: entries are put as references (CreateReference +
       PutReference)
     * `transaction.tspec`: changes are made in a transaction
+    * `put_memory.tspec`: how much memory is Ledger using after every insertion
+      in the basic case?
 * __Update entry__: How long does it take to update an existing value in Ledger
   (make several Put operations with the same key, but different values)?
     * `update_entry.tspec`: basic case
diff --git a/src/ledger/bin/tests/benchmark/put/put.cc b/src/ledger/bin/tests/benchmark/put/put.cc
index f9555dd..d6b28e0 100644
--- a/src/ledger/bin/tests/benchmark/put/put.cc
+++ b/src/ledger/bin/tests/benchmark/put/put.cc
@@ -19,6 +19,7 @@
 #include "src/ledger/bin/testing/data_generator.h"
 #include "src/ledger/bin/testing/get_ledger.h"
 #include "src/ledger/bin/testing/get_page_ensure_initialized.h"
+#include "src/ledger/bin/testing/ledger_memory_usage.h"
 #include "src/ledger/bin/testing/page_data_generator.h"
 #include "src/ledger/bin/testing/quit_on_error.h"
 #include "src/ledger/bin/testing/run_with_tracing.h"
@@ -119,6 +120,7 @@
   // Whether all expected watch notifications have been received. Shut down
   // should be blocked until this is set to true.
   bool all_watcher_notifications_received_ = false;
+  LedgerMemoryEstimator memory_estimator_;
 
   FXL_DISALLOW_COPY_AND_ASSIGN(PutBenchmark);
 };
@@ -167,6 +169,7 @@
   if (QuitOnError(QuitLoopClosure(), status, "GetLedger")) {
     return;
   }
+  FXL_CHECK(memory_estimator_.Init());
 
   GetPageEnsureInitialized(
       &ledger_, nullptr, DelayCallback::YES, QuitLoopClosure(),
@@ -266,6 +269,10 @@
   }
   PutEntry(std::move(keys[i]), std::move(value),
            [this, i, key_number, keys = std::move(keys)]() mutable {
+             uint64_t memory;
+             FXL_CHECK(memory_estimator_.GetLedgerMemoryUsage(&memory));
+             TRACE_COUNTER("benchmark", "ledger_memory_put", i, "memory",
+                           TA_UINT64(memory));
              if (transaction_size_ > 0 &&
                  (i % transaction_size_ == transaction_size_ - 1 ||
                   i + 1 == entry_count_)) {
diff --git a/src/ledger/bin/tests/benchmark/put/put.cmx b/src/ledger/bin/tests/benchmark/put/put.cmx
index 465d50a..f5d82fa 100644
--- a/src/ledger/bin/tests/benchmark/put/put.cmx
+++ b/src/ledger/bin/tests/benchmark/put/put.cmx
@@ -3,6 +3,7 @@
         "binary": "bin/ledger_benchmark_put"
     },
     "sandbox": {
+        "dev": [ "misc/sysinfo" ],
         "features": [
             "isolated-persistent-storage"
         ],
diff --git a/src/ledger/bin/tests/benchmark/put/put_memory.tspec b/src/ledger/bin/tests/benchmark/put/put_memory.tspec
new file mode 100644
index 0000000..2ff28e2
--- /dev/null
+++ b/src/ledger/bin/tests/benchmark/put/put_memory.tspec
@@ -0,0 +1,22 @@
+{
+  "test_suite_name": "fuchsia.ledger.put_entry",
+  "app": "fuchsia-pkg://fuchsia.com/ledger_benchmarks#meta/put.cmx",
+  "args": [
+    "--entry-count=100", "--transaction-size=0", "--key-size=100",
+    "--value-size=1000", "--refs=off"
+  ],
+  "categories": ["benchmark", "ledger"],
+  "duration": 60,
+  "measure": [
+    {
+      "type": "argument_value",
+      "output_test_name": "put_memory/memory",
+      "event_name": "ledger_memory_put",
+      "event_category": "benchmark",
+      "argument_name": "memory",
+      "argument_unit": "bytes",
+      "expected_sample_count": 100,
+      "split_first": true
+    }
+  ]
+}