mobile/android/base/background/healthreport/prune/PrunePolicy.java

changeset 0
6474c204b198
     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 +}

mercurial