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

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

Issue 2255813002: AddEngagementBudget and SpendBudget added to BudgetDatabase. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added expiration check in SpendBudget Created 4 years, 4 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 "components/leveldb_proto/proto_database_impl.h" 11 #include "components/leveldb_proto/proto_database_impl.h"
12 #include "content/public/browser/browser_thread.h" 12 #include "content/public/browser/browser_thread.h"
13 #include "url/gurl.h" 13 #include "url/gurl.h"
14 14
15 using content::BrowserThread; 15 using content::BrowserThread;
16 16
17 namespace { 17 namespace {
18 18
19 // UMA are logged for the database with this string as part of the name. 19 // UMA are logged for the database with this string as part of the name.
20 // They will be LevelDB.*.BackgroundBudgetService. Changes here should be 20 // They will be LevelDB.*.BackgroundBudgetService. Changes here should be
21 // synchronized with histograms.xml. 21 // synchronized with histograms.xml.
22 const char kDatabaseUMAName[] = "BackgroundBudgetService"; 22 const char kDatabaseUMAName[] = "BackgroundBudgetService";
23 23
24 // The default amount of time during which a budget will be valid. 24 // The default amount of time during which a budget will be valid.
25 // This is 3 days = 72 hours. 25 // This is 3 days = 72 hours.
26 constexpr double kBudgetDurationInHours = 72; 26 constexpr double kBudgetDurationInHours = 72;
27 27
28 } // namespace 28 } // namespace
29 29
30 BudgetDatabase::BudgetInfo::BudgetInfo() {}
31
32 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo& other)
33 : last_engagement_award(other.last_engagement_award) {
34 chunks = std::move(other.chunks);
johnme 2016/08/19 18:48:35 Doesn't make sense to std::move in a copy construc
harkness 2016/08/22 13:46:25 Done.
35 }
36
37 BudgetDatabase::BudgetInfo::~BudgetInfo() {}
38
30 BudgetDatabase::BudgetDatabase( 39 BudgetDatabase::BudgetDatabase(
31 const base::FilePath& database_dir, 40 const base::FilePath& database_dir,
32 const scoped_refptr<base::SequencedTaskRunner>& task_runner) 41 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
33 : db_(new leveldb_proto::ProtoDatabaseImpl<budget_service::Budget>( 42 : db_(new leveldb_proto::ProtoDatabaseImpl<budget_service::Budget>(
34 task_runner)), 43 task_runner)),
35 clock_(base::WrapUnique(new base::DefaultClock)), 44 clock_(base::WrapUnique(new base::DefaultClock)),
36 weak_ptr_factory_(this) { 45 weak_ptr_factory_(this) {
37 db_->Init(kDatabaseUMAName, database_dir, 46 db_->Init(kDatabaseUMAName, database_dir,
38 base::Bind(&BudgetDatabase::OnDatabaseInit, 47 base::Bind(&BudgetDatabase::OnDatabaseInit,
39 weak_ptr_factory_.GetWeakPtr())); 48 weak_ptr_factory_.GetWeakPtr()));
(...skipping 25 matching lines...) Expand all
65 } 74 }
66 75
67 void BudgetDatabase::AddBudget(const GURL& origin, 76 void BudgetDatabase::AddBudget(const GURL& origin,
68 double amount, 77 double amount,
69 const StoreBudgetCallback& callback) { 78 const StoreBudgetCallback& callback) {
70 DCHECK_EQ(origin.GetOrigin(), origin); 79 DCHECK_EQ(origin.GetOrigin(), origin);
71 80
72 // Add a new chunk of budget for the origin at the default expiration time. 81 // Add a new chunk of budget for the origin at the default expiration time.
73 base::Time expiration = 82 base::Time expiration =
74 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours); 83 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours);
75 budget_map_[origin.spec()].emplace_back(amount, expiration); 84 budget_map_[origin.spec()].chunks.emplace_back(amount, expiration);
76 85
77 // Now that the cache is updated, write the data to the database. 86 // Now that the cache is updated, write the data to the database.
78 WriteCachedValuesToDatabase(origin, callback); 87 WriteCachedValuesToDatabase(origin, callback);
79 } 88 }
80 89
90 void BudgetDatabase::AddEngagementBudget(const GURL& origin,
91 double sesScore,
johnme 2016/08/19 18:48:35 Nit: parameter name is different in .h file; it's
harkness 2016/08/22 13:46:25 Done.
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 awardRatio = 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)) {
johnme 2016/08/19 18:48:35 Nit: each call to AddEngagementBudget causes origi
harkness 2016/08/22 13:46:26 I'll definitely keep it in mind, and see if there
102 base::TimeDelta elapsed =
103 clock_->Now() - budget_map_[origin.spec()].last_engagement_award;
104 int elapsedHours = elapsed.InHours();
johnme 2016/08/19 18:48:36 If AddEngagementBudget gets called more than once
harkness 2016/08/22 13:46:26 Actually, I always meant to just return if the tim
johnme 2016/08/22 14:50:31 I see. Even so, `awardRatio = elapsedHours / kBudg
105 if (elapsedHours < kBudgetDurationInHours)
106 awardRatio = elapsedHours / kBudgetDurationInHours;
107 }
108
109 // Update the last_engagement_award to the current time.
johnme 2016/08/19 18:48:35 Nit: please reword this comment to clarify that th
harkness 2016/08/22 13:46:26 Done.
110 budget_map_[origin.spec()].last_engagement_award = clock_->Now();
111
112 // Pass to the base AddBudget to update the cache and write to the database.
113 AddBudget(origin, sesScore * awardRatio, callback);
114 }
115
116 void BudgetDatabase::SpendBudget(const GURL& origin,
117 double amount,
118 const StoreBudgetCallback& callback) {
119 DCHECK_EQ(origin.GetOrigin(), origin);
120
121 // First, cleanup any expired budget chunks for the origin.
122 CleanupExpiredBudget(origin);
123
124 if (!IsCached(origin)) {
125 callback.Run(false);
126 return;
127 }
128
129 // Walk the list of budget chunks to see if the origin has enough budget.
130 double total = 0;
131 for (const auto& chunk : budget_map_[origin.spec()].chunks)
johnme 2016/08/19 18:48:35 Nit: You can probably s/auto/BudgetChunk/ here sin
harkness 2016/08/22 13:46:26 Peter has been pushing for everything to be auto,
132 total += chunk.amount;
133
134 if (total < amount) {
135 callback.Run(false);
136 return;
137 }
138
139 double bill = amount;
140 auto& info = budget_map_[origin.spec()];
johnme 2016/08/19 18:48:36 Please move this up so that the for loop above can
harkness 2016/08/22 13:46:25 Done.
141 auto chunkIter = info.chunks.begin();
142 // This is guaranteed to exit because the total budget check above guarantees
143 // it.
144 while (bill > 0 && chunkIter != info.chunks.end()) {
johnme 2016/08/19 18:48:35 Nit: perhaps simpler as a for loop; more efficient
harkness 2016/08/22 13:46:25 You're right that this use means we should change
145 if (chunkIter->amount > bill) {
146 chunkIter->amount -= bill;
147 bill = 0;
148 } else {
149 bill -= chunkIter->amount;
150 chunkIter = info.chunks.erase(chunkIter);
151 }
152 }
153
154 // Now that the cache is updated, write the data to the database.
155 // TODO(harkness): Consider adding a second parameter to the callback so the
156 // caller can distinguish between not enough budget and a failed database
157 // write.
158 // TODO(harkness): If the database write fails, the cache will be out of sync
159 // with the database. Consider ways to mitigate this.
160 WriteCachedValuesToDatabase(origin, callback);
161 }
162
81 void BudgetDatabase::OnDatabaseInit(bool success) { 163 void BudgetDatabase::OnDatabaseInit(bool success) {
82 // TODO(harkness): Consider caching the budget database now? 164 // TODO(harkness): Consider caching the budget database now?
83 } 165 }
84 166
85 bool BudgetDatabase::IsCached(const GURL& origin) const { 167 bool BudgetDatabase::IsCached(const GURL& origin) const {
86 return budget_map_.find(origin.spec()) != budget_map_.end(); 168 return budget_map_.find(origin.spec()) != budget_map_.end();
87 } 169 }
88 170
89 void BudgetDatabase::AddToCache( 171 void BudgetDatabase::AddToCache(
90 const GURL& origin, 172 const GURL& origin,
91 const AddToCacheCallback& callback, 173 const AddToCacheCallback& callback,
92 bool success, 174 bool success,
93 std::unique_ptr<budget_service::Budget> budget_proto) { 175 std::unique_ptr<budget_service::Budget> budget_proto) {
94 // If the database read failed, there's nothing to add to the cache. 176 // If the database read failed, there's nothing to add to the cache.
95 if (!success || !budget_proto) { 177 if (!success || !budget_proto) {
96 callback.Run(success); 178 callback.Run(success);
97 return; 179 return;
98 } 180 }
99 181
100 // Add the data to the cache, converting from the proto format to an STL 182 // Add the data to the cache, converting from the proto format to an STL
101 // format which is better for removing things from the list. 183 // format which is better for removing things from the list.
102 BudgetChunks chunks; 184 BudgetChunks chunks;
103 for (const auto& chunk : budget_proto->budget()) { 185 for (const auto& chunk : budget_proto->budget()) {
104 chunks.emplace_back(chunk.amount(), 186 chunks.emplace_back(chunk.amount(),
105 base::Time::FromInternalValue(chunk.expiration())); 187 base::Time::FromInternalValue(chunk.expiration()));
106 } 188 }
107 189
108 budget_map_[origin.spec()] = std::move(chunks); 190 budget_map_[origin.spec()].chunks = std::move(chunks);
johnme 2016/08/19 18:48:36 You could just write the chunks directly into budg
harkness 2016/08/22 13:46:25 Done.
191 budget_map_[origin.spec()].last_engagement_award =
192 base::Time::FromInternalValue(budget_proto->last_updated());
109 193
110 callback.Run(success); 194 callback.Run(success);
111 } 195 }
112 196
113 void BudgetDatabase::DidGetBudget(const GURL& origin, 197 void BudgetDatabase::DidGetBudget(const GURL& origin,
114 const GetBudgetDetailsCallback& callback, 198 const GetBudgetDetailsCallback& callback,
115 bool success) { 199 bool success) {
116 // If the database wasn't able to read the information, return the 200 // If the database wasn't able to read the information, return the
117 // failure and an empty BudgetExpectation. 201 // failure and an empty BudgetExpectation.
118 if (!success) { 202 if (!success) {
119 callback.Run(success, BudgetExpectation()); 203 callback.Run(success, BudgetExpectation());
120 return; 204 return;
121 } 205 }
122 206
123 // First, cleanup any expired budget chunks for the origin. 207 // First, cleanup any expired budget chunks for the origin.
124 CleanupExpiredBudget(origin); 208 CleanupExpiredBudget(origin);
125 209
126 // Now, build up the BudgetExpection. This is different from the format 210 // Now, build up the BudgetExpection. This is different from the format
127 // in which the cache stores the data. The cache stores chunks of budget and 211 // in which the cache stores the data. The cache stores chunks of budget and
128 // when that budget expires. The BudgetExpectation describes a set of times 212 // when that budget expires. The BudgetExpectation describes a set of times
129 // and the budget at those times. 213 // and the budget at those times.
130 BudgetExpectation expectation; 214 BudgetExpectation expectation;
131 double total = 0; 215 double total = 0;
132 216
133 if (IsCached(origin)) { 217 if (IsCached(origin)) {
134 // Starting with the chunks that expire the farthest in the future, build up 218 // Starting with the chunks that expire the farthest in the future, build up
135 // the budget expectations for those future times. 219 // the budget expectations for those future times.
136 const BudgetChunks& chunks = budget_map_[origin.spec()]; 220 const BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
137 for (const auto& chunk : base::Reversed(chunks)) { 221 for (const auto& chunk : base::Reversed(chunks)) {
138 expectation.emplace_front(total, chunk.expiration); 222 expectation.emplace_front(total, chunk.expiration);
139 total += chunk.amount; 223 total += chunk.amount;
140 } 224 }
141 } 225 }
142 226
143 // Always add one entry at the front of the list for the total budget now. 227 // Always add one entry at the front of the list for the total budget now.
144 expectation.emplace_front(total, clock_->Now()); 228 expectation.emplace_front(total, clock_->Now());
145 229
146 callback.Run(true /* success */, expectation); 230 callback.Run(true /* success */, expectation);
(...skipping 15 matching lines...) Expand all
162 entries(new leveldb_proto::ProtoDatabase< 246 entries(new leveldb_proto::ProtoDatabase<
163 budget_service::Budget>::KeyEntryVector()); 247 budget_service::Budget>::KeyEntryVector());
164 std::unique_ptr<std::vector<std::string>> keys_to_remove( 248 std::unique_ptr<std::vector<std::string>> keys_to_remove(
165 new std::vector<std::string>()); 249 new std::vector<std::string>());
166 250
167 // Each operation can either update the existing budget or remove the origin's 251 // Each operation can either update the existing budget or remove the origin's
168 // budget information. 252 // budget information.
169 if (IsCached(origin)) { 253 if (IsCached(origin)) {
170 // Build the Budget proto object. 254 // Build the Budget proto object.
171 budget_service::Budget budget; 255 budget_service::Budget budget;
172 const BudgetChunks& chunks = budget_map_[origin.spec()]; 256 const BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
johnme 2016/08/19 18:48:36 Nit: store a reference to the BudgetInfo instead.
harkness 2016/08/22 13:46:26 Done.
173 for (const auto& chunk : chunks) { 257 for (const auto& chunk : chunks) {
174 budget_service::BudgetChunk* budget_chunk = budget.add_budget(); 258 budget_service::BudgetChunk* budget_chunk = budget.add_budget();
175 budget_chunk->set_amount(chunk.amount); 259 budget_chunk->set_amount(chunk.amount);
176 budget_chunk->set_expiration(chunk.expiration.ToInternalValue()); 260 budget_chunk->set_expiration(chunk.expiration.ToInternalValue());
177 } 261 }
262 budget.set_last_updated(
263 budget_map_[origin.spec()].last_engagement_award.ToInternalValue());
178 entries->push_back(std::make_pair(origin.spec(), budget)); 264 entries->push_back(std::make_pair(origin.spec(), budget));
179 } else { 265 } else {
180 // If the origin doesn't exist in the cache, this is a remove operation. 266 // If the origin doesn't exist in the cache, this is a remove operation.
181 keys_to_remove->push_back(origin.spec()); 267 keys_to_remove->push_back(origin.spec());
182 } 268 }
183 269
184 // Send the updates to the database. 270 // Send the updates to the database.
185 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback); 271 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback);
186 } 272 }
187 273
188 void BudgetDatabase::CleanupExpiredBudget(const GURL& origin) { 274 void BudgetDatabase::CleanupExpiredBudget(const GURL& origin) {
189 if (!IsCached(origin)) 275 if (!IsCached(origin))
190 return; 276 return;
191 277
192 base::Time now = clock_->Now(); 278 base::Time now = clock_->Now();
193 279
194 BudgetChunks& chunks = budget_map_[origin.spec()]; 280 BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
195 auto cleanup_iter = chunks.begin(); 281 auto cleanup_iter = chunks.begin();
196 282
197 // This relies on the list of chunks being in timestamp order. 283 // This relies on the list of chunks being in timestamp order.
198 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) 284 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now)
199 cleanup_iter = chunks.erase(cleanup_iter); 285 cleanup_iter = chunks.erase(cleanup_iter);
200 286
201 // If the entire budget is empty now, cleanup the map. 287 // If the entire budget is empty now AND there have been no engagements
202 if (chunks.empty()) 288 // in the last kBudgetDurationInHours hours, remove this from the cache.
289 if (chunks.empty() &&
290 budget_map_[origin.spec()].last_engagement_award <
291 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours))
203 budget_map_.erase(origin.spec()); 292 budget_map_.erase(origin.spec());
204 } 293 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698