Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96360198 authored by Luigi Semenzato's avatar Luigi Semenzato Committed by chrome-internal-fetch
Browse files

metrics_daemon: add zram stats collection

Memory compression stats are being collected by Chrome, but it
is more natural to do it here since they are system-wide rather than
Chrome-specific.

In addition, this provides better granularity for the compression ratio
(percents, from 100% to 600%) since we're especially interested in the
distribution of values between 1 and 2, and currently these all fall
in the same bucket.

Finally, we collect more interesting stats on zero pages.

BUG=chromium:315113
TEST=unit testing, checked about:histograms

Change-Id: I09c974989661d42f45d44afd428e8114e4ee1dbd
Reviewed-on: https://chromium-review.googlesource.com/202587


Reviewed-by: default avatarLuigi Semenzato <semenzato@chromium.org>
Commit-Queue: Luigi Semenzato <semenzato@chromium.org>
Tested-by: default avatarLuigi Semenzato <semenzato@chromium.org>
parent a756f7e6
Loading
Loading
Loading
Loading
+62 −1
Original line number Original line Diff line number Diff line
@@ -111,6 +111,12 @@ const int MetricsDaemon::kMetricsProcStatFirstLineItemsCount = 11;
const char MetricsDaemon::kMetricScaledCpuFrequencyName[] =
const char MetricsDaemon::kMetricScaledCpuFrequencyName[] =
    "Platform.CpuFrequencyThermalScaling";
    "Platform.CpuFrequencyThermalScaling";


// Zram sysfs entries.

const char MetricsDaemon::kComprDataSizeName[] = "compr_data_size";
const char MetricsDaemon::kOrigDataSizeName[] = "orig_data_size";
const char MetricsDaemon::kZeroPagesName[] = "zero_pages";

// Memory use stats collection intervals.  We collect some memory use interval
// Memory use stats collection intervals.  We collect some memory use interval
// at these intervals after boot, and we stop collecting after the last one,
// at these intervals after boot, and we stop collecting after the last one,
// with the assumption that in most cases the memory use won't change much
// with the assumption that in most cases the memory use won't change much
@@ -737,7 +743,62 @@ bool MetricsDaemon::MeminfoCallback() {
    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
    return false;
    return false;
  }
  }
  return ProcessMeminfo(meminfo_raw);
  // Make both calls even if the first one fails.
  bool success = ProcessMeminfo(meminfo_raw);
  return ReportZram(base::FilePath(FILE_PATH_LITERAL("/sys/block/zram0"))) &&
      success;
}

// static
bool MetricsDaemon::ReadFileToUint64(const base::FilePath& path,
                                     uint64* value) {
  std::string content;
  if (!base::ReadFileToString(path, &content)) {
    PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
    return false;
  }
  if (!base::StringToUint64(content, value)) {
    LOG(WARNING) << "invalid integer: " << content;
    return false;
  }
  return true;
}

bool MetricsDaemon::ReportZram(const base::FilePath& zram_dir) {
  // Data sizes are in bytes.  |zero_pages| is in number of pages.
  uint64 compr_data_size, orig_data_size, zero_pages;
  const size_t page_size = 4096;

  if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
                        &compr_data_size) ||
      !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
      !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
    return false;
  }

  // |orig_data_size| does not include zero-filled pages.
  orig_data_size += zero_pages * page_size;

  const int compr_data_size_mb = compr_data_size >> 20;
  const int savings_mb = (orig_data_size - compr_data_size) >> 20;
  const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;

  // Report compressed size in megabytes.  100 MB or less has little impact.
  SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
  SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
  // The compression ratio is multiplied by 100 for better resolution.  The
  // ratios of interest are between 1 and 6 (100% and 600% as reported).  We
  // don't want samples when very little memory is being compressed.
  if (compr_data_size_mb >= 1) {
    SendSample("Platform.ZramCompressionRatioPercent",
               orig_data_size * 100 / compr_data_size, 100, 600, 50);
  }
  // The values of interest for zero_pages are between 1MB and 1GB.  The units
  // are number of pages.
  SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
  SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);

  return true;
}
}


bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) {
bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) {
+15 −0
Original line number Original line Diff line number Diff line
@@ -36,6 +36,12 @@ class MetricsDaemon {
  // forking.
  // forking.
  void Run(bool run_as_daemon);
  void Run(bool run_as_daemon);


 protected:
  // Used also by the unit tests.
  static const char kComprDataSizeName[];
  static const char kOrigDataSizeName[];
  static const char kZeroPagesName[];

 private:
 private:
  friend class MetricsDaemonTest;
  friend class MetricsDaemonTest;
  FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
  FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
@@ -59,6 +65,7 @@ class MetricsDaemon {
  FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
  FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
  FRIEND_TEST(MetricsDaemonTest, SendSample);
  FRIEND_TEST(MetricsDaemonTest, SendSample);
  FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
  FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
  FRIEND_TEST(MetricsDaemonTest, SendZramMetrics);


  // State for disk stats collector callback.
  // State for disk stats collector callback.
  enum StatsState {
  enum StatsState {
@@ -270,6 +277,14 @@ class MetricsDaemon {
  // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
  // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
  static gboolean HandleUpdateStatsTimeout(gpointer data);
  static gboolean HandleUpdateStatsTimeout(gpointer data);


  // Reports zram statistics.
  bool ReportZram(const base::FilePath& zram_dir);

  // Reads a string from a file and converts it to uint64.
  static bool ReadFileToUint64(const base::FilePath& path, uint64* value);

  // VARIABLES

  // Test mode.
  // Test mode.
  bool testing_;
  bool testing_;


+56 −25
Original line number Original line Diff line number Diff line
@@ -9,6 +9,7 @@


#include <base/at_exit.h>
#include <base/at_exit.h>
#include <base/file_util.h>
#include <base/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include <gtest/gtest.h>
@@ -30,8 +31,7 @@ using ::testing::Return;
using ::testing::StrictMock;
using ::testing::StrictMock;
using chromeos_metrics::PersistentIntegerMock;
using chromeos_metrics::PersistentIntegerMock;


static const char kTestDir[] = "test";
static const char kFakeDiskStatsName[] = "fake-disk-stats";
static const char kFakeDiskStatsPath[] = "fake-disk-stats";
static const char kFakeDiskStatsFormat[] =
static const char kFakeDiskStatsFormat[] =
    "    1793     1788    %d   105580    "
    "    1793     1788    %d   105580    "
    "    196      175     %d    30290    "
    "    196      175     %d    30290    "
@@ -40,7 +40,7 @@ static string kFakeDiskStats[2];
static const int kFakeReadSectors[] = {80000, 100000};
static const int kFakeReadSectors[] = {80000, 100000};
static const int kFakeWriteSectors[] = {3000, 4000};
static const int kFakeWriteSectors[] = {3000, 4000};


static const char kFakeVmStatsPath[] = "fake-vm-stats";
static const char kFakeVmStatsName[] = "fake-vm-stats";
static const char kFakeScalingMaxFreqPath[] = "fake-scaling-max-freq";
static const char kFakeScalingMaxFreqPath[] = "fake-scaling-max-freq";
static const char kFakeCpuinfoMaxFreqPath[] = "fake-cpuinfo-max-freq";
static const char kFakeCpuinfoMaxFreqPath[] = "fake-cpuinfo-max-freq";


@@ -54,16 +54,13 @@ class MetricsDaemonTest : public testing::Test {
                                           kFakeReadSectors[1],
                                           kFakeReadSectors[1],
                                           kFakeWriteSectors[1]);
                                           kFakeWriteSectors[1]);
    CreateFakeDiskStatsFile(kFakeDiskStats[0].c_str());
    CreateFakeDiskStatsFile(kFakeDiskStats[0].c_str());
    CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, 10000000);
    CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 10000000);
    CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 10000000);
    CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 10000000);


    chromeos_metrics::PersistentInteger::SetTestingMode(true);
    chromeos_metrics::PersistentInteger::SetTestingMode(true);
    daemon_.Init(true, &metrics_lib_, kFakeDiskStatsPath, kFakeVmStatsPath,
    daemon_.Init(true, &metrics_lib_, kFakeDiskStatsName, kFakeVmStatsName,
        kFakeScalingMaxFreqPath, kFakeCpuinfoMaxFreqPath);
        kFakeScalingMaxFreqPath, kFakeCpuinfoMaxFreqPath);


    base::DeleteFile(FilePath(kTestDir), true);
    base::CreateDirectory(FilePath(kTestDir));

    // Replace original persistent values with mock ones.
    // Replace original persistent values with mock ones.
    daily_active_use_mock_ =
    daily_active_use_mock_ =
        new StrictMock<PersistentIntegerMock>("1.mock");
        new StrictMock<PersistentIntegerMock>("1.mock");
@@ -84,7 +81,7 @@ class MetricsDaemonTest : public testing::Test {
  }
  }


  virtual void TearDown() {
  virtual void TearDown() {
    EXPECT_EQ(0, unlink(kFakeDiskStatsPath));
    EXPECT_EQ(0, unlink(kFakeDiskStatsName));
    EXPECT_EQ(0, unlink(kFakeScalingMaxFreqPath));
    EXPECT_EQ(0, unlink(kFakeScalingMaxFreqPath));
    EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath));
    EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath));
  }
  }
