| Index: components/translate/core/browser/ranker_model_loader.cc
|
| diff --git a/components/translate/core/browser/ranker_model_loader.cc b/components/translate/core/browser/ranker_model_loader.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5ee147bf8230beb8cdcd385e6305b6478c8b9764
|
| --- /dev/null
|
| +++ b/components/translate/core/browser/ranker_model_loader.cc
|
| @@ -0,0 +1,269 @@
|
| +// Copyright 2016 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/translate/core/browser/ranker_model_loader.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/command_line.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/files/important_file_writer.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| +#include "base/profiler/scoped_tracker.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "base/task_runner.h"
|
| +#include "base/threading/platform_thread.h"
|
| +#include "components/translate/core/browser/translate_url_fetcher.h"
|
| +#include "components/translate/core/common/translate_switches.h"
|
| +#include "components/variations/variations_associated_data.h"
|
| +
|
| +namespace {
|
| +
|
| +class MyScopedHistogramTimer {
|
| + public:
|
| + MyScopedHistogramTimer(const base::StringPiece& name)
|
| + : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {}
|
| +
|
| + ~MyScopedHistogramTimer() {
|
| + base::TimeDelta duration = base::TimeTicks::Now() - start_;
|
| + base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
|
| + name_, base::TimeDelta::FromMilliseconds(10),
|
| + base::TimeDelta::FromMilliseconds(200000), 100,
|
| + base::HistogramBase::kUmaTargetedHistogramFlag);
|
| + if (counter)
|
| + counter->AddTime(duration);
|
| + }
|
| +
|
| + private:
|
| + const std::string name_;
|
| + const base::TimeTicks start_;
|
| +};
|
| +
|
| +bool IsExpired(const chrome_intelligence::RankerModel& model) {
|
| + DCHECK(model.has_metadata());
|
| + const auto& metadata = model.metadata();
|
| +
|
| + // If the age of the model cannot be determined, presume it to be expired.
|
| + if (!metadata.has_last_modified_sec())
|
| + return true;
|
| +
|
| + // If the model has no set cache duration, then it never expires.
|
| + if (!metadata.has_cache_duration_sec())
|
| + return false;
|
| +
|
| + // Otherwise, a model is expired if its age exceeds the cache duration.
|
| + base::Time last_modified =
|
| + base::Time() + base::TimeDelta::FromSeconds(metadata.last_modified_sec());
|
| + base::TimeDelta age = base::Time::Now() - last_modified;
|
| + return age > base::TimeDelta::FromSeconds(metadata.cache_duration_sec());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace translate {
|
| +
|
| +const int kUrlFetcherId = 2;
|
| +const char kWriteTimerHistogram[] = ".Timer.WriteModel";
|
| +const char kReadTimerHistogram[] = ".Timer.ReadModel";
|
| +const char kDownloadTimerHistogram[] = ".Timer.DownloadModel";
|
| +const char kParsetimerHistogram[] = ".Timer.ParseModel";
|
| +const char kModelStatusHistogram[] = ".Model.Status";
|
| +
|
| +RankerModelLoader::RankerModelLoader(RankerModelObserver* observer,
|
| + const base::FilePath& model_path,
|
| + const GURL& model_url,
|
| + const std::string& uma_prefix)
|
| + : thread_(uma_prefix + ".LoaderThread"),
|
| + observer_(observer),
|
| + model_path_(model_path),
|
| + model_url_(model_url),
|
| + uma_prefix_(uma_prefix) {
|
| + DCHECK(observer_ != nullptr);
|
| + base::Thread::Options options;
|
| + options.message_loop_type = base::MessageLoop::TYPE_IO;
|
| + options.priority = base::ThreadPriority::BACKGROUND;
|
| + thread_.StartWithOptions(options);
|
| +}
|
| +
|
| +RankerModelLoader::~RankerModelLoader() {}
|
| +
|
| +void RankerModelLoader::Start() {
|
| + thread_.task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RankerModelLoader::LoadFromFile, base::Unretained(this)));
|
| +}
|
| +
|
| +void RankerModelLoader::NotifyOfNetworkAvailability() {
|
| + if (url_fetcher_) {
|
| + thread_.task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RankerModelLoader::LoadFromURL, base::Unretained(this)));
|
| + }
|
| +}
|
| +
|
| +void RankerModelLoader::ReportModelStatus(RankerModelStatus model_status) {
|
| + base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
|
| + uma_prefix_ + kModelStatusHistogram, 1,
|
| + static_cast<int>(RankerModelStatus::MAX),
|
| + static_cast<int>(RankerModelStatus::MAX) + 1,
|
| + base::HistogramBase::kUmaTargetedHistogramFlag);
|
| + if (histogram)
|
| + histogram->Add(static_cast<int>(model_status));
|
| +}
|
| +
|
| +std::unique_ptr<chrome_intelligence::RankerModel> RankerModelLoader::Parse(
|
| + const std::string& data) {
|
| + MyScopedHistogramTimer timer(uma_prefix_ + kParsetimerHistogram);
|
| +
|
| + auto model = base::MakeUnique<chrome_intelligence::RankerModel>();
|
| +
|
| + if (!model->ParseFromString(data)) {
|
| + ReportModelStatus(RankerModelStatus::PARSE_FAILED);
|
| + return nullptr;
|
| + }
|
| +
|
| + RankerModelStatus model_status = observer_->Validate(*model);
|
| + ReportModelStatus(model_status);
|
| + if (model_status != RankerModelStatus::OK)
|
| + return nullptr;
|
| +
|
| + return model;
|
| +}
|
| +
|
| +void RankerModelLoader::LoadFromFile() {
|
| + // Attempt to read the model data from the cache file.
|
| + std::string data;
|
| + if (!model_path_.empty()) {
|
| + DVLOG(2) << "Attempting to load model from: " << model_path_.value();
|
| + MyScopedHistogramTimer timer(uma_prefix_ + kReadTimerHistogram);
|
| + if (!base::ReadFileToString(model_path_, &data)) {
|
| + DVLOG(2) << "Failed to read model from: " << model_path_.value();
|
| + data.clear();
|
| + }
|
| + }
|
| +
|
| + // If the model was successfully was read and is compatible, then notify
|
| + // the "owner" of this model loader of the models availability (transferring
|
| + // ownership of the model). If the model originated at the model_url_ then
|
| + // the model is also up to date, no need to download a new one.
|
| + if (!data.empty()) {
|
| + std::unique_ptr<chrome_intelligence::RankerModel> model = Parse(data);
|
| + if (model) {
|
| + DVLOG(2) << "Model in '" << model_path_.value()
|
| + << "'' was originally downloaded from '" << model_url_ << "'.";
|
| +
|
| + std::string source = model->metadata().source();
|
| + if (!IsExpired(*model)) {
|
| + TransferModelToObserver(std::move(model));
|
| + if (source == model_url_.spec())
|
| + return;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Reaching this point means that a model download is required. If there is
|
| + // no download URL configured, then there is nothing further to do.
|
| + if (!model_url_.is_valid())
|
| + return;
|
| +
|
| + // Otherwise, initialize the model fetcher to be non-null and trigger an
|
| + // initial download attempt.
|
| + url_fetcher_ = base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId);
|
| + url_fetcher_->set_max_retry_on_5xx(kMaxRetryOn5xx);
|
| + LoadFromURL();
|
| +}
|
| +
|
| +void RankerModelLoader::LoadFromURL() {
|
| + // Immediately exit if there is no download pending.
|
| + if (!url_fetcher_)
|
| + return;
|
| +
|
| + // If a request is already in flight, do not issue a new one.
|
| + if (url_fetcher_->state() == TranslateURLFetcher::REQUESTING) {
|
| + DVLOG(2) << "RankerModelLoader: Download is in progress.";
|
| + return;
|
| + }
|
| + // Do nothing if the download attempts should be throttled.
|
| + if (base::TimeTicks::Now() < next_earliest_download_time_) {
|
| + DVLOG(2) << "ModelLoadser: Last download attempt was too recent.";
|
| + return;
|
| + }
|
| +
|
| + DVLOG(2) << "Downloading model from: " << model_url_;
|
| +
|
| + // Reset the time of the next earliest allowable download attempt.
|
| + next_earliest_download_time_ =
|
| + base::TimeTicks::Now() +
|
| + base::TimeDelta::FromMinutes(kDownloadRefractoryPeriodMin);
|
| +
|
| + // Kick off the next download attempt.
|
| + download_start_time_ = base::TimeTicks::Now();
|
| + bool result = url_fetcher_->Request(
|
| + model_url_, base::Bind(&RankerModelLoader::OnDownloadComplete,
|
| + base::Unretained(this)));
|
| +
|
| + // The maximum number of download attempts has been surpassed. Don't make
|
| + // any further attempts.
|
| + if (!result) {
|
| + DVLOG(2) << "Model download abandoned.";
|
| + ReportModelStatus(RankerModelStatus::DOWNLOAD_FAILED);
|
| + url_fetcher_.reset();
|
| + }
|
| +}
|
| +
|
| +void RankerModelLoader::OnDownloadComplete(int /* id */,
|
| + bool success,
|
| + const std::string& data) {
|
| + // Record the duration of the download.
|
| + base::TimeDelta duration = base::TimeTicks::Now() - download_start_time_;
|
| + base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
|
| + uma_prefix_ + kDownloadTimerHistogram,
|
| + base::TimeDelta::FromMilliseconds(10),
|
| + base::TimeDelta::FromMilliseconds(200000), 100,
|
| + base::HistogramBase::kUmaTargetedHistogramFlag);
|
| + if (counter)
|
| + counter->AddTime(duration);
|
| +
|
| + // On failure, we just abort. The TranslateRanker will retry on a subsequent
|
| + // translation opportunity. The TranslateURLFetcher enforces a limit for
|
| + // retried requests.
|
| + if (!success) {
|
| + DVLOG(2) << "Download from '" << model_url_ << "'' failed.";
|
| + return;
|
| + }
|
| +
|
| + auto model = Parse(data);
|
| + if (!model) {
|
| + DVLOG(2) << "Model from '" << model_url_ << "'' failed to parse.";
|
| + return;
|
| + }
|
| + url_fetcher_.reset();
|
| +
|
| + auto* metadata = model->mutable_metadata();
|
| + metadata->set_source(model_url_.spec());
|
| + metadata->set_last_modified_sec(
|
| + (base::Time::Now() - base::Time()).InSeconds());
|
| +
|
| + if (!model_path_.empty()) {
|
| + DVLOG(2) << "Saving model from '" << model_url_ << "'' to '"
|
| + << model_path_.value() << "'.";
|
| + MyScopedHistogramTimer timer(uma_prefix_ + kWriteTimerHistogram);
|
| + base::ImportantFileWriter::WriteFileAtomically(model_path_,
|
| + model->SerializeAsString());
|
| + }
|
| +
|
| + // Notify the owner that a compatible model is available.
|
| + TransferModelToObserver(std::move(model));
|
| +}
|
| +
|
| +void RankerModelLoader::TransferModelToObserver(
|
| + std::unique_ptr<chrome_intelligence::RankerModel> model) {
|
| + observer_->OnModelAvailable(std::move(model));
|
| +}
|
| +
|
| +} // namespace translate
|
|
|