chromium/ash/components/arc/metrics/arc_daily_metrics.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/components/arc/metrics/arc_daily_metrics.h"

#include <unordered_set>

#include "ash/components/arc/arc_prefs.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "components/prefs/pref_service.h"

namespace arc {

namespace {

// DailyObserver is used by ArcDailyMetrics to know when to report metrics.
// Every metrics logging method calls CheckInterval, and if a day has passed,
// all daily metrics are logged. We won't wait too long beyond a day because
// OnLowMemoryKillCounts is called every 10 minutes.
class DailyObserver : public metrics::DailyEvent::Observer {
 public:
  explicit DailyObserver(ArcDailyMetrics& arc_daily_metrics)
      : arc_daily_metrics_(arc_daily_metrics) {}

  DailyObserver(const DailyObserver&) = delete;
  DailyObserver& operator=(const DailyObserver&) = delete;

  ~DailyObserver() override = default;

  // Callback called when the daily event happen.
  void OnDailyEvent(metrics::DailyEvent::IntervalType type) override {
    arc_daily_metrics_->OnDailyEvent(type);
  }

 private:
  const raw_ref<ArcDailyMetrics> arc_daily_metrics_;
};

class KillCounts {
 public:
  KillCounts(const std::string& pref_prefix, const std::string& hist_prefix);
  ~KillCounts() = default;

  void Load(const base::Value::Dict& pref);
  void Save(base::Value::Dict& out_pref);
  void Increment(int oom, int foreground, int perceptible, int cached);
  void UpdateUmaDaily();

