1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/background/healthreport/prune/PrunePolicy.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,233 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.background.healthreport.prune; 1.9 + 1.10 +import org.mozilla.gecko.background.common.log.Logger; 1.11 +import org.mozilla.gecko.background.healthreport.HealthReportConstants; 1.12 + 1.13 +import android.content.SharedPreferences; 1.14 + 1.15 +/** 1.16 + * Manages scheduling of the pruning of old Firefox Health Report data. 1.17 + * 1.18 + * There are three main actions that take place: 1.19 + * 1) Excessive storage pruning: The recorded data is taking up an unreasonable amount of space. 1.20 + * 2) Expired data pruning: Data that is kept around longer than is useful. 1.21 + * 3) Cleanup: To deal with storage maintenance (e.g. bloat and fragmentation) 1.22 + * 1.23 + * (1) and (2) are performed periodically on their own schedules. (3) will activate after a 1.24 + * certain duration but only after (1) or (2) is performed. 1.25 + */ 1.26 +public class PrunePolicy { 1.27 + public static final String LOG_TAG = PrunePolicy.class.getSimpleName(); 1.28 + 1.29 + protected final PrunePolicyStorage storage; 1.30 + protected final SharedPreferences sharedPreferences; 1.31 + protected final Editor editor; 1.32 + 1.33 + public PrunePolicy(final PrunePolicyStorage storage, final SharedPreferences sharedPrefs) { 1.34 + this.storage = storage; 1.35 + this.sharedPreferences = sharedPrefs; 1.36 + this.editor = new Editor(this.sharedPreferences.edit()); 1.37 + } 1.38 + 1.39 + protected SharedPreferences getSharedPreferences() { 1.40 + return this.sharedPreferences; 1.41 + } 1.42 + 1.43 + public void tick(final long time) { 1.44 + try { 1.45 + try { 1.46 + boolean pruned = attemptPruneBySize(time); 1.47 + pruned = attemptExpiration(time) ? true : pruned; 1.48 + // We only need to cleanup after a large pruning. 1.49 + if (pruned) { 1.50 + attemptStorageCleanup(time); 1.51 + } 1.52 + } catch (Exception e) { 1.53 + // While catching Exception is ordinarily bad form, this Service runs in the same process 1.54 + // as Fennec so if we crash, it crashes. Additionally, this Service runs regularly so 1.55 + // these crashes could be regular. Thus, we choose to quietly fail instead. 1.56 + Logger.error(LOG_TAG, "Got exception pruning document.", e); 1.57 + } finally { 1.58 + editor.commit(); 1.59 + } 1.60 + } catch (Exception e) { 1.61 + Logger.error(LOG_TAG, "Got exception committing to SharedPreferences.", e); 1.62 + } finally { 1.63 + storage.close(); 1.64 + } 1.65 + } 1.66 + 1.67 + protected boolean attemptPruneBySize(final long time) { 1.68 + final long nextPrune = getNextPruneBySizeTime(); 1.69 + if (nextPrune < 0) { 1.70 + Logger.debug(LOG_TAG, "Initializing prune-by-size time."); 1.71 + editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks()); 1.72 + return false; 1.73 + } 1.74 + 1.75 + // If the system clock is skewed into the past, making the time between prunes too long, reset 1.76 + // the clock. 1.77 + if (nextPrune > getMinimumTimeBetweenPruneBySizeChecks() + time) { 1.78 + Logger.debug(LOG_TAG, "Clock skew detected - resetting prune-by-size time."); 1.79 + editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks()); 1.80 + return false; 1.81 + } 1.82 + 1.83 + if (nextPrune > time) { 1.84 + Logger.debug(LOG_TAG, "Skipping prune-by-size - wait period has not yet elapsed."); 1.85 + return false; 1.86 + } 1.87 + 1.88 + Logger.debug(LOG_TAG, "Attempting prune-by-size."); 1.89 + 1.90 + // Prune environments first because their cascading deletions may delete some events. These 1.91 + // environments are pruned in order of least-recently used first. Note that orphaned 1.92 + // environments are ignored here and should be removed elsewhere. 1.93 + final int environmentCount = storage.getEnvironmentCount(); 1.94 + if (environmentCount > getMaxEnvironmentCount()) { 1.95 + final int environmentPruneCount = environmentCount - getEnvironmentCountAfterPrune(); 1.96 + Logger.debug(LOG_TAG, "Pruning " + environmentPruneCount + " environments."); 1.97 + storage.pruneEnvironments(environmentPruneCount); 1.98 + } 1.99 + 1.100 + final int eventCount = storage.getEventCount(); 1.101 + if (eventCount > getMaxEventCount()) { 1.102 + final int eventPruneCount = eventCount - getEventCountAfterPrune(); 1.103 + Logger.debug(LOG_TAG, "Pruning up to " + eventPruneCount + " events."); 1.104 + storage.pruneEvents(eventPruneCount); 1.105 + } 1.106 + editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks()); 1.107 + return true; 1.108 + } 1.109 + 1.110 + protected boolean attemptExpiration(final long time) { 1.111 + final long nextPrune = getNextExpirationTime(); 1.112 + if (nextPrune < 0) { 1.113 + Logger.debug(LOG_TAG, "Initializing expiration time."); 1.114 + editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks()); 1.115 + return false; 1.116 + } 1.117 + 1.118 + // If the system clock is skewed into the past, making the time between prunes too long, reset 1.119 + // the clock. 1.120 + if (nextPrune > getMinimumTimeBetweenExpirationChecks() + time) { 1.121 + Logger.debug(LOG_TAG, "Clock skew detected - resetting expiration time."); 1.122 + editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks()); 1.123 + return false; 1.124 + } 1.125 + 1.126 + if (nextPrune > time) { 1.127 + Logger.debug(LOG_TAG, "Skipping expiration - wait period has not yet elapsed."); 1.128 + return false; 1.129 + } 1.130 + 1.131 + final long oldEventTime = time - getEventExistenceDuration(); 1.132 + Logger.debug(LOG_TAG, "Pruning data older than " + oldEventTime + "."); 1.133 + storage.deleteDataBefore(oldEventTime); 1.134 + editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks()); 1.135 + return true; 1.136 + } 1.137 + 1.138 + protected boolean attemptStorageCleanup(final long time) { 1.139 + // Cleanup if max duration since last cleanup is exceeded. 1.140 + final long nextCleanup = getNextCleanupTime(); 1.141 + if (nextCleanup < 0) { 1.142 + Logger.debug(LOG_TAG, "Initializing cleanup time."); 1.143 + editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks()); 1.144 + return false; 1.145 + } 1.146 + 1.147 + // If the system clock is skewed into the past, making the time between cleanups too long, 1.148 + // reset the clock. 1.149 + if (nextCleanup > getMinimumTimeBetweenCleanupChecks() + time) { 1.150 + Logger.debug(LOG_TAG, "Clock skew detected - resetting cleanup time."); 1.151 + editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks()); 1.152 + return false; 1.153 + } 1.154 + 1.155 + if (nextCleanup > time) { 1.156 + Logger.debug(LOG_TAG, "Skipping cleanup - wait period has not yet elapsed."); 1.157 + return false; 1.158 + } 1.159 + 1.160 + editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks()); 1.161 + Logger.debug(LOG_TAG, "Cleaning up storage."); 1.162 + storage.cleanup(); 1.163 + return true; 1.164 + } 1.165 + 1.166 + protected static class Editor { 1.167 + protected final SharedPreferences.Editor editor; 1.168 + 1.169 + public Editor(final SharedPreferences.Editor editor) { 1.170 + this.editor = editor; 1.171 + } 1.172 + 1.173 + public void commit() { 1.174 + editor.commit(); 1.175 + } 1.176 + 1.177 + public Editor setNextExpirationTime(final long time) { 1.178 + editor.putLong(HealthReportConstants.PREF_EXPIRATION_TIME, time); 1.179 + return this; 1.180 + } 1.181 + 1.182 + public Editor setNextPruneBySizeTime(final long time) { 1.183 + editor.putLong(HealthReportConstants.PREF_PRUNE_BY_SIZE_TIME, time); 1.184 + return this; 1.185 + } 1.186 + 1.187 + public Editor setNextCleanupTime(final long time) { 1.188 + editor.putLong(HealthReportConstants.PREF_CLEANUP_TIME, time); 1.189 + return this; 1.190 + } 1.191 + } 1.192 + 1.193 + private long getNextExpirationTime() { 1.194 + return getSharedPreferences().getLong(HealthReportConstants.PREF_EXPIRATION_TIME, -1L); 1.195 + } 1.196 + 1.197 + private long getEventExistenceDuration() { 1.198 + return HealthReportConstants.EVENT_EXISTENCE_DURATION; 1.199 + } 1.200 + 1.201 + private long getMinimumTimeBetweenExpirationChecks() { 1.202 + return HealthReportConstants.MINIMUM_TIME_BETWEEN_EXPIRATION_CHECKS_MILLIS; 1.203 + } 1.204 + 1.205 + private long getNextPruneBySizeTime() { 1.206 + return getSharedPreferences().getLong(HealthReportConstants.PREF_PRUNE_BY_SIZE_TIME, -1L); 1.207 + } 1.208 + 1.209 + private long getMinimumTimeBetweenPruneBySizeChecks() { 1.210 + return HealthReportConstants.MINIMUM_TIME_BETWEEN_PRUNE_BY_SIZE_CHECKS_MILLIS; 1.211 + } 1.212 + 1.213 + private int getMaxEnvironmentCount() { 1.214 + return HealthReportConstants.MAX_ENVIRONMENT_COUNT; 1.215 + } 1.216 + 1.217 + private int getEnvironmentCountAfterPrune() { 1.218 + return HealthReportConstants.ENVIRONMENT_COUNT_AFTER_PRUNE; 1.219 + } 1.220 + 1.221 + private int getMaxEventCount() { 1.222 + return HealthReportConstants.MAX_EVENT_COUNT; 1.223 + } 1.224 + 1.225 + private int getEventCountAfterPrune() { 1.226 + return HealthReportConstants.EVENT_COUNT_AFTER_PRUNE; 1.227 + } 1.228 + 1.229 + private long getNextCleanupTime() { 1.230 + return getSharedPreferences().getLong(HealthReportConstants.PREF_CLEANUP_TIME, -1L); 1.231 + } 1.232 + 1.233 + private long getMinimumTimeBetweenCleanupChecks() { 1.234 + return HealthReportConstants.MINIMUM_TIME_BETWEEN_CLEANUP_CHECKS_MILLIS; 1.235 + } 1.236 +}