Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 package org.chromium.chrome.browser.crash; | 5 package org.chromium.chrome.browser.crash; |
| 6 | 6 |
| 7 import org.chromium.base.Log; | 7 import org.chromium.base.Log; |
| 8 import org.chromium.base.VisibleForTesting; | 8 import org.chromium.base.VisibleForTesting; |
| 9 | 9 |
| 10 import java.io.File; | 10 import java.io.File; |
| 11 import java.io.FilenameFilter; | 11 import java.io.FilenameFilter; |
| 12 import java.io.IOException; | 12 import java.io.IOException; |
| 13 import java.util.Arrays; | 13 import java.util.Arrays; |
| 14 import java.util.Comparator; | 14 import java.util.Comparator; |
| 15 import java.util.Date; | |
| 16 import java.util.HashSet; | |
| 17 import java.util.Set; | |
| 18 import java.util.concurrent.TimeUnit; | |
| 15 import java.util.regex.Matcher; | 19 import java.util.regex.Matcher; |
| 16 import java.util.regex.Pattern; | 20 import java.util.regex.Pattern; |
| 17 | 21 |
| 18 /** | 22 /** |
| 19 * Responsible for the Crash Report directory. It routinely scans the directory | 23 * Responsible for the Crash Report directory. It routinely scans the directory |
| 20 * for new Minidump files and takes appropriate actions by either uploading new | 24 * for new Minidump files and takes appropriate actions by either uploading new |
| 21 * crash dumps or deleting old ones. | 25 * crash dumps or deleting old ones. |
| 22 */ | 26 */ |
| 23 public class CrashFileManager { | 27 public class CrashFileManager { |
| 24 private static final String TAG = "CrashFileManager"; | 28 private static final String TAG = "CrashFileManager"; |
| 25 | 29 |
| 26 @VisibleForTesting | 30 @VisibleForTesting |
| 27 static final String CRASH_DUMP_DIR = "Crash Reports"; | 31 static final String CRASH_DUMP_DIR = "Crash Reports"; |
| 28 | 32 |
| 29 // This should mirror the C++ CrashUploadList::kReporterLogFilename variable . | 33 // This should mirror the C++ CrashUploadList::kReporterLogFilename variable . |
| 30 @VisibleForTesting | 34 @VisibleForTesting |
| 31 static final String CRASH_DUMP_LOGFILE = CRASH_DUMP_DIR + "/uploads.log"; | 35 static final String CRASH_DUMP_LOGFILE = "uploads.log"; |
| 32 | 36 |
| 33 private static final Pattern MINIDUMP_FIRST_TRY_PATTERN = | 37 private static final Pattern MINIDUMP_FIRST_TRY_PATTERN = |
| 34 Pattern.compile("\\.dmp([0-9]*)$\\z"); | 38 Pattern.compile("\\.dmp([0-9]*)$\\z"); |
| 35 | 39 |
| 36 private static final Pattern MINIDUMP_MIME_FIRST_TRY_PATTERN = | 40 private static final Pattern MINIDUMP_MIME_FIRST_TRY_PATTERN = |
| 37 Pattern.compile("\\.dmp([0-9]+)$\\z"); | 41 Pattern.compile("\\.dmp([0-9]+)$\\z"); |
| 38 | 42 |
| 39 private static final Pattern MINIDUMP_PATTERN = | 43 private static final Pattern MINIDUMP_PATTERN = |
| 40 Pattern.compile("\\.dmp([0-9]*)(\\.try[0-9])?\\z"); | 44 Pattern.compile("\\.dmp([0-9]*)(\\.try[0-9])?\\z"); |
| 41 | 45 |
| 42 private static final Pattern UPLOADED_MINIDUMP_PATTERN = Pattern.compile("\\ .up([0-9]*)\\z"); | 46 private static final Pattern UPLOADED_MINIDUMP_PATTERN = Pattern.compile("\\ .up([0-9]*)\\z"); |
| 43 | 47 |
| 44 private static final String UPLOADED_MINIDUMP_SUFFIX = ".up"; | 48 private static final String UPLOADED_MINIDUMP_SUFFIX = ".up"; |
| 45 | 49 |
| 46 private static final String UPLOAD_ATTEMPT_DELIMITER = ".try"; | 50 private static final String UPLOAD_ATTEMPT_DELIMITER = ".try"; |
| 47 | 51 |
| 48 @VisibleForTesting | 52 @VisibleForTesting |
| 49 protected static final String TMP_SUFFIX = ".tmp"; | 53 protected static final String TMP_SUFFIX = ".tmp"; |
| 50 | 54 |
| 51 private static final Pattern TMP_PATTERN = Pattern.compile("\\.tmp\\z"); | 55 private static final Pattern TMP_PATTERN = Pattern.compile("\\.tmp\\z"); |
| 52 | 56 |
| 57 // The maximum number of non-uploaded crashes that may be kept in the crash reports directory. | |
| 58 // Chosen to attempt to balance between keeping a generous number of crashes , and not using up | |
| 59 // too much filesystem storage space for obsolete crash reports. | |
| 60 @VisibleForTesting | |
| 61 protected static final int MAX_CRASH_REPORTS_TO_KEEP = 10; | |
| 62 | |
| 63 // The maximum age, in days, considered acceptable for a crash report. Repor ts older than this | |
| 64 // age will be removed. The constant is chosen to be quite conservative, whi le still allowing | |
| 65 // users to eventually reclaim filesystem storage space from obsolete crash reports. | |
| 66 private static final int MAX_CRASH_REPORT_AGE_IN_DAYS = 30; | |
| 67 | |
| 53 /** | 68 /** |
| 54 * Comparator used for sorting files by modification | 69 * Comparator used for sorting files by modification |
| 55 * Note that the behavior is undecided if the files are created at the same time | 70 * Note that the behavior is undecided if the files are created at the same time |
| 56 * @return Comparator for prioritizing the more recently modified file | 71 * @return Comparator for prioritizing the more recently modified file |
| 57 */ | 72 */ |
| 58 @VisibleForTesting | 73 @VisibleForTesting |
| 59 protected static final Comparator<File> sFileComparator = new Comparator<Fi le>() { | 74 protected static final Comparator<File> sFileComparator = new Comparator<Fi le>() { |
| 60 @Override | 75 @Override |
| 61 public int compare(File lhs, File rhs) { | 76 public int compare(File lhs, File rhs) { |
| 62 if (lhs == rhs) { | 77 if (lhs == rhs) { |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 147 public File[] getAllMinidumpFiles() { | 162 public File[] getAllMinidumpFiles() { |
| 148 return getMatchingFiles(MINIDUMP_PATTERN); | 163 return getMatchingFiles(MINIDUMP_PATTERN); |
| 149 } | 164 } |
| 150 | 165 |
| 151 public File[] getAllMinidumpFilesSorted() { | 166 public File[] getAllMinidumpFilesSorted() { |
| 152 File[] minidumps = getAllMinidumpFiles(); | 167 File[] minidumps = getAllMinidumpFiles(); |
| 153 Arrays.sort(minidumps, sFileComparator); | 168 Arrays.sort(minidumps, sFileComparator); |
| 154 return minidumps; | 169 return minidumps; |
| 155 } | 170 } |
| 156 | 171 |
| 172 @VisibleForTesting | |
| 173 protected File[] getAllFilesSorted() { | |
|
gayane -on leave until 09-2017
2016/08/29 18:56:47
Should we make getAllFilesSorted function to retur
Ilya Sherman
2016/08/29 22:33:01
The intention is for getAllFilesSorted() to return
gayane -on leave until 09-2017
2016/08/30 15:37:12
I would imagine smth like getAllAvailableMinidumpS
| |
| 174 File crashDir = getCrashDirectoryIfExists(); | |
| 175 if (crashDir == null) { | |
| 176 return new File[] {}; | |
| 177 } | |
| 178 | |
| 179 File[] files = crashDir.listFiles(); | |
| 180 Arrays.sort(files, sFileComparator); | |
| 181 return files; | |
| 182 } | |
| 183 | |
| 157 public void cleanOutAllNonFreshMinidumpFiles() { | 184 public void cleanOutAllNonFreshMinidumpFiles() { |
| 158 for (File f : getAllUploadedFiles()) { | 185 for (File f : getAllUploadedFiles()) { |
| 159 deleteFile(f); | 186 deleteFile(f); |
| 160 } | 187 } |
| 161 for (File f : getAllTempFiles()) { | 188 for (File f : getAllTempFiles()) { |
| 162 deleteFile(f); | 189 deleteFile(f); |
| 163 } | 190 } |
| 191 | |
| 192 Set<String> recentCrashes = new HashSet<String>(); | |
| 193 for (File f : getAllFilesSorted()) { | |
| 194 // The uploads.log file should always be preserved, as it stores the metadata that | |
| 195 // powers the chrome://crashes UI. | |
| 196 if (f.getName().equals(CRASH_DUMP_LOGFILE)) { | |
| 197 continue; | |
| 198 } | |
| 199 | |
| 200 // Delete the oldest crash reports that exceed the cap on the number of allowed reports. | |
| 201 // Each crash typically has two files associated with it: a .dmp fil e and a .logcat | |
| 202 // file. These have the same filename other than the file extension. | |
| 203 String fileNameSansExtension = f.getName().split("\\.")[0]; | |
|
gayane -on leave until 09-2017
2016/08/29 18:56:47
According to the filenameWithIncrementedAttemptNum
Ilya Sherman
2016/08/29 22:33:01
Yeah, I'm not sure why tests allow ".try1.dmp" as
gayane -on leave until 09-2017
2016/08/30 15:37:12
no, particular input, just misunderstood the split
| |
| 204 if (recentCrashes.size() < MAX_CRASH_REPORTS_TO_KEEP) { | |
| 205 recentCrashes.add(fileNameSansExtension); | |
| 206 } else if (!recentCrashes.contains(fileNameSansExtension)) { | |
| 207 deleteFile(f); | |
| 208 continue; | |
| 209 } | |
| 210 | |
| 211 // Delete any crash reports that are especially old. | |
| 212 long ageInMillis = new Date().getTime() - f.lastModified(); | |
| 213 long ageInDays = TimeUnit.DAYS.convert(ageInMillis, TimeUnit.MILLISE CONDS); | |
| 214 if (ageInDays > MAX_CRASH_REPORT_AGE_IN_DAYS) { | |
|
gayane -on leave until 09-2017
2016/08/29 18:56:47
Shouldn't this be before we decide to add the repo
Ilya Sherman
2016/08/29 22:33:01
The behavior should be equivalent, but I agree it
| |
| 215 deleteFile(f); | |
| 216 } | |
| 217 } | |
| 164 } | 218 } |
| 165 | 219 |
| 166 @VisibleForTesting | 220 @VisibleForTesting |
| 167 File[] getMatchingFiles(final Pattern pattern) { | 221 File[] getMatchingFiles(final Pattern pattern) { |
| 168 // Get dump dir and get all files with specified suffix.. The path | 222 // Get dump dir and get all files with specified suffix. The path |
| 169 // constructed here must match chrome_paths.cc (see case | 223 // constructed here must match chrome_paths.cc (see case |
| 170 // chrome::DIR_CRASH_DUMPS). | 224 // chrome::DIR_CRASH_DUMPS). |
| 171 File crashDir = getCrashDirectory(); | 225 File crashDir = getCrashDirectoryIfExists(); |
| 172 if (!crashDir.exists()) { | 226 if (crashDir == null) { |
| 173 Log.w(TAG, crashDir.getAbsolutePath() + " does not exist!"); | |
| 174 return new File[] {}; | |
| 175 } | |
| 176 if (!crashDir.isDirectory()) { | |
| 177 Log.w(TAG, crashDir.getAbsolutePath() + " is not a directory!"); | |
| 178 return new File[] {}; | 227 return new File[] {}; |
| 179 } | 228 } |
| 180 File[] minidumps = crashDir.listFiles(new FilenameFilter() { | 229 File[] minidumps = crashDir.listFiles(new FilenameFilter() { |
| 181 @Override | 230 @Override |
| 182 public boolean accept(File dir, String filename) { | 231 public boolean accept(File dir, String filename) { |
| 183 Matcher match = pattern.matcher(filename); | 232 Matcher match = pattern.matcher(filename); |
| 184 int tries = readAttemptNumber(filename); | 233 int tries = readAttemptNumber(filename); |
| 185 return match.find() && tries < MinidumpUploadService.MAX_TRIES_A LLOWED; | 234 return match.find() && tries < MinidumpUploadService.MAX_TRIES_A LLOWED; |
| 186 } | 235 } |
| 187 }); | 236 }); |
| 188 return minidumps; | 237 return minidumps; |
| 189 } | 238 } |
| 190 | 239 |
| 191 @VisibleForTesting | 240 @VisibleForTesting |
| 192 File[] getAllUploadedFiles() { | 241 File[] getAllUploadedFiles() { |
| 193 return getMatchingFiles(UPLOADED_MINIDUMP_PATTERN); | 242 return getMatchingFiles(UPLOADED_MINIDUMP_PATTERN); |
| 194 } | 243 } |
| 195 | 244 |
| 196 @VisibleForTesting | 245 @VisibleForTesting |
| 197 File getCrashDirectory() { | 246 File getCrashDirectory() { |
| 198 return new File(mCacheDir, CRASH_DUMP_DIR); | 247 return new File(mCacheDir, CRASH_DUMP_DIR); |
| 199 } | 248 } |
| 200 | 249 |
| 250 @VisibleForTesting | |
| 251 File getCrashDirectoryIfExists() { | |
| 252 File crashDirectory = getCrashDirectory(); | |
| 253 if (!crashDirectory.exists()) { | |
| 254 Log.w(TAG, crashDirectory.getAbsolutePath() + " does not exist!"); | |
| 255 return null; | |
| 256 } | |
| 257 if (!crashDirectory.isDirectory()) { | |
| 258 Log.w(TAG, crashDirectory.getAbsolutePath() + " is not a directory!" ); | |
| 259 return null; | |
| 260 } | |
| 261 return crashDirectory; | |
| 262 } | |
| 263 | |
| 201 public File createNewTempFile(String name) throws IOException { | 264 public File createNewTempFile(String name) throws IOException { |
| 202 File f = new File(getCrashDirectory(), name); | 265 File f = new File(getCrashDirectory(), name); |
| 203 if (f.exists()) { | 266 if (f.exists()) { |
| 204 if (f.delete()) { | 267 if (f.delete()) { |
| 205 f = new File(getCrashDirectory(), name); | 268 f = new File(getCrashDirectory(), name); |
| 206 } else { | 269 } else { |
| 207 Log.w(TAG, "Unable to delete previous logfile" | 270 Log.w(TAG, "Unable to delete previous logfile" |
| 208 + f.getAbsolutePath()); | 271 + f.getAbsolutePath()); |
| 209 } | 272 } |
| 210 } | 273 } |
| 211 return f; | 274 return f; |
| 212 } | 275 } |
| 213 | 276 |
| 214 File getCrashFile(String filename) { | 277 File getCrashFile(String filename) { |
| 215 return new File(getCrashDirectory(), filename); | 278 return new File(getCrashDirectory(), filename); |
| 216 } | 279 } |
| 217 | 280 |
| 218 File getCrashUploadLogFile() { | 281 File getCrashUploadLogFile() { |
| 219 return new File(mCacheDir, CRASH_DUMP_LOGFILE); | 282 return new File(getCrashDirectory(), CRASH_DUMP_LOGFILE); |
| 220 } | 283 } |
| 221 | 284 |
| 222 private File[] getAllTempFiles() { | 285 private File[] getAllTempFiles() { |
| 223 return getMatchingFiles(TMP_PATTERN); | 286 return getMatchingFiles(TMP_PATTERN); |
| 224 } | 287 } |
| 225 } | 288 } |
| OLD | NEW |