ml: Get memory usage from proc/[pid]/status

The previous code parses /proc/[pid]/totmaps to obtain the memory usage,
but this is deprecated and has been removed upstream (crbug.com/979781).
In the modification, we instead parse the /proc/[pid]/status and use
VmRSS+VmSwap as the memory usage.

The new method does not use the hand-coded indices so the result
should be more robust.

The new number reported will be a bit different from the previous one
because it will include the shared memory. This should be fine because
it is the relative sizes of the numbers that matter, rather the absolute
values.

BUG=chromium:979781
TEST=Added util_test.cc and ran emerge-pyro ml
TEST=Also ran cros_workon_make --board=$BOARD $PACKAGE_NAME --test
TEST=Confirmed it fixed the crash in crbug.com/979781 on device

Change-Id: I7fbfb6a1daa2db046bab9644d954404272c54330
Reviewed-on: https://chromium-review.googlesource.com/1692241
Tested-by: Honglin Yu <honglinyu@chromium.org>
Commit-Ready: Honglin Yu <honglinyu@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Andrew Moylan <amoylan@chromium.org>
diff --git a/ml/BUILD.gn b/ml/BUILD.gn
index 4056de0..dd31b1a 100644
--- a/ml/BUILD.gn
+++ b/ml/BUILD.gn
@@ -51,6 +51,7 @@
     "model_impl.cc",
     "model_metadata.cc",
     "tensor_view.cc",
+    "util.cc",
   ]
 }
 
@@ -79,6 +80,7 @@
       "model_impl_test.cc",
       "test_utils.cc",
       "testrunner.cc",
+      "util_test.cc",
     ]
   }
 }
diff --git a/ml/README.md b/ml/README.md
index c74b6d7..953932a 100644
--- a/ml/README.md
+++ b/ml/README.md
@@ -29,10 +29,10 @@
 
 * MachineLearningService.MojoConnectionEvent: Success/failure of the
   D-Bus->Mojo bootstrap.
-* MachineLearningService.PrivateMemoryKb: Private (unshared) memory footprint
+* MachineLearningService.TotalMemoryKb: Total (shared+unshared) memory footprint
   every 5 minutes.
-* MachineLearningService.PeakPrivateMemoryKb: Peak value of
-  MachineLearningService.PrivateMemoryKb per 24 hour period. Daemon code can
+* MachineLearningService.PeakTotalMemoryKb: Peak value of
+  MachineLearningService.TotalMemoryKb per 24 hour period. Daemon code can
   also call ml::Metrics::UpdateCumulativeMetricsNow() at any time to take a
   peak-memory observation, to catch short-lived memory usage spikes.
 * MachineLearningService.CpuUsageMilliPercent: Fraction of total CPU resources
@@ -42,7 +42,7 @@
 request:
 
 * MachineLearningService.|request|.Event: OK/ErrorType of the request.
-* MachineLearningService.|request|.PrivateMemoryDeltaKb: Private (unshared)
+* MachineLearningService.|request|.TotalMemoryDeltaKb: Total (shared+unshared)
   memory delta caused by the request.
 * MachineLearningService.|request|.ElapsedTimeMicrosec: Time cost of the
   request.
diff --git a/ml/metrics.cc b/ml/metrics.cc
index 54c08a9..65c6403 100644
--- a/ml/metrics.cc
+++ b/ml/metrics.cc
@@ -13,6 +13,8 @@
 #include <base/sys_info.h>
 #include <base/time/time.h>
 
