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