@@ -157,23 +154,22 @@ class MetricsDaemonTest : public testing::Test {


  // Creates or overwrites an input file containing fake disk stats.
  // Creates or overwrites an input file containing fake disk stats.
  void CreateFakeDiskStatsFile(const char* fake_stats) {
  void CreateFakeDiskStatsFile(const char* fake_stats) {
    if (unlink(kFakeDiskStatsPath) < 0) {
    if (unlink(kFakeDiskStatsName) < 0) {
      EXPECT_EQ(errno, ENOENT);
      EXPECT_EQ(errno, ENOENT);
    }
    }
    FILE* f = fopen(kFakeDiskStatsPath, "w");
    FILE* f = fopen(kFakeDiskStatsName, "w");
    EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
    EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
    EXPECT_EQ(0, fclose(f));
    EXPECT_EQ(0, fclose(f));
  }
  }


  // Creates or overwrites an input file containing a fake CPU frequency.
  // Creates or overwrites the file in |path| so that it contains the printable
  void CreateFakeCpuFrequencyFile(const char* filename, int frequency) {
  // representation of |value|.
    FilePath path(filename);
  void CreateUint64ValueFile(const base::FilePath& path, uint64 value) {
    base::DeleteFile(path, false);
    base::DeleteFile(path, false);
    std::string frequency_string = StringPrintf("%d\n", frequency);
    std::string value_string = base::Uint64ToString(value);
    int frequency_string_length = frequency_string.length();
    ASSERT_EQ(value_string.length(),
    EXPECT_EQ(frequency_string.length(),
              base::WriteFile(path, value_string.c_str(),
              base::WriteFile(path, frequency_string.c_str(),
                              value_string.length()));
                              frequency_string_length));
  }
  }


  // The MetricsDaemon under test.
  // The MetricsDaemon under test.
@@ -351,8 +347,9 @@ TEST_F(MetricsDaemonTest, ReadFreqToInt) {
  const int fake_max_freq = 2000000;
  const int fake_max_freq = 2000000;
  int scaled_freq = 0;
  int scaled_freq = 0;
  int max_freq = 0;
  int max_freq = 0;
  CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, fake_scaled_freq);
  CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath),
  CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, fake_max_freq);
                        fake_scaled_freq);
  CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), fake_max_freq);
  EXPECT_TRUE(daemon_.testing_);
  EXPECT_TRUE(daemon_.testing_);
  EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeScalingMaxFreqPath, &scaled_freq));
  EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeScalingMaxFreqPath, &scaled_freq));
  EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeCpuinfoMaxFreqPath, &max_freq));
  EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeCpuinfoMaxFreqPath, &max_freq));
@@ -361,17 +358,51 @@ TEST_F(MetricsDaemonTest, ReadFreqToInt) {
}
}


TEST_F(MetricsDaemonTest, SendCpuThrottleMetrics) {
TEST_F(MetricsDaemonTest, SendCpuThrottleMetrics) {
  CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, 2001000);
  CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 2001000);
  // Test the 101% and 100% cases.
  // Test the 101% and 100% cases.
  CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 2001000);
  CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2001000);
  EXPECT_TRUE(daemon_.testing_);
  EXPECT_TRUE(daemon_.testing_);
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 101, 101));
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 101, 101));
  daemon_.SendCpuThrottleMetrics();
  daemon_.SendCpuThrottleMetrics();
  CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 2000000);
  CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2000000);
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 100, 101));
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 100, 101));
  daemon_.SendCpuThrottleMetrics();
  daemon_.SendCpuThrottleMetrics();
}
}


TEST_F(MetricsDaemonTest, SendZramMetrics) {
  EXPECT_TRUE(daemon_.testing_);

  // |compr_data_size| is the size in bytes of compressed data.
  const uint64 compr_data_size = 50 * 1000 * 1000;
  // The constant '3' is a realistic but random choice.
  // |orig_data_size| does not include zero pages.
  const uint64 orig_data_size = compr_data_size * 3;
  const uint64 page_size = 4096;
  const uint64 zero_pages = 10 * 1000 * 1000 / page_size;

  CreateUint64ValueFile(base::FilePath(MetricsDaemon::kComprDataSizeName),
                        compr_data_size);
  CreateUint64ValueFile(base::FilePath(MetricsDaemon::kOrigDataSizeName),
                        orig_data_size);
  CreateUint64ValueFile(base::FilePath(MetricsDaemon::kZeroPagesName),
                        zero_pages);

  const uint64 real_orig_size = orig_data_size + zero_pages * page_size;
  const uint64 zero_ratio_percent =
      zero_pages * page_size * 100 / real_orig_size;
  // Ratio samples are in percents.
  const uint64 actual_ratio_sample = real_orig_size * 100 / compr_data_size;

  EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
  EXPECT_CALL(metrics_lib_,
              SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
  EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
  EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
  EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));

  EXPECT_TRUE(daemon_.ReportZram(base::FilePath(".")));
}

int main(int argc, char** argv) {
int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  testing::InitGoogleTest(&argc, argv);
  // Some libchrome calls need this.
  // Some libchrome calls need this.