| 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 M. |
| 84 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) 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 permissions.putBoolean(ChromeMinidumpUploaderDelegate.IS_CLIENT_IN_METRI
CS_SAMPLE, |
| 100 permissionManager.isClientInMetricsSample()); |
| 101 permissions.putBoolean( |
| 102 ChromeMinidumpUploaderDelegate.IS_CRASH_UPLOAD_DISABLED_BY_COMMA
ND_LINE, |
| 103 permissionManager.isCrashUploadDisabledByCommandLine()); |
| 104 permissions.putBoolean(ChromeMinidumpUploaderDelegate.IS_UPLOAD_ENABLED_
FOR_TESTS, |
| 105 permissionManager.isUploadEnabledForTests()); |
| 106 |
| 107 JobInfo.Builder builder = |
| 108 new JobInfo |
| 109 .Builder(TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID, |
| 110 new ComponentName(context, ChromeMinidumpUploadJ
obService.class)) |
| 111 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) |
| 112 .setExtras(permissions); |
| 113 MinidumpUploadJobService.scheduleUpload(context, builder); |
| 114 } |
| 115 |
| 116 /** |
| 71 * Stores the successes and failures from uploading crash to UMA, | 117 * Stores the successes and failures from uploading crash to UMA, |
| 72 */ | 118 */ |
| 73 public static void storeBreakpadUploadStatsInUma(ChromePreferenceManager pre
f) { | 119 public static void storeBreakpadUploadStatsInUma(ChromePreferenceManager pre
f) { |
| 74 for (String type : TYPES) { | 120 for (String type : TYPES) { |
| 75 for (int success = pref.getCrashSuccessUploadCount(type); success >
0; success--) { | 121 for (int success = pref.getCrashSuccessUploadCount(type); success >
0; success--) { |
| 76 RecordHistogram.recordEnumeratedHistogram( | 122 RecordHistogram.recordEnumeratedHistogram( |
| 77 HISTOGRAM_NAME_PREFIX + type, SUCCESS, HISTOGRAM_MAX); | 123 HISTOGRAM_NAME_PREFIX + type, SUCCESS, HISTOGRAM_MAX); |
| 78 } | 124 } |
| 79 for (int fail = pref.getCrashFailureUploadCount(type); fail > 0; fai
l--) { | 125 for (int fail = pref.getCrashFailureUploadCount(type); fail > 0; fai
l--) { |
| 80 RecordHistogram.recordEnumeratedHistogram( | 126 RecordHistogram.recordEnumeratedHistogram( |
| (...skipping 18 matching lines...) Expand all Loading... |
| 99 Log.w(TAG, "Cannot upload crash data since minidump is absent."); | 145 Log.w(TAG, "Cannot upload crash data since minidump is absent."); |
| 100 return; | 146 return; |
| 101 } | 147 } |
| 102 File minidumpFile = new File(minidumpFileName); | 148 File minidumpFile = new File(minidumpFileName); |
| 103 if (!minidumpFile.isFile()) { | 149 if (!minidumpFile.isFile()) { |
| 104 Log.w(TAG, "Cannot upload crash data since specified minidump " | 150 Log.w(TAG, "Cannot upload crash data since specified minidump " |
| 105 + minidumpFileName + " is not present."); | 151 + minidumpFileName + " is not present."); |
| 106 return; | 152 return; |
| 107 } | 153 } |
| 108 int tries = CrashFileManager.readAttemptNumber(minidumpFileName); | 154 int tries = CrashFileManager.readAttemptNumber(minidumpFileName); |
| 109 // -1 means no attempt number was read. | |
| 110 if (tries == -1) { | |
| 111 tries = 0; | |
| 112 } | |
| 113 | 155 |
| 114 // Since we do not rename a file after reaching max number of tries, | 156 // Since we do not rename a file after reaching max number of tries, |
| 115 // files that have maxed out tries will NOT reach this. | 157 // files that have maxed out tries will NOT reach this. |
| 116 if (tries >= MAX_TRIES_ALLOWED || tries < 0) { | 158 if (tries >= MAX_TRIES_ALLOWED || tries < 0) { |
| 117 // Reachable only if the file naming is incorrect by current standar
d. | 159 // Reachable only if the file naming is incorrect by current standar
d. |
| 118 // Thus we log an error instead of recording failure to UMA. | 160 // Thus we log an error instead of recording failure to UMA. |
| 119 Log.e(TAG, "Giving up on trying to upload " + minidumpFileName | 161 Log.e(TAG, "Giving up on trying to upload " + minidumpFileName |
| 120 + " after failing to read a valid attempt number."); | 162 + " after failing to read a valid attempt number."); |
| 121 return; | 163 return; |
| 122 } | 164 } |
| 123 | 165 |
| 124 String logfileName = intent.getStringExtra(UPLOAD_LOG_KEY); | 166 String logfileName = intent.getStringExtra(UPLOAD_LOG_KEY); |
| 125 File logfile = new File(logfileName); | 167 File logfile = new File(logfileName); |
| 126 | 168 |
| 127 // Try to upload minidump | 169 // Try to upload minidump |
| 128 MinidumpUploadCallable minidumpUploadCallable = | 170 MinidumpUploadCallable minidumpUploadCallable = |
| 129 createMinidumpUploadCallable(minidumpFile, logfile); | 171 createMinidumpUploadCallable(minidumpFile, logfile); |
| 130 @MinidumpUploadCallable.MinidumpUploadStatus int uploadStatus = | 172 @MinidumpUploadCallable.MinidumpUploadStatus int uploadStatus = |
| 131 minidumpUploadCallable.call(); | 173 minidumpUploadCallable.call(); |
| 132 | 174 |
| 133 if (uploadStatus == MinidumpUploadCallable.UPLOAD_SUCCESS) { | 175 if (uploadStatus == MinidumpUploadCallable.UPLOAD_SUCCESS) { |
| 134 // Only update UMA stats if an intended and successful upload. | 176 // Only update UMA stats if an intended and successful upload. |
| 135 incrementCrashSuccessUploadCount(getNewNameAfterSuccessfulUpload(min
idumpFileName)); | 177 incrementCrashSuccessUploadCount(minidumpFileName); |
| 136 } else if (uploadStatus == MinidumpUploadCallable.UPLOAD_FAILURE) { | 178 } else if (uploadStatus == MinidumpUploadCallable.UPLOAD_FAILURE) { |
| 137 // Unable to upload minidump. Incrementing try number and restarting
. | 179 // Unable to upload minidump. Incrementing try number and restarting
. |
| 180 ++tries; |
| 181 if (tries == MAX_TRIES_ALLOWED) { |
| 182 // Only record failure to UMA after we have maxed out the allott
ed tries. |
| 183 incrementCrashFailureUploadCount(minidumpFileName); |
| 184 } |
| 138 | 185 |
| 139 // Only create another attempt if we have successfully renamed | 186 // Only create another attempt if we have successfully renamed the f
ile. |
| 140 // the file. | |
| 141 String newName = CrashFileManager.tryIncrementAttemptNumber(minidump
File); | 187 String newName = CrashFileManager.tryIncrementAttemptNumber(minidump
File); |
| 142 if (newName != null) { | 188 if (newName != null) { |
| 143 if (++tries < MAX_TRIES_ALLOWED) { | 189 if (tries < MAX_TRIES_ALLOWED) { |
| 144 // TODO(nyquist): Do this as an exponential backoff. | 190 // TODO(nyquist): Do this as an exponential backoff. |
| 145 MinidumpUploadRetry.scheduleRetry( | 191 MinidumpUploadRetry.scheduleRetry( |
| 146 getApplicationContext(), getCrashReportingPermission
Manager()); | 192 getApplicationContext(), getCrashReportingPermission
Manager()); |
| 147 } else { | 193 } 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 " | 194 Log.d(TAG, "Giving up on trying to upload " + minidumpFileNa
me + "after " |
| 151 + tries + " number of tries."); | 195 + tries + " number of tries."); |
| 152 } | 196 } |
| 153 } else { | 197 } else { |
| 154 Log.w(TAG, "Failed to rename minidump " + minidumpFileName); | 198 Log.w(TAG, "Failed to rename minidump " + minidumpFileName); |
| 155 } | 199 } |
| 156 } | 200 } |
| 157 } | 201 } |
| 158 | 202 |
| 159 /** | 203 /** |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 201 } | 245 } |
| 202 } catch (IOException e) { | 246 } catch (IOException e) { |
| 203 Log.w(TAG, "Error while reading crash file.", e.toString()); | 247 Log.w(TAG, "Error while reading crash file.", e.toString()); |
| 204 } finally { | 248 } finally { |
| 205 StreamUtil.closeQuietly(fileReader); | 249 StreamUtil.closeQuietly(fileReader); |
| 206 } | 250 } |
| 207 return OTHER; | 251 return OTHER; |
| 208 } | 252 } |
| 209 | 253 |
| 210 /** | 254 /** |
| 211 * Increment the count of success/failure by 1 and distinguish between diffe
rent types of | 255 * Increments the count of successful uploads by 1. Distinguishes between di
fferent types of |
| 212 * crashes by looking into the file. | 256 * 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. | 257 * the main Chrome activity is no longer running, the counts are stored in s
hared preferences; |
| 258 * they are later read and recorded as metrics by the main Chrome activity. |
| 259 * NOTE: This method should be called *after* renaming the file, since renam
ing occurs as a |
| 260 * side-effect of a successful upload. |
| 261 * @param originalFilename The name of the successfully uploaded minidump, *
prior* to uploading. |
| 214 */ | 262 */ |
| 215 private void incrementCrashSuccessUploadCount(String fileName) { | 263 public static void incrementCrashSuccessUploadCount(String originalFilename)
{ |
| 216 ChromePreferenceManager.getInstance().incrementCrashSuccessUploadCount( | 264 ChromePreferenceManager.getInstance().incrementCrashSuccessUploadCount( |
| 217 getCrashType(fileName)); | 265 getCrashType(getNewNameAfterSuccessfulUpload(originalFilename)))
; |
| 218 } | |
| 219 | |
| 220 private void incrementCrashFailureUploadCount(String fileName) { | |
| 221 ChromePreferenceManager.getInstance().incrementCrashFailureUploadCount( | |
| 222 getCrashType(fileName)); | |
| 223 } | 266 } |
| 224 | 267 |
| 225 /** | 268 /** |
| 269 * Increments the count of failed uploads by 1. Distinguishes between differ
ent types of crashes |
| 270 * by looking into the file contents. Because this code can execute in a con
text when the main |
| 271 * Chrome activity is no longer running, the counts are stored in shared pre
ferences; they are |
| 272 * later read and recorded as metrics by the main Chrome activity. |
| 273 * NOTE: This method should be called *prior* to renaming the file. |
| 274 * @param originalFilename The name of the successfully uploaded minidump, *
prior* to uploading. |
| 275 */ |
| 276 public static void incrementCrashFailureUploadCount(String originalFilename)
{ |
| 277 ChromePreferenceManager.getInstance().incrementCrashFailureUploadCount( |
| 278 getCrashType(originalFilename)); |
| 279 } |
| 280 |
| 281 /** |
| 226 * Factory method for creating minidump callables. | 282 * Factory method for creating minidump callables. |
| 227 * | 283 * |
| 228 * This may be overridden for tests. | 284 * This may be overridden for tests. |
| 229 * | 285 * |
| 230 * @param minidumpFile the File to upload. | 286 * @param minidumpFile the File to upload. |
| 231 * @param logfile the Log file to write to upon successful uploads. | 287 * @param logfile the Log file to write to upon successful uploads. |
| 232 * @return a new MinidumpUploadCallable. | 288 * @return a new MinidumpUploadCallable. |
| 233 */ | 289 */ |
| 234 @VisibleForTesting | 290 @VisibleForTesting |
| 235 MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File
logfile) { | 291 MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File
logfile) { |
| 236 return new MinidumpUploadCallable( | 292 return new MinidumpUploadCallable( |
| 237 minidumpFile, logfile, getCrashReportingPermissionManager()); | 293 minidumpFile, logfile, getCrashReportingPermissionManager()); |
| 238 } | 294 } |
| 239 | 295 |
| 240 /** | 296 /** |
| 241 * Attempts to upload the specified {@param minidumpFile}. | 297 * Attempts to upload the specified {@param minidumpFile}. |
| 242 * | 298 * |
| 243 * Note that this method is asynchronous. All that is guaranteed is that an
upload attempt will | 299 * Note that this method is asynchronous. All that is guaranteed is that an
upload attempt will |
| 244 * be enqueued. | 300 * be enqueued. |
| 245 * | 301 * |
| 246 * @param context The application context, in which to initiate the crash re
port upload. | 302 * @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 | 303 * @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. | 304 * service. This can only happen on KitKat and below, due to a frame
work bug. |
| 249 */ | 305 */ |
| 250 public static void tryUploadCrashDump(Context context, File minidumpFile) | 306 public static void tryUploadCrashDump(Context context, File minidumpFile) |
| 251 throws SecurityException { | 307 throws SecurityException { |
| 308 assert !shouldUseJobSchedulerForUploads(); |
| 252 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); | 309 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); |
| 253 Intent intent = new Intent(context, MinidumpUploadService.class); | 310 Intent intent = new Intent(context, MinidumpUploadService.class); |
| 254 intent.setAction(ACTION_UPLOAD); | 311 intent.setAction(ACTION_UPLOAD); |
| 255 intent.putExtra(FILE_TO_UPLOAD_KEY, minidumpFile.getAbsolutePath()); | 312 intent.putExtra(FILE_TO_UPLOAD_KEY, minidumpFile.getAbsolutePath()); |
| 256 intent.putExtra(UPLOAD_LOG_KEY, fileManager.getCrashUploadLogFile().getA
bsolutePath()); | 313 intent.putExtra(UPLOAD_LOG_KEY, fileManager.getCrashUploadLogFile().getA
bsolutePath()); |
| 257 context.startService(intent); | 314 context.startService(intent); |
| 258 } | 315 } |
| 259 | 316 |
| 260 /** | 317 /** |
| 261 * Attempts to upload all minidump files using the given {@link android.cont
ent.Context}. | 318 * Attempts to upload all minidump files using the given {@link android.cont
ent.Context}. |
| 262 * | 319 * |
| 263 * Note that this method is asynchronous. All that is guaranteed is that | 320 * Note that this method is asynchronous. All that is guaranteed is that |
| 264 * upload attempts will be enqueued. | 321 * upload attempts will be enqueued. |
| 265 * | 322 * |
| 266 * This method is safe to call from the UI thread. | 323 * This method is safe to call from the UI thread. |
| 267 * | 324 * |
| 268 * @param context Context of the application. | 325 * @param context Context of the application. |
| 269 */ | 326 */ |
| 270 public static void tryUploadAllCrashDumps(Context context) { | 327 public static void tryUploadAllCrashDumps(Context context) { |
| 328 assert !shouldUseJobSchedulerForUploads(); |
| 271 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); | 329 CrashFileManager fileManager = new CrashFileManager(context.getCacheDir(
)); |
| 272 File[] minidumps = fileManager.getAllMinidumpFiles(MAX_TRIES_ALLOWED); | 330 File[] minidumps = fileManager.getAllMinidumpFiles(MAX_TRIES_ALLOWED); |
| 273 Log.i(TAG, "Attempting to upload accumulated crash dumps."); | 331 Log.i(TAG, "Attempting to upload accumulated crash dumps."); |
| 274 for (File minidump : minidumps) { | 332 for (File minidump : minidumps) { |
| 275 MinidumpUploadService.tryUploadCrashDump(context, minidump); | 333 tryUploadCrashDump(context, minidump); |
| 276 } | 334 } |
| 277 } | 335 } |
| 278 | 336 |
| 279 /** | 337 /** |
| 280 * Attempts to upload the crash report with the given local ID. | 338 * Attempts to upload the crash report with the given local ID. |
| 281 * | 339 * |
| 282 * Note that this method is asynchronous. All that is guaranteed is that | 340 * Note that this method is asynchronous. All that is guaranteed is that |
| 283 * upload attempts will be enqueued. | 341 * upload attempts will be enqueued. |
| 284 * | 342 * |
| 285 * This method is safe to call from the UI thread. | 343 * This method is safe to call from the UI thread. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 298 File minidumpFile = fileManager.getCrashFileWithLocalId(localId); | 356 File minidumpFile = fileManager.getCrashFileWithLocalId(localId); |
| 299 if (minidumpFile == null) { | 357 if (minidumpFile == null) { |
| 300 Log.w(TAG, "Could not find a crash dump with local ID " + localId); | 358 Log.w(TAG, "Could not find a crash dump with local ID " + localId); |
| 301 return; | 359 return; |
| 302 } | 360 } |
| 303 File renamedMinidumpFile = CrashFileManager.trySetForcedUpload(minidumpF
ile); | 361 File renamedMinidumpFile = CrashFileManager.trySetForcedUpload(minidumpF
ile); |
| 304 if (renamedMinidumpFile == null) { | 362 if (renamedMinidumpFile == null) { |
| 305 Log.w(TAG, "Could not rename the file " + minidumpFile.getName() + "
for re-upload"); | 363 Log.w(TAG, "Could not rename the file " + minidumpFile.getName() + "
for re-upload"); |
| 306 return; | 364 return; |
| 307 } | 365 } |
| 308 MinidumpUploadService.tryUploadCrashDump(context, renamedMinidumpFile); | 366 |
| 367 if (shouldUseJobSchedulerForUploads()) { |
| 368 scheduleUploadJob(context); |
| 369 } else { |
| 370 tryUploadCrashDump(context, renamedMinidumpFile); |
| 371 } |
| 309 } | 372 } |
| 310 } | 373 } |
| OLD | NEW |