 private:
  const std::string pref_prefix_;
  const std::string hist_prefix_;
  int oom_ = 0;
  int foreground_ = 0;
  int perceptible_ = 0;
  int cached_ = 0;
};

KillCounts::KillCounts(const std::string& pref_prefix,
                       const std::string& hist_prefix)
    : pref_prefix_(pref_prefix), hist_prefix_(hist_prefix) {}

void KillCounts::Load(const base::Value::Dict& pref) {
  oom_ = pref.FindInt(pref_prefix_ + "oom").value_or(0);
  foreground_ = pref.FindInt(pref_prefix_ + "foreground").value_or(0);
  perceptible_ = pref.FindInt(pref_prefix_ + "perceptible").value_or(0);
  cached_ = pref.FindInt(pref_prefix_ + "cached").value_or(0);
}

void KillCounts::Save(base::Value::Dict& out_pref) {
  // Only save counter values that are non-zero, to reduce the size of prefs.
  if (oom_ > 0) {
    out_pref.Set(pref_prefix_ + "oom", oom_);
  }
  if (foreground_ > 0) {
    out_pref.Set(pref_prefix_ + "foreground", foreground_);
  }
  if (perceptible_ > 0) {
    out_pref.Set(pref_prefix_ + "perceptible", perceptible_);
  }
  if (cached_ > 0) {
    out_pref.Set(pref_prefix_ + "cached", cached_);
  }
}

void KillCounts::Increment(int oom,
                           int foreground,
                           int perceptible,
                           int cached) {
  oom_ += oom;
  foreground_ += foreground;
  perceptible_ += perceptible;
  cached_ += cached;
}

void KillCounts::UpdateUmaDaily() {
  base::UmaHistogramExactLinear(
      base::StringPrintf("Arc.App.LowMemoryKills%s.OomDaily",
                         hist_prefix_.c_str()),
      oom_, 50);
  base::UmaHistogramExactLinear(
      base::StringPrintf("Arc.App.LowMemoryKills%s.ForegroundDaily",
                         hist_prefix_.c_str()),
      foreground_, 50);
  base::UmaHistogramExactLinear(
      base::StringPrintf("Arc.App.LowMemoryKills%s.PerceptibleDaily",
                         hist_prefix_.c_str()),
      perceptible_, 50);
  base::UmaHistogramExactLinear(
      base::StringPrintf("Arc.App.LowMemoryKills%s.CachedDaily",
                         hist_prefix_.c_str()),
      cached_, 50);

  // Reset the counts for the next day. ArcDailyMetrics is responsible for
  // resetting the cached values in prefs.
  oom_ = 0;
  foreground_ = 0;
  perceptible_ = 0;
  cached_ = 0;
}

}  // namespace

const vm_tools::concierge::VmInfo_VmType
    ArcDailyMetrics::kKillCountTypeVm[ArcDailyMetrics::kKillCountNum] = {
        vm_tools::concierge::VmInfo_VmType_UNKNOWN,    // kKillCountAll not used
        vm_tools::concierge::VmInfo_VmType_UNKNOWN,    // kKillCountOnlyArc not
                                                       // used
        vm_tools::concierge::VmInfo_VmType_TERMINA,    // kKillCountCrostini
        vm_tools::concierge::VmInfo_VmType_PLUGIN_VM,  // kKillCountPluginVm
        vm_tools::concierge::VmInfo_VmType_BOREALIS,   // kKillCountSteam
        vm_tools::concierge::VmInfo_VmType_UNKNOWN,    // kKillCountUnknownVm
};

const char ArcDailyMetrics::kDailyEventHistogramName[] =
    "Arc.DailyEventInterval";

ArcDailyMetrics::ArcDailyMetrics(PrefService* pref_service)
    : prefs_(pref_service),
      daily_event_(
          std::make_unique<metrics::DailyEvent>(pref_service,
                                                prefs::kArcDailyMetricsSample,
                                                kDailyEventHistogramName)),
      kills_{std::make_unique<KillCounts>("", ""),
             std::make_unique<KillCounts>("arc_", ".OnlyArc"),
             std::make_unique<KillCounts>("crostini_", ".Crostini"),
             std::make_unique<KillCounts>("plugin_", ".PluginVm"),
             std::make_unique<KillCounts>("steam_", ".Steam"),
             std::make_unique<KillCounts>("unknown_", ".UnknownVm")} {
  // Restore kill counts.
  const auto& pref = prefs_->GetDict(prefs::kArcDailyMetricsKills);
  for (auto& kill_counts : kills_) {
    kill_counts->Load(pref);
  }

  // Set up daily event.
  daily_event_->AddObserver(std::make_unique<DailyObserver>(*this));
  daily_event_->CheckInterval();
}

ArcDailyMetrics::~ArcDailyMetrics() = default;

void ArcDailyMetrics::OnLowMemoryKillCounts(
    std::optional<vm_tools::concierge::ListVmsResponse> vms_list,
    int oom,
    int foreground,
    int perceptible,
    int cached) {
  // Build set of VMs running now.
  std::unordered_set<vm_tools::concierge::VmInfo_VmType> curr_vms;
  if (vms_list && vms_list->success()) {
    for (int i = 0; i < vms_list->vms_size(); i++) {
      const auto& vm = vms_list->vms(i);
      if (vm.has_vm_info()) {
        const auto& info = vm.vm_info();
        switch (info.vm_type()) {
          case vm_tools::concierge::VmInfo_VmType_ARC_VM:
          case vm_tools::concierge::VmInfo_VmType_BOREALIS:
          case vm_tools::concierge::VmInfo_VmType_PLUGIN_VM:
          case vm_tools::concierge::VmInfo_VmType_TERMINA:
            curr_vms.insert(info.vm_type());
            break;

          default:
            // Map all unknown VMs to VmInfo_VmType_UNKNOWN
            curr_vms.insert(vm_tools::concierge::VmInfo_VmType_UNKNOWN);
            break;
        }

      } else {
        LOG(WARNING) << "VmListToSet got VM " << vm.name()
                     << " with no vm_info.";
      }
    }
  }

  // Which VMs are context for a kill is the union of the VMs running now, and
  //  the VMs running at the end of the last sample.
  std::unordered_set<vm_tools::concierge::VmInfo_VmType> vms;
  vms.insert(curr_vms.begin(), curr_vms.end());
  vms.insert(kills_prev_vms_.begin(), kills_prev_vms_.end());
  kills_prev_vms_ = curr_vms;

  kills_[kKillCountAll]->Increment(oom, foreground, perceptible, cached);

  // If ARCVM is the only VM.
  if (vms.count(vm_tools::concierge::VmInfo_VmType_ARC_VM) != 0 &&
      vms.size() == 1) {
    kills_[kKillCountOnlyArc]->Increment(oom, foreground, perceptible, cached);
  }

  // All background VM counters don't care what other VMs are running.
  for (int i = kKillCountFirstBackgroundVm; i < kKillCountNum; i++) {
    if (vms.count(kKillCountTypeVm[i]) != 0) {
      kills_[i]->Increment(oom, foreground, perceptible, cached);
    }
  }

  // Updating prefs is not free, so skip it if we didn't have any kill counts.
  if (oom != 0 || foreground != 0 || perceptible != 0 || cached != 0) {
    base::Value::Dict pref;
    for (auto& kill_counts : kills_) {
      kill_counts->Save(pref);
    }
    prefs_->SetDict(prefs::kArcDailyMetricsKills, std::move(pref));
  }

  daily_event_->CheckInterval();
}

void ArcDailyMetrics::OnDailyEvent(metrics::DailyEvent::IntervalType type) {
  for (auto& kill_counts : kills_) {
    kill_counts->UpdateUmaDaily();
  }

  // Reset counters.
  prefs_->SetDict(prefs::kArcDailyMetricsKills, base::Value::Dict());
}

void ArcDailyMetrics::SetDailyEventForTesting(
    std::unique_ptr<metrics::DailyEvent> daily_event) {
  daily_event_ = std::move(daily_event);
  daily_event_->AddObserver(std::make_unique<DailyObserver>(*this));
}

}  // namespace arc