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 |