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

Side by Side Diff: chrome/browser/budget_service/budget_database.cc

Issue 2272563005: Start plumbing connections from the BudgetManager to the BudgetDatabase (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix the task runner Created 4 years, 3 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
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/budget_service/budget_database.h" 5 #include "chrome/browser/budget_service/budget_database.h"
6 6
7 #include "base/containers/adapters.h" 7 #include "base/containers/adapters.h"
8 #include "base/time/clock.h" 8 #include "base/time/clock.h"
9 #include "base/time/default_clock.h" 9 #include "base/time/default_clock.h"
10 #include "chrome/browser/budget_service/budget.pb.h" 10 #include "chrome/browser/budget_service/budget.pb.h"
11 #include "chrome/browser/engagement/site_engagement_score.h"
12 #include "chrome/browser/engagement/site_engagement_service.h"
13 #include "chrome/browser/profiles/profile.h"
11 #include "components/leveldb_proto/proto_database_impl.h" 14 #include "components/leveldb_proto/proto_database_impl.h"
12 #include "content/public/browser/browser_thread.h" 15 #include "content/public/browser/browser_thread.h"
13 #include "url/gurl.h" 16 #include "url/gurl.h"
14 17
15 using content::BrowserThread; 18 using content::BrowserThread;
16 19
17 namespace { 20 namespace {
18 21
19 // UMA are logged for the database with this string as part of the name. 22 // UMA are logged for the database with this string as part of the name.
20 // They will be LevelDB.*.BudgetManager. Changes here should be synchronized 23 // They will be LevelDB.*.BudgetManager. Changes here should be synchronized
21 // with histograms.xml. 24 // with histograms.xml.
22 const char kDatabaseUMAName[] = "BudgetManager"; 25 const char kDatabaseUMAName[] = "BudgetManager";
23 26
24 // The default amount of time during which a budget will be valid. 27 // The default amount of time during which a budget will be valid.
25 // This is 3 days = 72 hours. 28 // This is 3 days = 72 hours.
26 constexpr double kBudgetDurationInHours = 72; 29 constexpr double kBudgetDurationInHours = 72;
27 30
28 } // namespace 31 } // namespace
29 32
30 BudgetDatabase::BudgetInfo::BudgetInfo() {} 33 BudgetDatabase::BudgetInfo::BudgetInfo() {}
31 34
32 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other) 35 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other)
33 : last_engagement_award(other.last_engagement_award) { 36 : last_engagement_award(other.last_engagement_award) {
34 chunks = std::move(other.chunks); 37 chunks = std::move(other.chunks);
35 } 38 }
36 39
37 BudgetDatabase::BudgetInfo::~BudgetInfo() {} 40 BudgetDatabase::BudgetInfo::~BudgetInfo() {}
38 41
39 BudgetDatabase::BudgetDatabase( 42 BudgetDatabase::BudgetDatabase(
43 Profile* profile,
40 const base::FilePath& database_dir, 44 const base::FilePath& database_dir,
41 const scoped_refptr<base::SequencedTaskRunner>& task_runner) 45 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
42 : db_(new leveldb_proto::ProtoDatabaseImpl<budget_service::Budget>( 46 : profile_(profile),
47 db_(new leveldb_proto::ProtoDatabaseImpl<budget_service::Budget>(
43 task_runner)), 48 task_runner)),
44 clock_(base::WrapUnique(new base::DefaultClock)), 49 clock_(base::WrapUnique(new base::DefaultClock)),
45 weak_ptr_factory_(this) { 50 weak_ptr_factory_(this) {
46 db_->Init(kDatabaseUMAName, database_dir, 51 db_->Init(kDatabaseUMAName, database_dir,
47 base::Bind(&BudgetDatabase::OnDatabaseInit, 52 base::Bind(&BudgetDatabase::OnDatabaseInit,
48 weak_ptr_factory_.GetWeakPtr())); 53 weak_ptr_factory_.GetWeakPtr()));
49 } 54 }
50 55
51 BudgetDatabase::~BudgetDatabase() {} 56 BudgetDatabase::~BudgetDatabase() {}
52 57
53 void BudgetDatabase::GetBudgetDetails( 58 void BudgetDatabase::GetBudgetDetails(
54 const GURL& origin, 59 const GURL& origin,
55 const GetBudgetDetailsCallback& callback) { 60 const GetBudgetDetailsCallback& callback) {
56 DCHECK_EQ(origin.GetOrigin(), origin); 61 DCHECK_EQ(origin.GetOrigin(), origin);
57 62
58 // If this origin is already in the cache, immediately return the data. 63 SyncCache(origin,
59 if (IsCached(origin)) { 64 base::Bind(&BudgetDatabase::GetBudgetAfterSync,
60 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 65 weak_ptr_factory_.GetWeakPtr(), origin, callback));
61 base::Bind(&BudgetDatabase::DidGetBudget,
62 weak_ptr_factory_.GetWeakPtr(), origin,
63 callback, true /* success */));
64 return;
65 }
66
67 // Otherwise, query for the data, add it to the cache, then return the result.
68 AddToCacheCallback cache_callback =
69 base::Bind(&BudgetDatabase::DidGetBudget, weak_ptr_factory_.GetWeakPtr(),
70 origin, callback);
71 db_->GetEntry(origin.spec(), base::Bind(&BudgetDatabase::AddToCache,
72 weak_ptr_factory_.GetWeakPtr(),
73 origin, cache_callback));
74 }
75
76 void BudgetDatabase::AddBudget(const GURL& origin,
77 double amount,
78 const StoreBudgetCallback& callback) {
79 DCHECK_EQ(origin.GetOrigin(), origin);
80
81 // Add a new chunk of budget for the origin at the default expiration time.
82 base::Time expiration =
83 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours);
84 budget_map_[origin.spec()].chunks.emplace_back(amount, expiration);
85
86 // Now that the cache is updated, write the data to the database.
87 WriteCachedValuesToDatabase(origin, callback);
88 }
89
90 void BudgetDatabase::AddEngagementBudget(const GURL& origin,
91 double score,
92 const StoreBudgetCallback& callback) {
93 DCHECK_EQ(origin.GetOrigin(), origin);
94
95 // By default we award the "full" award. Then that ratio is decreased if
96 // there have been other awards recently.
97 double ratio = 1.0;
98
99 // Calculate how much budget should be awarded. If the origin is not cached,
100 // then we award a full amount.
101 if (IsCached(origin)) {
102 base::TimeDelta elapsed =
103 clock_->Now() - budget_map_[origin.spec()].last_engagement_award;
104 int elapsed_hours = elapsed.InHours();
105 if (elapsed_hours == 0) {
106 // Don't give engagement awards for periods less than an hour.
107 callback.Run(true);
108 return;
109 }
110 if (elapsed_hours < kBudgetDurationInHours)
111 ratio = elapsed_hours / kBudgetDurationInHours;
112 }
113
114 // Update the last_engagement_award to the current time. If the origin wasn't
115 // already in the map, this adds a new entry for it.
116 budget_map_[origin.spec()].last_engagement_award = clock_->Now();
117
118 // Pass to the base AddBudget to update the cache and write to the database.
119 AddBudget(origin, score * ratio, callback);
120 } 66 }
121 67
122 void BudgetDatabase::SpendBudget(const GURL& origin, 68 void BudgetDatabase::SpendBudget(const GURL& origin,
123 double amount, 69 double amount,
124 const StoreBudgetCallback& callback) { 70 const StoreBudgetCallback& callback) {
125 DCHECK_EQ(origin.GetOrigin(), origin); 71 SyncCache(origin, base::Bind(&BudgetDatabase::SpendBudgetAfterSync,
72 weak_ptr_factory_.GetWeakPtr(), origin, amount,
73 callback));
74 }
126 75
127 // First, cleanup any expired budget chunks for the origin. 76 void BudgetDatabase::SetClockForTesting(std::unique_ptr<base::Clock> clock) {
128 CleanupExpiredBudget(origin); 77 clock_ = std::move(clock);
129
130 if (!IsCached(origin)) {
131 callback.Run(false);
132 return;
133 }
134
135 // Walk the list of budget chunks to see if the origin has enough budget.
136 double total = 0;
137 BudgetInfo& info = budget_map_[origin.spec()];
138 for (const BudgetChunk& chunk : info.chunks)
139 total += chunk.amount;
140
141 if (total < amount) {
142 callback.Run(false);
143 return;
144 }
145
146 // Walk the chunks and remove enough budget to cover the needed amount.
147 double bill = amount;
148 for (auto iter = info.chunks.begin(); iter != info.chunks.end();) {
149 if (iter->amount > bill) {
150 iter->amount -= bill;
151 bill = 0;
152 ++iter;
153 break;
154 }
155 bill -= iter->amount;
156 iter = info.chunks.erase(iter);
157 }
158
159 // There should have been enough budget to cover the entire bill.
160 DCHECK_EQ(0, bill);
161
162 // Now that the cache is updated, write the data to the database.
163 // TODO(harkness): Consider adding a second parameter to the callback so the
164 // caller can distinguish between not enough budget and a failed database
165 // write.
166 // TODO(harkness): If the database write fails, the cache will be out of sync
167 // with the database. Consider ways to mitigate this.
168 WriteCachedValuesToDatabase(origin, callback);
169 } 78 }
170 79
171 void BudgetDatabase::OnDatabaseInit(bool success) { 80 void BudgetDatabase::OnDatabaseInit(bool success) {
172 // TODO(harkness): Consider caching the budget database now? 81 // TODO(harkness): Consider caching the budget database now?
173 } 82 }
174 83
175 bool BudgetDatabase::IsCached(const GURL& origin) const { 84 bool BudgetDatabase::IsCached(const GURL& origin) const {
176 return budget_map_.find(origin.spec()) != budget_map_.end(); 85 return budget_map_.find(origin.spec()) != budget_map_.end();
177 } 86 }
178 87
179 void BudgetDatabase::AddToCache( 88 void BudgetDatabase::AddToCache(
180 const GURL& origin, 89 const GURL& origin,
181 const AddToCacheCallback& callback, 90 const AddToCacheCallback& callback,
182 bool success, 91 bool success,
183 std::unique_ptr<budget_service::Budget> budget_proto) { 92 std::unique_ptr<budget_service::Budget> budget_proto) {
184 // If the database read failed, there's nothing to add to the cache. 93 // If the database read failed, there's nothing to add to the cache.
185 if (!success || !budget_proto) { 94 if (!success || !budget_proto) {
186 callback.Run(success); 95 callback.Run(success);
187 return; 96 return;
188 } 97 }
189 98
99 // If there were two simultaneous loads, don't overwrite the cache value,
100 // which might have been updated after the previous load.
101 if (IsCached(origin)) {
102 callback.Run(success);
103 return;
104 }
105
190 // Add the data to the cache, converting from the proto format to an STL 106 // Add the data to the cache, converting from the proto format to an STL
191 // format which is better for removing things from the list. 107 // format which is better for removing things from the list.
192 BudgetInfo& info = budget_map_[origin.spec()]; 108 BudgetInfo& info = budget_map_[origin.spec()];
193 for (const auto& chunk : budget_proto->budget()) { 109 for (const auto& chunk : budget_proto->budget()) {
194 info.chunks.emplace_back(chunk.amount(), 110 info.chunks.emplace_back(chunk.amount(),
195 base::Time::FromInternalValue(chunk.expiration())); 111 base::Time::FromInternalValue(chunk.expiration()));
196 } 112 }
197 113
198 info.last_engagement_award = 114 info.last_engagement_award =
199 base::Time::FromInternalValue(budget_proto->engagement_last_updated()); 115 base::Time::FromInternalValue(budget_proto->engagement_last_updated());
200 116
201 callback.Run(success); 117 callback.Run(success);
202 } 118 }
203 119
204 void BudgetDatabase::DidGetBudget(const GURL& origin, 120 void BudgetDatabase::GetBudgetAfterSync(
205 const GetBudgetDetailsCallback& callback, 121 const GURL& origin,
206 bool success) { 122 const GetBudgetDetailsCallback& callback,
123 bool success) {
207 // If the database wasn't able to read the information, return the 124 // If the database wasn't able to read the information, return the
208 // failure and an empty BudgetPrediction. 125 // failure and an empty BudgetPrediction.
209 if (!success) { 126 if (!success) {
210 callback.Run(success, BudgetPrediction()); 127 callback.Run(success, BudgetPrediction());
211 return; 128 return;
212 } 129 }
213 130
214 // First, cleanup any expired budget chunks for the origin.
215 CleanupExpiredBudget(origin);
216
217 // Now, build up the BudgetExpection. This is different from the format 131 // Now, build up the BudgetExpection. This is different from the format
218 // in which the cache stores the data. The cache stores chunks of budget and 132 // in which the cache stores the data. The cache stores chunks of budget and
219 // when that budget expires. The BudgetPrediction describes a set of times 133 // when that budget expires. The BudgetPrediction describes a set of times
220 // and the budget at those times. 134 // and the budget at those times.
221 BudgetPrediction prediction; 135 BudgetPrediction prediction;
222 double total = 0; 136 double total = 0;
223 137
224 if (IsCached(origin)) { 138 // Starting with the chunks that expire the farthest in the future, build up
225 // Starting with the chunks that expire the farthest in the future, build up 139 // the budget predictions for those future times.
226 // the budget predictions for those future times. 140 const BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
227 const BudgetChunks& chunks = budget_map_[origin.spec()].chunks; 141 for (const auto& chunk : base::Reversed(chunks)) {
228 for (const auto& chunk : base::Reversed(chunks)) { 142 prediction.emplace_front(total, chunk.expiration);
229 prediction.emplace_front(total, chunk.expiration); 143 total += chunk.amount;
230 total += chunk.amount;
231 }
232 } 144 }
233 145
234 // Always add one entry at the front of the list for the total budget now. 146 // Always add one entry at the front of the list for the total budget now.
235 prediction.emplace_front(total, clock_->Now()); 147 prediction.emplace_front(total, clock_->Now());
236 148
237 callback.Run(true /* success */, prediction); 149 callback.Run(true /* success */, prediction);
238 } 150 }
239 151
240 void BudgetDatabase::SetClockForTesting(std::unique_ptr<base::Clock> clock) { 152 void BudgetDatabase::SpendBudgetAfterSync(const GURL& origin,
241 clock_ = std::move(clock); 153 double amount,
154 const StoreBudgetCallback& callback,
155 bool success) {
156 if (!success) {
157 callback.Run(false /* success */);
158 return;
159 }
160
161 // Walk the list of budget chunks to see if the origin has enough budget.
162 double total = 0;
163 BudgetInfo& info = budget_map_[origin.spec()];
164 for (const BudgetChunk& chunk : info.chunks)
165 total += chunk.amount;
166
167 if (total < amount) {
168 callback.Run(false /* success */);
169 return;
170 }
171
172 // Walk the chunks and remove enough budget to cover the needed amount.
173 double bill = amount;
174 for (auto iter = info.chunks.begin(); iter != info.chunks.end();) {
175 if (iter->amount > bill) {
176 iter->amount -= bill;
177 bill = 0;
178 break;
179 }
180 bill -= iter->amount;
181 iter = info.chunks.erase(iter);
182 }
183
184 // There should have been enough budget to cover the entire bill.
185 DCHECK_EQ(0, bill);
186
187 // Now that the cache is updated, write the data to the database.
188 // TODO(harkness): Consider adding a second parameter to the callback so the
189 // caller can distinguish between not enough budget and a failed database
190 // write.
191 // TODO(harkness): If the database write fails, the cache will be out of sync
192 // with the database. Consider ways to mitigate this.
193 WriteCachedValuesToDatabase(origin, callback);
242 } 194 }
243 195
244 void BudgetDatabase::WriteCachedValuesToDatabase( 196 void BudgetDatabase::WriteCachedValuesToDatabase(
245 const GURL& origin, 197 const GURL& origin,
246 const StoreBudgetCallback& callback) { 198 const StoreBudgetCallback& callback) {
247 // First, cleanup any expired budget chunks for the origin.
248 CleanupExpiredBudget(origin);
249
250 // Create the data structures that are passed to the ProtoDatabase. 199 // Create the data structures that are passed to the ProtoDatabase.
251 std::unique_ptr< 200 std::unique_ptr<
252 leveldb_proto::ProtoDatabase<budget_service::Budget>::KeyEntryVector> 201 leveldb_proto::ProtoDatabase<budget_service::Budget>::KeyEntryVector>
253 entries(new leveldb_proto::ProtoDatabase< 202 entries(new leveldb_proto::ProtoDatabase<
254 budget_service::Budget>::KeyEntryVector()); 203 budget_service::Budget>::KeyEntryVector());
255 std::unique_ptr<std::vector<std::string>> keys_to_remove( 204 std::unique_ptr<std::vector<std::string>> keys_to_remove(
256 new std::vector<std::string>()); 205 new std::vector<std::string>());
257 206
258 // Each operation can either update the existing budget or remove the origin's 207 // Each operation can either update the existing budget or remove the origin's
259 // budget information. 208 // budget information.
(...skipping 11 matching lines...) Expand all
271 entries->push_back(std::make_pair(origin.spec(), budget)); 220 entries->push_back(std::make_pair(origin.spec(), budget));
272 } else { 221 } else {
273 // If the origin doesn't exist in the cache, this is a remove operation. 222 // If the origin doesn't exist in the cache, this is a remove operation.
274 keys_to_remove->push_back(origin.spec()); 223 keys_to_remove->push_back(origin.spec());
275 } 224 }
276 225
277 // Send the updates to the database. 226 // Send the updates to the database.
278 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback); 227 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback);
279 } 228 }
280 229
281 void BudgetDatabase::CleanupExpiredBudget(const GURL& origin) { 230 void BudgetDatabase::SyncCache(const GURL& origin,
231 const SyncCacheCallback& callback) {
232 DCHECK_EQ(origin, origin.GetOrigin());
233
234 // If the origin isn't already cached, add it to the cache.
235 if (!IsCached(origin)) {
236 AddToCacheCallback add_callback =
237 base::Bind(&BudgetDatabase::SyncLoadedCache,
238 weak_ptr_factory_.GetWeakPtr(), origin, callback);
239 db_->GetEntry(origin.spec(), base::Bind(&BudgetDatabase::AddToCache,
240 weak_ptr_factory_.GetWeakPtr(),
241 origin, add_callback));
242 return;
243 }
244 SyncLoadedCache(origin, callback, true /* success */);
245 }
246
247 void BudgetDatabase::SyncLoadedCache(const GURL& origin,
248 const SyncCacheCallback& callback,
249 bool success) {
250 if (!success) {
251 callback.Run(false /* success */);
252 return;
253 }
254
255 // Get the SES score and add engagement budget for the site.
256 AddEngagementBudget(origin);
257
258 // Now, cleanup any expired budget chunks for the origin.
259 bool needs_write = CleanupExpiredBudget(origin);
260
261 if (needs_write)
262 WriteCachedValuesToDatabase(origin, callback);
263 else
264 callback.Run(success);
265 }
266
267 void BudgetDatabase::AddEngagementBudget(const GURL& origin) {
268 // Get the current SES score, which we'll use to set a new budget.
269 SiteEngagementService* service = SiteEngagementService::Get(profile_);
270 double score = service->GetScore(origin);
271
272 // By default we award the "full" award. Then that ratio is decreased if
273 // there have been other awards recently.
274 double ratio = 1.0;
275
276 // Calculate how much budget should be awarded. If there is no entry in the
277 // cache then we award a full amount.
278 if (IsCached(origin)) {
279 base::TimeDelta elapsed =
280 clock_->Now() - budget_map_[origin.spec()].last_engagement_award;
281 int elapsed_hours = elapsed.InHours();
282 // Don't give engagement awards for periods less than an hour.
283 if (elapsed_hours < 1)
284 return;
285 if (elapsed_hours < kBudgetDurationInHours)
286 ratio = elapsed_hours / kBudgetDurationInHours;
287 }
288
289 // Update the last_engagement_award to the current time. If the origin wasn't
290 // already in the map, this adds a new entry for it.
291 budget_map_[origin.spec()].last_engagement_award = clock_->Now();
292
293 // Add a new chunk of budget for the origin at the default expiration time.
294 base::Time expiration =
295 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours);
296 budget_map_[origin.spec()].chunks.emplace_back(ratio * score, expiration);
297 }
298
299 // Cleans up budget in the cache. Relies on the caller eventually writing the
300 // cache back to the database.
301 bool BudgetDatabase::CleanupExpiredBudget(const GURL& origin) {
282 if (!IsCached(origin)) 302 if (!IsCached(origin))
283 return; 303 return false;
284 304
285 base::Time now = clock_->Now(); 305 base::Time now = clock_->Now();
286
287 BudgetChunks& chunks = budget_map_[origin.spec()].chunks; 306 BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
288 auto cleanup_iter = chunks.begin(); 307 auto cleanup_iter = chunks.begin();
289 308
290 // This relies on the list of chunks being in timestamp order. 309 // This relies on the list of chunks being in timestamp order.
291 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) 310 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now)
292 cleanup_iter = chunks.erase(cleanup_iter); 311 cleanup_iter = chunks.erase(cleanup_iter);
293 312
294 // If the entire budget is empty now AND there have been no engagements 313 // If the entire budget is empty now AND there have been no engagements
295 // in the last kBudgetDurationInHours hours, remove this from the cache. 314 // in the last kBudgetDurationInHours hours, remove this from the cache.
296 if (chunks.empty() && 315 if (chunks.empty() &&
297 budget_map_[origin.spec()].last_engagement_award < 316 budget_map_[origin.spec()].last_engagement_award <
298 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours)) 317 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours)) {
299 budget_map_.erase(origin.spec()); 318 budget_map_.erase(origin.spec());
319 return true;
320 }
321
322 // Although some things may have expired, there are some chunks still valid.
323 // Don't write to the DB now, write either when all chunks expire or when the
324 // origin spends some budget.
325 return false;
300 } 326 }
OLDNEW
« no previous file with comments | « chrome/browser/budget_service/budget_database.h ('k') | chrome/browser/budget_service/budget_database_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698