Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(394)

Side by Side Diff: components/translate/core/browser/ranker_model_loader.cc

Issue 2925733002: Move ranker_model_loader to a new component. (Closed)
Patch Set: Adressing sdefresne's comments. Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/translate/core/browser/ranker_model_loader.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/profiler/scoped_tracker.h"
18 #include "base/sequenced_task_runner.h"
19 #include "base/strings/string_util.h"
20 #include "base/task_runner_util.h"
21 #include "base/task_scheduler/post_task.h"
22 #include "base/threading/sequenced_task_runner_handle.h"
23 #include "components/translate/core/browser/proto/ranker_model.pb.h"
24 #include "components/translate/core/browser/ranker_model.h"
25 #include "components/translate/core/browser/translate_url_fetcher.h"
26
27 namespace translate {
28 namespace {
29
30 using chrome_intelligence::RankerModel;
31 using chrome_intelligence::RankerModelProto;
32
33 constexpr int kUrlFetcherId = 2;
34
35 // The minimum duration, in minutes, between download attempts.
36 constexpr int kMinRetryDelayMins = 3;
37
38 // Suffixes for the various histograms produced by the backend.
39 const char kWriteTimerHistogram[] = ".Timer.WriteModel";
40 const char kReadTimerHistogram[] = ".Timer.ReadModel";
41 const char kDownloadTimerHistogram[] = ".Timer.DownloadModel";
42 const char kParsetimerHistogram[] = ".Timer.ParseModel";
43 const char kModelStatusHistogram[] = ".Model.Status";
44
45 // Helper function to UMA log a timer histograms.
46 void RecordTimerHistogram(const std::string& name, base::TimeDelta duration) {
47 base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
48 name, base::TimeDelta::FromMilliseconds(10),
49 base::TimeDelta::FromMilliseconds(200000), 100,
50 base::HistogramBase::kUmaTargetedHistogramFlag);
51 DCHECK(counter);
52 counter->AddTime(duration);
53 }
54
55 // A helper class to produce a scoped timer histogram that supports using a
56 // non-static-const name.
57 class MyScopedHistogramTimer {
58 public:
59 MyScopedHistogramTimer(const base::StringPiece& name)
60 : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {}
61
62 ~MyScopedHistogramTimer() {
63 RecordTimerHistogram(name_, base::TimeTicks::Now() - start_);
64 }
65
66 private:
67 const std::string name_;
68 const base::TimeTicks start_;
69
70 DISALLOW_COPY_AND_ASSIGN(MyScopedHistogramTimer);
71 };
72
73 std::string LoadFromFile(const base::FilePath& model_path) {
74 DCHECK(!model_path.empty());
75 DVLOG(2) << "Reading data from: " << model_path.value();
76 std::string data;
77 if (!base::ReadFileToString(model_path, &data) || data.empty()) {
78 DVLOG(2) << "Failed to read data from: " << model_path.value();
79 data.clear();
80 }
81 return data;
82 }
83
84 void SaveToFile(const GURL& model_url,
85 const base::FilePath& model_path,
86 const std::string& model_data,
87 const std::string& uma_prefix) {
88 DVLOG(2) << "Saving model from '" << model_url << "'' to '"
89 << model_path.value() << "'.";
90 MyScopedHistogramTimer timer(uma_prefix + kWriteTimerHistogram);
91 base::ImportantFileWriter::WriteFileAtomically(model_path, model_data);
92 }
93
94 } // namespace
95
96 RankerModelLoader::RankerModelLoader(
97 ValidateModelCallback validate_model_cb,
98 OnModelAvailableCallback on_model_available_cb,
99 base::FilePath model_path,
100 GURL model_url,
101 std::string uma_prefix)
102 : background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
103 {base::MayBlock(), base::TaskPriority::BACKGROUND,
104 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
105 validate_model_cb_(std::move(validate_model_cb)),
106 on_model_available_cb_(std::move(on_model_available_cb)),
107 model_path_(std::move(model_path)),
108 model_url_(std::move(model_url)),
109 uma_prefix_(std::move(uma_prefix)),
110 url_fetcher_(base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId)),
111 weak_ptr_factory_(this) {}
112
113 RankerModelLoader::~RankerModelLoader() {
114 DCHECK(sequence_checker_.CalledOnValidSequence());
115 }
116
117 void RankerModelLoader::NotifyOfRankerActivity() {
118 DCHECK(sequence_checker_.CalledOnValidSequence());
119 switch (state_) {
120 case LoaderState::NOT_STARTED:
121 if (!model_path_.empty()) {
122 StartLoadFromFile();
123 break;
124 }
125 // There was no configured model path. Switch the state to IDLE and
126 // fall through to consider the URL.
127 state_ = LoaderState::IDLE;
128 case LoaderState::IDLE:
129 if (model_url_.is_valid()) {
130 StartLoadFromURL();
131 break;
132 }
133 // There was no configured model URL. Switch the state to FINISHED and
134 // fall through.
135 state_ = LoaderState::FINISHED;
136 case LoaderState::FINISHED:
137 case LoaderState::LOADING_FROM_FILE:
138 case LoaderState::LOADING_FROM_URL:
139 // Nothing to do.
140 break;
141 }
142 }
143
144 void RankerModelLoader::StartLoadFromFile() {
145 DCHECK(sequence_checker_.CalledOnValidSequence());
146 DCHECK_EQ(state_, LoaderState::NOT_STARTED);
147 DCHECK(!model_path_.empty());
148 state_ = LoaderState::LOADING_FROM_FILE;
149 load_start_time_ = base::TimeTicks::Now();
150 base::PostTaskAndReplyWithResult(background_task_runner_.get(), FROM_HERE,
151 base::Bind(&LoadFromFile, model_path_),
152 base::Bind(&RankerModelLoader::OnFileLoaded,
153 weak_ptr_factory_.GetWeakPtr()));
154 }
155
156 void RankerModelLoader::OnFileLoaded(const std::string& data) {
157 DCHECK(sequence_checker_.CalledOnValidSequence());
158 DCHECK_EQ(state_, LoaderState::LOADING_FROM_FILE);
159
160 // Record the duration of the download.
161 RecordTimerHistogram(uma_prefix_ + kReadTimerHistogram,
162 base::TimeTicks::Now() - load_start_time_);
163
164 // Empty data means |model_path| wasn't successfully read. Otherwise,
165 // parse and validate the model.
166 std::unique_ptr<RankerModel> model;
167 if (data.empty()) {
168 ReportModelStatus(RankerModelStatus::LOAD_FROM_CACHE_FAILED);
169 } else {
170 model = CreateAndValidateModel(data);
171 }
172
173 // If |model| is nullptr, then data is empty or the parse failed. Transition
174 // to IDLE, from which URL download can be attempted.
175 if (!model) {
176 state_ = LoaderState::IDLE;
177 } else {
178 // The model is valid. The client is willing/able to use it. Keep track
179 // of where it originated and whether or not is has expired.
180 std::string url_spec = model->GetSourceURL();
181 bool is_expired = model->IsExpired();
182 bool is_finished = url_spec == model_url_.spec() && !is_expired;
183
184 DVLOG(2) << (is_expired ? "Expired m" : "M") << "odel in '"
185 << model_path_.value() << "' was originally downloaded from '"
186 << url_spec << "'.";
187
188 // If the cached model came from currently configured |model_url_| and has
189 // not expired, transition to FINISHED, as there is no need for a model
190 // download; otherwise, transition to IDLE.
191 state_ = is_finished ? LoaderState::FINISHED : LoaderState::IDLE;
192
193 // Transfer the model to the client.
194 on_model_available_cb_.Run(std::move(model));
195 }
196
197 // Notify the state machine. This will immediately kick off a download if
198 // one is required, instead of waiting for the next organic detection of
199 // ranker activity.
200 NotifyOfRankerActivity();
201 }
202
203 void RankerModelLoader::StartLoadFromURL() {
204 DCHECK(sequence_checker_.CalledOnValidSequence());
205 DCHECK_EQ(state_, LoaderState::IDLE);
206 DCHECK(model_url_.is_valid());
207
208 // Do nothing if the download attempts should be throttled.
209 if (base::TimeTicks::Now() < next_earliest_download_time_) {
210 DVLOG(2) << "Last download attempt was too recent.";
211 ReportModelStatus(RankerModelStatus::DOWNLOAD_THROTTLED);
212 return;
213 }
214
215 // Kick off the next download attempt and reset the time of the next earliest
216 // allowable download attempt.
217 DVLOG(2) << "Downloading model from: " << model_url_;
218 state_ = LoaderState::LOADING_FROM_URL;
219 load_start_time_ = base::TimeTicks::Now();
220 next_earliest_download_time_ =
221 load_start_time_ + base::TimeDelta::FromMinutes(kMinRetryDelayMins);
222 bool request_started = url_fetcher_->Request(
223 model_url_, base::Bind(&RankerModelLoader::OnURLFetched,
224 weak_ptr_factory_.GetWeakPtr()));
225
226 // |url_fetcher_| maintains a request retry counter. If all allowed attempts
227 // have already been exhausted, then the loader is finished and has abandoned
228 // loading the model.
229 if (!request_started) {
230 DVLOG(2) << "Model download abandoned.";
231 ReportModelStatus(RankerModelStatus::MODEL_LOADING_ABANDONED);
232 state_ = LoaderState::FINISHED;
233 }
234 }
235
236 void RankerModelLoader::OnURLFetched(int /* id */,
237 bool success,
238 const std::string& data) {
239 DCHECK(sequence_checker_.CalledOnValidSequence());
240 DCHECK_EQ(state_, LoaderState::LOADING_FROM_URL);
241
242 // Record the duration of the download.
243 RecordTimerHistogram(uma_prefix_ + kDownloadTimerHistogram,
244 base::TimeTicks::Now() - load_start_time_);
245
246 // On request failure, transition back to IDLE. The loader will retry, or
247 // enforce the max download attempts, later.
248 if (!success || data.empty()) {
249 DVLOG(2) << "Download from '" << model_url_ << "'' failed.";
250 ReportModelStatus(RankerModelStatus::DOWNLOAD_FAILED);
251 state_ = LoaderState::IDLE;
252 return;
253 }
254
255 // Attempt to loads the model. If this fails, transition back to IDLE. The
256 // loader will retry, or enfore the max download attempts, later.
257 auto model = CreateAndValidateModel(data);
258 if (!model) {
259 DVLOG(2) << "Model from '" << model_url_ << "'' not valid.";
260 state_ = LoaderState::IDLE;
261 return;
262 }
263
264 // The model is valid. Update the metadata to track the source URL and
265 // download timestamp.
266 auto* metadata = model->mutable_proto()->mutable_metadata();
267 metadata->set_source(model_url_.spec());
268 metadata->set_last_modified_sec(
269 (base::Time::Now() - base::Time()).InSeconds());
270
271 // Cache the model to model_path_, in the background.
272 if (!model_path_.empty()) {
273 background_task_runner_->PostTask(
274 FROM_HERE, base::BindOnce(&SaveToFile, model_url_, model_path_,
275 model->SerializeAsString(), uma_prefix_));
276 }
277
278 // The loader is finished. Transfer the model to the client.
279 state_ = LoaderState::FINISHED;
280 on_model_available_cb_.Run(std::move(model));
281 }
282
283 std::unique_ptr<chrome_intelligence::RankerModel>
284 RankerModelLoader::CreateAndValidateModel(const std::string& data) {
285 DCHECK(sequence_checker_.CalledOnValidSequence());
286 MyScopedHistogramTimer timer(uma_prefix_ + kParsetimerHistogram);
287 auto model = RankerModel::FromString(data);
288 if (ReportModelStatus(model ? validate_model_cb_.Run(*model)
289 : RankerModelStatus::PARSE_FAILED) !=
290 RankerModelStatus::OK) {
291 return nullptr;
292 }
293 return model;
294 }
295
296 RankerModelStatus RankerModelLoader::ReportModelStatus(
297 RankerModelStatus model_status) {
298 DCHECK(sequence_checker_.CalledOnValidSequence());
299 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
300 uma_prefix_ + kModelStatusHistogram, 1,
301 static_cast<int>(RankerModelStatus::MAX),
302 static_cast<int>(RankerModelStatus::MAX) + 1,
303 base::HistogramBase::kUmaTargetedHistogramFlag);
304 if (histogram)
305 histogram->Add(static_cast<int>(model_status));
306 return model_status;
307 }
308
309 } // namespace translate
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698