1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "storage/browser/quota/quota_temporary_storage_evictor.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/metrics/histogram.h"
11#include "storage/browser/quota/quota_manager.h"
12#include "url/gurl.h"
13
14#define UMA_HISTOGRAM_MBYTES(name, sample)          \
15  UMA_HISTOGRAM_CUSTOM_COUNTS(                      \
16      (name), static_cast<int>((sample) / kMBytes), \
17      1, 10 * 1024 * 1024 /* 10TB */, 100)
18
19#define UMA_HISTOGRAM_MINUTES(name, sample) \
20  UMA_HISTOGRAM_CUSTOM_TIMES(             \
21      (name), (sample),                   \
22      base::TimeDelta::FromMinutes(1),    \
23      base::TimeDelta::FromDays(1), 50)
24
25namespace {
26const int64 kMBytes = 1024 * 1024;
27const double kUsageRatioToStartEviction = 0.7;
28const int kThresholdOfErrorsToStopEviction = 5;
29const int kHistogramReportIntervalMinutes = 60;
30}
31
32namespace storage {
33
34const int QuotaTemporaryStorageEvictor::
35    kMinAvailableDiskSpaceToStartEvictionNotSpecified = -1;
36
37QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics()
38    : in_round(false),
39      is_initialized(false),
40      usage_overage_at_round(-1),
41      diskspace_shortage_at_round(-1),
42      usage_on_beginning_of_round(-1),
43      usage_on_end_of_round(-1),
44      num_evicted_origins_in_round(0) {
45}
46
47QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor(
48    QuotaEvictionHandler* quota_eviction_handler,
49    int64 interval_ms)
50    : min_available_disk_space_to_start_eviction_(
51          kMinAvailableDiskSpaceToStartEvictionNotSpecified),
52      quota_eviction_handler_(quota_eviction_handler),
53      interval_ms_(interval_ms),
54      repeated_eviction_(true),
55      weak_factory_(this) {
56  DCHECK(quota_eviction_handler);
57}
58
59QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() {
60}
61
62void QuotaTemporaryStorageEvictor::GetStatistics(
63    std::map<std::string, int64>* statistics) {
64  DCHECK(statistics);
65
66  (*statistics)["errors-on-evicting-origin"] =
67      statistics_.num_errors_on_evicting_origin;
68  (*statistics)["errors-on-getting-usage-and-quota"] =
69      statistics_.num_errors_on_getting_usage_and_quota;
70  (*statistics)["evicted-origins"] =
71      statistics_.num_evicted_origins;
72  (*statistics)["eviction-rounds"] =
73      statistics_.num_eviction_rounds;
74  (*statistics)["skipped-eviction-rounds"] =
75      statistics_.num_skipped_eviction_rounds;
76}
77
78void QuotaTemporaryStorageEvictor::ReportPerRoundHistogram() {
79  DCHECK(round_statistics_.in_round);
80  DCHECK(round_statistics_.is_initialized);
81
82  base::Time now = base::Time::Now();
83  UMA_HISTOGRAM_TIMES("Quota.TimeSpentToAEvictionRound",
84                      now - round_statistics_.start_time);
85  if (!time_of_end_of_last_round_.is_null())
86    UMA_HISTOGRAM_MINUTES("Quota.TimeDeltaOfEvictionRounds",
87                          now - time_of_end_of_last_round_);
88  UMA_HISTOGRAM_MBYTES("Quota.UsageOverageOfTemporaryGlobalStorage",
89                       round_statistics_.usage_overage_at_round);
90  UMA_HISTOGRAM_MBYTES("Quota.DiskspaceShortage",
91                       round_statistics_.diskspace_shortage_at_round);
92  UMA_HISTOGRAM_MBYTES("Quota.EvictedBytesPerRound",
93                       round_statistics_.usage_on_beginning_of_round -
94                       round_statistics_.usage_on_end_of_round);
95  UMA_HISTOGRAM_COUNTS("Quota.NumberOfEvictedOriginsPerRound",
96                       round_statistics_.num_evicted_origins_in_round);
97}
98
99void QuotaTemporaryStorageEvictor::ReportPerHourHistogram() {
100  Statistics stats_in_hour(statistics_);
101  stats_in_hour.subtract_assign(previous_statistics_);
102  previous_statistics_ = statistics_;
103
104  UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnEvictingOriginPerHour",
105                       stats_in_hour.num_errors_on_evicting_origin);
106  UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnGettingUsageAndQuotaPerHour",
107                       stats_in_hour.num_errors_on_getting_usage_and_quota);
108  UMA_HISTOGRAM_COUNTS("Quota.EvictedOriginsPerHour",
109                       stats_in_hour.num_evicted_origins);
110  UMA_HISTOGRAM_COUNTS("Quota.EvictionRoundsPerHour",
111                       stats_in_hour.num_eviction_rounds);
112  UMA_HISTOGRAM_COUNTS("Quota.SkippedEvictionRoundsPerHour",
113                       stats_in_hour.num_skipped_eviction_rounds);
114}
115
116void QuotaTemporaryStorageEvictor::OnEvictionRoundStarted() {
117  if (round_statistics_.in_round)
118    return;
119  round_statistics_.in_round = true;
120  round_statistics_.start_time = base::Time::Now();
121  ++statistics_.num_eviction_rounds;
122}
123
124void QuotaTemporaryStorageEvictor::OnEvictionRoundFinished() {
125  // Check if skipped round
126  if (round_statistics_.num_evicted_origins_in_round) {
127    ReportPerRoundHistogram();
128    time_of_end_of_last_nonskipped_round_ = base::Time::Now();
129  } else {
130    ++statistics_.num_skipped_eviction_rounds;
131  }
132  // Reset stats for next round.
133  round_statistics_ = EvictionRoundStatistics();
134}
135
136void QuotaTemporaryStorageEvictor::Start() {
137  DCHECK(CalledOnValidThread());
138  StartEvictionTimerWithDelay(0);
139
140  if (histogram_timer_.IsRunning())
141    return;
142
143  histogram_timer_.Start(
144      FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes),
145      this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram);
146}
147
148void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) {
149  if (eviction_timer_.IsRunning())
150    return;
151  eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms),
152                        this, &QuotaTemporaryStorageEvictor::ConsiderEviction);
153}
154
155void QuotaTemporaryStorageEvictor::ConsiderEviction() {
156  OnEvictionRoundStarted();
157
158  // Get usage and disk space, then continue.
159  quota_eviction_handler_->GetUsageAndQuotaForEviction(
160      base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction,
161                 weak_factory_.GetWeakPtr()));
162}
163
164void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction(
165    QuotaStatusCode status,
166    const UsageAndQuota& qau) {
167  DCHECK(CalledOnValidThread());
168
169  int64 usage = qau.global_limited_usage;
170  DCHECK_GE(usage, 0);
171
172  if (status != kQuotaStatusOk)
173    ++statistics_.num_errors_on_getting_usage_and_quota;
174
175  int64 usage_overage = std::max(
176      static_cast<int64>(0),
177      usage - static_cast<int64>(qau.quota * kUsageRatioToStartEviction));
178
179  // min_available_disk_space_to_start_eviction_ might be < 0 if no value
180  // is explicitly configured yet.
181  int64 diskspace_shortage = std::max(
182      static_cast<int64>(0),
183      min_available_disk_space_to_start_eviction_ - qau.available_disk_space);
184
185  if (!round_statistics_.is_initialized) {
186    round_statistics_.usage_overage_at_round = usage_overage;
187    round_statistics_.diskspace_shortage_at_round = diskspace_shortage;
188    round_statistics_.usage_on_beginning_of_round = usage;
189    round_statistics_.is_initialized = true;
190  }
191  round_statistics_.usage_on_end_of_round = usage;
192
193  int64 amount_to_evict = std::max(usage_overage, diskspace_shortage);
194  if (status == kQuotaStatusOk && amount_to_evict > 0) {
195    // Space is getting tight. Get the least recently used origin and continue.
196    // TODO(michaeln): if the reason for eviction is low physical disk space,
197    // make 'unlimited' origins subject to eviction too.
198    quota_eviction_handler_->GetLRUOrigin(
199        kStorageTypeTemporary,
200        base::Bind(&QuotaTemporaryStorageEvictor::OnGotLRUOrigin,
201                   weak_factory_.GetWeakPtr()));
202  } else {
203    if (repeated_eviction_) {
204      // No action required, sleep for a while and check again later.
205      if (statistics_.num_errors_on_getting_usage_and_quota <
206          kThresholdOfErrorsToStopEviction) {
207        StartEvictionTimerWithDelay(interval_ms_);
208      } else {
209        // TODO(dmikurube): Try restarting eviction after a while.
210        LOG(WARNING) << "Stopped eviction of temporary storage due to errors "
211                        "in GetUsageAndQuotaForEviction.";
212      }
213    }
214    OnEvictionRoundFinished();
215  }
216
217  // TODO(dmikurube): Add error handling for the case status != kQuotaStatusOk.
218}
219
220void QuotaTemporaryStorageEvictor::OnGotLRUOrigin(const GURL& origin) {
221  DCHECK(CalledOnValidThread());
222
223  if (origin.is_empty()) {
224    if (repeated_eviction_)
225      StartEvictionTimerWithDelay(interval_ms_);
226    OnEvictionRoundFinished();
227    return;
228  }
229
230  quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary,
231      base::Bind(
232          &QuotaTemporaryStorageEvictor::OnEvictionComplete,
233          weak_factory_.GetWeakPtr()));
234}
235
236void QuotaTemporaryStorageEvictor::OnEvictionComplete(
237    QuotaStatusCode status) {
238  DCHECK(CalledOnValidThread());
239
240  // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is
241  // ok.  No need to deal with the case that all of the Delete operations fail
242  // for a certain origin.  It doesn't result in trying to evict the same
243  // origin permanently.  The evictor skips origins which had deletion errors
244  // a few times.
245
246  if (status == kQuotaStatusOk) {
247    ++statistics_.num_evicted_origins;
248    ++round_statistics_.num_evicted_origins_in_round;
249    // We many need to get rid of more space so reconsider immediately.
250    ConsiderEviction();
251  } else {
252    ++statistics_.num_errors_on_evicting_origin;
253    if (repeated_eviction_) {
254      // Sleep for a while and retry again until we see too many errors.
255      StartEvictionTimerWithDelay(interval_ms_);
256    }
257    OnEvictionRoundFinished();
258  }
259}
260
261}  // namespace storage
262