| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 "storage/browser/quota/quota_temporary_storage_evictor.h" | 5 #include "storage/browser/quota/quota_temporary_storage_evictor.h" |
| 6 | 6 |
| 7 #include <stdint.h> | 7 #include <stdint.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 | 10 |
| 11 #include "base/auto_reset.h" |
| 11 #include "base/bind.h" | 12 #include "base/bind.h" |
| 12 #include "base/metrics/histogram_macros.h" | 13 #include "base/metrics/histogram_macros.h" |
| 13 #include "storage/browser/quota/quota_manager.h" | 14 #include "storage/browser/quota/quota_manager.h" |
| 14 #include "url/gurl.h" | 15 #include "url/gurl.h" |
| 15 | 16 |
| 16 #define UMA_HISTOGRAM_MBYTES(name, sample) \ | 17 #define UMA_HISTOGRAM_MBYTES(name, sample) \ |
| 17 UMA_HISTOGRAM_CUSTOM_COUNTS( \ | 18 UMA_HISTOGRAM_CUSTOM_COUNTS( \ |
| 18 (name), static_cast<int>((sample) / kMBytes), \ | 19 (name), static_cast<int>((sample) / kMBytes), \ |
| 19 1, 10 * 1024 * 1024 /* 10TB */, 100) | 20 1, 10 * 1024 * 1024 /* 10TB */, 100) |
| 20 | 21 |
| 21 #define UMA_HISTOGRAM_MINUTES(name, sample) \ | 22 #define UMA_HISTOGRAM_MINUTES(name, sample) \ |
| 22 UMA_HISTOGRAM_CUSTOM_TIMES( \ | 23 UMA_HISTOGRAM_CUSTOM_TIMES( \ |
| 23 (name), (sample), \ | 24 (name), (sample), \ |
| 24 base::TimeDelta::FromMinutes(1), \ | 25 base::TimeDelta::FromMinutes(1), \ |
| 25 base::TimeDelta::FromDays(1), 50) | 26 base::TimeDelta::FromDays(1), 50) |
| 26 | 27 |
| 27 namespace { | 28 namespace { |
| 28 const int64_t kMBytes = 1024 * 1024; | 29 const int64_t kMBytes = 1024 * 1024; |
| 29 const double kUsageRatioToStartEviction = 0.7; | 30 const double kUsageRatioToStartEviction = 0.7; |
| 30 const int kThresholdOfErrorsToStopEviction = 5; | 31 const int kThresholdOfErrorsToStopEviction = 5; |
| 31 const int kHistogramReportIntervalMinutes = 60; | 32 const int kHistogramReportIntervalMinutes = 60; |
| 32 const double kMustRemainAvailableRatio = 0.1; | |
| 33 const int64_t kDefaultMustRemainAvailableSpace = 1024 * kMBytes; | |
| 34 const double kDiskSpaceShortageAllowanceRatio = 0.5; | 33 const double kDiskSpaceShortageAllowanceRatio = 0.5; |
| 35 } | 34 } |
| 36 | 35 |
| 37 namespace storage { | 36 namespace storage { |
| 38 | 37 |
| 39 const int QuotaTemporaryStorageEvictor:: | |
| 40 kMinAvailableToStartEvictionNotSpecified = -1; | |
| 41 | |
| 42 QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics() | 38 QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics() |
| 43 : in_round(false), | 39 : in_round(false), |
| 44 is_initialized(false), | 40 is_initialized(false), |
| 45 usage_overage_at_round(-1), | 41 usage_overage_at_round(-1), |
| 46 diskspace_shortage_at_round(-1), | 42 diskspace_shortage_at_round(-1), |
| 47 usage_on_beginning_of_round(-1), | 43 usage_on_beginning_of_round(-1), |
| 48 usage_on_end_of_round(-1), | 44 usage_on_end_of_round(-1), |
| 49 num_evicted_origins_in_round(0) { | 45 num_evicted_origins_in_round(0) { |
| 50 } | 46 } |
| 51 | 47 |
| 52 QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor( | 48 QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor( |
| 53 QuotaEvictionHandler* quota_eviction_handler, | 49 QuotaEvictionHandler* quota_eviction_handler, |
| 54 int64_t interval_ms) | 50 int64_t interval_ms) |
| 55 : min_available_to_start_eviction_( | 51 : quota_eviction_handler_(quota_eviction_handler), |
| 56 kMinAvailableToStartEvictionNotSpecified), | |
| 57 quota_eviction_handler_(quota_eviction_handler), | |
| 58 interval_ms_(interval_ms), | 52 interval_ms_(interval_ms), |
| 59 repeated_eviction_(true), | 53 timer_disabled_for_testing_(false), |
| 60 weak_factory_(this) { | 54 weak_factory_(this) { |
| 61 DCHECK(quota_eviction_handler); | 55 DCHECK(quota_eviction_handler); |
| 62 } | 56 } |
| 63 | 57 |
| 64 QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() { | 58 QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() { |
| 65 } | 59 } |
| 66 | 60 |
| 67 void QuotaTemporaryStorageEvictor::GetStatistics( | 61 void QuotaTemporaryStorageEvictor::GetStatistics( |
| 68 std::map<std::string, int64_t>* statistics) { | 62 std::map<std::string, int64_t>* statistics) { |
| 69 DCHECK(statistics); | 63 DCHECK(statistics); |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 135 time_of_end_of_last_nonskipped_round_ = base::Time::Now(); | 129 time_of_end_of_last_nonskipped_round_ = base::Time::Now(); |
| 136 } else { | 130 } else { |
| 137 ++statistics_.num_skipped_eviction_rounds; | 131 ++statistics_.num_skipped_eviction_rounds; |
| 138 } | 132 } |
| 139 // Reset stats for next round. | 133 // Reset stats for next round. |
| 140 round_statistics_ = EvictionRoundStatistics(); | 134 round_statistics_ = EvictionRoundStatistics(); |
| 141 } | 135 } |
| 142 | 136 |
| 143 void QuotaTemporaryStorageEvictor::Start() { | 137 void QuotaTemporaryStorageEvictor::Start() { |
| 144 DCHECK(CalledOnValidThread()); | 138 DCHECK(CalledOnValidThread()); |
| 139 |
| 140 base::AutoReset<bool> auto_reset(&timer_disabled_for_testing_, false); |
| 145 StartEvictionTimerWithDelay(0); | 141 StartEvictionTimerWithDelay(0); |
| 146 | 142 |
| 147 if (histogram_timer_.IsRunning()) | 143 if (histogram_timer_.IsRunning()) |
| 148 return; | 144 return; |
| 149 | 145 |
| 150 histogram_timer_.Start( | 146 histogram_timer_.Start( |
| 151 FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes), | 147 FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes), |
| 152 this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram); | 148 this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram); |
| 153 } | 149 } |
| 154 | 150 |
| 155 void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) { | 151 void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) { |
| 156 if (eviction_timer_.IsRunning()) | 152 if (eviction_timer_.IsRunning() || timer_disabled_for_testing_) |
| 157 return; | 153 return; |
| 158 eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms), | 154 eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms), |
| 159 this, &QuotaTemporaryStorageEvictor::ConsiderEviction); | 155 this, &QuotaTemporaryStorageEvictor::ConsiderEviction); |
| 160 } | 156 } |
| 161 | 157 |
| 162 void QuotaTemporaryStorageEvictor::ConsiderEviction() { | 158 void QuotaTemporaryStorageEvictor::ConsiderEviction() { |
| 163 OnEvictionRoundStarted(); | 159 OnEvictionRoundStarted(); |
| 164 | 160 quota_eviction_handler_->GetEvictionRoundInfo( |
| 165 if (min_available_to_start_eviction_ == | 161 base::Bind(&QuotaTemporaryStorageEvictor::OnGotEvictionRoundInfo, |
| 166 kMinAvailableToStartEvictionNotSpecified) { | 162 weak_factory_.GetWeakPtr())); |
| 167 quota_eviction_handler_->AsyncGetVolumeInfo( | |
| 168 base::Bind(&QuotaTemporaryStorageEvictor::OnGotVolumeInfo, | |
| 169 weak_factory_.GetWeakPtr())); | |
| 170 } else { | |
| 171 quota_eviction_handler_->GetUsageAndQuotaForEviction( | |
| 172 base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction, | |
| 173 weak_factory_.GetWeakPtr(), | |
| 174 min_available_to_start_eviction_)); | |
| 175 } | |
| 176 } | 163 } |
| 177 | 164 |
| 178 void QuotaTemporaryStorageEvictor::OnGotVolumeInfo( | 165 void QuotaTemporaryStorageEvictor::OnGotEvictionRoundInfo( |
| 179 bool success, uint64_t available_space, uint64_t total_size) { | 166 QuotaStatusCode status, |
| 180 // Compute how much to keep free as a function of total disk size. | 167 const QuotaSettings& settings, |
| 181 int64_t must_remain_available_space = success ? | 168 int64_t available_space, |
| 182 static_cast<int64_t>(total_size * kMustRemainAvailableRatio) : | 169 int64_t total_space, |
| 183 kDefaultMustRemainAvailableSpace; | 170 int64_t current_usage, |
| 171 bool current_usage_is_complete) { |
| 172 DCHECK_GE(current_usage, 0); |
| 184 | 173 |
| 185 quota_eviction_handler_->GetUsageAndQuotaForEviction( | 174 // Note: if there is no storage pressure, |current_usage| |
| 186 base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction, | 175 // may not be fully calculated and may be 0. |
| 187 weak_factory_.GetWeakPtr(), must_remain_available_space)); | |
| 188 } | |
| 189 | |
| 190 void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction( | |
| 191 int64_t must_remain_available_space, | |
| 192 QuotaStatusCode status, | |
| 193 const UsageAndQuota& qau) { | |
| 194 DCHECK(CalledOnValidThread()); | |
| 195 | |
| 196 int64_t usage = qau.global_limited_usage; | |
| 197 DCHECK_GE(usage, 0); | |
| 198 | 176 |
| 199 if (status != kQuotaStatusOk) | 177 if (status != kQuotaStatusOk) |
| 200 ++statistics_.num_errors_on_getting_usage_and_quota; | 178 ++statistics_.num_errors_on_getting_usage_and_quota; |
| 201 | 179 |
| 202 int64_t usage_overage = std::max( | 180 int64_t usage_overage = std::max( |
| 203 static_cast<int64_t>(0), | 181 INT64_C(0), |
| 204 usage - static_cast<int64_t>(qau.quota * kUsageRatioToStartEviction)); | 182 current_usage - static_cast<int64_t>(settings.pool_size * |
| 205 | 183 kUsageRatioToStartEviction)); |
| 206 int64_t diskspace_shortage = std::max( | 184 int64_t diskspace_shortage = |
| 207 static_cast<int64_t>(0), | 185 std::max(INT64_C(0), |
| 208 must_remain_available_space - qau.available_disk_space); | 186 settings.should_remain_available - available_space); |
| 187 DCHECK(current_usage_is_complete || diskspace_shortage == 0); |
| 209 | 188 |
| 210 // If we're using so little that freeing all of it wouldn't help, | 189 // If we're using so little that freeing all of it wouldn't help, |
| 211 // don't let the low space condition cause us to delete it all. | 190 // don't let the low space condition cause us to delete it all. |
| 212 if (usage < static_cast<int64_t>(diskspace_shortage * | 191 if (current_usage < static_cast<int64_t>(diskspace_shortage * |
| 213 kDiskSpaceShortageAllowanceRatio)) { | 192 kDiskSpaceShortageAllowanceRatio)) { |
| 214 diskspace_shortage = 0; | 193 diskspace_shortage = 0; |
| 215 } | 194 } |
| 216 | 195 |
| 217 if (!round_statistics_.is_initialized) { | 196 if (!round_statistics_.is_initialized) { |
| 218 round_statistics_.usage_overage_at_round = usage_overage; | 197 round_statistics_.usage_overage_at_round = usage_overage; |
| 219 round_statistics_.diskspace_shortage_at_round = diskspace_shortage; | 198 round_statistics_.diskspace_shortage_at_round = diskspace_shortage; |
| 220 round_statistics_.usage_on_beginning_of_round = usage; | 199 round_statistics_.usage_on_beginning_of_round = current_usage; |
| 221 round_statistics_.is_initialized = true; | 200 round_statistics_.is_initialized = true; |
| 222 } | 201 } |
| 223 round_statistics_.usage_on_end_of_round = usage; | 202 round_statistics_.usage_on_end_of_round = current_usage; |
| 224 | 203 |
| 225 int64_t amount_to_evict = std::max(usage_overage, diskspace_shortage); | 204 int64_t amount_to_evict = std::max(usage_overage, diskspace_shortage); |
| 226 if (status == kQuotaStatusOk && amount_to_evict > 0) { | 205 if (status == kQuotaStatusOk && amount_to_evict > 0) { |
| 227 // Space is getting tight. Get the least recently used origin and continue. | 206 // Space is getting tight. Get the least recently used origin and continue. |
| 228 // TODO(michaeln): if the reason for eviction is low physical disk space, | 207 // TODO(michaeln): if the reason for eviction is low physical disk space, |
| 229 // make 'unlimited' origins subject to eviction too. | 208 // make 'unlimited' origins subject to eviction too. |
| 230 quota_eviction_handler_->GetEvictionOrigin( | 209 quota_eviction_handler_->GetEvictionOrigin( |
| 231 kStorageTypeTemporary, in_progress_eviction_origins_, qau.quota, | 210 kStorageTypeTemporary, in_progress_eviction_origins_, |
| 211 settings.pool_size, |
| 232 base::Bind(&QuotaTemporaryStorageEvictor::OnGotEvictionOrigin, | 212 base::Bind(&QuotaTemporaryStorageEvictor::OnGotEvictionOrigin, |
| 233 weak_factory_.GetWeakPtr())); | 213 weak_factory_.GetWeakPtr())); |
| 234 } else { | 214 return; |
| 235 if (repeated_eviction_) { | |
| 236 // No action required, sleep for a while and check again later. | |
| 237 if (statistics_.num_errors_on_getting_usage_and_quota < | |
| 238 kThresholdOfErrorsToStopEviction) { | |
| 239 StartEvictionTimerWithDelay(interval_ms_); | |
| 240 } else { | |
| 241 // TODO(dmikurube): Try restarting eviction after a while. | |
| 242 LOG(WARNING) << "Stopped eviction of temporary storage due to errors " | |
| 243 "in GetUsageAndQuotaForEviction."; | |
| 244 } | |
| 245 } | |
| 246 OnEvictionRoundFinished(); | |
| 247 } | 215 } |
| 248 | 216 |
| 249 // TODO(dmikurube): Add error handling for the case status != kQuotaStatusOk. | 217 // No action required, sleep for a while and check again later. |
| 218 if (statistics_.num_errors_on_getting_usage_and_quota < |
| 219 kThresholdOfErrorsToStopEviction) { |
| 220 StartEvictionTimerWithDelay(interval_ms_); |
| 221 } else { |
| 222 // TODO(dmikurube): Add error handling for the case status is not OK. |
| 223 // TODO(dmikurube): Try restarting eviction after a while. |
| 224 LOG(WARNING) << "Stopped eviction of temporary storage due to errors"; |
| 225 } |
| 226 |
| 227 OnEvictionRoundFinished(); |
| 250 } | 228 } |
| 251 | 229 |
| 252 void QuotaTemporaryStorageEvictor::OnGotEvictionOrigin(const GURL& origin) { | 230 void QuotaTemporaryStorageEvictor::OnGotEvictionOrigin(const GURL& origin) { |
| 253 DCHECK(CalledOnValidThread()); | 231 DCHECK(CalledOnValidThread()); |
| 254 | 232 |
| 255 if (origin.is_empty()) { | 233 if (origin.is_empty()) { |
| 256 if (repeated_eviction_) | 234 StartEvictionTimerWithDelay(interval_ms_); |
| 257 StartEvictionTimerWithDelay(interval_ms_); | |
| 258 OnEvictionRoundFinished(); | 235 OnEvictionRoundFinished(); |
| 259 return; | 236 return; |
| 260 } | 237 } |
| 261 | 238 |
| 262 in_progress_eviction_origins_.insert(origin); | 239 in_progress_eviction_origins_.insert(origin); |
| 263 | 240 |
| 264 quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary, | 241 quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary, |
| 265 base::Bind( | 242 base::Bind( |
| 266 &QuotaTemporaryStorageEvictor::OnEvictionComplete, | 243 &QuotaTemporaryStorageEvictor::OnEvictionComplete, |
| 267 weak_factory_.GetWeakPtr())); | 244 weak_factory_.GetWeakPtr())); |
| 268 } | 245 } |
| 269 | 246 |
| 270 void QuotaTemporaryStorageEvictor::OnEvictionComplete( | 247 void QuotaTemporaryStorageEvictor::OnEvictionComplete( |
| 271 QuotaStatusCode status) { | 248 QuotaStatusCode status) { |
| 272 DCHECK(CalledOnValidThread()); | 249 DCHECK(CalledOnValidThread()); |
| 273 | 250 |
| 274 // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is | 251 // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is |
| 275 // ok. No need to deal with the case that all of the Delete operations fail | 252 // ok. No need to deal with the case that all of the Delete operations fail |
| 276 // for a certain origin. It doesn't result in trying to evict the same | 253 // for a certain origin. It doesn't result in trying to evict the same |
| 277 // origin permanently. The evictor skips origins which had deletion errors | 254 // origin permanently. The evictor skips origins which had deletion errors |
| 278 // a few times. | 255 // a few times. |
| 279 | 256 |
| 280 if (status == kQuotaStatusOk) { | 257 if (status == kQuotaStatusOk) { |
| 281 ++statistics_.num_evicted_origins; | 258 ++statistics_.num_evicted_origins; |
| 282 ++round_statistics_.num_evicted_origins_in_round; | 259 ++round_statistics_.num_evicted_origins_in_round; |
| 283 // We many need to get rid of more space so reconsider immediately. | 260 // We many need to get rid of more space so reconsider immediately. |
| 284 ConsiderEviction(); | 261 ConsiderEviction(); |
| 285 } else { | 262 } else { |
| 286 ++statistics_.num_errors_on_evicting_origin; | 263 ++statistics_.num_errors_on_evicting_origin; |
| 287 if (repeated_eviction_) { | 264 // Sleep for a while and retry again until we see too many errors. |
| 288 // Sleep for a while and retry again until we see too many errors. | 265 StartEvictionTimerWithDelay(interval_ms_); |
| 289 StartEvictionTimerWithDelay(interval_ms_); | |
| 290 } | |
| 291 OnEvictionRoundFinished(); | 266 OnEvictionRoundFinished(); |
| 292 } | 267 } |
| 293 } | 268 } |
| 294 | 269 |
| 295 } // namespace storage | 270 } // namespace storage |
| OLD | NEW |