+#include "ml/util.h"
+
 namespace ml {
 
 namespace {
@@ -22,10 +24,10 @@
     "MachineLearningService.CpuUsageMilliPercent";
 constexpr char kMojoConnectionEventMetricName[] =
     "MachineLearningService.MojoConnectionEvent";
-constexpr char kPrivateMemoryMetricName[] =
-    "MachineLearningService.PrivateMemoryKb";
-constexpr char kPeakPrivateMemoryMetricName[] =
-    "MachineLearningService.PeakPrivateMemoryKb";
+constexpr char kTotalMemoryMetricName[] =
+    "MachineLearningService.TotalMemoryKb";
+constexpr char kPeakTotalMemoryMetricName[] =
+    "MachineLearningService.PeakTotalMemoryKb";
 
 // UMA histogram ranges:
 constexpr int kCpuUsageMinMilliPercent = 1;       // 0.001%
@@ -37,8 +39,8 @@
 
 // chromeos_metrics::CumulativeMetrics constants:
 constexpr char kCumulativeMetricsBackingDir[] = "/var/lib/ml_service/metrics";
-constexpr char kPeakPrivateMemoryCumulativeStatName[] =
-    "peak_private_memory_kb";
+constexpr char kPeakTotalMemoryCumulativeStatName[] =
+    "peak_total_memory_kb";
 
 constexpr base::TimeDelta kCumulativeMetricsUpdatePeriod =
     base::TimeDelta::FromMinutes(5);
@@ -49,8 +51,8 @@
     MetricsLibrary* const metrics_library,
     chromeos_metrics::CumulativeMetrics* const cumulative_metrics) {
   metrics_library->SendToUMA(
-      kPeakPrivateMemoryMetricName,
-      cumulative_metrics->Get(kPeakPrivateMemoryCumulativeStatName),
+      kPeakTotalMemoryMetricName,
+      cumulative_metrics->Get(kPeakTotalMemoryCumulativeStatName),
       kMemoryUsageMinKb, kMemoryUsageMaxKb, kMemoryUsageBuckets);
 }
 
@@ -70,7 +72,7 @@
 
   cumulative_metrics_ = std::make_unique<chromeos_metrics::CumulativeMetrics>(
       base::FilePath(kCumulativeMetricsBackingDir),
-      std::vector<std::string>{kPeakPrivateMemoryCumulativeStatName},
+      std::vector<std::string>{kPeakTotalMemoryCumulativeStatName},
       kCumulativeMetricsUpdatePeriod,
       base::Bind(&Metrics::UpdateAndRecordMetrics, base::Unretained(this),
                  true /*record_current_metrics*/),
@@ -90,14 +92,15 @@
 void Metrics::UpdateAndRecordMetrics(
     const bool record_current_metrics,
     chromeos_metrics::CumulativeMetrics* const cumulative_metrics) {
-
-  // Query memory usage.
-  base::WorkingSetKBytes usage;
-  process_metrics_->GetWorkingSetKBytes(&usage);
+  size_t usage = 0;
+  if (!GetTotalProcessMemoryUsage(&usage)) {
+    LOG(DFATAL) << "Getting process memory usage failed";
+    return;
+  }
 
   // Update max memory stats.
-  cumulative_metrics->Max(kPeakPrivateMemoryCumulativeStatName,
-                          static_cast<int64_t>(usage.priv));
+  cumulative_metrics->Max(kPeakTotalMemoryCumulativeStatName,
+                          static_cast<int64_t>(usage));
 
   if (record_current_metrics) {
     // Record CPU usage (units = milli-percent i.e. 0.001%):
@@ -108,7 +111,7 @@
                                kCpuUsageMinMilliPercent,
                                kCpuUsageMaxMilliPercent, kCpuUsageBuckets);
     // Record memory usage:
-    metrics_library_.SendToUMA(kPrivateMemoryMetricName, usage.priv,
+    metrics_library_.SendToUMA(kTotalMemoryMetricName, usage,
                                kMemoryUsageMinKb, kMemoryUsageMaxKb,
                                kMemoryUsageBuckets);
   }
diff --git a/ml/request_metrics.h b/ml/request_metrics.h
index fea03d3..0dd7502 100644
--- a/ml/request_metrics.h
+++ b/ml/request_metrics.h
@@ -5,8 +5,6 @@
 #ifndef ML_REQUEST_METRICS_H_
 #define ML_REQUEST_METRICS_H_
 
-#include "metrics/timer.h"
-
 #include <algorithm>
 #include <memory>
 #include <string>
@@ -21,6 +19,9 @@
 #include <base/time/time.h>
 #include <metrics/metrics_library.h>
 
+#include "metrics/timer.h"
+#include "ml/util.h"
+
 namespace ml {
 
 // Performs UMA metrics logging for LoadModel, CreateGraphExecutor and Execute.
@@ -64,7 +65,7 @@
 // UMA metric names:
 constexpr char kGlobalMetricsPrefix[] = "MachineLearningService.";
 constexpr char kEventSuffix[] = ".Event";
-constexpr char kPrivateMemoryDeltaSuffix[] = ".PrivateMemoryDeltaKb";
+constexpr char kTotalMemoryDeltaSuffix[] = ".TotalMemoryDeltaKb";
 constexpr char kElapsedTimeSuffix[] = ".ElapsedTimeMicrosec";
 constexpr char kCpuTimeSuffix[] = ".CpuTimeMicrosec";
 
@@ -102,9 +103,12 @@
   process_metrics_->GetCPUUsage();
   timer_.Start();
   // Query memory usage.
-  base::WorkingSetKBytes usage;
-  process_metrics_->GetWorkingSetKBytes(&usage);
-  initial_memory_ = static_cast<int64_t>(usage.priv);
+  size_t usage = 0;
+  if (!GetTotalProcessMemoryUsage(&usage)) {
+    LOG(DFATAL) << "Getting process memory usage failed.";
+    return;
+  }
+  initial_memory_ = static_cast<int64_t>(usage);
 }
 
 template <class RequestEventEnum>
@@ -128,12 +132,15 @@
       static_cast<int64_t>(cpu_usage_percent * elapsed_time_microsec / 100.);
 
   // Memory usage
-  base::WorkingSetKBytes usage;
-  process_metrics_->GetWorkingSetKBytes(&usage);
+  size_t usage = 0;
+  if (!GetTotalProcessMemoryUsage(&usage)) {
+    LOG(DFATAL) << "Getting process memory usage failed.";
+    return;
+  }
   const int64_t memory_usage_kb =
-      static_cast<int64_t>(usage.priv) - initial_memory_;
+      static_cast<int64_t>(usage) - initial_memory_;
 
-  metrics_library_.SendToUMA(name_base_ + kPrivateMemoryDeltaSuffix,
+  metrics_library_.SendToUMA(name_base_ + kTotalMemoryDeltaSuffix,
                              memory_usage_kb,
                              kMemoryDeltaMinKb,
                              kMemoryDeltaMaxKb,
diff --git a/ml/util.cc b/ml/util.cc
new file mode 100644
index 0000000..73f598f
--- /dev/null
+++ b/ml/util.cc
@@ -0,0 +1,100 @@
+// Copyright 2019 The Chromium OS 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 "ml/util.h"
+
+#include <string>
+#include <vector>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/files/file_util.h>
+#include <base/process/process.h>
+
+namespace ml {
+
+namespace {
+
+// Extracts the value from a value string of /proc/[pid]/status.
+// Only works for value strings in the form of "value kB".
+// Returns true if value could be extracted, false otherwise.
+bool GetValueFromProcStatusValueStr(const std::string& value_str,
+                                    size_t* value) {
+  const std::vector<base::StringPiece> split_value_str = base::SplitStringPiece(
+      value_str, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  if (split_value_str.size() != 2 || split_value_str[1] != "kB")
+    return false;
+
+  return StringToSizeT(split_value_str[0], value);
+}
+
+}  // namespace
+
+bool MemoryUsage::operator==(const MemoryUsage& other) const {
+  return this->VmRSSKb == other.VmRSSKb && this->VmSwapKb == other.VmSwapKb;
+}
+
+bool GetProcessMemoryUsageFromFile(MemoryUsage* memory_usage,
+                                   const base::FilePath& file_path) {
+  std::string status_data;
+  if (!ReadFileToString(file_path, &status_data)) {
+    LOG(WARNING) << "Can not open status file";
+    return false;
+  }
+
+  base::StringPairs key_value_pairs;
+  base::SplitStringIntoKeyValuePairs(status_data, ':', '\n', &key_value_pairs);
+
+  bool vmrss_found = false;
+  bool vmswap_found = false;
+
+  for (auto& pair : key_value_pairs) {
+    std::string& key = pair.first;
+    std::string& value_str = pair.second;
+
+    base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key);
+
+    if (key == "VmRSS") {
+      if (vmrss_found)
+        return false;  // Duplicates should not happen.
+
+      base::TrimWhitespaceASCII(value_str, base::TRIM_ALL, &value_str);
+      if (!GetValueFromProcStatusValueStr(value_str, &memory_usage->VmRSSKb))
+        return false;
+      vmrss_found = true;
+    }
+    if (key == "VmSwap") {
+      if (vmswap_found)
+        return false;  // Duplicates should not happen.
+
+      base::TrimWhitespaceASCII(value_str, base::TRIM_ALL, &value_str);
+      if (!GetValueFromProcStatusValueStr(value_str, &memory_usage->VmSwapKb))
+        return false;
+      vmswap_found = true;
+    }
+  }
+
+  return vmrss_found && vmswap_found;
+}
+
+bool GetProcessMemoryUsage(MemoryUsage* memory_usage) {
+  const base::FilePath status_file_path =
+      base::FilePath("/proc")
+          .Append(base::IntToString(base::Process::Current().Pid()))
+          .Append("status");
+  return GetProcessMemoryUsageFromFile(memory_usage, status_file_path);
+}
+
+bool GetTotalProcessMemoryUsage(size_t* total_memory) {
+  MemoryUsage memory_usage;
+  if (GetProcessMemoryUsage(&memory_usage)) {
+    *total_memory = memory_usage.VmRSSKb + memory_usage.VmSwapKb;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace ml
diff --git a/ml/util.h b/ml/util.h
new file mode 100644
index 0000000..807ef1b
--- /dev/null
+++ b/ml/util.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium OS 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 ML_UTIL_H_
+#define ML_UTIL_H_
+
+#include <base/files/file_path.h>
+
+namespace ml {
+
+// The memory usage (typically of a process).
+// One can extend this struct to include more terms. Currently, it only
+// includes `VmSwap` and `VmRSS` to fulfill the needs.
+struct MemoryUsage {
+  size_t VmRSSKb;
+  size_t VmSwapKb;
+
+  bool operator==(const MemoryUsage& other) const;
+};
+
+// Gets the memory usage by parsing a file (typically `/proc/[pid]/status`)
+// This function assumes that the memory unit used in /proc/[pid]/status is
+// "kB".
+// Return true if successful, false otherwise.
+bool GetProcessMemoryUsageFromFile(MemoryUsage* memory_usage,
+                                   const base::FilePath& file_path);
+
+// Same as GetProcessMemoryUsageFromFile(memory_usage, "/prod/[pid]/status")
+// for the calling process's pid.
+// Return true if successful, false otherwise.
+bool GetProcessMemoryUsage(MemoryUsage* memory_usage);
+
+// Gets the total memory usage for this process, which we define as VmSwap+VmRSS
+// extracted from the /proc/pid/status file.
+// Return true if successful, false otherwise.
+bool GetTotalProcessMemoryUsage(size_t* total_memory);
+
+}  // namespace ml
+
+#endif  // ML_UTIL_H_
diff --git a/ml/util_test.cc b/ml/util_test.cc
new file mode 100644
index 0000000..a9a6ed2
--- /dev/null
+++ b/ml/util_test.cc
@@ -0,0 +1,241 @@
+// Copyright 2019 The Chromium OS 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 <limits>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/files/file_util.h>
+#include <brillo/file_utils.h>
+
+#include "ml/test_utils.h"
+#include "ml/util.h"
+
+namespace ml {
+namespace {
+
+// Represents a temp status file valid for the lifetime of this object.
+// The constructor creates a temp file named "status" in a temp folder and
+// writes |content| to that file.
+// Use GetPath() to obtain the path fo the temporary file.
+class ScopedTempStatusFile {
+ public:
+  explicit ScopedTempStatusFile(const std::string& content) {
+    CHECK(dir_.CreateUniqueTempDir());
+    file_path_ = dir_.GetPath().Append("status");
+    CHECK(brillo::WriteStringToFile(file_path_, content));
+  }
+  base::FilePath GetPath() { return file_path_; }
+
+ private:
+  base::ScopedTempDir dir_;
+  base::FilePath file_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedTempStatusFile);
+};
+
+// Status file does not exist.
+TEST(GetProcessMemoryUsageTest, InvalidFile) {
+  ScopedTempStatusFile status_file("");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(GetProcessMemoryUsageFromFile(
+      &memory_usage, status_file.GetPath().Append("nonexistfile")));
+}
+
+TEST(GetProcessMemoryUsageTest, EmptyFile) {
+  ScopedTempStatusFile status_file("");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, MissingVmSwap) {
+  ScopedTempStatusFile status_file("VmRSS: 3235 kB");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, MissingVmRSS) {
+  ScopedTempStatusFile status_file("VmSwap: 34213 kB");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, MissingBothValues) {
+  ScopedTempStatusFile status_file("VmRSS:  kB \n   VmSwap:  kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, MissingVmRSSValue) {
+  ScopedTempStatusFile status_file("VmRSS: kB \n   VmSwap: 421532 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, MissingVmSwapValue) {
+  ScopedTempStatusFile status_file("VmRSS: 32432 kB \n   VmSwap: kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, InvalidVmSwapValueNan) {
+  ScopedTempStatusFile status_file(
+      "VmRSS:  767234322 kB \n   VmSwap: nan kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, InvalidVmRSSValueNan) {
+  ScopedTempStatusFile status_file("VmRSS:  nan kB \n   VmSwap: 4214 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, Duplicate) {
+  ScopedTempStatusFile status_file(
+      "VmRSS:  432 kB \n   VmSwap: 421532 kB\n"
+      "VmRSS:  432 kB \n   VmSwap: 421532 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_FALSE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+}
+
+TEST(GetProcessMemoryUsageTest, ValidInputNonZeroValue) {
+  ScopedTempStatusFile status_file("VmRSS:  432 kB \n   VmSwap: 421532 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 432);
+  EXPECT_EQ(memory_usage.VmSwapKb, 421532);
+}
+
+TEST(GetProcessMemoryUsageTest, ValidInputZeroValue) {
+  ScopedTempStatusFile status_file("VmRSS:  0 kB \n   VmSwap: 0 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 0);
+  EXPECT_EQ(memory_usage.VmSwapKb, 0);
+}
+
+TEST(GetProcessMemoryUsageTest, ValidInputZeroLead) {
+  ScopedTempStatusFile status_file(
+      "VmRSS:    0242 kB \n   VmSwap:    03523 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 242);
+  EXPECT_EQ(memory_usage.VmSwapKb, 3523);
+}
+
+// Checks the maximum value of size_t. It may fail if treated as int32.
+TEST(GetProcessMemoryUsageTest, ValidInputMaxSizeT) {
+  constexpr size_t kSizeTMax = std::numeric_limits<size_t>::max();
+
+  ScopedTempStatusFile status_file(base::StringPrintf(
+      "VmRSS:   %zu kB\nVmSwap:    %zu kB\n", kSizeTMax, kSizeTMax));
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, kSizeTMax);
+  EXPECT_EQ(memory_usage.VmSwapKb, kSizeTMax);
+}
+
+TEST(GetProcessMemoryUsageTest, OrderChanged) {
+  ScopedTempStatusFile status_file(
+      "VmSwap:       34 kB\nVmRSS:        123 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 123);
+  EXPECT_EQ(memory_usage.VmSwapKb, 34);
+}
+
+TEST(GetProcessMemoryUsageTest, MissingNonMemoryValue) {
+  ScopedTempStatusFile status_file(
+      "VmSize:          \nVmSwap:       34 kB\nVmRSS:        123 kB\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 123);
+  EXPECT_EQ(memory_usage.VmSwapKb, 34);
+}
+
+TEST(GetProcessMemoryUsageTest, RealisticProcStatus) {
+  ScopedTempStatusFile status_file(
+      "Name:   cat\n"
+      "Umask:  0022\n"
+      "State:  R (running)\n"
+      "Tgid:   21255\n"
+      "Ngid:   0\n"
+      "Pid:    21255\n"
+      "PPid:   7\n"
+      "TracerPid:      0\n"
+      "Uid:    694971  694971  694971  694971\n"
+      "Gid:    89939   89939   89939   89939\n"
+      "FDSize: 256\n"
+      "Groups: 4 11 18 19 20 27 250 89939\n"
+      "NStgid: 21255\n"
+      "NSpid:  21255\n"
+      "NSpgid: 21255\n"
+      "NSsid:  0\n"
+      "VmPeak:     6048 kB\n"
+      "VmSize:     6048 kB\n"
+      "VmLck:         0 kB\n"
+      "VmPin:         0 kB\n"
+      "VmHWM:       732 kB\n"
+      "VmRSS:       732 kB\n"
+      "RssAnon:              68 kB\n"
+      "RssFile:             664 kB\n"
+      "RssShmem:              0 kB\n"
+      "VmData:      312 kB\n"
+      "VmStk:       136 kB\n"
+      "VmExe:        40 kB\n"
+      "VmLib:      1872 kB\n"
+      "VmPTE:        52 kB\n"
+      "VmSwap:      321 kB\n"
+      "HugetlbPages:          0 kB\n"
+      "CoreDumping:    0\n"
+      "Threads:        1\n"
+      "SigQ:   0/767737\n"
+      "SigPnd: 0000000000000000\n"
+      "ShdPnd: 0000000000000000\n"
+      "SigBlk: 0000000000000000\n"
+      "SigIgn: 0000000001001000\n"
+      "SigCgt: 0000000000000000\n"
+      "CapInh: 0000000000000000\n"
+      "CapPrm: 0000000000000000\n"
+      "CapEff: 0000000000000000\n"
+      "CapBnd: 0000003fffffffff\n"
+      "CapAmb: 0000000000000000\n"
+      "NoNewPrivs:     0\n"
+      "Seccomp:        0\n"
+      "Speculation_Store_Bypass:       thread vulnerable\n"
+      "Cpus_allowed:   ff,ffffffff,ffffffff\n"
+      "Cpus_allowed_list:      0-71\n"
+      "Mems_allowed:   00000000,00000003\n"
+      "Mems_allowed_list:      0-1\n"
+      "voluntary_ctxt_switches:        0\n"
+      "nonvoluntary_ctxt_switches:     1\n");
+  MemoryUsage memory_usage;
+  EXPECT_TRUE(
+      GetProcessMemoryUsageFromFile(&memory_usage, status_file.GetPath()));
+  EXPECT_EQ(memory_usage.VmRSSKb, 732);
+  EXPECT_EQ(memory_usage.VmSwapKb, 321);
+}
+
+}  // namespace
+}  // namespace ml