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

Commit 900d9df1 authored by Bertrand Simonnet's avatar Bertrand Simonnet Committed by Gerrit Code Review
Browse files

Merge "metricsd: Collect average cpu usage information."

parents 77cdfbd5 0ada2ca1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ metrics_client_sources := \

metrics_daemon_common := \
  collectors/averaged_statistics_collector.cc \
  collectors/cpu_usage_collector.cc \
  collectors/disk_usage_collector.cc \
  metrics_daemon.cc \
  persistent_integer.cc \
@@ -41,6 +42,7 @@ metrics_daemon_common := \

metrics_tests_sources := \
  collectors/averaged_statistics_collector_test.cc \
  collectors/cpu_usage_collector_test.cc \
  metrics_daemon_test.cc \
  metrics_library_test.cc \
  persistent_integer_test.cc \
+125 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "collectors/cpu_usage_collector.h"

#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/message_loop/message_loop.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/sys_info.h>

#include "metrics/metrics_library.h"

namespace {

const char kCpuUsagePercent[] = "Platform.CpuUsage.Percent";
const char kMetricsProcStatFileName[] = "/proc/stat";
const int kMetricsProcStatFirstLineItemsCount = 11;

// Collect every minute.
const int kCollectionIntervalSecs = 60;

}  // namespace

using base::TimeDelta;

CpuUsageCollector::CpuUsageCollector(MetricsLibraryInterface* metrics_library) {
  CHECK(metrics_library);
  metrics_lib_ = metrics_library;
  collect_interval_ = TimeDelta::FromSeconds(kCollectionIntervalSecs);
}

void CpuUsageCollector::Init() {
  num_cpu_ = base::SysInfo::NumberOfProcessors();

  // Get ticks per second (HZ) on this system.
  // Sysconf cannot fail, so no sanity checks are needed.
  ticks_per_second_ = sysconf(_SC_CLK_TCK);
  CHECK_GT(ticks_per_second_, uint64_t(0))
      << "Number of ticks per seconds should be positive.";

  latest_cpu_use_ = GetCumulativeCpuUse();
}

void CpuUsageCollector::CollectCallback() {
  Collect();
  Schedule();
}

void CpuUsageCollector::Schedule() {
  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
      base::Bind(&CpuUsageCollector::CollectCallback, base::Unretained(this)),
      collect_interval_);
}

void CpuUsageCollector::Collect() {
  TimeDelta cpu_use = GetCumulativeCpuUse();
  TimeDelta diff_per_cpu = (cpu_use - latest_cpu_use_) / num_cpu_;
  latest_cpu_use_ = cpu_use;

  // Report the cpu usage as a percentage of the total cpu usage possible.
  int percent_use = diff_per_cpu.InMilliseconds() * 100 /
      (kCollectionIntervalSecs * 1000);

  metrics_lib_->SendEnumToUMA(kCpuUsagePercent, percent_use, 101);
}

TimeDelta CpuUsageCollector::GetCumulativeCpuUse() {
  base::FilePath proc_stat_path(kMetricsProcStatFileName);
  std::string proc_stat_string;
  if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
    LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
    return TimeDelta();
  }

  uint64_t user_ticks, user_nice_ticks, system_ticks;
  if (!ParseProcStat(proc_stat_string, &user_ticks, &user_nice_ticks,
                     &system_ticks)) {
    return TimeDelta();
  }

  uint64_t total = user_ticks + user_nice_ticks + system_ticks;
  return TimeDelta::FromMicroseconds(
      total * 1000 * 1000 / ticks_per_second_);
}

bool CpuUsageCollector::ParseProcStat(const std::string& stat_content,
                                      uint64_t *user_ticks,
                                      uint64_t *user_nice_ticks,
                                      uint64_t *system_ticks) {
  std::vector<std::string> proc_stat_lines;
  base::SplitString(stat_content, '\n', &proc_stat_lines);
  if (proc_stat_lines.empty()) {
    LOG(WARNING) << "No lines found in " << kMetricsProcStatFileName;
    return false;
  }
  std::vector<std::string> proc_stat_totals;
  base::SplitStringAlongWhitespace(proc_stat_lines[0], &proc_stat_totals);

  if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
      proc_stat_totals[0] != "cpu" ||
      !base::StringToUint64(proc_stat_totals[1], user_ticks) ||
      !base::StringToUint64(proc_stat_totals[2], user_nice_ticks) ||
      !base::StringToUint64(proc_stat_totals[3], system_ticks)) {
    LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
    return false;
  }
  return true;
}
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
#define METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_

