| 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 android.annotation.SuppressLint; |
| 7 import android.app.IntentService; | 8 import android.app.IntentService; |
| 9 import android.app.job.JobInfo; |
| 10 import android.content.ComponentName; |
| 8 import android.content.Context; | 11 import android.content.Context; |
| 9 import android.content.Intent; | 12 import android.content.Intent; |
| 13 import android.os.Build; |
| 14 import android.os.PersistableBundle; |
| 10 import android.support.annotation.StringDef; | 15 import android.support.annotation.StringDef; |
| 11 | 16 |
| 12 import org.chromium.base.Log; | 17 import org.chromium.base.Log; |
| 13 import org.chromium.base.StreamUtil; | 18 import org.chromium.base.StreamUtil; |
| 14 import org.chromium.base.VisibleForTesting; | 19 import org.chromium.base.VisibleForTesting; |
| 15 import org.chromium.base.annotations.CalledByNative; | 20 import org.chromium.base.annotations.CalledByNative; |
| 16 import org.chromium.base.metrics.RecordHistogram; | 21 import org.chromium.base.metrics.RecordHistogram; |
| 22 import org.chromium.chrome.browser.ChromeFeatureList; |
| 17 import org.chromium.chrome.browser.preferences.ChromePreferenceManager; | 23 import org.chromium.chrome.browser.preferences.ChromePreferenceManager; |
| 18 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager
; | 24 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager
; |
| 25 import org.chromium.components.background_task_scheduler.TaskIds; |
| 19 import org.chromium.components.minidump_uploader.CrashFileManager; | 26 import org.chromium.components.minidump_uploader.CrashFileManager; |
| 20 import org.chromium.components.minidump_uploader.MinidumpUploadCallable; | 27 import org.chromium.components.minidump_uploader.MinidumpUploadCallable; |
| 28 import org.chromium.components.minidump_uploader.MinidumpUploadJobService; |
| 21 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionMa
nager; | 29 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionMa
nager; |
| 22 | 30 |
| 23 import java.io.BufferedReader; | 31 import java.io.BufferedReader; |
| 24 import java.io.File; | 32 import java.io.File; |
| 25 import java.io.FileReader; | 33 import java.io.FileReader; |
| 26 import java.io.IOException; | 34 import java.io.IOException; |
| 27 | 35 |
| 28 /** | 36 /** |
| 29 * Service that is responsible for uploading crash minidumps to the Google crash
server. | 37 * Service that is responsible for uploading crash minidumps to the Google crash
server. |
| 30 */ | 38 */ |
| (...skipping 30 matching lines...) Expand all Loading... |
| 61 static final String OTHER = "Other"; | 69 static final String OTHER = "Other"; |
| 62 | 70 |
| 63 static final String[] TYPES = {BROWSER, RENDERER, GPU, OTHER}; | 71 static final String[] TYPES = {BROWSER, RENDERER, GPU, OTHER}; |
| 64 | 72 |
| 65 public MinidumpUploadService() { | 73 public MinidumpUploadService() { |
| 66 super(TAG); | 74 super(TAG); |
| 67 setIntentRedelivery(true); | 75 setIntentRedelivery(true); |
| 68 } | 76 } |
| 69 | 77 |
| 70 /** | 78 /** |
| 79 * @return Whether to use the JobSchduler API to upload crash reports, rathe
r than directly |
| 80 * creating a service for uploading. |
| 81 */ |
| 82 public static boolean shouldUseJobSchedulerForUploads() { |
| 83 // The JobScheduler API is only available as of Android L. |
| 84 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false; |
| 85 |
| 86 return ChromeFeatureList.isEnabled( |
| 87 ChromeFeatureList.UPLOAD_CRASH_REPORTS_USING_JOB_SCHEDULER); |
| 88 } |
| 89 |
| 90 /** |
| 91 * Schedules uploading of all pending minidumps, using the JobScheduler API. |
| 92 */ |
| 93 @SuppressLint("NewApi") |
| 94 public static void scheduleUploadJob(Context context) { |
| 95 assert shouldUseJobSchedulerForUploads(); |
| 96 |
| 97 CrashReportingPermissionManager permissionManager = PrivacyPreferencesMa
nager.getInstance(); |
| 98 PersistableBundle permissions = new PersistableBundle(); |
| 99 // Note: putBoolean is only available in API level 22, so massage the da
ta into ints |
| 100 // instead. |
| 101 permissions.putInt(ChromeMinidumpUploaderDelegate.IS_CLIENT_IN_METRICS_S
AMPLE, |
| 102 permissionManager.isClientInMetricsSample() ? 1 : 0); |
| 103 permissions.putInt(ChromeMinidumpUploaderDelegate.IS_CRASH_UPLOAD_DISABL
ED_BY_COMMAND_LINE, |
| 104 permissionManager.isCrashUploadDisabledByCommandLine() ? 1 : 0); |
| 105 permissions.putInt(ChromeMinidumpUploaderDelegate.IS_UPLOAD_ENABLED_FOR_
TESTS, |
| 106 permissionManager.isUploadEnabledForTests() ? 1 : 0); |
| 107 |
| 108 JobInfo.Builder builder = |
| 109 new JobInfo |
| 110 .Builder(TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID, |
| 111 new ComponentName(context, ChromeMinidumpUploadJ
obService.class)) |
| 112 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) |
| 113 .setExtras(permissions); |
| 114 MinidumpUploadJobService.scheduleUpload(context, builder); |
| 115 } |
| 116 |
| 117 /** |
| 71 * Stores the successes and failures from uploading crash to UMA, | 118 * Stores the successes and failures from uploading crash to UMA, |
| 72 */ | 119 */ |
| 73 public static void storeBreakpadUploadStatsInUma(ChromePreferenceManager pre
f) { | 120 public static void storeBreakpadUploadStatsInUma(ChromePreferenceManager pre
f) { |
| 74 for (String type : TYPES) { | 121 for (String type : TYPES) { |
| 75 for (int success = pref.getCrashSuccessUploadCount(type); success >
0; success--) { | 122 for (int success = pref.getCrashSuccessUploadCount(type); success >
0; success--) { |
| 76 RecordHistogram.recordEnumeratedHistogram( | 123 RecordHistogram.recordEnumeratedHistogram( |
| 77 HISTOGRAM_NAME_PREFIX + type, SUCCESS, HISTOGRAM_MAX); | 124 HISTOGRAM_NAME_PREFIX + type, SUCCESS, HISTOGRAM_MAX); |
| 78 } | 125 } |
| 79 for (int fail = pref.getCrashFailureUploadCount(type); fail > 0; fai
l--) { | 126 for (int fail = pref.getCrashFailureUploadCount(type); fail > 0; fai
l--) { |
| 80 RecordHistogram.recordEnumeratedHistogram( | 127 RecordHistogram.recordEnumeratedHistogram( |
| (...skipping 18 matching lines...) Expand all Loading... |
| 99 Log.w(TAG, "Cannot upload crash data since minidump is absent."); | 146 Log.w(TAG, "Cannot upload crash data since minidump is absent."); |
| 100 return; | 147 return; |
| 101 } | 148 } |
| 102 File minidumpFile = new File(minidumpFileName); | 149 File minidumpFile = new File(minidumpFileName); |
| 103 if (!minidumpFile.isFile()) { | 150 if (!minidumpFile.isFile()) { |
| 104 Log.w(TAG, "Cannot upload crash data since specified minidump " | 151 Log.w(TAG, "Cannot upload crash data since specified minidump " |
| 105 + minidumpFileName + " is not present."); | 152 + minidumpFileName + " is not present."); |
| 106 return; | 153 return; |
| 107 } | 154 } |
| 108 int tries = CrashFileManager.readAttemptNumber(minidumpFileName); | 155 int tries = CrashFileManager.readAttemptNumber(minidumpFileName); |
| 109 // -1 means no attempt number was read. | |
| 110 if (tries == -1) { | |
| 111 tries = 0; | |
| 112 } | |
| 113 | 156 |
| 114 // Since we do not rename a file after reaching max number of tries, | 157 // Since we do not rename a file after reaching max number of tries, |
| 115 // files that have maxed out tries will NOT reach this. | 158 // files that have maxed out tries will NOT reach this. |
| 116 if (tries >= MAX_TRIES_ALLOWED || tries < 0) { | 159 if (tries >= MAX_TRIES_ALLOWED || tries < 0) { |
| 117 // Reachable only if the file naming is incorrect by current standar
d. | 160 // Reachable only if the file naming is incorrect by current standar
d. |
| 118 // Thus we log an error instead of recording failure to UMA. | 161 // Thus we log an error instead of recording failure to UMA. |
| 119 Log.e(TAG, "Giving up on trying to upload " + minidumpFileName | 162 Log.e(TAG, "Giving up on trying to upload " + minidumpFileName |
| 120 + " after failing to read a valid attempt number."); | 163 + " after failing to read a valid attempt number."); |
| 121 return; | 164 return; |
| 122 } | 165 } |
| 123 | 166 |
| 124 String logfileName = intent.getStringExtra(UPLOAD_LOG_KEY); | 167 String logfileName = intent.getStringExtra(UPLOAD_LOG_KEY); |
| 125 File logfile = new File(logfileName); | 168 File logfile = new File(logfileName); |
| 126 | 169 |
| 127 // Try to upload minidump | 170 // Try to upload minidump |
| 128 MinidumpUploadCallable minidumpUploadCallable = | 171 MinidumpUploadCallable minidumpUploadCallable = |
| 129 createMinidumpUploadCallable(minidumpFile, logfile); | 172 createMinidumpUploadCallable(minidumpFile, logfile); |
| 130 @MinidumpUploadCallable.MinidumpUploadStatus int uploadStatus = | 173 @MinidumpUploadCallable.MinidumpUploadStatus int uploadStatus = |
| 131 minidumpUploadCallable.call(); | 174 minidumpUploadCallable.call(); |
| 132 | 175 |
| 133 if (uploadStatus == MinidumpUploadCallable.UPLOAD_SUCCESS) { | 176 if (uploadStatus == MinidumpUploadCallable.UPLOAD_SUCCESS) { |
| 134 // Only update UMA stats if an intended and successful upload. | 177 // Only update UMA stats if an intended and successful upload. |
| 135 incrementCrashSuccessUploadCount(getNewNameAfterSuccessfulUpload(min
idumpFileName)); | 178 incrementCrashSuccessUploadCount(minidumpFileName); |
| 136 } else if (uploadStatus == MinidumpUploadCallable.UPLOAD_FAILURE) { | 179 } else if (uploadStatus == MinidumpUploadCallable.UPLOAD_FAILURE) { |
| 137 // Unable to upload minidump. Incrementing try number and restarting
. | 180 // Unable to upload minidump. Incrementing try number and restarting
. |
| 181 ++tries; |
| 182 if (tries == MAX_TRIES_ALLOWED) { |
| 183 // Only record failure to UMA after we have maxed out the allott
ed tries. |
| 184 incrementCrashFailureUploadCount(minidumpFileName); |
| 185 } |
| 138 | 186 |
| 139 // Only create another attempt if we have successfully renamed | 187 // Only create another attempt if we have successfully renamed the f
ile. |
| 140 // the file. | |
| 141 String newName = CrashFileManager.tryIncrementAttemptNumber(minidump
File); | 188 String newName = CrashFileManager.tryIncrementAttemptNumber(minidump
File); |
| 142 if (newName != null) { | 189 if (newName != null) { |
| 143 if (++tries < MAX_TRIES_ALLOWED) { | 190 if (tries < MAX_TRIES_ALLOWED) { |
| 144 // TODO(nyquist): Do this as an exponential backoff. | 191 // TODO(nyquist): Do this as an exponential backoff. |
| 145 MinidumpUploadRetry.scheduleRetry( | 192 MinidumpUploadRetry.scheduleRetry( |
| 146 getApplicationContext(), getCrashReportingPermission
Manager()); | 193 getApplicationContext(), getCrashReportingPermission
Manager()); |
| 147 } else { | 194 } else { |
| 148 // Only record failure to UMA after we have maxed out the al
lotted tries. | |
| 149 incrementCrashFailureUploadCount(newName); | |
| 150 Log.d(TAG, "Giving up on trying to upload " + minidumpFileNa
me + "after " | 195 Log.d(TAG, "Giving up on trying to upload " + minidumpFileNa
me + "after " |
| 151 + tries + " number of tries."); | 196 + tries + " number of tries."); |
| 152 } | 197 } |
| 153 } else { | 198 } else { |
| 154 Log.w(TAG, "Failed to rename minidump " + minidumpFileName); | 199 Log.w(TAG, "Failed to rename minidump " + minidumpFileName); |
| 155 } | 200 } |
| 156 } | 201 } |
| 157 } | 202 } |
| 158 | 203 |
| 159 /** | 204 /** |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 201 } | 246 } |
| 202 } catch (IOException e) { | 247 } catch (IOException e) { |
| 203 Log.w(TAG, "Error while reading crash file %s: %s", fileName, e.toSt
ring()); | 248 Log.w(TAG, "Error while reading crash file %s: %s", fileName, e.toSt
ring()); |
| 204 } finally { | 249 } finally { |
| 205 StreamUtil.closeQuietly(fileReader); | 250 StreamUtil.closeQuietly(fileReader); |
| 206 } | 251 } |
| 207 return OTHER; | 252 return OTHER; |
| 208 } | 253 } |
| 209 | 254 |
| 210 /** | 255 /** |
| 211 * Increment the count of success/failure by 1 and distinguish between diffe
rent types of | 256 * Increments the count of successful uploads by 1. Distinguishes between di
fferent types of |
| 212 * crashes by looking into the file. | 257 * crashes by looking into the file contents. Because this code can execute
in a context when |
| 213 * @param fileName is the name of a minidump file that contains the type of
crash. | 258 * the main Chrome activity is no longer running, the counts are stored in s
hared preferences; |
| 259 * they are later read and recorded as metrics by the main Chrome activity. |
| 260 * NOTE: This method should be called *after* renaming the file, since renam
ing occurs as a |
| 261 * side-effect of a successful upload. |
| 262 * @param originalFilename The name of the successfully uploaded minidump, *
prior* to uploading. |
| 214 */ | 263 */ |
| 215 private void incrementCrashSuccessUploadCount(String fileName) { | 264 public static void incrementCrashSuccessUploadCount(String originalFilename)
{ |
| 216 ChromePreferenceManager.getInstance().incrementCrashSuccessUploadCount( | 265 ChromePreferenceManager.getInstance().incrementCrashSuccessUploadCount( |
| 217 getCrashType(fileName)); | 266 getCrashType(getNewNameAfterSuccessfulUpload(originalFilename)))
; |
| 218 } | |
| 219 | |
| 220 private void incrementCrashFailureUploadCount(String fileName) { | |
| 221 ChromePreferenceManager.getInstance().incrementCrashFailureUploadCount( | |
| 222 getCrashType(fileName)); | |
| 223 } | 267 } |
| 224 | 268 |
| 225 /** | 269 /** |
| 270 * Increments the count of failed uploads by 1. Distinguishes between differ
ent types of crashes |
| 271 * by looking into the file contents. Because this code can execute in a con
text when the main |
| 272 * Chrome activity is no longer running, the counts are stored in shared pre
ferences; they are |
| 273 * later read and recorded as metrics by the main Chrome activity. |
| 274 * NOTE: This method should be called *prior* to renaming the file. |
| 275 * @param originalFilename The name of the successfully uploaded minidump, *
prior* to uploading. |
| 276 */ |
| 277 public static void incrementCrashFailureUploadCount(String originalFilename)
{ |
| 278 ChromePreferenceManager.getInstance().incrementCrashFailureUploadCount( |
| 279 getCrashType(originalFilename)); |
| 280 } |
| 281 |
| 282 /** |
| 226 * Factory method for creating minidump callables. | 283 * Factory method for creating minidump callables. |
| 227 * | 284 * |
| 228 * This may be overridden for tests. | 285 * This may be overridden for tests. |
| 229 * | 286 * |
| 230 * @param minidumpFile the File to upload. | 287 * @param minidumpFile the File to upload. |
| 231 * @param logfile the Log file to write to upon successful uploads. | 288 * @param logfile the Log file to write to upon successful uploads. |
| 232 * @return a new MinidumpUploadCallable. | 289 * @return a new MinidumpUploadCallable. |
| 233 */ | 290 */ |
| 234 @VisibleForTesting | 291 @VisibleForTesting |
| 235 MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File
logfile) { | 292 MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File
logfile) { |
| 236 return new MinidumpUploadCallable( | 293 return new MinidumpUploadCallable( |
| 237 minidumpFile, logfile, getCrashReportingPermissionManager()); | 294 minidumpFile, logfile, getCrashReportingPermissionManager()); |
| 238 } | 295 } |
| 239 | 296 |
| 240 /** | 297 /** |
| 241 * Attempts to upload the specified {@param minidumpFile}. | 298 * Attempts to upload the specified {@param minidumpFile}. |
| 242 * | 299 * |
| 243 * Note that this method is asynchronous. All that is guaranteed is that an
upload attempt will | 300 * Note that this method is asynchronous. All that is guaranteed is that an
upload attempt will |
| 244 * be enqueued. | 301 * be enqueued. |
| 245 * | 302 * |
| 246 * @param context The application context, in which to initiate the crash re
port upload. | 303 * @param context The application context, in which to initiate the crash re
port upload. |
| 247 * @throws A security excpetion if the caller doesn't have permission to sta
rt the upload | 304 * @throws A security excpetion if the caller doesn't have permission to sta
rt the upload |
| 248 * service. This can only happen on KitKat and below, due to a frame
work bug. | 305 * service. This can only happen on KitKat and below, due to a frame
work bug. |
| 249 */ | 306 */ |
| 250 public static void tryUploadCrashDump(Context context, File minidumpFile) | 307 public static void tryUploadCrashDump(Context context, File minidumpFile) |
| 251 throws SecurityException { | 308 throws SecurityException { |
| 309 assert !shouldUseJobSchedulerForUploads(); |
| 252 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); | 310 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); |
| 253 Intent intent = new Intent(context, MinidumpUploadService.class); | 311 Intent intent = new Intent(context, MinidumpUploadService.class); |
| 254 intent.setAction(ACTION_UPLOAD); | 312 intent.setAction(ACTION_UPLOAD); |
| 255 intent.putExtra(FILE_TO_UPLOAD_KEY, minidumpFile.getAbsolutePath()); | 313 intent.putExtra(FILE_TO_UPLOAD_KEY, minidumpFile.getAbsolutePath()); |
| 256 intent.putExtra(UPLOAD_LOG_KEY, fileManager.getCrashUploadLogFile().getA
bsolutePath()); | 314 intent.putExtra(UPLOAD_LOG_KEY, fileManager.getCrashUploadLogFile().getA
bsolutePath()); |
| 257 context.startService(intent); | 315 context.startService(intent); |
| 258 } | 316 } |
| 259 | 317 |
| 260 /** | 318 /** |
| 261 * Attempts to upload all minidump files using the given {@link android.cont
ent.Context}. | 319 * Attempts to upload all minidump files using the given {@link android.cont
ent.Context}. |
| 262 * | 320 * |
| 263 * Note that this method is asynchronous. All that is guaranteed is that | 321 * Note that this method is asynchronous. All that is guaranteed is that |
| 264 * upload attempts will be enqueued. | 322 * upload attempts will be enqueued. |
| 265 * | 323 * |
| 266 * This method is safe to call from the UI thread. | 324 * This method is safe to call from the UI thread. |
| 267 * | 325 * |
| 268 * @param context Context of the application. | 326 * @param context Context of the application. |
| 269 */ | 327 */ |
| 270 public static void tryUploadAllCrashDumps(Context context) { | 328 public static void tryUploadAllCrashDumps(Context context) { |
| 329 assert !shouldUseJobSchedulerForUploads(); |
| 271 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); | 330 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); |
| 272 File[] minidumps = fileManager.getAllMinidumpFiles(MAX_TRIES_ALLOWED); | 331 File[] minidumps = fileManager.getAllMinidumpFiles(MAX_TRIES_ALLOWED); |
| 273 Log.i(TAG, "Attempting to upload accumulated crash dumps."); | 332 Log.i(TAG, "Attempting to upload accumulated crash dumps."); |
| 274 for (File minidump : minidumps) { | 333 for (File minidump : minidumps) { |
| 275 MinidumpUploadService.tryUploadCrashDump(context, minidump); | 334 tryUploadCrashDump(context, minidump); |
| 276 } | 335 } |
| 277 } | 336 } |
| 278 | 337 |
| 279 /** | 338 /** |
| 280 * Attempts to upload the crash report with the given local ID. | 339 * Attempts to upload the crash report with the given local ID. |
| 281 * | 340 * |
| 282 * Note that this method is asynchronous. All that is guaranteed is that | 341 * Note that this method is asynchronous. All that is guaranteed is that |
| 283 * upload attempts will be enqueued. | 342 * upload attempts will be enqueued. |
| 284 * | 343 * |
| 285 * This method is safe to call from the UI thread. | 344 * This method is safe to call from the UI thread. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 298 File minidumpFile = fileManager.getCrashFileWithLocalId(localId); | 357 File minidumpFile = fileManager.getCrashFileWithLocalId(localId); |
| 299 if (minidumpFile == null) { | 358 if (minidumpFile == null) { |
| 300 Log.w(TAG, "Could not find a crash dump with local ID " + localId); | 359 Log.w(TAG, "Could not find a crash dump with local ID " + localId); |
| 301 return; | 360 return; |
| 302 } | 361 } |
| 303 File renamedMinidumpFile = CrashFileManager.trySetForcedUpload(minidumpF
ile); | 362 File renamedMinidumpFile = CrashFileManager.trySetForcedUpload(minidumpF
ile); |
| 304 if (renamedMinidumpFile == null) { | 363 if (renamedMinidumpFile == null) { |
| 305 Log.w(TAG, "Could not rename the file " + minidumpFile.getName() + "
for re-upload"); | 364 Log.w(TAG, "Could not rename the file " + minidumpFile.getName() + "
for re-upload"); |
| 306 return; | 365 return; |
| 307 } | 366 } |
| 308 MinidumpUploadService.tryUploadCrashDump(context, renamedMinidumpFile); | 367 |
| 368 if (shouldUseJobSchedulerForUploads()) { |
| 369 scheduleUploadJob(context); |
| 370 } else { |
| 371 tryUploadCrashDump(context, renamedMinidumpFile); |
| 372 } |
| 309 } | 373 } |
| 310 } | 374 } |
| OLD | NEW |