chromium/chromecast/browser/metrics/external_metrics.cc

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

#include "chromecast/browser/metrics/external_metrics.h"

#include <stddef.h>

#include <memory>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "chromecast/base/metrics/cast_histograms.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/browser/metrics/cast_stability_metrics_provider.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/serialization/metric_sample.h"
#include "components/metrics/serialization/serialization_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"

namespace chromecast {
namespace metrics {

namespace {

bool CheckValues(const std::string& name,
                 int minimum,
                 int maximum,
                 size_t bucket_count) {
  if (!base::Histogram::InspectConstructionArguments(
          name, &minimum, &maximum, &bucket_count))
    return false;
  base::HistogramBase* histogram =
      base::StatisticsRecorder::FindHistogram(name);
  if (!histogram)
    return true;
  return histogram->HasConstructionArguments(minimum, maximum, bucket_count);
}

bool CheckLinearValues(const std::string& name, int maximum) {
  return CheckValues(name, 1, maximum, maximum + 1);
}

// Returns a task runner appropriate for running background tasks that perform
// file I/O.
scoped_refptr<base::SequencedTaskRunner> CreateTaskRunner() {
  // Note that CollectEvents accesses a global singleton, and thus
  // scheduling with CONTINUE_ON_SHUTDOWN might not be safe.
  return base::ThreadPool::CreateSequencedTaskRunner(
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}

}  // namespace

// The interval between external metrics collections in seconds
static const int kExternalMetricsCollectionIntervalSeconds = 30;

ExternalMetrics::ExternalMetrics(
    CastStabilityMetricsProvider* stability_provider,
    const std::string& uma_events_file)
    : stability_provider_(stability_provider),
      uma_events_file_(uma_events_file),
      task_runner_(CreateTaskRunner()),
      weak_factory_(this) {
  DCHECK(stability_provider);

  // The sequence checker verifies that all of the interesting work done by this
  // class is done on the |task_runner_|, rather than on the sequence that this
  // object was created on.
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

ExternalMetrics::~ExternalMetrics() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void ExternalMetrics::StopAndDestroy() {
  task_runner_->DeleteSoon(FROM_HERE, this);
}

void ExternalMetrics::Start() {
  ScheduleCollection();
}

void ExternalMetrics::ProcessExternalEvents(base::OnceClosure cb) {
  task_runner_->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(base::IgnoreResult(&ExternalMetrics::CollectEvents),
                     weak_factory_.GetWeakPtr()),
      std::move(cb));
}

void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&CastStabilityMetricsProvider::LogExternalCrash,
                     base::Unretained(stability_provider_), crash_kind));
}

void ExternalMetrics::RecordSparseHistogram(
    const ::metrics::MetricSample& sample) {
  CHECK_EQ(::metrics::MetricSample::SPARSE_HISTOGRAM, sample.type());
  base::HistogramBase* counter = base::SparseHistogram::FactoryGet(
      sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag);
  counter->Add(sample.sample());
}

int ExternalMetrics::CollectEvents() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::WILL_BLOCK);

  std::vector<std::unique_ptr<::metrics::MetricSample>> samples;
  ::metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(
      uma_events_file_, &samples);

  for (auto it = samples.begin(); it != samples.end(); ++it) {
    const ::metrics::MetricSample& sample = **it;

    switch (sample.type()) {
      case ::metrics::MetricSample::CRASH:
        RecordCrash(sample.name());
        break;
      case ::metrics::MetricSample::USER_ACTION:
        CastMetricsHelper::GetInstance()->RecordSimpleAction(sample.name());
        break;
      case ::metrics::MetricSample::HISTOGRAM:
        if (!CheckValues(sample.name(), sample.min(), sample.max(),
                         sample.bucket_count())) {
          DLOG(ERROR) << "Invalid histogram: " << sample.name();
          break;
        }
        UMA_HISTOGRAM_CUSTOM_COUNTS_NO_CACHE(sample.name(),
                                             sample.sample(),
                                             sample.min(),
                                             sample.max(),
                                             sample.bucket_count(),
                                             1);
        break;
      case ::metrics::MetricSample::LINEAR_HISTOGRAM:
        if (!CheckLinearValues(sample.name(), sample.max())) {
          DLOG(ERROR) << "Invalid linear histogram: " << sample.name();
          break;
        }
        UMA_HISTOGRAM_ENUMERATION_NO_CACHE(
            sample.name(), sample.sample(), sample.max());
        break;
      case ::metrics::MetricSample::SPARSE_HISTOGRAM:
        RecordSparseHistogram(sample);
        break;
    }
  }

  return samples.size();
}

void ExternalMetrics::CollectEventsAndReschedule() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CollectEvents();
  ScheduleCollection();
}

void ExternalMetrics::ScheduleCollection() {
  task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&ExternalMetrics::CollectEventsAndReschedule,
                     weak_factory_.GetWeakPtr()),
      base::Seconds(kExternalMetricsCollectionIntervalSeconds));
}

}  // namespace metrics
}  // namespace chromecast