chromium/ash/components/arc/metrics/arc_metrics_anr.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.

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

#include <string>

#include "ash/components/arc/arc_prefs.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "components/prefs/pref_service.h"

namespace arc {

namespace {
// Intervals to collect ANR rate after login. Collected only once per session.
// In case the session is shorter than |kMinStartPeriodDuration| interval, it
// is discarded. Shorter sessions distort stats due to ANR is long process that
// may take 10, 20 and more seconds. This also done to avoid false reporting
// when session gets restarted due to applying configuration, policy, and so on.
constexpr base::TimeDelta kMaxStartPeriodDuration = base::Minutes(10);
constexpr base::TimeDelta kMinStartPeriodDuration = base::Minutes(2);

// Interval to report ANR rate. ANR rate is reported each interval of ARC
// active state.
constexpr base::TimeDelta kRateInterval = base::Hours(4);

// Interval to update ANR interval. Once accumulated interval is
// greater than  |kArcAnrRateInterval| UMA is updated.
constexpr base::TimeDelta kUpdateInterval = base::Minutes(5);

// It is very unlikely to do have more ANR events than
// |kForPeriodMaxCount| times in |kMaxStartPeriodDuration| or
// |kRateInterval|. This acts as an upper bound of UMA buckets.
constexpr int kForPeriodMaxCount = 64;

constexpr char kStartPeriodHistogram[] = "Arc.Anr.First10MinutesAfterStart";
constexpr char kRegularPeriodHistogram[] = "Arc.Anr.Per4Hours";

// App types to report.
constexpr char kAppTypeArcAppLauncher[] = "ArcAppLauncher";
constexpr char kAppTypeArcOther[] = "ArcOther";
constexpr char kAppTypeFirstParty[] = "FirstParty";
constexpr char kAppTypeGmsCore[] = "GmsCore";
constexpr char kAppTypePlayStore[] = "PlayStore";
constexpr char kAppTypeSystemServer[] = "SystemServer";
constexpr char kAppTypeSystem[] = "SystemApp";
constexpr char kAppTypeOther[] = "Other";

std::string SourceToTableName(mojom::AnrSource value) {
  switch (value) {
    case mojom::AnrSource::OTHER:
      return kAppTypeOther;
    case mojom::AnrSource::SYSTEM_SERVER:
      return kAppTypeSystemServer;
    case mojom::AnrSource::SYSTEM_APP:
      return kAppTypeSystem;
    case mojom::AnrSource::GMS_CORE:
      return kAppTypeGmsCore;
    case mojom::AnrSource::PLAY_STORE:
      return kAppTypePlayStore;
    case mojom::AnrSource::FIRST_PARTY:
      return kAppTypeFirstParty;
    case mojom::AnrSource::ARC_OTHER:
      return kAppTypeArcOther;
    case mojom::AnrSource::ARC_APP_LAUNCHER:
      return kAppTypeArcAppLauncher;
    default:
      LOG(ERROR) << "Unrecognized source ANR " << value;
      return kAppTypeOther;
  }
}

void RecordUmaWithSuffix(const std::string& name,
                         int count,
                         int max,
                         const std::string& uma_suffix) {
  base::UmaHistogramExactLinear(name, count, max);
  if (uma_suffix.empty()) {
    LOG(ERROR) << "Boot type is unknown. Skip recording " << name
               << " with a suffix";
    return;
  }
  // In addition to e.g. Arc.Anr.Per4Hours, record e.g.
  // Arc.Anr.Per4Hours.FirstBootAfterUpdate.
  base::UmaHistogramExactLinear(
      base::StringPrintf("%s%s", name.c_str(), uma_suffix.c_str()), count, max);
}

}  // namespace

ArcMetricsAnr::ArcMetricsAnr(PrefService* prefs) : prefs_(prefs) {
  pending_start_timer_.Start(
      FROM_HERE, kMinStartPeriodDuration,
      base::BindOnce(&ArcMetricsAnr::SetLogOnStartPending,
                     base::Unretained(this)));
  start_timer_.Start(
      FROM_HERE, kMaxStartPeriodDuration,
      base::BindOnce(&ArcMetricsAnr::LogOnStart, base::Unretained(this)));
  period_updater_.Start(
      FROM_HERE, kUpdateInterval,
      base::BindRepeating(&ArcMetricsAnr::UpdateRate, base::Unretained(this)));
}

ArcMetricsAnr::~ArcMetricsAnr() {
  if (log_on_start_pending_) {
    // Session is shorter than |kMaxStartPeriodDuration| but longer than
    // |kMinStartPeriodDuration|.
    RecordUmaWithSuffix(kStartPeriodHistogram, count_10min_after_start_,
                        kForPeriodMaxCount, uma_suffix_);
  }
}

void ArcMetricsAnr::Report(mojom::AnrPtr anr) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // TODO (b/227337741): Retire these in favor of ANR rate.
  base::UmaHistogramEnumeration("Arc.Anr.Overall", anr->type);
  base::UmaHistogramEnumeration("Arc.Anr." + SourceToTableName(anr->source),
                                anr->type);
  ++count_10min_after_start_;
  prefs_->SetInteger(prefs::kAnrPendingCount,
                     prefs_->GetInteger(prefs::kAnrPendingCount) + 1);
}

void ArcMetricsAnr::LogOnStart() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  RecordUmaWithSuffix(kStartPeriodHistogram, count_10min_after_start_,
                      kForPeriodMaxCount, uma_suffix_);
  // We already reported ANR count on start for this session.
  log_on_start_pending_ = false;
}

void ArcMetricsAnr::UpdateRate() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  base::TimeDelta duration =
      prefs_->GetTimeDelta(prefs::kAnrPendingDuration) + kUpdateInterval;
  if (duration >= kRateInterval) {
    duration = base::TimeDelta();
    RecordUmaWithSuffix(kRegularPeriodHistogram,
                        prefs_->GetInteger(prefs::kAnrPendingCount),
                        kForPeriodMaxCount, uma_suffix_);
    prefs_->SetInteger(prefs::kAnrPendingCount, 0);
  }

  prefs_->SetTimeDelta(prefs::kAnrPendingDuration, duration);
}

void ArcMetricsAnr::SetLogOnStartPending() {
  log_on_start_pending_ = true;
}

}  // namespace arc