package org.commcare.logging.analytics; import org.commcare.CommCareApp; import org.commcare.resources.model.InstallStatsLogger; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.util.Date; import java.util.Hashtable; /** * Statistics associated with attempting to stage resources into the app's * update table. * * @author Phillip Mates (pmates@dimagi.com) */ public class UpdateStats implements InstallStatsLogger, Serializable { private static final String TOP_LEVEL_STATS_KEY = "top-level-update-exceptions"; private static final String UPGRADE_STATS_KEY = "upgrade_table_stats"; private static final long TWO_WEEKS_IN_MS = 1000 * 60 * 60 * 24 * 24; private static final int ATTEMPTS_UNTIL_UPDATE_STALE = 5; private final Hashtable<String, InstallAttempts<String>> resourceInstallStats; private long startInstallTime; private int restartCount = 0; private UpdateStats() { startInstallTime = new Date().getTime(); resourceInstallStats = new Hashtable<>(); resourceInstallStats.put(TOP_LEVEL_STATS_KEY, new InstallAttempts<String>(TOP_LEVEL_STATS_KEY)); } /** * Load update statistics associated with upgrade table from app * preferences * * @return Persistently-stored update stats or if no stats found then a new * update stats object. */ public static UpdateStats loadUpdateStats(CommCareApp app) { Object stats = PrefStats.loadStats(app, UPGRADE_STATS_KEY); if (stats != null) { return (UpdateStats)stats; } else { return new UpdateStats(); } } /** * Save update stats to app preferences for reuse if the update is ever * resumed. */ public static void saveStatsPersistently(CommCareApp app, UpdateStats stats) { PrefStats.saveStatsPersistently(app, UPGRADE_STATS_KEY, stats); } public void resetStats(CommCareApp app) { clearPersistedStats(app); startInstallTime = new Date().getTime(); resourceInstallStats.clear(); restartCount = 0; } /** * Wipe stats associated with upgrade table from app preferences. */ public static void clearPersistedStats(CommCareApp app) { PrefStats.clearPersistedStats(app, UPGRADE_STATS_KEY); } /** * Register attempt to download resources into update table. */ public void registerStagingAttempt() { restartCount++; } /** * Register stack trace for exception raised during update. */ public void registerUpdateException(Exception e) { recordResourceInstallFailure(TOP_LEVEL_STATS_KEY, e); } /** * @return Should the update be considered stale due to elapse time or too * many unsuccessful installs? */ public boolean isUpgradeStale() { long currentTime = new Date().getTime(); return (restartCount > ATTEMPTS_UNTIL_UPDATE_STALE || (currentTime - startInstallTime) > TWO_WEEKS_IN_MS); } @Override public void recordResourceInstallSuccess(String resourceName) { InstallAttempts<String> attempts = resourceInstallStats.get(resourceName); if (attempts == null) { attempts = new InstallAttempts<>(resourceName); resourceInstallStats.put(resourceName, attempts); } attempts.registerSuccesfulInstall(); } @Override public void recordResourceInstallFailure(String resourceName, Exception errorMsg) { InstallAttempts<String> attempts = resourceInstallStats.get(resourceName); if (attempts == null) { attempts = new InstallAttempts<>(resourceName); resourceInstallStats.put(resourceName, attempts); } String stackTrace = getStackTraceString(errorMsg); attempts.addFailure(stackTrace); } private static String getStackTraceString(Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); return sw.toString(); } @Override public String toString() { StringBuilder statsStringBuilder = new StringBuilder(); statsStringBuilder.append("Update first started: ") .append(new Date(startInstallTime).toString()) .append(".\n") .append("Update restarted ") .append(restartCount) .append(" times.\n") .append("Failures logged to the update table: \n") .append(resourceInstallStats.get(TOP_LEVEL_STATS_KEY).toString()) .append("\n"); for (String resourceName : resourceInstallStats.keySet()) { if (!resourceName.equals(TOP_LEVEL_STATS_KEY)) { statsStringBuilder.append(resourceInstallStats.get(resourceName).toString()).append("\n"); } } return statsStringBuilder.toString(); } public int getRestartCount() { return restartCount; } }