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 %s: %s", fileName, e.toSt
ring()); | 247 Log.w(TAG, "Error while reading crash file %s: %s", fileName, e.toSt
ring()); |
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 |