#include <base/time/time.h>

#include "metrics/metrics_library.h"

class CpuUsageCollector {
 public:
  CpuUsageCollector(MetricsLibraryInterface* metrics_library);

  // Initialize this collector's state.
  void Init();

  // Schedule a collection interval.
  void Schedule();

  // Callback called at the end of the collection interval.
  void CollectCallback();

  // Measure the cpu use and report it.
  void Collect();

  // Gets the current cumulated Cpu usage.
  base::TimeDelta GetCumulativeCpuUse();

 private:
  FRIEND_TEST(CpuUsageTest, ParseProcStat);
  bool ParseProcStat(const std::string& stat_content,
                     uint64_t *user_ticks,
                     uint64_t *user_nice_ticks,
                     uint64_t *system_ticks);

  int num_cpu_;
  uint32_t ticks_per_second_;

  base::TimeDelta collect_interval_;
  base::TimeDelta latest_cpu_use_;

  MetricsLibraryInterface* metrics_lib_;
};

#endif  // METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
+50 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>

#include "collectors/cpu_usage_collector.h"
#include "metrics/metrics_library_mock.h"


TEST(CpuUsageTest, ParseProcStat) {
  MetricsLibraryMock metrics_lib_mock;
  CpuUsageCollector collector(&metrics_lib_mock);
  std::vector<std::string> invalid_contents = {
    "",
    // First line does not start with cpu.
    "spu  17191 11 36579 151118 289 0 2 0 0 0\n"
    "cpu0 1564 2 866 48650 68 0 2 0 0 0\n"
    "cpu1 14299 0 35116 1844 81 0 0 0 0 0\n",
    // One of the field is not a number.
    "cpu  a17191 11 36579 151118 289 0 2 0 0 0",
    // To many numbers in the first line.
    "cpu  17191 11 36579 151118 289 0 2 0 0 0 102"
  };

  uint64_t user, nice, system;
  for (int i = 0; i < invalid_contents.size(); i++) {
    ASSERT_FALSE(collector.ParseProcStat(invalid_contents[i], &user, &nice,
                                         &system));
  }

  ASSERT_TRUE(collector.ParseProcStat(
      std::string("cpu  17191 11 36579 151118 289 0 2 0 0 0"),
      &user, &nice, &system));
  ASSERT_EQ(17191, user);
  ASSERT_EQ(11, nice);
  ASSERT_EQ(36579, system);
}
+10 −57
Original line number Diff line number Diff line
@@ -71,10 +71,8 @@ const char kUncleanShutdownDetectedFile[] =

const int kMetricMeminfoInterval = 30;    // seconds

const char kMetricsProcStatFileName[] = "/proc/stat";
const char kMeminfoFileName[] = "/proc/meminfo";
const char kVmStatFileName[] = "/proc/vmstat";
const int kMetricsProcStatFirstLineItemsCount = 11;

// Thermal CPU throttling.

