| // Copyright 2018 The Chromium 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 "chrome/browser/chromeos/crostini/crosvm_metrics.h" | 
 |  | 
 | #include <unistd.h> | 
 |  | 
 | #include <cmath> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/task/post_task.h" | 
 | #include "base/task/thread_pool.h" | 
 | #include "chrome/browser/chromeos/crostini/crosvm_process_list.h" | 
 |  | 
 | namespace crostini { | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kCrosvmProcessesHistogram[] = "Crostini.Crosvm.Processes.Count"; | 
 | constexpr char kCrosvmCpuPercentageHistogram[] = | 
 |     "Crostini.Crosvm.CpuPercentage"; | 
 | constexpr char kCrosvmRssPercentageHistogram[] = | 
 |     "Crostini.Crosvm.RssPercentage"; | 
 |  | 
 | constexpr base::TimeDelta kCrosvmMetricsInterval = | 
 |     base::TimeDelta::FromMinutes(10); | 
 |  | 
 | }  // namespace | 
 |  | 
 | CrosvmMetrics::CrosvmMetrics() | 
 |     : task_runner_(base::ThreadPool::CreateSequencedTaskRunner( | 
 |           {base::MayBlock(), base::TaskPriority::BEST_EFFORT, | 
 |            base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})), | 
 |       page_size_(sysconf(_SC_PAGESIZE)) {} | 
 |  | 
 | CrosvmMetrics::~CrosvmMetrics() = default; | 
 |  | 
 | void CrosvmMetrics::Start() { | 
 |   task_runner_->PostNonNestableTask( | 
 |       FROM_HERE, base::BindOnce(&CrosvmMetrics::CollectCycleStartData, | 
 |                                 weak_ptr_factory_.GetWeakPtr())); | 
 |   timer_.Start(FROM_HERE, kCrosvmMetricsInterval, this, | 
 |                &CrosvmMetrics::MetricsCycleCallback); | 
 | } | 
 |  | 
 | void CrosvmMetrics::CollectCycleStartData() { | 
 |   previous_pid_stat_map_ = GetCrosvmPidStatMap(); | 
 |   base::Optional<int64_t> total_cpu_time = ash::system::GetCpuTimeJiffies(); | 
 |   if (!total_cpu_time.has_value()) { | 
 |     cycle_start_data_collected_ = false; | 
 |     return; | 
 |   } | 
 |   previous_total_cpu_time_ = total_cpu_time.value(); | 
 |   cycle_start_data_collected_ = true; | 
 | } | 
 |  | 
 | // static | 
 | int CrosvmMetrics::CalculateCrosvmRssPercentage(const PidStatMap& pid_stat_map, | 
 |                                                 int64_t mem_used, | 
 |                                                 int64_t page_size) { | 
 |   int64_t total_rss = 0; | 
 |   for (const auto& pair : pid_stat_map) { | 
 |     total_rss += pair.second.rss; | 
 |   } | 
 |   return std::lround(static_cast<double>(total_rss) / | 
 |                      (mem_used * 1024 / page_size) * 100); | 
 | } | 
 |  | 
 | // static | 
 | int CrosvmMetrics::CalculateCrosvmCpuPercentage( | 
 |     const PidStatMap& pid_stat_map, | 
 |     const PidStatMap& previous_pid_stat_map, | 
 |     int64_t cycle_cpu_time) { | 
 |   int64_t total_cpu_time = 0; | 
 |   for (const auto& pair : pid_stat_map) { | 
 |     auto it = previous_pid_stat_map.find(pair.first); | 
 |     if (it == previous_pid_stat_map.end()) { | 
 |       total_cpu_time += pair.second.utime + pair.second.stime; | 
 |     } else { | 
 |       total_cpu_time += (pair.second.utime + pair.second.stime) - | 
 |                         (it->second.utime + it->second.stime); | 
 |     } | 
 |   } | 
 |   return std::lround(static_cast<double>(total_cpu_time) / cycle_cpu_time * | 
 |                      100); | 
 | } | 
 |  | 
 | void CrosvmMetrics::MetricsCycleCallback() { | 
 |   task_runner_->PostNonNestableTask( | 
 |       FROM_HERE, base::BindOnce(&CrosvmMetrics::MetricsCycle, | 
 |                                 weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void CrosvmMetrics::MetricsCycle() { | 
 |   if (!cycle_start_data_collected_) { | 
 |     CollectCycleStartData(); | 
 |     return; | 
 |   } | 
 |  | 
 |   PidStatMap pid_stat_map = GetCrosvmPidStatMap(); | 
 |   base::Optional<int64_t> total_cpu_time = ash::system::GetCpuTimeJiffies(); | 
 |   if (!total_cpu_time.has_value()) { | 
 |     cycle_start_data_collected_ = false; | 
 |     return; | 
 |   } | 
 |   int64_t cycle_cpu_time = total_cpu_time.value() - previous_total_cpu_time_; | 
 |   base::Optional<int64_t> mem_used = ash::system::GetUsedMemTotalKB(); | 
 |   if (!mem_used.has_value()) { | 
 |     cycle_start_data_collected_ = false; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (pid_stat_map.empty()) { | 
 |     previous_pid_stat_map_ = pid_stat_map; | 
 |     previous_total_cpu_time_ = total_cpu_time.value(); | 
 |     return; | 
 |   } | 
 |  | 
 |   int rss_percentage = | 
 |       CalculateCrosvmRssPercentage(pid_stat_map, mem_used.value(), page_size_); | 
 |   int cpu_percentage = CalculateCrosvmCpuPercentage( | 
 |       pid_stat_map, previous_pid_stat_map_, cycle_cpu_time); | 
 |   UMA_HISTOGRAM_COUNTS_100(kCrosvmProcessesHistogram, pid_stat_map.size()); | 
 |   UMA_HISTOGRAM_PERCENTAGE(kCrosvmCpuPercentageHistogram, cpu_percentage); | 
 |   UMA_HISTOGRAM_PERCENTAGE(kCrosvmRssPercentageHistogram, rss_percentage); | 
 |   previous_pid_stat_map_ = pid_stat_map; | 
 |   previous_total_cpu_time_ = total_cpu_time.value(); | 
 | } | 
 |  | 
 | }  // namespace crostini |