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

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: Code review comments 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.*.BudgetManager. Changes here should be synchronized 20 // They will be LevelDB.*.BudgetManager. Changes here should be synchronized
21 // with histograms.xml. 21 // with histograms.xml.
22 const char kDatabaseUMAName[] = "BudgetManager"; 22 const char kDatabaseUMAName[] = "BudgetManager";
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);
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,
Peter Beverloo 2016/08/22 18:04:27 nit: s/sesScore/score/
harkness 2016/08/23 09:55:55 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;
Peter Beverloo 2016/08/22 18:04:27 nit: s/awardRatio/ratio/
harkness 2016/08/23 09:55:55 Done.
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 elapsedHours = elapsed.InHours();
Peter Beverloo 2016/08/22 18:04:27 nit: s/elapsedHours/elapsed_hours/ It'd be amazin
harkness 2016/08/23 09:55:55 I'm not sure whether you or I would appreciate tha
105 if (elapsedHours == 0) {
106 // Don't give engagement awards for periods less than an hour.
107 callback.Run(true);
108 return;
109 }
110 if (elapsedHours < kBudgetDurationInHours)
111 awardRatio = elapsedHours / 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, sesScore * awardRatio, callback);
120 }
121
122 void BudgetDatabase::SpendBudget(const GURL& origin,
123 double amount,
124 const StoreBudgetCallback& callback) {
125 DCHECK_EQ(origin.GetOrigin(), origin);
126
127 // First, cleanup any expired budget chunks for the origin.
128 CleanupExpiredBudget(origin);
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 auto& info = budget_map_[origin.spec()];
johnme 2016/08/22 14:50:31 consistency. https://google.github.io/styleguide/
harkness 2016/08/23 09:55:56 Done.
138 for (const auto& 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 BudgetChunks::iterator chunkIter;
149 for (chunkIter = info.chunks.begin(); chunkIter != info.chunks.end();
150 chunkIter++) {
johnme 2016/08/22 14:50:31 Always ++it not it++ with iterators to avoid copyi
harkness 2016/08/23 09:55:55 Done.
151 if (chunkIter->amount > bill) {
152 chunkIter->amount -= bill;
153 bill = 0;
154 break;
155 }
156 bill -= chunkIter->amount;
157 }
158 info.chunks.erase(info.chunks.begin(), chunkIter);
johnme 2016/08/22 14:50:31 Now that you're using a std::list, and since you k
harkness 2016/08/23 09:55:56 Done.
159
160 // There should have been enough budget to cover the entire bill.
161 DCHECK_EQ(0, bill);
162
163 // Now that the cache is updated, write the data to the database.
164 // TODO(harkness): Consider adding a second parameter to the callback so the
165 // caller can distinguish between not enough budget and a failed database
166 // write.
167 // TODO(harkness): If the database write fails, the cache will be out of sync
168 // with the database. Consider ways to mitigate this.
169 WriteCachedValuesToDatabase(origin, callback);
170 }
171
81 void BudgetDatabase::OnDatabaseInit(bool success) { 172 void BudgetDatabase::OnDatabaseInit(bool success) {
82 // TODO(harkness): Consider caching the budget database now? 173 // TODO(harkness): Consider caching the budget database now?
83 } 174 }
84 175
85 bool BudgetDatabase::IsCached(const GURL& origin) const { 176 bool BudgetDatabase::IsCached(const GURL& origin) const {
86 return budget_map_.find(origin.spec()) != budget_map_.end(); 177 return budget_map_.find(origin.spec()) != budget_map_.end();
87 } 178 }
88 179
89 void BudgetDatabase::AddToCache( 180 void BudgetDatabase::AddToCache(
90 const GURL& origin, 181 const GURL& origin,
91 const AddToCacheCallback& callback, 182 const AddToCacheCallback& callback,
92 bool success, 183 bool success,
93 std::unique_ptr<budget_service::Budget> budget_proto) { 184 std::unique_ptr<budget_service::Budget> budget_proto) {
94 // If the database read failed, there's nothing to add to the cache. 185 // If the database read failed, there's nothing to add to the cache.
95 if (!success || !budget_proto) { 186 if (!success || !budget_proto) {
96 callback.Run(success); 187 callback.Run(success);
97 return; 188 return;
98 } 189 }
99 190
100 // Add the data to the cache, converting from the proto format to an STL 191 // 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. 192 // format which is better for removing things from the list.
102 BudgetChunks chunks; 193 BudgetInfo& info = budget_map_[origin.spec()];
194 BudgetChunks& chunks = info.chunks;
johnme 2016/08/22 14:50:31 Micro-nit: you only use this variable once - inlin
harkness 2016/08/23 09:55:55 Done.
103 for (const auto& chunk : budget_proto->budget()) { 195 for (const auto& chunk : budget_proto->budget()) {
104 chunks.emplace_back(chunk.amount(), 196 chunks.emplace_back(chunk.amount(),
105 base::Time::FromInternalValue(chunk.expiration())); 197 base::Time::FromInternalValue(chunk.expiration()));
106 } 198 }
107 199
108 budget_map_[origin.spec()] = std::move(chunks); 200 info.last_engagement_award =
201 base::Time::FromInternalValue(budget_proto->engagement_last_updated());
109 202
110 callback.Run(success); 203 callback.Run(success);
111 } 204 }
112 205
113 void BudgetDatabase::DidGetBudget(const GURL& origin, 206 void BudgetDatabase::DidGetBudget(const GURL& origin,
114 const GetBudgetDetailsCallback& callback, 207 const GetBudgetDetailsCallback& callback,
115 bool success) { 208 bool success) {
116 // If the database wasn't able to read the information, return the 209 // If the database wasn't able to read the information, return the
117 // failure and an empty BudgetExpectation. 210 // failure and an empty BudgetPrediction.
118 if (!success) { 211 if (!success) {
119 callback.Run(success, BudgetExpectation()); 212 callback.Run(success, BudgetPrediction());
120 return; 213 return;
121 } 214 }
122 215
123 // First, cleanup any expired budget chunks for the origin. 216 // First, cleanup any expired budget chunks for the origin.
124 CleanupExpiredBudget(origin); 217 CleanupExpiredBudget(origin);
125 218
126 // Now, build up the BudgetExpection. This is different from the format 219 // 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 220 // 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 221 // when that budget expires. The BudgetPrediction describes a set of times
129 // and the budget at those times. 222 // and the budget at those times.
130 BudgetExpectation expectation; 223 BudgetPrediction prediction;
131 double total = 0; 224 double total = 0;
132 225
133 if (IsCached(origin)) { 226 if (IsCached(origin)) {
134 // Starting with the chunks that expire the farthest in the future, build up 227 // Starting with the chunks that expire the farthest in the future, build up
135 // the budget expectations for those future times. 228 // the budget predictions for those future times.
136 const BudgetChunks& chunks = budget_map_[origin.spec()]; 229 const BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
137 for (const auto& chunk : base::Reversed(chunks)) { 230 for (const auto& chunk : base::Reversed(chunks)) {
138 expectation.emplace_front(total, chunk.expiration); 231 prediction.emplace_front(total, chunk.expiration);
139 total += chunk.amount; 232 total += chunk.amount;
140 } 233 }
141 } 234 }
142 235
143 // Always add one entry at the front of the list for the total budget now. 236 // Always add one entry at the front of the list for the total budget now.
144 expectation.emplace_front(total, clock_->Now()); 237 prediction.emplace_front(total, clock_->Now());
145 238
146 callback.Run(true /* success */, expectation); 239 callback.Run(true /* success */, prediction);
147 } 240 }
148 241
149 void BudgetDatabase::SetClockForTesting(std::unique_ptr<base::Clock> clock) { 242 void BudgetDatabase::SetClockForTesting(std::unique_ptr<base::Clock> clock) {
150 clock_ = std::move(clock); 243 clock_ = std::move(clock);
151 } 244 }
152 245
153 void BudgetDatabase::WriteCachedValuesToDatabase( 246 void BudgetDatabase::WriteCachedValuesToDatabase(
154 const GURL& origin, 247 const GURL& origin,
155 const StoreBudgetCallback& callback) { 248 const StoreBudgetCallback& callback) {
156 // First, cleanup any expired budget chunks for the origin. 249 // First, cleanup any expired budget chunks for the origin.
157 CleanupExpiredBudget(origin); 250 CleanupExpiredBudget(origin);
158 251
159 // Create the data structures that are passed to the ProtoDatabase. 252 // Create the data structures that are passed to the ProtoDatabase.
160 std::unique_ptr< 253 std::unique_ptr<
161 leveldb_proto::ProtoDatabase<budget_service::Budget>::KeyEntryVector> 254 leveldb_proto::ProtoDatabase<budget_service::Budget>::KeyEntryVector>
162 entries(new leveldb_proto::ProtoDatabase< 255 entries(new leveldb_proto::ProtoDatabase<
163 budget_service::Budget>::KeyEntryVector()); 256 budget_service::Budget>::KeyEntryVector());
164 std::unique_ptr<std::vector<std::string>> keys_to_remove( 257 std::unique_ptr<std::vector<std::string>> keys_to_remove(
165 new std::vector<std::string>()); 258 new std::vector<std::string>());
166 259
167 // Each operation can either update the existing budget or remove the origin's 260 // Each operation can either update the existing budget or remove the origin's
168 // budget information. 261 // budget information.
169 if (IsCached(origin)) { 262 if (IsCached(origin)) {
170 // Build the Budget proto object. 263 // Build the Budget proto object.
171 budget_service::Budget budget; 264 budget_service::Budget budget;
172 const BudgetChunks& chunks = budget_map_[origin.spec()]; 265 const BudgetInfo& info = budget_map_[origin.spec()];
173 for (const auto& chunk : chunks) { 266 for (const auto& chunk : info.chunks) {
174 budget_service::BudgetChunk* budget_chunk = budget.add_budget(); 267 budget_service::BudgetChunk* budget_chunk = budget.add_budget();
175 budget_chunk->set_amount(chunk.amount); 268 budget_chunk->set_amount(chunk.amount);
176 budget_chunk->set_expiration(chunk.expiration.ToInternalValue()); 269 budget_chunk->set_expiration(chunk.expiration.ToInternalValue());
177 } 270 }
271 budget.set_engagement_last_updated(
272 info.last_engagement_award.ToInternalValue());
178 entries->push_back(std::make_pair(origin.spec(), budget)); 273 entries->push_back(std::make_pair(origin.spec(), budget));
179 } else { 274 } else {
180 // If the origin doesn't exist in the cache, this is a remove operation. 275 // If the origin doesn't exist in the cache, this is a remove operation.
181 keys_to_remove->push_back(origin.spec()); 276 keys_to_remove->push_back(origin.spec());
182 } 277 }
183 278
184 // Send the updates to the database. 279 // Send the updates to the database.
185 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback); 280 db_->UpdateEntries(std::move(entries), std::move(keys_to_remove), callback);
186 } 281 }
187 282
188 void BudgetDatabase::CleanupExpiredBudget(const GURL& origin) { 283 void BudgetDatabase::CleanupExpiredBudget(const GURL& origin) {
189 if (!IsCached(origin)) 284 if (!IsCached(origin))
190 return; 285 return;
191 286
192 base::Time now = clock_->Now(); 287 base::Time now = clock_->Now();
193 288
194 BudgetChunks& chunks = budget_map_[origin.spec()]; 289 BudgetChunks& chunks = budget_map_[origin.spec()].chunks;
195 auto cleanup_iter = chunks.begin(); 290 auto cleanup_iter = chunks.begin();
196 291
197 // This relies on the list of chunks being in timestamp order. 292 // This relies on the list of chunks being in timestamp order.
198 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) 293 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now)
199 cleanup_iter = chunks.erase(cleanup_iter); 294 cleanup_iter = chunks.erase(cleanup_iter);
200 295
201 // If the entire budget is empty now, cleanup the map. 296 // If the entire budget is empty now AND there have been no engagements
202 if (chunks.empty()) 297 // in the last kBudgetDurationInHours hours, remove this from the cache.
298 if (chunks.empty() &&
299 budget_map_[origin.spec()].last_engagement_award <
300 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours))
203 budget_map_.erase(origin.spec()); 301 budget_map_.erase(origin.spec());
204 } 302 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698