@@ -103,9 +101,7 @@ static const int kMemuseIntervals[] = {

MetricsDaemon::MetricsDaemon()
    : memuse_final_time_(0),
      memuse_interval_index_(0),
      ticks_per_second_(0),
      latest_cpu_use_ticks_(0) {}
      memuse_interval_index_(0) {}

MetricsDaemon::~MetricsDaemon() {
}
@@ -188,10 +184,6 @@ void MetricsDaemon::Init(bool testing,
  upload_interval_ = upload_interval;
  server_ = server;

  // Get ticks per second (HZ) on this system.
  // Sysconf cannot fail, so no sanity checks are needed.
  ticks_per_second_ = sysconf(_SC_CLK_TCK);

  daily_active_use_.reset(
      new PersistentInteger("Platform.UseTime.PerDay"));
  version_cumulative_active_use_.reset(
@@ -235,6 +227,7 @@ void MetricsDaemon::Init(bool testing,
  averaged_stats_collector_.reset(
      new AveragedStatisticsCollector(metrics_lib_, diskstats_path,
                                      kVmStatFileName));
  cpu_usage_collector_.reset(new CpuUsageCollector(metrics_lib_));
}

int MetricsDaemon::OnInit() {
@@ -290,6 +283,7 @@ int MetricsDaemon::OnInit() {
        base::Bind(&MetricsDaemon::OnDisableMetrics, base::Unretained(this)));
  }

  latest_cpu_use_microseconds_ = cpu_usage_collector_->GetCumulativeCpuUse();
  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
      base::Bind(&MetricsDaemon::HandleUpdateStatsTimeout,
                 base::Unretained(this)),
@@ -404,53 +398,6 @@ DBusHandlerResult MetricsDaemon::MessageFilter(DBusConnection* connection,
  return DBUS_HANDLER_RESULT_HANDLED;
}

// One might argue that parts of this should go into
// chromium/src/base/sys_info_chromeos.c instead, but put it here for now.

TimeDelta MetricsDaemon::GetIncrementalCpuUse() {
  FilePath proc_stat_path = FilePath(kMetricsProcStatFileName);
  std::string proc_stat_string;
  if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
    LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
    return TimeDelta();
  }

  std::vector<std::string> proc_stat_lines;
  base::SplitString(proc_stat_string, '\n', &proc_stat_lines);
  if (proc_stat_lines.empty()) {
    LOG(WARNING) << "cannot parse " << kMetricsProcStatFileName
                 << ": " << proc_stat_string;
    return TimeDelta();
  }
  std::vector<std::string> proc_stat_totals;
  base::SplitStringAlongWhitespace(proc_stat_lines[0], &proc_stat_totals);

  uint64_t user_ticks, user_nice_ticks, system_ticks;
  if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
      proc_stat_totals[0] != "cpu" ||
      !base::StringToUint64(proc_stat_totals[1], &user_ticks) ||
      !base::StringToUint64(proc_stat_totals[2], &user_nice_ticks) ||
      !base::StringToUint64(proc_stat_totals[3], &system_ticks)) {
    LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
    return TimeDelta(base::TimeDelta::FromSeconds(0));
  }

  uint64_t total_cpu_use_ticks = user_ticks + user_nice_ticks + system_ticks;

  // Sanity check.
  if (total_cpu_use_ticks < latest_cpu_use_ticks_) {
    LOG(WARNING) << "CPU time decreasing from " << latest_cpu_use_ticks_
                 << " to " << total_cpu_use_ticks;
    return TimeDelta();
  }

  uint64_t diff = total_cpu_use_ticks - latest_cpu_use_ticks_;
  latest_cpu_use_ticks_ = total_cpu_use_ticks;
  // Use microseconds to avoid significant truncations.
  return base::TimeDelta::FromMicroseconds(
      diff * 1000 * 1000 / ticks_per_second_);
}

void MetricsDaemon::ProcessUserCrash() {
  // Counts the active time up to now.
  UpdateStats(TimeTicks::Now(), Time::Now());
@@ -506,6 +453,9 @@ bool MetricsDaemon::CheckSystemCrash(const string& crash_file) {
void MetricsDaemon::StatsReporterInit() {
  disk_usage_collector_->Schedule();

  cpu_usage_collector_->Init();
  cpu_usage_collector_->Schedule();

  // Don't start a collection cycle during the first run to avoid delaying the
  // boot.
  averaged_stats_collector_->ScheduleWait();
@@ -910,7 +860,10 @@ void MetricsDaemon::UpdateStats(TimeTicks now_ticks,
  version_cumulative_active_use_->Add(elapsed_seconds);
  user_crash_interval_->Add(elapsed_seconds);
  kernel_crash_interval_->Add(elapsed_seconds);
  version_cumulative_cpu_use_->Add(GetIncrementalCpuUse().InMilliseconds());
  TimeDelta cpu_use = cpu_usage_collector_->GetCumulativeCpuUse();
  version_cumulative_cpu_use_->Add(
      (cpu_use - latest_cpu_use_microseconds_).InMilliseconds());
  latest_cpu_use_microseconds_ = cpu_use;
  last_update_stats_time_ = now_ticks;

  const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch();
Loading