Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / chromecast / browser / android / apk / src / org / chromium / chromecast / shell / CastCrashUploader.java
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chromecast.shell;
6
7 import android.net.http.AndroidHttpClient;
8 import android.util.Log;
9
10 import org.apache.http.HttpHost;
11 import org.apache.http.HttpResponse;
12 import org.apache.http.HttpStatus;
13 import org.apache.http.client.methods.HttpPost;
14 import org.apache.http.entity.InputStreamEntity;
15
16 import java.io.BufferedReader;
17 import java.io.ByteArrayInputStream;
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.io.SequenceInputStream;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.TimeUnit;
27
28 /**
29  * Crash crashdump uploader. Scans the crash dump location provided by CastCrashReporterClient
30  * for dump files, attempting to upload all crash dumps to the crash server.
31  *
32  * Uploading is intended to happen in a background thread, and this method will likely be called
33  * on startup, looking for crash dumps from previous runs, since Chromium's crash code
34  * explicitly blocks any post-dump hooks or uploading for Android builds.
35  */
36 public final class CastCrashUploader {
37     private static final String TAG = "CastCrashUploader";
38     private static final String CRASH_REPORT_HOST = "clients2.google.com";
39     private static final String CAST_SHELL_USER_AGENT = android.os.Build.MODEL + "/CastShell";
40
41     private final ExecutorService mExecutorService;
42     private final String mCrashDumpPath;
43     private final String mCrashReportUploadUrl;
44
45     public CastCrashUploader(String crashDumpPath, boolean isDebugBuild) {
46         this.mCrashDumpPath = crashDumpPath;
47         mCrashReportUploadUrl = isDebugBuild ?
48                 "http://clients2.google.com/cr/staging_report" :
49                 "http://clients2.google.com/cr/report";
50         mExecutorService = Executors.newFixedThreadPool(1);
51     }
52
53     public void removeCrashDumpsSync() {
54         mExecutorService.submit(new Runnable() {
55             @Override
56             public void run() {
57                 File crashDumpDirectory = new File(mCrashDumpPath);
58                 for (File potentialDump : crashDumpDirectory.listFiles()) {
59                     if (potentialDump.getName().matches(".*\\.dmp\\d*")) {
60                         potentialDump.delete();
61                     }
62                 }
63             }
64         });
65         waitForTasksToFinish();
66     }
67
68     /**
69      * Synchronously uploads the crash dump from the current process, along with an attached
70      * log file.
71      * @param logFilePath Full path to the log file for the current process.
72      */
73     public void uploadCurrentProcessDumpSync(String logFilePath) {
74         int pid = android.os.Process.myPid();
75         Log.d(TAG, "Immediately attempting a crash upload with logs, looking for: .dmp" + pid);
76
77         queueAllCrashDumpUpload(".*\\.dmp" + pid, new File(logFilePath));
78         waitForTasksToFinish();
79     }
80
81     private void waitForTasksToFinish() {
82         try {
83             mExecutorService.shutdown();
84             boolean finished = mExecutorService.awaitTermination(60, TimeUnit.SECONDS);
85             if (!finished) {
86                 Log.d(TAG, "Crash dump handling did not finish executing in time. Exiting.");
87             }
88         } catch (InterruptedException e) {
89             Log.e(TAG, "Interrupted waiting for asynchronous execution", e);
90         }
91     }
92
93     /**
94      * Scans the given location for crash dump files and queues background
95      * uploads of each file.
96      */
97     public void uploadRecentCrashesAsync() {
98         mExecutorService.submit(new Runnable() {
99             @Override
100             public void run() {
101                 // Multipart dump filename has format "[random string].dmp[pid]", e.g.
102                 // 20597a65-b822-008e-31f8fc8e-02bb45c0.dmp18169
103                 queueAllCrashDumpUpload(".*\\.dmp\\d*", null);
104             }
105         });
106     }
107
108     /**
109      * Searches for files matching the given regex in the crash dump folder, queueing each
110      * one for upload.
111      * @param dumpFileRegex Regex to test against the name of each file in the crash dump folder.
112      * @param logFile Log file to include, if any.
113      */
114     private void queueAllCrashDumpUpload(String dumpFileRegex, File logFile) {
115         if (mCrashDumpPath == null) return;
116         final AndroidHttpClient httpClient = AndroidHttpClient.newInstance(CAST_SHELL_USER_AGENT);
117         File crashDumpDirectory = new File(mCrashDumpPath);
118         for (File potentialDump : crashDumpDirectory.listFiles()) {
119             if (potentialDump.getName().matches(dumpFileRegex)) {
120                 queueCrashDumpUpload(httpClient, potentialDump, logFile);
121             }
122         }
123
124         // When finished uploading all of the queued dumps, release httpClient
125         mExecutorService.submit(new Runnable() {
126             @Override
127             public void run() {
128                 httpClient.close();
129             }
130         });
131     }
132
133     /**
134      * Enqueues a background task to upload a single crash dump file.
135      */
136     private void queueCrashDumpUpload(final AndroidHttpClient httpClient, final File dumpFile,
137             final File logFile) {
138         mExecutorService.submit(new Runnable() {
139             @Override
140             public void run() {
141                 Log.d(TAG, "Uploading dump crash log: " + dumpFile.getName());
142
143                 try {
144                     // Dump file is already in multipart MIME format and has a boundary throughout.
145                     // Scrape the first line, remove two dashes, call that the "boundary" and add it
146                     // to the content-type.
147                     FileInputStream dumpFileStream = new FileInputStream(dumpFile);
148                     String dumpFirstLine = getFirstLine(dumpFileStream);
149                     String mimeBoundary = dumpFirstLine.substring(2);
150
151                     InputStream uploadCrashDumpStream = new FileInputStream(dumpFile);
152                     InputStream logFileStream = null;
153
154                     if (logFile != null) {
155                         Log.d(TAG, "Including log file: " + logFile.getName());
156                         StringBuffer logHeader = new StringBuffer();
157                         logHeader.append(dumpFirstLine);
158                         logHeader.append("\n");
159                         logHeader.append(
160                                 "Content-Disposition: form-data; name=\"log\"; filename=\"log\"\n");
161                         logHeader.append("Content-Type: text/plain\n\n");
162                         InputStream logHeaderStream =
163                                 new ByteArrayInputStream(logHeader.toString().getBytes());
164                         logFileStream = new FileInputStream(logFile);
165
166                         // Upload: prepend the log file for uploading
167                         uploadCrashDumpStream = new SequenceInputStream(
168                                 new SequenceInputStream(logHeaderStream, logFileStream),
169                                 uploadCrashDumpStream);
170                     }
171
172                     InputStreamEntity entity = new InputStreamEntity(uploadCrashDumpStream, -1);
173                     entity.setContentType("multipart/form-data; boundary=" + mimeBoundary);
174
175                     HttpPost uploadRequest = new HttpPost(mCrashReportUploadUrl);
176                     uploadRequest.setEntity(entity);
177                     HttpResponse response =
178                             httpClient.execute(new HttpHost(CRASH_REPORT_HOST), uploadRequest);
179
180                     // Expect a report ID as the entire response
181                     String responseLine = getFirstLine(response.getEntity().getContent());
182
183                     int statusCode = response.getStatusLine().getStatusCode();
184                     if (statusCode != HttpStatus.SC_OK) {
185                         Log.e(TAG, "Failed response (" + statusCode + "): " + responseLine);
186
187                         // 400 Bad Request is returned if the dump file is malformed. Delete file
188                         // to prevent re-attempting later.
189                         if (statusCode == HttpStatus.SC_BAD_REQUEST) {
190                             dumpFile.delete();
191                         }
192                         return;
193                     }
194
195                     Log.d(TAG, "Successfully uploaded as report ID: " + responseLine);
196
197                     // Delete the file so we don't re-upload it next time.
198                     dumpFileStream.close();
199                     dumpFile.delete();
200                 } catch (IOException e) {
201                     Log.e(TAG, "File I/O error trying to upload crash dump", e);
202                 }
203             }
204         });
205     }
206
207     /**
208      * Gets the first line from an input stream, opening and closing readers to do so.
209      * We're targeting Java 6, so this is still tedious to do.
210      * @return First line of the input stream.
211      * @throws IOException
212      */
213     private String getFirstLine(InputStream inputStream) throws IOException {
214         InputStreamReader streamReader = null;
215         BufferedReader reader = null;
216         try {
217             streamReader = new InputStreamReader(inputStream);
218             reader = new BufferedReader(streamReader);
219             return reader.readLine();
220         } finally {
221             if (reader != null) {
222                 reader.close();
223             }
224             if (streamReader != null) {
225                 streamReader.close();
226             }
227         }
228     }
229 }