optimization_guide_store.cc 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221
  1. // Copyright 2019 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. #include "components/optimization_guide/core/optimization_guide_store.h"
  5. #include <memory>
  6. #include <string>
  7. #include "base/bind.h"
  8. #include "base/callback_helpers.h"
  9. #include "base/files/file_util.h"
  10. #include "base/logging.h"
  11. #include "base/metrics/histogram_functions.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/sequence_checker.h"
  14. #include "base/strings/strcat.h"
  15. #include "base/strings/string_number_conversions.h"
  16. #include "base/strings/string_util.h"
  17. #include "base/task/thread_pool.h"
  18. #include "components/leveldb_proto/public/proto_database.h"
  19. #include "components/leveldb_proto/public/proto_database_provider.h"
  20. #include "components/leveldb_proto/public/shared_proto_database_client_list.h"
  21. #include "components/optimization_guide/core/memory_hint.h"
  22. #include "components/optimization_guide/core/model_util.h"
  23. #include "components/optimization_guide/core/optimization_guide_features.h"
  24. #include "components/optimization_guide/core/optimization_guide_prefs.h"
  25. #include "components/optimization_guide/core/optimization_guide_util.h"
  26. #include "components/optimization_guide/proto/hint_cache.pb.h"
  27. #include "components/prefs/pref_service.h"
  28. #include "components/prefs/scoped_user_pref_update.h"
  29. #include "third_party/abseil-cpp/absl/types/optional.h"
  30. namespace optimization_guide {
  31. namespace {
  32. // Enforce that StoreEntryType enum is synced with the StoreEntryType proto
  33. // (components/previews/content/proto/hint_cache.proto)
  34. static_assert(
  35. proto::StoreEntryType_MAX ==
  36. static_cast<int>(OptimizationGuideStore::StoreEntryType::kMaxValue),
  37. "mismatched StoreEntryType enums");
  38. // The amount of data to build up in memory before converting to a sorted on-
  39. // disk file.
  40. constexpr size_t kDatabaseWriteBufferSizeBytes = 128 * 1024;
  41. // Delimiter that appears between the sections of a store entry key.
  42. // Examples:
  43. // "[StoreEntryType::kMetadata]_[MetadataType]"
  44. // "[StoreEntryType::kComponentHint]_[component_version]_[host]"
  45. constexpr char kKeySectionDelimiter = '_';
  46. // Enumerates the possible outcomes of loading metadata. Used in UMA histograms,
  47. // so the order of enumerators should not be changed.
  48. //
  49. // Keep in sync with OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult
  50. // in tools/metrics/histograms/enums.xml.
  51. enum class OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult {
  52. kSuccess = 0,
  53. kLoadMetadataFailed = 1,
  54. kSchemaMetadataMissing = 2,
  55. kSchemaMetadataWrongVersion = 3,
  56. kComponentMetadataMissing = 4,
  57. kFetchedMetadataMissing = 5,
  58. kComponentAndFetchedMetadataMissing = 6,
  59. kMaxValue = kComponentAndFetchedMetadataMissing,
  60. };
  61. // Util class for recording the result of loading the metadata. The result is
  62. // recorded when it goes out of scope and its destructor is called.
  63. class ScopedLoadMetadataResultRecorder {
  64. public:
  65. ScopedLoadMetadataResultRecorder() = default;
  66. ~ScopedLoadMetadataResultRecorder() {
  67. UMA_HISTOGRAM_ENUMERATION(
  68. "OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", result_);
  69. }
  70. void set_result(
  71. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result) {
  72. result_ = result;
  73. }
  74. private:
  75. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result_ =
  76. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::kSuccess;
  77. };
  78. void RecordStatusChange(OptimizationGuideStore::Status status) {
  79. UMA_HISTOGRAM_ENUMERATION("OptimizationGuide.HintCacheLevelDBStore.Status",
  80. status);
  81. }
  82. // Returns true if |key_prefix| is a prefix of |key|.
  83. bool DatabasePrefixFilter(const std::string& key_prefix,
  84. const std::string& key) {
  85. return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
  86. }
  87. // Returns true if |key| is in |key_set|.
  88. bool KeySetFilter(const base::flat_set<std::string>& key_set,
  89. const std::string& key) {
  90. return key_set.find(key) != key_set.end();
  91. }
  92. bool CheckAllPathsExist(
  93. const std::vector<base::FilePath>& file_paths_to_check) {
  94. for (const base::FilePath& file_path : file_paths_to_check) {
  95. if (!base::PathExists(file_path)) {
  96. return false;
  97. }
  98. }
  99. return true;
  100. }
  101. } // namespace
  102. OptimizationGuideStore::OptimizationGuideStore(
  103. leveldb_proto::ProtoDatabaseProvider* database_provider,
  104. const base::FilePath& database_dir,
  105. scoped_refptr<base::SequencedTaskRunner> store_task_runner,
  106. PrefService* pref_service)
  107. : store_task_runner_(store_task_runner), pref_service_(pref_service) {
  108. database_ = database_provider->GetDB<proto::StoreEntry>(
  109. leveldb_proto::ProtoDbType::HINT_CACHE_STORE, database_dir,
  110. store_task_runner_);
  111. RecordStatusChange(status_);
  112. // Clean up any file paths that were slated for deletion in previous sessions.
  113. CleanUpFilePaths();
  114. }
  115. OptimizationGuideStore::OptimizationGuideStore(
  116. std::unique_ptr<leveldb_proto::ProtoDatabase<proto::StoreEntry>> database,
  117. scoped_refptr<base::SequencedTaskRunner> store_task_runner,
  118. PrefService* pref_service)
  119. : database_(std::move(database)),
  120. store_task_runner_(store_task_runner),
  121. pref_service_(pref_service) {
  122. RecordStatusChange(status_);
  123. // Clean up any file paths that were slated for deletion in previous sessions.
  124. CleanUpFilePaths();
  125. }
  126. OptimizationGuideStore::~OptimizationGuideStore() {
  127. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  128. }
  129. void OptimizationGuideStore::Initialize(bool purge_existing_data,
  130. base::OnceClosure callback) {
  131. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  132. if (status_ >= Status::kInitializing) {
  133. // Already initializing - just run callback. There is an edge case where it
  134. // is still initializing and new callbacks will be run prematurely, but any
  135. // operations that need to deal with store require the background thread and
  136. // are guaranteed to happen after the first initialization has completed.
  137. std::move(callback).Run();
  138. return;
  139. }
  140. UpdateStatus(Status::kInitializing);
  141. // Asynchronously initialize the store and run the callback once
  142. // initialization completes. Initialization consists of the following steps:
  143. // 1. Initialize the database.
  144. // 2. If |purge_existing_data| is set to true, unconditionally purge
  145. // database and skip to step 6.
  146. // 3. Otherwise, retrieve the metadata entries (e.g. Schema and Component).
  147. // 4. If schema is the wrong version, purge database and skip to step 6.
  148. // 5. Otherwise, load all hint entry keys.
  149. // 6. Run callback after purging database or retrieving hint entry keys.
  150. leveldb_env::Options options = leveldb_proto::CreateSimpleOptions();
  151. options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
  152. database_->Init(options,
  153. base::BindOnce(&OptimizationGuideStore::OnDatabaseInitialized,
  154. weak_ptr_factory_.GetWeakPtr(),
  155. purge_existing_data, std::move(callback)));
  156. }
  157. std::unique_ptr<StoreUpdateData>
  158. OptimizationGuideStore::MaybeCreateUpdateDataForComponentHints(
  159. const base::Version& version) const {
  160. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  161. DCHECK(version.IsValid());
  162. if (!IsAvailable()) {
  163. return nullptr;
  164. }
  165. // Component updates are only permitted when the update version is newer than
  166. // the store's current one.
  167. if (component_version_.has_value() && version <= component_version_) {
  168. return nullptr;
  169. }
  170. // Create and return a StoreUpdateData object. This object has
  171. // hints from the component moved into it and organizes them in a format
  172. // usable by the store; the object will returned to the store in
  173. // StoreComponentHints().
  174. return StoreUpdateData::CreateComponentStoreUpdateData(version);
  175. }
  176. std::unique_ptr<StoreUpdateData>
  177. OptimizationGuideStore::CreateUpdateDataForFetchedHints(
  178. base::Time update_time) const {
  179. // Create and returns a StoreUpdateData object. This object has has hints
  180. // from the GetHintsResponse moved into and organizes them in a format
  181. // usable by the store. The object will be store with UpdateFetchedData().
  182. return StoreUpdateData::CreateFetchedStoreUpdateData(update_time);
  183. }
  184. void OptimizationGuideStore::UpdateComponentHints(
  185. std::unique_ptr<StoreUpdateData> component_data,
  186. base::OnceClosure callback) {
  187. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  188. DCHECK(component_data);
  189. DCHECK(component_data->component_version());
  190. if (!IsAvailable()) {
  191. std::move(callback).Run();
  192. return;
  193. }
  194. // If this isn't a newer component version than the store's current one, then
  195. // the simply return. There's nothing to update.
  196. if (component_version_.has_value() &&
  197. component_data->component_version() <= component_version_) {
  198. std::move(callback).Run();
  199. return;
  200. }
  201. // Set the component version prior to requesting the update. This ensures that
  202. // a second update request for the same component version won't be allowed. In
  203. // the case where the update fails, the store will become unavailable, so it's
  204. // safe to treat component version in the update as the new one.
  205. SetComponentVersion(*component_data->component_version());
  206. // The current keys are about to be removed, so clear out the keys available
  207. // within the store. The keys will be populated after the component data
  208. // update completes.
  209. entry_keys_.reset();
  210. // Purge any component hints that are missing the new component version
  211. // prefix.
  212. EntryKeyPrefix retain_prefix =
  213. GetComponentHintEntryKeyPrefix(component_version_.value());
  214. EntryKeyPrefix filter_prefix = GetComponentHintEntryKeyPrefixWithoutVersion();
  215. // Add the new component data and purge any old component hints from the db.
  216. // After processing finishes, OnUpdateStore() is called, which loads
  217. // the updated hint entry keys from the database.
  218. database_->UpdateEntriesWithRemoveFilter(
  219. component_data->TakeUpdateEntries(),
  220. base::BindRepeating(
  221. [](const EntryKeyPrefix& retain_prefix,
  222. const EntryKeyPrefix& filter_prefix, const std::string& key) {
  223. return key.compare(0, retain_prefix.length(), retain_prefix) != 0 &&
  224. key.compare(0, filter_prefix.length(), filter_prefix) == 0;
  225. },
  226. retain_prefix, filter_prefix),
  227. base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
  228. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  229. }
  230. void OptimizationGuideStore::UpdateFetchedHints(
  231. std::unique_ptr<StoreUpdateData> fetched_hints_data,
  232. base::OnceClosure callback) {
  233. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  234. DCHECK(fetched_hints_data);
  235. DCHECK(fetched_hints_data->update_time());
  236. if (!IsAvailable()) {
  237. std::move(callback).Run();
  238. return;
  239. }
  240. fetched_update_time_ = *fetched_hints_data->update_time();
  241. entry_keys_.reset();
  242. // This will remove the fetched metadata entry and insert all the entries
  243. // currently in |leveldb_fetched_hints_data|.
  244. database_->UpdateEntriesWithRemoveFilter(
  245. fetched_hints_data->TakeUpdateEntries(),
  246. base::BindRepeating(&DatabasePrefixFilter,
  247. GetMetadataTypeEntryKey(MetadataType::kFetched)),
  248. base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
  249. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  250. }
  251. void OptimizationGuideStore::PurgeExpiredFetchedHints() {
  252. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  253. if (!IsAvailable())
  254. return;
  255. // Load all the fetched hints to check their expiry times.
  256. database_->LoadKeysAndEntriesWithFilter(
  257. base::BindRepeating(&DatabasePrefixFilter,
  258. GetFetchedHintEntryKeyPrefix()),
  259. base::BindOnce(&OptimizationGuideStore::OnLoadEntriesToPurgeExpired,
  260. weak_ptr_factory_.GetWeakPtr()));
  261. }
  262. void OptimizationGuideStore::PurgeInactiveModels() {
  263. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  264. if (!IsAvailable())
  265. return;
  266. // Load all models to check their expiry times.
  267. database_->LoadKeysAndEntriesWithFilter(
  268. base::BindRepeating(&DatabasePrefixFilter,
  269. GetPredictionModelEntryKeyPrefix()),
  270. base::BindOnce(
  271. &OptimizationGuideStore::OnLoadModelsToBeUpdated,
  272. weak_ptr_factory_.GetWeakPtr(), std::make_unique<EntryVector>(),
  273. std::make_unique<leveldb_proto::KeyVector>(), base::DoNothing()));
  274. }
  275. void OptimizationGuideStore::OnLoadEntriesToPurgeExpired(
  276. bool success,
  277. std::unique_ptr<EntryMap> entries) {
  278. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  279. if (!success || !entries)
  280. return;
  281. EntryKeySet expired_keys_to_remove;
  282. int64_t now_since_epoch =
  283. base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
  284. for (const auto& entry : *entries) {
  285. if (entry.second.has_expiry_time_secs() &&
  286. entry.second.expiry_time_secs() <= now_since_epoch) {
  287. expired_keys_to_remove.insert(entry.first);
  288. }
  289. }
  290. entry_keys_.reset();
  291. database_->UpdateEntriesWithRemoveFilter(
  292. std::make_unique<EntryVector>(),
  293. base::BindRepeating(&KeySetFilter, std::move(expired_keys_to_remove)),
  294. base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
  295. weak_ptr_factory_.GetWeakPtr(), base::DoNothing()));
  296. }
  297. void OptimizationGuideStore::RemoveFetchedHintsByKey(
  298. base::OnceClosure on_success,
  299. const base::flat_set<std::string>& hint_keys) {
  300. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  301. EntryKeySet keys_to_remove;
  302. for (const std::string& key : hint_keys) {
  303. EntryKey store_key;
  304. if (FindEntryKeyForHostWithPrefix(key, &store_key,
  305. GetFetchedHintEntryKeyPrefix())) {
  306. keys_to_remove.insert(store_key);
  307. }
  308. }
  309. if (keys_to_remove.empty()) {
  310. std::move(on_success).Run();
  311. return;
  312. }
  313. for (const EntryKey& key : keys_to_remove) {
  314. entry_keys_->erase(key);
  315. }
  316. database_->UpdateEntriesWithRemoveFilter(
  317. std::make_unique<EntryVector>(),
  318. base::BindRepeating(&KeySetFilter, keys_to_remove),
  319. base::BindOnce(&OptimizationGuideStore::OnFetchedEntriesRemoved,
  320. weak_ptr_factory_.GetWeakPtr(), std::move(on_success),
  321. keys_to_remove));
  322. }
  323. void OptimizationGuideStore::OnFetchedEntriesRemoved(
  324. base::OnceClosure on_success,
  325. const EntryKeySet& keys,
  326. bool success) {
  327. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  328. if (!success) {
  329. UpdateStatus(Status::kFailed);
  330. // |on_success| is intentionally not run here since the operation did not
  331. // succeed.
  332. return;
  333. }
  334. std::move(on_success).Run();
  335. }
  336. bool OptimizationGuideStore::FindHintEntryKey(
  337. const std::string& host,
  338. EntryKey* out_hint_entry_key) const {
  339. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  340. // Search for kFetched hint entry keys first, fetched hints should be
  341. // fresher and preferred.
  342. if (FindEntryKeyForHostWithPrefix(host, out_hint_entry_key,
  343. GetFetchedHintEntryKeyPrefix())) {
  344. return true;
  345. }
  346. // Search for kComponent hint entry keys next.
  347. DCHECK(!component_version_.has_value() ||
  348. component_hint_entry_key_prefix_ ==
  349. GetComponentHintEntryKeyPrefix(component_version_.value()));
  350. if (FindEntryKeyForHostWithPrefix(host, out_hint_entry_key,
  351. component_hint_entry_key_prefix_)) {
  352. return true;
  353. }
  354. return false;
  355. }
  356. bool OptimizationGuideStore::FindEntryKeyForHostWithPrefix(
  357. const std::string& host,
  358. EntryKey* out_entry_key,
  359. const EntryKeyPrefix& entry_key_prefix) const {
  360. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  361. DCHECK(out_entry_key);
  362. // Look for entry key for host.
  363. *out_entry_key = entry_key_prefix + host;
  364. return entry_keys_ && entry_keys_->find(*out_entry_key) != entry_keys_->end();
  365. }
  366. void OptimizationGuideStore::LoadHint(const EntryKey& hint_entry_key,
  367. HintLoadedCallback callback) {
  368. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  369. if (!IsAvailable()) {
  370. std::move(callback).Run(hint_entry_key, nullptr);
  371. return;
  372. }
  373. database_->GetEntry(hint_entry_key,
  374. base::BindOnce(&OptimizationGuideStore::OnLoadHint,
  375. weak_ptr_factory_.GetWeakPtr(),
  376. hint_entry_key, std::move(callback)));
  377. }
  378. base::Time OptimizationGuideStore::GetFetchedHintsUpdateTime() const {
  379. // If the store is not available, the metadata entries have not been loaded
  380. // so there are no fetched hints.
  381. if (!IsAvailable())
  382. return base::Time();
  383. return fetched_update_time_;
  384. }
  385. // static
  386. const char OptimizationGuideStore::kStoreSchemaVersion[] = "1";
  387. // static
  388. OptimizationGuideStore::EntryKeyPrefix
  389. OptimizationGuideStore::GetMetadataEntryKeyPrefix() {
  390. return base::NumberToString(static_cast<int>(
  391. OptimizationGuideStore::StoreEntryType::kMetadata)) +
  392. kKeySectionDelimiter;
  393. }
  394. // static
  395. OptimizationGuideStore::EntryKey
  396. OptimizationGuideStore::GetMetadataTypeEntryKey(MetadataType metadata_type) {
  397. return GetMetadataEntryKeyPrefix() +
  398. base::NumberToString(static_cast<int>(metadata_type));
  399. }
  400. // static
  401. OptimizationGuideStore::EntryKeyPrefix
  402. OptimizationGuideStore::GetComponentHintEntryKeyPrefixWithoutVersion() {
  403. return base::NumberToString(static_cast<int>(
  404. OptimizationGuideStore::StoreEntryType::kComponentHint)) +
  405. kKeySectionDelimiter;
  406. }
  407. // static
  408. OptimizationGuideStore::EntryKeyPrefix
  409. OptimizationGuideStore::GetComponentHintEntryKeyPrefix(
  410. const base::Version& component_version) {
  411. return GetComponentHintEntryKeyPrefixWithoutVersion() +
  412. component_version.GetString() + kKeySectionDelimiter;
  413. }
  414. // static
  415. OptimizationGuideStore::EntryKeyPrefix
  416. OptimizationGuideStore::GetFetchedHintEntryKeyPrefix() {
  417. return base::NumberToString(static_cast<int>(
  418. OptimizationGuideStore::StoreEntryType::kFetchedHint)) +
  419. kKeySectionDelimiter;
  420. }
  421. // static
  422. OptimizationGuideStore::EntryKeyPrefix
  423. OptimizationGuideStore::GetPredictionModelEntryKeyPrefix() {
  424. return base::NumberToString(static_cast<int>(
  425. OptimizationGuideStore::StoreEntryType::kPredictionModel)) +
  426. kKeySectionDelimiter;
  427. }
  428. // static
  429. proto::OptimizationTarget
  430. OptimizationGuideStore::GetOptimizationTargetFromPredictionModelEntryKey(
  431. const EntryKey& prediction_model_entry_key) {
  432. base::StringPiece optimization_target_number_string =
  433. base::TrimString(base::StringPiece(prediction_model_entry_key),
  434. base::StringPiece(GetPredictionModelEntryKeyPrefix()),
  435. base::TRIM_LEADING);
  436. int optimization_target_number;
  437. if (!base::StringToInt(optimization_target_number_string,
  438. &optimization_target_number)) {
  439. return proto::OPTIMIZATION_TARGET_UNKNOWN;
  440. }
  441. if (!proto::OptimizationTarget_IsValid(optimization_target_number)) {
  442. return proto::OPTIMIZATION_TARGET_UNKNOWN;
  443. }
  444. return static_cast<proto::OptimizationTarget>(optimization_target_number);
  445. }
  446. void OptimizationGuideStore::UpdateStatus(Status new_status) {
  447. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  448. // Status::kUninitialized can only transition to Status::kInitializing.
  449. DCHECK(status_ != Status::kUninitialized ||
  450. new_status == Status::kInitializing);
  451. // Status::kInitializing can only transition to Status::kAvailable or
  452. // Status::kFailed.
  453. DCHECK(status_ != Status::kInitializing || new_status == Status::kAvailable ||
  454. new_status == Status::kFailed);
  455. // Status::kAvailable can only transition to kStatus::Failed.
  456. DCHECK(status_ != Status::kAvailable || new_status == Status::kFailed);
  457. // The status can never transition from Status::kFailed.
  458. DCHECK(status_ != Status::kFailed || new_status == Status::kFailed);
  459. // If the status is not changing, simply return; the remaining logic handles
  460. // status changes.
  461. if (status_ == new_status) {
  462. return;
  463. }
  464. status_ = new_status;
  465. RecordStatusChange(status_);
  466. if (status_ == Status::kFailed) {
  467. database_->Destroy(
  468. base::BindOnce(&OptimizationGuideStore::OnDatabaseDestroyed,
  469. weak_ptr_factory_.GetWeakPtr()));
  470. ClearComponentVersion();
  471. entry_keys_.reset();
  472. }
  473. }
  474. bool OptimizationGuideStore::IsAvailable() const {
  475. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  476. return status_ == Status::kAvailable;
  477. }
  478. void OptimizationGuideStore::PurgeDatabase(base::OnceClosure callback) {
  479. // When purging the database, update the schema version to the current one.
  480. EntryKey schema_entry_key = GetMetadataTypeEntryKey(MetadataType::kSchema);
  481. proto::StoreEntry schema_entry;
  482. schema_entry.set_version(kStoreSchemaVersion);
  483. auto entries_to_save = std::make_unique<EntryVector>();
  484. entries_to_save->emplace_back(schema_entry_key, schema_entry);
  485. database_->UpdateEntriesWithRemoveFilter(
  486. std::move(entries_to_save),
  487. base::BindRepeating(
  488. [](const std::string& schema_entry_key, const std::string& key) {
  489. return key.compare(0, schema_entry_key.length(),
  490. schema_entry_key) != 0;
  491. },
  492. schema_entry_key),
  493. base::BindOnce(&OptimizationGuideStore::OnPurgeDatabase,
  494. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  495. }
  496. void OptimizationGuideStore::SetComponentVersion(
  497. const base::Version& component_version) {
  498. DCHECK(component_version.IsValid());
  499. component_version_ = component_version;
  500. component_hint_entry_key_prefix_ =
  501. GetComponentHintEntryKeyPrefix(component_version_.value());
  502. }
  503. void OptimizationGuideStore::ClearComponentVersion() {
  504. component_version_.reset();
  505. component_hint_entry_key_prefix_.clear();
  506. }
  507. void OptimizationGuideStore::ClearFetchedHintsFromDatabase() {
  508. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  509. base::UmaHistogramBoolean(
  510. "OptimizationGuide.ClearFetchedHints.StoreAvailable", IsAvailable());
  511. if (!IsAvailable())
  512. return;
  513. auto entries_to_save = std::make_unique<EntryVector>();
  514. // TODO(mcrouse): Add histogram to record the number of hints being removed.
  515. entry_keys_.reset();
  516. // Removes all |kFetchedHint| store entries. OnUpdateStore will handle
  517. // updating status and re-filling entry_keys with the entries still in the
  518. // store.
  519. database_->UpdateEntriesWithRemoveFilter(
  520. std::move(entries_to_save), // this should be empty.
  521. base::BindRepeating(&DatabasePrefixFilter,
  522. GetFetchedHintEntryKeyPrefix()),
  523. base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
  524. weak_ptr_factory_.GetWeakPtr(), base::DoNothing()));
  525. }
  526. void OptimizationGuideStore::MaybeLoadEntryKeys(base::OnceClosure callback) {
  527. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  528. // If the database is unavailable don't load the hint keys. Simply run the
  529. // callback.
  530. if (!IsAvailable()) {
  531. std::move(callback).Run();
  532. return;
  533. }
  534. // Create a new KeySet object. This is populated by the store's keys as the
  535. // filter is run with each key on the DB's background thread. The filter
  536. // itself always returns false, ensuring that no entries are ever actually
  537. // loaded by the DB. Ownership of the KeySet is passed into the
  538. // LoadKeysAndEntriesCallback callback, guaranteeing that the KeySet has a
  539. // lifespan longer than the filter calls.
  540. std::unique_ptr<EntryKeySet> entry_keys(std::make_unique<EntryKeySet>());
  541. EntryKeySet* raw_entry_keys_pointer = entry_keys.get();
  542. database_->LoadKeysAndEntriesWithFilter(
  543. base::BindRepeating(
  544. [](EntryKeySet* entry_keys, const std::string& filter_prefix,
  545. const std::string& entry_key) {
  546. if (entry_key.compare(0, filter_prefix.length(), filter_prefix) !=
  547. 0) {
  548. entry_keys->insert(entry_key);
  549. }
  550. return false;
  551. },
  552. raw_entry_keys_pointer, GetMetadataEntryKeyPrefix()),
  553. base::BindOnce(&OptimizationGuideStore::OnLoadEntryKeys,
  554. weak_ptr_factory_.GetWeakPtr(), std::move(entry_keys),
  555. std::move(callback)));
  556. }
  557. size_t OptimizationGuideStore::GetEntryKeyCount() const {
  558. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  559. return entry_keys_ ? entry_keys_->size() : 0;
  560. }
  561. void OptimizationGuideStore::OnDatabaseInitialized(
  562. bool purge_existing_data,
  563. base::OnceClosure callback,
  564. leveldb_proto::Enums::InitStatus status) {
  565. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  566. if (status != leveldb_proto::Enums::InitStatus::kOK) {
  567. UpdateStatus(Status::kFailed);
  568. std::move(callback).Run();
  569. return;
  570. }
  571. // If initialization is set to purge all existing data, then simply trigger
  572. // the purge and return. There's no need to load metadata entries that'll
  573. // immediately be purged.
  574. if (purge_existing_data) {
  575. PurgeDatabase(std::move(callback));
  576. return;
  577. }
  578. // Load all entries from the DB with the metadata key prefix.
  579. database_->LoadKeysAndEntriesWithFilter(
  580. leveldb_proto::KeyFilter(), leveldb::ReadOptions(),
  581. GetMetadataEntryKeyPrefix(),
  582. base::BindOnce(&OptimizationGuideStore::OnLoadMetadata,
  583. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  584. }
  585. void OptimizationGuideStore::OnDatabaseDestroyed(bool /*success*/) {
  586. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  587. }
  588. void OptimizationGuideStore::OnLoadMetadata(
  589. base::OnceClosure callback,
  590. bool success,
  591. std::unique_ptr<EntryMap> metadata_entries) {
  592. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  593. // Create a scoped load metadata result recorder. It records the result when
  594. // its destructor is called.
  595. ScopedLoadMetadataResultRecorder result_recorder;
  596. if (!success || !metadata_entries) {
  597. result_recorder.set_result(
  598. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  599. kLoadMetadataFailed);
  600. UpdateStatus(Status::kFailed);
  601. std::move(callback).Run();
  602. return;
  603. }
  604. // If the schema version in the DB is not the current version, then purge
  605. // the database.
  606. auto schema_entry =
  607. metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kSchema));
  608. if (schema_entry == metadata_entries->end() ||
  609. !schema_entry->second.has_version() ||
  610. schema_entry->second.version() != kStoreSchemaVersion) {
  611. if (schema_entry == metadata_entries->end()) {
  612. result_recorder.set_result(
  613. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  614. kSchemaMetadataMissing);
  615. } else {
  616. result_recorder.set_result(
  617. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  618. kSchemaMetadataWrongVersion);
  619. }
  620. PurgeDatabase(std::move(callback));
  621. return;
  622. }
  623. // If the component metadata entry exists, then use it to set the component
  624. // version.
  625. bool component_metadata_missing = false;
  626. auto component_entry =
  627. metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kComponent));
  628. if (component_entry != metadata_entries->end()) {
  629. DCHECK(component_entry->second.has_version());
  630. SetComponentVersion(base::Version(component_entry->second.version()));
  631. } else {
  632. result_recorder.set_result(
  633. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  634. kComponentMetadataMissing);
  635. component_metadata_missing = true;
  636. }
  637. auto fetched_entry =
  638. metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kFetched));
  639. if (fetched_entry != metadata_entries->end()) {
  640. DCHECK(fetched_entry->second.has_update_time_secs());
  641. fetched_update_time_ = base::Time::FromDeltaSinceWindowsEpoch(
  642. base::Seconds(fetched_entry->second.update_time_secs()));
  643. } else {
  644. if (component_metadata_missing) {
  645. result_recorder.set_result(
  646. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  647. kComponentAndFetchedMetadataMissing);
  648. } else {
  649. result_recorder.set_result(
  650. OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
  651. kFetchedMetadataMissing);
  652. }
  653. fetched_update_time_ = base::Time();
  654. }
  655. UpdateStatus(Status::kAvailable);
  656. MaybeLoadEntryKeys(std::move(callback));
  657. }
  658. void OptimizationGuideStore::OnPurgeDatabase(base::OnceClosure callback,
  659. bool success) {
  660. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  661. // The database can only be purged during initialization.
  662. DCHECK_EQ(status_, Status::kInitializing);
  663. UpdateStatus(success ? Status::kAvailable : Status::kFailed);
  664. std::move(callback).Run();
  665. }
  666. void OptimizationGuideStore::OnUpdateStore(base::OnceClosure callback,
  667. bool success) {
  668. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  669. if (!success) {
  670. UpdateStatus(Status::kFailed);
  671. std::move(callback).Run();
  672. return;
  673. }
  674. MaybeLoadEntryKeys(std::move(callback));
  675. }
  676. void OptimizationGuideStore::OnLoadEntryKeys(
  677. std::unique_ptr<EntryKeySet> hint_entry_keys,
  678. base::OnceClosure callback,
  679. bool success,
  680. std::unique_ptr<EntryMap> /*unused*/) {
  681. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  682. if (!success) {
  683. UpdateStatus(Status::kFailed);
  684. std::move(callback).Run();
  685. return;
  686. }
  687. // If the store was set to unavailable after the request was started, then the
  688. // loaded keys should not be considered valid. Reset the keys so that they are
  689. // cleared.
  690. if (!IsAvailable())
  691. hint_entry_keys.reset();
  692. entry_keys_ = std::move(hint_entry_keys);
  693. std::move(callback).Run();
  694. }
  695. void OptimizationGuideStore::OnLoadHint(
  696. const std::string& entry_key,
  697. HintLoadedCallback callback,
  698. bool success,
  699. std::unique_ptr<proto::StoreEntry> entry) {
  700. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  701. // If either the request failed, the store was set to unavailable after the
  702. // request was started, or there's an in-flight component data update, which
  703. // means the entry is about to be invalidated, then the loaded hint should
  704. // not be considered valid. Reset the entry so that no hint is returned to
  705. // the requester.
  706. if (!success || !IsAvailable()) {
  707. entry.reset();
  708. }
  709. if (!entry || !entry->has_hint()) {
  710. std::unique_ptr<MemoryHint> memory_hint;
  711. std::move(callback).Run(entry_key, std::move(memory_hint));
  712. return;
  713. }
  714. if (entry->has_expiry_time_secs() &&
  715. entry->expiry_time_secs() <=
  716. base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds()) {
  717. // An expired hint should be loaded rarely if the user is regularly fetching
  718. // and storing fresh hints. Expired fetched hints are removed each time
  719. // fresh hints are fetched and placed into the store. In the future, the
  720. // expired hints could be asynchronously removed if necessary.
  721. // An empty hint is returned instead of the expired one.
  722. LOCAL_HISTOGRAM_BOOLEAN(
  723. "OptimizationGuide.HintCacheStore.OnLoadHint.FetchedHintExpired", true);
  724. std::unique_ptr<MemoryHint> memory_hint(nullptr);
  725. std::move(callback).Run(entry_key, std::move(memory_hint));
  726. return;
  727. }
  728. StoreEntryType store_entry_type =
  729. static_cast<StoreEntryType>(entry->entry_type());
  730. UMA_HISTOGRAM_ENUMERATION("OptimizationGuide.HintCache.HintType.Loaded",
  731. store_entry_type);
  732. absl::optional<base::Time> expiry_time;
  733. if (entry->has_expiry_time_secs()) {
  734. expiry_time = base::Time::FromDeltaSinceWindowsEpoch(
  735. base::Seconds(entry->expiry_time_secs()));
  736. LOCAL_HISTOGRAM_CUSTOM_TIMES(
  737. "OptimizationGuide.HintCache.FetchedHint.TimeToExpiration",
  738. *expiry_time - base::Time::Now(), base::Hours(1), base::Days(15), 50);
  739. }
  740. std::move(callback).Run(
  741. entry_key,
  742. std::make_unique<MemoryHint>(
  743. expiry_time, std::unique_ptr<proto::Hint>(entry->release_hint())));
  744. }
  745. std::unique_ptr<StoreUpdateData>
  746. OptimizationGuideStore::CreateUpdateDataForPredictionModels(
  747. base::Time expiry_time) const {
  748. // Create and returns a StoreUpdateData object. This object has prediction
  749. // models from the GetModelsResponse moved into and organizes them in a format
  750. // usable by the store. The object will be stored with
  751. // UpdatePredictionModels().
  752. return StoreUpdateData::CreatePredictionModelStoreUpdateData(expiry_time);
  753. }
  754. void OptimizationGuideStore::UpdatePredictionModels(
  755. std::unique_ptr<StoreUpdateData> prediction_models_update_data,
  756. base::OnceClosure callback) {
  757. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  758. DCHECK(prediction_models_update_data);
  759. if (!IsAvailable()) {
  760. std::move(callback).Run();
  761. return;
  762. }
  763. std::unique_ptr<EntryVector> entry_vector =
  764. prediction_models_update_data->TakeUpdateEntries();
  765. EntryKeySet keys_to_update;
  766. for (const auto& entry : *entry_vector)
  767. keys_to_update.insert(entry.first);
  768. // Load the models that are to be updated and delete the old model file, if
  769. // applicable.
  770. database_->LoadKeysAndEntriesWithFilter(
  771. base::BindRepeating(&KeySetFilter, std::move(keys_to_update)),
  772. base::BindOnce(&OptimizationGuideStore::OnLoadModelsToBeUpdated,
  773. weak_ptr_factory_.GetWeakPtr(), std::move(entry_vector),
  774. std::make_unique<leveldb_proto::KeyVector>(),
  775. std::move(callback)));
  776. }
  777. // This method is called during browser startup when we need to check if any
  778. // models have expired. When this occurs, |update_vector| and |remove_vector|
  779. // will both be empty. Otherwise, this method may also be called whenever a
  780. // model is updated or its deletion is requested in which case one of or both
  781. // |update_vector| and |remove_vector| will have entries.
  782. void OptimizationGuideStore::OnLoadModelsToBeUpdated(
  783. std::unique_ptr<EntryVector> update_vector,
  784. std::unique_ptr<leveldb_proto::KeyVector> remove_vector,
  785. base::OnceClosure callback,
  786. bool success,
  787. std::unique_ptr<EntryMap> entries) {
  788. if (!success || !entries) {
  789. std::move(callback).Run();
  790. return;
  791. }
  792. int64_t now_since_epoch =
  793. base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
  794. bool had_entries_to_update_or_remove =
  795. !update_vector->empty() || !remove_vector->empty();
  796. for (const auto& entry : *entries) {
  797. absl::optional<std::string> delete_download_file;
  798. if (had_entries_to_update_or_remove &&
  799. entry.second.has_prediction_model() &&
  800. !entry.second.prediction_model().model().download_url().empty()) {
  801. delete_download_file =
  802. entry.second.prediction_model().model().download_url();
  803. }
  804. // Only check expiry if we weren't explicitly passed in entries to update or
  805. // remove.
  806. if (!had_entries_to_update_or_remove) {
  807. if (entry.second.keep_beyond_valid_duration()) {
  808. continue;
  809. }
  810. if (entry.second.has_expiry_time_secs()) {
  811. if (entry.second.expiry_time_secs() <= now_since_epoch) {
  812. // Update the entry to remove the model.
  813. if (entry.second.has_prediction_model() &&
  814. !entry.second.prediction_model().model().download_url().empty()) {
  815. delete_download_file =
  816. entry.second.prediction_model().model().download_url();
  817. }
  818. remove_vector->push_back(entry.first);
  819. proto::OptimizationTarget optimization_target =
  820. GetOptimizationTargetFromPredictionModelEntryKey(entry.first);
  821. base::UmaHistogramBoolean(
  822. "OptimizationGuide.PredictionModelExpired." +
  823. GetStringNameForOptimizationTarget(optimization_target),
  824. true);
  825. base::UmaHistogramSparse(
  826. "OptimizationGuide.PredictionModelExpiredVersion." +
  827. GetStringNameForOptimizationTarget(optimization_target),
  828. entry.second.prediction_model().model_info().version());
  829. }
  830. } else {
  831. // If we were checking expiry and the entry did not have an expiration
  832. // time associated with it, add one with a default TTL.
  833. update_vector->push_back(entry);
  834. update_vector->back().second.set_expiry_time_secs(
  835. now_since_epoch +
  836. features::StoredModelsValidDuration().InSeconds());
  837. }
  838. }
  839. // Delete files (the model itself and any additional files) that are
  840. // provided by the model in its directory.
  841. if (delete_download_file) {
  842. // |StringToFilePath| only returns nullopt when
  843. // |delete_download_file| is empty.
  844. base::FilePath model_file_path =
  845. StringToFilePath(*delete_download_file).value();
  846. base::FilePath path_to_delete;
  847. // Backwards compatibility: Once upon a time (<M93), model files were
  848. // stored as
  849. // `$CHROME_DATA/OptGuideModels/${MODELTARGET}_${MODELVERSION}.tfl` but
  850. // were later moved to
  851. // `$CHROME_DATA/OptGuideModels/${MODELTARGET}_${MODELVERSION}/model.tfl`
  852. // to support additional files to be packaged alongside the model. Since
  853. // the current code needs to recursively delete the whole directory, we'd
  854. // normally just take the directory name of the model file. However, doing
  855. // this on a freshly updated browser to newer code would cause the entire
  856. // OptGuide directory to be blown away, causing collateral damage to other
  857. // downloaded models. This is detected by checking whether the base name
  858. // of the model file is the old or new version, and acting accordingly.
  859. if (model_file_path.BaseName() == GetBaseFileNameForModels()) {
  860. path_to_delete = model_file_path.DirName();
  861. } else {
  862. path_to_delete = model_file_path;
  863. }
  864. if (pref_service_) {
  865. DictionaryPrefUpdate pref_update(pref_service_,
  866. prefs::kStoreFilePathsToDelete);
  867. pref_update->SetBoolKey(FilePathToString(path_to_delete), true);
  868. } else {
  869. // |pref_service_| should always be provided by owning classes; however,
  870. // if it is not, just default back to deleting it here. This has the
  871. // potential to be racy though.
  872. // Note that the delete function doesn't care whether the target is a
  873. // directory or file. But in the case of a directory, it is recursively
  874. // deleted.
  875. store_task_runner_->PostTask(
  876. FROM_HERE, base::GetDeletePathRecursivelyCallback(path_to_delete));
  877. }
  878. }
  879. }
  880. database_->UpdateEntries(
  881. std::move(update_vector), std::move(remove_vector),
  882. base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
  883. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  884. }
  885. bool OptimizationGuideStore::FindPredictionModelEntryKey(
  886. proto::OptimizationTarget optimization_target,
  887. OptimizationGuideStore::EntryKey* out_prediction_model_entry_key) {
  888. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  889. if (!entry_keys_)
  890. return false;
  891. *out_prediction_model_entry_key =
  892. GetPredictionModelEntryKeyPrefix() +
  893. base::NumberToString(static_cast<int>(optimization_target));
  894. return entry_keys_->find(*out_prediction_model_entry_key) !=
  895. entry_keys_->end();
  896. }
  897. bool OptimizationGuideStore::RemovePredictionModelFromEntryKey(
  898. const EntryKey& entry_key) {
  899. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  900. if (!IsAvailable() || !entry_keys_ ||
  901. entry_keys_->find(entry_key) == entry_keys_->end()) {
  902. return false;
  903. }
  904. auto key_to_remove = std::make_unique<leveldb_proto::KeyVector>();
  905. key_to_remove->push_back(entry_key);
  906. EntryKeySet key_set;
  907. key_set.insert(entry_key);
  908. // Load the model that is to be removed and delete the old model file, if
  909. // applicable.
  910. database_->LoadKeysAndEntriesWithFilter(
  911. base::BindRepeating(&KeySetFilter, std::move(key_set)),
  912. base::BindOnce(&OptimizationGuideStore::OnLoadModelsToBeUpdated,
  913. weak_ptr_factory_.GetWeakPtr(),
  914. std::make_unique<EntryVector>(), std::move(key_to_remove),
  915. base::DoNothing()));
  916. return true;
  917. }
  918. void OptimizationGuideStore::LoadPredictionModel(
  919. const EntryKey& prediction_model_entry_key,
  920. PredictionModelLoadedCallback callback) {
  921. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  922. if (!IsAvailable()) {
  923. std::move(callback).Run(nullptr);
  924. return;
  925. }
  926. database_->GetEntry(
  927. prediction_model_entry_key,
  928. base::BindOnce(&OptimizationGuideStore::OnLoadPredictionModel,
  929. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  930. }
  931. void OptimizationGuideStore::OnLoadPredictionModel(
  932. PredictionModelLoadedCallback callback,
  933. bool success,
  934. std::unique_ptr<proto::StoreEntry> entry) {
  935. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  936. // If either the request failed or the store was set to unavailable after the
  937. // request was started, then the loaded model should not be considered valid.
  938. // Reset the entry so that nothing is returned to
  939. // the requester.
  940. if (!success || !IsAvailable())
  941. entry.reset();
  942. if (!entry || !entry->has_prediction_model()) {
  943. std::unique_ptr<proto::PredictionModel> loaded_prediction_model(nullptr);
  944. std::move(callback).Run(std::move(loaded_prediction_model));
  945. return;
  946. }
  947. // Also ensure that nothing is returned if the model is expired.
  948. int64_t now_since_epoch =
  949. base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
  950. if (!entry->keep_beyond_valid_duration() &&
  951. entry->expiry_time_secs() <= now_since_epoch) {
  952. // Leave the actual model deletions to |PurgeInactiveModels| and return
  953. // early.
  954. std::unique_ptr<proto::PredictionModel> loaded_prediction_model(nullptr);
  955. std::move(callback).Run(std::move(loaded_prediction_model));
  956. return;
  957. }
  958. std::unique_ptr<proto::PredictionModel> loaded_prediction_model(
  959. entry->release_prediction_model());
  960. if (loaded_prediction_model->model().download_url().empty()) {
  961. std::move(callback).Run(std::move(loaded_prediction_model));
  962. return;
  963. }
  964. // Make sure the model file path and all additional files still exist before
  965. // we send it back to the load initiator.
  966. std::vector<base::FilePath> file_paths_to_check;
  967. absl::optional<base::FilePath> model_file_path =
  968. StringToFilePath(loaded_prediction_model->model().download_url());
  969. if (model_file_path) {
  970. file_paths_to_check.emplace_back(*model_file_path);
  971. }
  972. for (const proto::AdditionalModelFile& additional_file :
  973. loaded_prediction_model->model_info().additional_files()) {
  974. absl::optional<base::FilePath> additional_file_path =
  975. StringToFilePath(additional_file.file_path());
  976. if (additional_file_path) {
  977. file_paths_to_check.emplace_back(*additional_file_path);
  978. }
  979. }
  980. store_task_runner_->PostTaskAndReplyWithResult(
  981. FROM_HERE, base::BindOnce(&CheckAllPathsExist, file_paths_to_check),
  982. base::BindOnce(&OptimizationGuideStore::OnModelFilePathVerified,
  983. weak_ptr_factory_.GetWeakPtr(),
  984. std::move(loaded_prediction_model), std::move(callback)));
  985. }
  986. void OptimizationGuideStore::OnModelFilePathVerified(
  987. std::unique_ptr<proto::PredictionModel> loaded_model,
  988. PredictionModelLoadedCallback callback,
  989. bool success) {
  990. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  991. base::UmaHistogramBoolean(
  992. "OptimizationGuide.ModelFilesVerified." +
  993. GetStringNameForOptimizationTarget(
  994. loaded_model->model_info().optimization_target()),
  995. success);
  996. if (success) {
  997. std::move(callback).Run(std::move(loaded_model));
  998. return;
  999. }
  1000. // If the model no longer exists, remove the prediction model from the store.
  1001. DCHECK(loaded_model);
  1002. OptimizationGuideStore::EntryKey model_entry_key;
  1003. if (FindPredictionModelEntryKey(
  1004. loaded_model->model_info().optimization_target(), &model_entry_key)) {
  1005. RemovePredictionModelFromEntryKey(model_entry_key);
  1006. }
  1007. std::move(callback).Run(nullptr);
  1008. }
  1009. void OptimizationGuideStore::CleanUpFilePaths() {
  1010. if (!pref_service_) {
  1011. return;
  1012. }
  1013. DictionaryPrefUpdate file_paths_to_delete_pref(
  1014. pref_service_, prefs::kStoreFilePathsToDelete);
  1015. for (const auto entry : file_paths_to_delete_pref->DictItems()) {
  1016. absl::optional<base::FilePath> path_to_delete =
  1017. StringToFilePath(entry.first);
  1018. if (!path_to_delete) {
  1019. // This is probably not a real file path so delete it from the pref, so we
  1020. // don't go through this sequence again.
  1021. OnFilePathDeleted(entry.first, /*success=*/true);
  1022. continue;
  1023. }
  1024. // Note that the delete function doesn't care whether the target is a
  1025. // directory or file. But in the case of a directory, it is recursively
  1026. // deleted.
  1027. //
  1028. // We post it to the generic thread pool since we don't really care what
  1029. // thread deletes it at this point as long as it gets deleted.
  1030. base::ThreadPool::PostTaskAndReplyWithResult(
  1031. FROM_HERE,
  1032. {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
  1033. base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
  1034. base::BindOnce(&base::DeletePathRecursively, *path_to_delete),
  1035. base::BindOnce(&OptimizationGuideStore::OnFilePathDeleted,
  1036. weak_ptr_factory_.GetWeakPtr(), entry.first));
  1037. }
  1038. }
  1039. void OptimizationGuideStore::OnFilePathDeleted(
  1040. const std::string& file_path_to_clean_up,
  1041. bool success) {
  1042. if (!success) {
  1043. // Try to delete again later.
  1044. return;
  1045. }
  1046. // If we get here, we should have a pref service.
  1047. DCHECK(pref_service_);
  1048. DictionaryPrefUpdate update(pref_service_, prefs::kStoreFilePathsToDelete);
  1049. update->RemoveKey(file_path_to_clean_up);
  1050. }
  1051. } // namespace optimization_guide