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 // For context, silent push messages cost 2 each, so this allows 6 silent push |
| 34 // messages a day for a fully engaged site. See budget_manager.cc for costs of |
| 35 // various actions. |
| 36 constexpr double kMaximumHourlyBudget = 12.0 / 24.0; |
32 | 37 |
33 } // namespace | 38 } // namespace |
34 | 39 |
35 BudgetDatabase::BudgetInfo::BudgetInfo() {} | 40 BudgetDatabase::BudgetInfo::BudgetInfo() {} |
36 | 41 |
37 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other) | 42 BudgetDatabase::BudgetInfo::BudgetInfo(const BudgetInfo&& other) |
38 : last_engagement_award(other.last_engagement_award) { | 43 : last_engagement_award(other.last_engagement_award) { |
39 chunks = std::move(other.chunks); | 44 chunks = std::move(other.chunks); |
40 } | 45 } |
41 | 46 |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
157 // expiration times going forward. | 162 // expiration times going forward. |
158 const BudgetChunks& chunks = budget_map_[origin].chunks; | 163 const BudgetChunks& chunks = budget_map_[origin].chunks; |
159 for (const auto& chunk : chunks) { | 164 for (const auto& chunk : chunks) { |
160 blink::mojom::BudgetStatePtr prediction(blink::mojom::BudgetState::New()); | 165 blink::mojom::BudgetStatePtr prediction(blink::mojom::BudgetState::New()); |
161 total -= chunk.amount; | 166 total -= chunk.amount; |
162 prediction->budget_at = total; | 167 prediction->budget_at = total; |
163 prediction->time = chunk.expiration.ToDoubleT(); | 168 prediction->time = chunk.expiration.ToDoubleT(); |
164 predictions.push_back(std::move(prediction)); | 169 predictions.push_back(std::move(prediction)); |
165 } | 170 } |
166 | 171 |
167 DCHECK_EQ(0, total); | |
168 | |
169 callback.Run(blink::mojom::BudgetServiceErrorType::NONE, | 172 callback.Run(blink::mojom::BudgetServiceErrorType::NONE, |
170 std::move(predictions)); | 173 std::move(predictions)); |
171 } | 174 } |
172 | 175 |
173 void BudgetDatabase::SpendBudgetAfterSync(const url::Origin& origin, | 176 void BudgetDatabase::SpendBudgetAfterSync(const url::Origin& origin, |
174 double amount, | 177 double amount, |
175 const SpendBudgetCallback& callback, | 178 const SpendBudgetCallback& callback, |
176 bool success) { | 179 bool success) { |
177 if (!success) { | 180 if (!success) { |
178 callback.Run(blink::mojom::BudgetServiceErrorType::DATABASE_ERROR, | 181 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. | 300 // Get the SES score and add engagement budget for the site. |
298 AddEngagementBudget(origin); | 301 AddEngagementBudget(origin); |
299 | 302 |
300 if (needs_write) | 303 if (needs_write) |
301 WriteCachedValuesToDatabase(origin, callback); | 304 WriteCachedValuesToDatabase(origin, callback); |
302 else | 305 else |
303 callback.Run(success); | 306 callback.Run(success); |
304 } | 307 } |
305 | 308 |
306 void BudgetDatabase::AddEngagementBudget(const url::Origin& origin) { | 309 void BudgetDatabase::AddEngagementBudget(const url::Origin& origin) { |
307 // Get the current SES score, which we'll use to set a new budget. | 310 // Calculate how much budget should be awarded. The award depends on the |
| 311 // time elapsed since the last award and the SES score. |
| 312 // By default, give the origin kBudgetDurationInDays worth of budget, but |
| 313 // reduce that if budget has already been given during that period. |
| 314 base::TimeDelta elapsed = base::TimeDelta::FromDays(kBudgetDurationInDays); |
| 315 if (IsCached(origin)) { |
| 316 elapsed = clock_->Now() - budget_map_[origin].last_engagement_award; |
| 317 // Don't give engagement awards for periods less than an hour. |
| 318 if (elapsed.InHours() < 1) |
| 319 return; |
| 320 // Cap elapsed time to the budget duration. |
| 321 if (elapsed.InDays() > kBudgetDurationInDays) |
| 322 elapsed = base::TimeDelta::FromDays(kBudgetDurationInDays); |
| 323 } |
| 324 |
| 325 // Get the current SES score, and calculate the hourly budget for that score. |
308 SiteEngagementService* service = SiteEngagementService::Get(profile_); | 326 SiteEngagementService* service = SiteEngagementService::Get(profile_); |
309 double score = service->GetScore(origin.GetURL()); | 327 double hourly_budget = kMaximumHourlyBudget * |
310 | 328 service->GetScore(origin.GetURL()) / |
311 // By default we award the "full" award. Then that ratio is decreased if | 329 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 | 330 |
328 // Update the last_engagement_award to the current time. If the origin wasn't | 331 // 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. | 332 // already in the map, this adds a new entry for it. |
330 budget_map_[origin].last_engagement_award = clock_->Now(); | 333 budget_map_[origin].last_engagement_award = clock_->Now(); |
331 | 334 |
332 // Add a new chunk of budget for the origin at the default expiration time. | 335 // Add a new chunk of budget for the origin at the default expiration time. |
333 base::Time expiration = | 336 base::Time expiration = |
334 clock_->Now() + base::TimeDelta::FromHours(kBudgetDurationInHours); | 337 clock_->Now() + base::TimeDelta::FromDays(kBudgetDurationInDays); |
335 budget_map_[origin].chunks.emplace_back(ratio * score, expiration); | 338 budget_map_[origin].chunks.emplace_back(elapsed.InHours() * hourly_budget, |
| 339 expiration); |
336 | 340 |
337 // Any time we award engagement budget, which is done at most once an hour | 341 // Any time we award engagement budget, which is done at most once an hour |
338 // whenever any budget action is taken, record the budget. | 342 // whenever any budget action is taken, record the budget. |
339 double budget = GetBudget(origin); | 343 double budget = GetBudget(origin); |
340 UMA_HISTOGRAM_COUNTS_100("PushMessaging.BackgroundBudget", budget); | 344 UMA_HISTOGRAM_COUNTS_100("PushMessaging.BackgroundBudget", budget); |
341 } | 345 } |
342 | 346 |
343 // Cleans up budget in the cache. Relies on the caller eventually writing the | 347 // Cleans up budget in the cache. Relies on the caller eventually writing the |
344 // cache back to the database. | 348 // cache back to the database. |
345 bool BudgetDatabase::CleanupExpiredBudget(const url::Origin& origin) { | 349 bool BudgetDatabase::CleanupExpiredBudget(const url::Origin& origin) { |
346 if (!IsCached(origin)) | 350 if (!IsCached(origin)) |
347 return false; | 351 return false; |
348 | 352 |
349 base::Time now = clock_->Now(); | 353 base::Time now = clock_->Now(); |
350 BudgetChunks& chunks = budget_map_[origin].chunks; | 354 BudgetChunks& chunks = budget_map_[origin].chunks; |
351 auto cleanup_iter = chunks.begin(); | 355 auto cleanup_iter = chunks.begin(); |
352 | 356 |
353 // This relies on the list of chunks being in timestamp order. | 357 // This relies on the list of chunks being in timestamp order. |
354 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) | 358 while (cleanup_iter != chunks.end() && cleanup_iter->expiration <= now) |
355 cleanup_iter = chunks.erase(cleanup_iter); | 359 cleanup_iter = chunks.erase(cleanup_iter); |
356 | 360 |
357 // If the entire budget is empty now AND there have been no engagements | 361 // If the entire budget is empty now AND there have been no engagements |
358 // in the last kBudgetDurationInHours hours, remove this from the cache. | 362 // in the last kBudgetDurationInDays days, remove this from the cache. |
359 if (chunks.empty() && | 363 if (chunks.empty() && |
360 budget_map_[origin].last_engagement_award < | 364 budget_map_[origin].last_engagement_award < |
361 clock_->Now() - base::TimeDelta::FromHours(kBudgetDurationInHours)) { | 365 clock_->Now() - base::TimeDelta::FromDays(kBudgetDurationInDays)) { |
362 budget_map_.erase(origin); | 366 budget_map_.erase(origin); |
363 return true; | 367 return true; |
364 } | 368 } |
365 | 369 |
366 // Although some things may have expired, there are some chunks still valid. | 370 // 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 | 371 // Don't write to the DB now, write either when all chunks expire or when the |
368 // origin spends some budget. | 372 // origin spends some budget. |
369 return false; | 373 return false; |
370 } | 374 } |
OLD | NEW |