OLD | NEW |
---|---|
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/memory/ptr_util.h" | 7 #include "base/memory/ptr_util.h" |
8 #include "base/metrics/histogram_macros.h" | 8 #include "base/metrics/histogram_macros.h" |
9 #include "base/time/clock.h" | 9 #include "base/time/clock.h" |
10 #include "base/time/default_clock.h" | 10 #include "base/time/default_clock.h" |
11 #include "chrome/browser/budget_service/budget.pb.h" | 11 #include "chrome/browser/budget_service/budget.pb.h" |
12 #include "chrome/browser/engagement/site_engagement_score.h" | 12 #include "chrome/browser/engagement/site_engagement_score.h" |
13 #include "chrome/browser/engagement/site_engagement_service.h" | 13 #include "chrome/browser/engagement/site_engagement_service.h" |
14 #include "chrome/browser/profiles/profile.h" | 14 #include "chrome/browser/profiles/profile.h" |
15 #include "components/leveldb_proto/proto_database_impl.h" | 15 #include "components/leveldb_proto/proto_database_impl.h" |
16 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
17 #include "url/gurl.h" | 17 #include "url/gurl.h" |
18 #include "url/origin.h" | 18 #include "url/origin.h" |
19 | 19 |
20 using content::BrowserThread; | 20 using content::BrowserThread; |
21 | 21 |
22 namespace { | 22 namespace { |
23 | 23 |
24 // UMA are logged for the database with this string as part of the name. | 24 // UMA are logged for the database with this string as part of the name. |
25 // They will be LevelDB.*.BudgetManager. Changes here should be synchronized | 25 // They will be LevelDB.*.BudgetManager. Changes here should be synchronized |
26 // with histograms.xml. | 26 // with histograms.xml. |
27 const char kDatabaseUMAName[] = "BudgetManager"; | 27 const char kDatabaseUMAName[] = "BudgetManager"; |
28 | 28 |
29 // The default amount of time during which a budget will be valid. | 29 // The default amount of time during which a budget will be valid. |
30 // This is 4 days = 96 hours. | 30 constexpr int kBudgetDurationInDays = 4; |
31 constexpr double kBudgetDurationInHours = 96; | 31 |
32 // The amount of budget that a maximally engaged site should receive per hour. | |
33 // This allows 6 silent push messages a day for a maximal site. | |
Peter Beverloo
2017/01/12 14:40:04
Can we make this comment slightly clearer, since i
harkness
2017/01/12 17:44:14
I updated the comment here. I'd like to keep the c
| |
34 constexpr double kMaximumHourlyBudget = 12.0 / 24.0; | |
32 | 35 |
33 } // namespace | 36 } // namespace |
34 | 37 |
35 BudgetDatabase::BudgetInfo::BudgetInfo() {} | 38 BudgetDatabase::BudgetInfo::BudgetInfo() {} |
36 | 39 |
37 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other) | 40 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other) |
38 : last_engagement_award(other.last_engagement_award) { | 41 : last_engagement_award(other.last_engagement_award) { |
39 chunks = std::move(other.chunks); | 42 chunks = std::move(other.chunks); |
40 } | 43 } |
41 | 44 |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
157 // expiration times going forward. | 160 // expiration times going forward. |
158 const BudgetChunks& chunks = budget_map_[origin].chunks; | 161 const BudgetChunks& chunks = budget_map_[origin].chunks; |
159 for (const auto& chunk : chunks) { | 162 for (const auto& chunk : chunks) { |
160 blink::mojom::BudgetStatePtr prediction(blink::mojom::BudgetState::New()); | 163 blink::mojom::BudgetStatePtr prediction(blink::mojom::BudgetState::New()); |
161 total -= chunk.amount; | 164 total -= chunk.amount; |
162 prediction->budget_at = total; | 165 prediction->budget_at = total; |
163 prediction->time = chunk.expiration.ToDoubleT(); | 166 prediction->time = chunk.expiration.ToDoubleT(); |
164 predictions.push_back(std::move(prediction)); | 167 predictions.push_back(std::move(prediction)); |
165 } | 168 } |
166 | 169 |
167 DCHECK_EQ(0, total); | |
168 | |
169 callback.Run(blink::mojom::BudgetServiceErrorType::NONE, | 170 callback.Run(blink::mojom::BudgetServiceErrorType::NONE, |
170 std::move(predictions)); | 171 std::move(predictions)); |
171 } | 172 } |
172 | 173 |
173 void BudgetDatabase::SpendBudgetAfterSync(const url::Origin& origin, | 174 void BudgetDatabase::SpendBudgetAfterSync(const url::Origin& origin, |
174 double amount, | 175 double amount, |
175 const SpendBudgetCallback& callback, | 176 const SpendBudgetCallback& callback, |
176 bool success) { | 177 bool success) { |
177 if (!success) { | 178 if (!success) { |
178 callback.Run(blink::mojom::BudgetServiceErrorType::DATABASE_ERROR, | 179 callback.Run(blink::mojom::BudgetServiceErrorType::DATABASE_ERROR, |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
297 // Get the SES score and add engagement budget for the site. | 298 // Get the SES score and add engagement budget for the site. |
298 AddEngagementBudget(origin); | 299 AddEngagementBudget(origin); |
299 | 300 |
300 if (needs_write) | 301 if (needs_write) |
301 WriteCachedValuesToDatabase(origin, callback); | 302 WriteCachedValuesToDatabase(origin, callback); |
302 else | 303 else |
303 callback.Run(success); | 304 callback.Run(success); |
304 } | 305 } |
305 | 306 |
306 void BudgetDatabase::AddEngagementBudget(const url::Origin& origin) { | 307 void BudgetDatabase::AddEngagementBudget(const url::Origin& origin) { |
307 // Get the current SES score, which we'll use to set a new budget. | 308 // Calculate how much budget should be awarded. The award depends on the |
309 // time elapsed since the last award and the SES score. | |
310 base::TimeDelta elapsed = base::TimeDelta::FromDays(kBudgetDurationInDays); | |
Peter Beverloo
2017/01/12 14:40:04
nit: I think the "By default..." comment would sti
harkness
2017/01/12 17:44:14
Done.
| |
311 if (IsCached(origin)) { | |
312 elapsed = clock_->Now() - budget_map_[origin].last_engagement_award; | |
313 // Don't give engagement awards for periods less than an hour. | |
314 if (elapsed.InHours() < 1) | |
315 return; | |
316 // Cap elapsed time to the budget duration. | |
317 if (elapsed.InDays() > kBudgetDurationInDays) | |
318 elapsed = base::TimeDelta::FromDays(kBudgetDurationInDays); | |
319 } | |
320 | |
321 // Get the current SES score, and calculate the hourly budget for that score. | |
308 SiteEngagementService* service = SiteEngagementService::Get(profile_); | 322 SiteEngagementService* service = SiteEngagementService::Get(profile_); |
309 double score = service->GetScore(origin.GetURL()); | 323 double hourly_budget = kMaximumHourlyBudget * |
310 | 324 service->GetScore(origin.GetURL()) / |
311 // By default we award the "full" award. Then that ratio is decreased if | 325 service->GetMaxPoints(); |
312 // there have been other awards recently. | |
313 double ratio = 1.0; | |
314 | |
315 // Calculate how much budget should be awarded. If there is no entry in the | |
316 // cache then we award a full amount. | |
317 if (IsCached(origin)) { | |
318 base::TimeDelta elapsed = | |
319 clock_->Now() - budget_map_[origin].last_engagement_award; | |
320 int elapsed_hours = elapsed.InHours(); | |
321 // Don't give engagement awards for periods less than an hour. | |
322 if (elapsed_hours < 1) | |
323 return; | |
324 if (elapsed_hours < kBudgetDurationInHours) | |
325 ratio = elapsed_hours / kBudgetDurationInHours; | |
326 } | |
327 | 326 |
328 // Update the last_engagement_award to the current time. If the origin wasn't | 327 // Update the last_engagement_award to the current time. If the origin wasn't |
329 // already in the map, this adds a new entry for it. | 328 // already in the map, this adds a new entry for it. |
330 budget_map_[origin].last_engagement_award = clock_->Now(); | 329 budget_map_[origin].last_engagement_award = clock_->Now(); |
331 | 330 |
332 // Add a new chunk of budget for the origin at the default expiration time. | 331 // Add a new chunk of budget for the origin at the default expiration time. |
333 base::Time expiration = | 332 base::Time expiration = |
334 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours); | 333 clock_->Now() + base::TimeDelta::FromDays(kBudgetDurationInDays); |
335 budget_map_[origin].chunks.emplace_back(ratio * score, expiration); | 334 budget_map_[origin].chunks.emplace_back(elapsed.InHours() * hourly_budget, |
335 expiration); | |
336 | 336 |
337 // Any time we award engagement budget, which is done at most once an hour | 337 // Any time we award engagement budget, which is done at most once an hour |
338 // whenever any budget action is taken, record the budget. | 338 // whenever any budget action is taken, record the budget. |
339 double budget = GetBudget(origin); | 339 double budget = GetBudget(origin); |
340 UMA_HISTOGRAM_COUNTS_100("PushMessaging.BackgroundBudget", budget); | 340 UMA_HISTOGRAM_COUNTS_100("PushMessaging.BackgroundBudget", budget); |
341 } | 341 } |
342 | 342 |
343 // Cleans up budget in the cache. Relies on the caller eventually writing the | 343 // Cleans up budget in the cache. Relies on the caller eventually writing the |
344 // cache back to the database. | 344 // cache back to the database. |
345 bool BudgetDatabase::CleanupExpiredBudget(const url::Origin& origin) { | 345 bool BudgetDatabase::CleanupExpiredBudget(const url::Origin& origin) { |
346 if (!IsCached(origin)) | 346 if (!IsCached(origin)) |
347 return false; | 347 return false; |
348 | 348 |
349 base::Time now = clock_->Now(); | 349 base::Time now = clock_->Now(); |
350 BudgetChunks& chunks = budget_map_[origin].chunks; | 350 BudgetChunks& chunks = budget_map_[origin].chunks; |
351 auto cleanup_iter = chunks.begin(); | 351 auto cleanup_iter = chunks.begin(); |
352 | 352 |
353 // This relies on the list of chunks being in timestamp order. | 353 // This relies on the list of chunks being in timestamp order. |
354 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) | 354 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) |
355 cleanup_iter = chunks.erase(cleanup_iter); | 355 cleanup_iter = chunks.erase(cleanup_iter); |
356 | 356 |
357 // If the entire budget is empty now AND there have been no engagements | 357 // If the entire budget is empty now AND there have been no engagements |
358 // in the last kBudgetDurationInHours hours, remove this from the cache. | 358 // in the last kBudgetDurationInDays days, remove this from the cache. |
359 if (chunks.empty() && | 359 if (chunks.empty() && |
360 budget_map_[origin].last_engagement_award < | 360 budget_map_[origin].last_engagement_award < |
361 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours)) { | 361 clock_->Now() - base::TimeDelta::FromDays(kBudgetDurationInDays)) { |
362 budget_map_.erase(origin); | 362 budget_map_.erase(origin); |
363 return true; | 363 return true; |
364 } | 364 } |
365 | 365 |
366 // Although some things may have expired, there are some chunks still valid. | 366 // Although some things may have expired, there are some chunks still valid. |
367 // Don't write to the DB now, write either when all chunks expire or when the | 367 // Don't write to the DB now, write either when all chunks expire or when the |
368 // origin spends some budget. | 368 // origin spends some budget. |
369 return false; | 369 return false; |
370 } | 370 } |
OLD | NEW |