blob: b15b3fdcb406956343c6460813756425b3a6eacc [file] [log] [blame]
// Copyright 2017 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 "components/offline_pages/core/prefetch/metrics_finalization_task.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "components/offline_pages/core/prefetch/prefetch_types.h"
#include "components/offline_pages/core/prefetch/store/prefetch_store.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace offline_pages {
namespace {
// In-memory representation of URL metadata fetched from SQLite storage.
struct PrefetchItemStats {
PrefetchItemStats(int64_t offline_id,
int generate_bundle_attempts,
int get_operation_attempts,
int download_initiation_attempts,
int64_t archive_body_length,
base::Time creation_time,
PrefetchItemErrorCode error_code,
int64_t file_size)
: offline_id(offline_id),
generate_bundle_attempts(generate_bundle_attempts),
get_operation_attempts(get_operation_attempts),
download_initiation_attempts(download_initiation_attempts),
archive_body_length(archive_body_length),
creation_time(creation_time),
error_code(error_code),
file_size(file_size) {}
int64_t offline_id;
int generate_bundle_attempts;
int get_operation_attempts;
int download_initiation_attempts;
int64_t archive_body_length;
base::Time creation_time;
PrefetchItemErrorCode error_code;
int64_t file_size;
};
std::vector<PrefetchItemStats> FetchUrlsSync(sql::Connection* db) {
static const char kSql[] = R"(
SELECT offline_id, generate_bundle_attempts, get_operation_attempts,
download_initiation_attempts, archive_body_length, creation_time,
error_code, file_size
FROM prefetch_items
WHERE state = ?
)";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindInt(0, static_cast<int>(PrefetchItemState::FINISHED));
std::vector<PrefetchItemStats> urls;
while (statement.Step()) {
urls.emplace_back(statement.ColumnInt64(0), // offline_id
statement.ColumnInt(1), // generate_bundle_attempts
statement.ColumnInt(2), // get_operation_attempts
statement.ColumnInt(3), // download_initiation_attempts
statement.ColumnInt64(4), // archive_body_length
store_utils::FromDatabaseTime(
statement.ColumnInt64(5)), // creation_time
static_cast<PrefetchItemErrorCode>(
statement.ColumnInt(6)), // error_code
statement.ColumnInt64(7) // file_size
);
}
return urls;
}
bool MarkUrlAsZombie(sql::Connection* db,
base::Time freshness_time,
int64_t offline_id) {
static const char kSql[] =
"UPDATE prefetch_items SET state = ?, freshness_time = ? WHERE "
"offline_id = ?";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindInt(0, static_cast<int>(PrefetchItemState::ZOMBIE));
statement.BindInt(1, store_utils::ToDatabaseTime(freshness_time));
statement.BindInt64(2, offline_id);
return statement.Run();
}
void LogStateCountMetrics(PrefetchItemState state, int count) {
// The histogram below is an expansion of the UMA_HISTOGRAM_ENUMERATION
// macro adapted to allow for setting a count.
// Note: The factory creates and owns the histogram.
base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
"OfflinePages.Prefetching.StateCounts",
base::HistogramBase::kUmaTargetedHistogramFlag);
histogram->AddCount(static_cast<int>(state), count);
}
void CountEntriesInEachState(sql::Connection* db) {
static const char kSql[] =
"SELECT state, COUNT (*) FROM prefetch_items GROUP BY state";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
while (statement.Step()) {
PrefetchItemState state =
static_cast<PrefetchItemState>(statement.ColumnInt(0));
int count = statement.ColumnInt(1);
LogStateCountMetrics(state, count);
}
}
// These constants represent important indexes in the
// OfflinePrefetchArchiveActualSizeVsExpected histogram enum.
const int kEnumZeroArchiveBodyLength = 0;
const int kEnumRange0To100Min = 1;
const int kEnumRange0To100Max = 10;
const int kEnumSizeMatches100 = 11;
const int kEnumRange100To200OrMoreMin = 12;
const int kEnumRange100To200OrMoreMax = 22;
// Value meaning the file size should not be reported (not a valid index in the
// OfflinePrefetchArchiveActualSizeVsExpected enum).
const int kShouldNotReportFileSize = -1;
// Returned values conform to the definition of the
// OfflinePrefetchArchiveActualSizeVsExpected histogram enum. Except for -1
// which means "do not report".
int GetFileSizeEnumValueFor(int64_t archive_body_length, int64_t file_size) {
// Archiving service reported body length as zero.
if (archive_body_length == 0)
return kEnumZeroArchiveBodyLength;
// For other cases, reporting should only happen if both size values were set
// meaning the item reached at least the successfully downloaded state.
if (archive_body_length < 0 || file_size < 0)
return kShouldNotReportFileSize;
if (archive_body_length == file_size)
return kEnumSizeMatches100;
// For smaller sizes than expected, return an index in the 0% <= ratio < 100%
// range.
double ratio = static_cast<double>(file_size) / archive_body_length;
if (archive_body_length > file_size) {
return std::max(
kEnumRange0To100Min,
std::min(kEnumRange0To100Max, static_cast<int>(ratio * 10) + 1));
}
// Otherwise return an index in the 100% < ratio range.
return std::max(
kEnumRange100To200OrMoreMin,
std::min(kEnumRange100To200OrMoreMax, static_cast<int>(ratio * 10) + 2));
}
void ReportMetricsFor(const PrefetchItemStats& url, const base::Time now) {
// Lifetime reporting.
static const int kFourWeeksInSeconds =
base::TimeDelta::FromDays(28).InSeconds();
const bool successful = url.error_code == PrefetchItemErrorCode::SUCCESS;
int64_t lifetime_seconds = (now - url.creation_time).InSeconds();
if (successful) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"OfflinePages.Prefetching.ItemLifetime.Successful", lifetime_seconds, 1,
kFourWeeksInSeconds, 50);
} else {
UMA_HISTOGRAM_CUSTOM_COUNTS("OfflinePages.Prefetching.ItemLifetime.Failed",
lifetime_seconds, 1, kFourWeeksInSeconds, 50);
}
// Error code reporting.
base::UmaHistogramSparse("OfflinePages.Prefetching.FinishedItemErrorCode",
static_cast<int>(url.error_code));
// Unexpected file size reporting.
int file_size_enum_value =
GetFileSizeEnumValueFor(url.archive_body_length, url.file_size);
if (file_size_enum_value != kShouldNotReportFileSize) {
UMA_HISTOGRAM_ENUMERATION(
"OfflinePages.Prefetching.DownloadedArchiveSizeVsExpected",
file_size_enum_value, kEnumRange100To200OrMoreMax);
}
// Attempt counts reporting.
static const int kMaxPossibleRetries = 20;
UMA_HISTOGRAM_EXACT_LINEAR(
"OfflinePages.Prefetching.ActionAttempts.GeneratePageBundle",
url.generate_bundle_attempts, kMaxPossibleRetries);
UMA_HISTOGRAM_EXACT_LINEAR(
"OfflinePages.Prefetching.ActionAttempts.GetOperation",
url.get_operation_attempts, kMaxPossibleRetries);
UMA_HISTOGRAM_EXACT_LINEAR(
"OfflinePages.Prefetching.ActionAttempts.DownloadInitiation",
url.download_initiation_attempts, kMaxPossibleRetries);
}
bool ReportMetricsAndFinalizeSync(sql::Connection* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
// Gather metrics about the current number of entries with each state. Check
// before zombification so that we will be able to see entries in the finished
// state, otherwise we will only see zombies, never finished entries.
CountEntriesInEachState(db);
const std::vector<PrefetchItemStats> urls = FetchUrlsSync(db);
base::Time now = base::Time::Now();
for (const auto& url : urls) {
MarkUrlAsZombie(db, now, url.offline_id);
}
if (transaction.Commit()) {
for (const auto& url : urls) {
DVLOG(1) << "Finalized prefetch item with error code "
<< static_cast<int>(url.error_code) << ": (" << url.offline_id
<< ", " << url.generate_bundle_attempts << ", "
<< url.get_operation_attempts << ", "
<< url.download_initiation_attempts << ", "
<< url.archive_body_length << ", " << url.creation_time << ", "
<< url.file_size << ")";
ReportMetricsFor(url, now);
}
return true;
}
return false;
}
} // namespace
MetricsFinalizationTask::MetricsFinalizationTask(PrefetchStore* prefetch_store)
: prefetch_store_(prefetch_store), weak_factory_(this) {}
MetricsFinalizationTask::~MetricsFinalizationTask() {}
void MetricsFinalizationTask::Run() {
prefetch_store_->Execute(
base::BindOnce(&ReportMetricsAndFinalizeSync),
base::BindOnce(&MetricsFinalizationTask::MetricsFinalized,
weak_factory_.GetWeakPtr()),
false);
}
void MetricsFinalizationTask::MetricsFinalized(bool result) {
TaskComplete();
}
} // namespace offline_pages