1 // Copyright 2013 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.
5 #include "chrome/browser/media/webrtc_log_uploader.h"
7 #include "base/files/file_enumerator.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/logging.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/media/webrtc_log_list.h"
17 #include "chrome/browser/media/webrtc_log_util.h"
18 #include "chrome/common/chrome_version_info.h"
19 #include "chrome/common/partial_circular_buffer.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "net/base/mime_util.h"
22 #include "net/url_request/url_fetcher.h"
23 #include "third_party/zlib/zlib.h"
27 const int kLogCountLimit
= 5;
28 const uint32 kIntermediateCompressionBufferBytes
= 256 * 1024; // 256 KB
29 const int kLogListLimitLines
= 50;
31 const char kUploadURL
[] = "https://clients2.google.com/cr/report";
32 const char kUploadContentType
[] = "multipart/form-data";
33 const char kMultipartBoundary
[] =
34 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
36 const int kHttpResponseOk
= 200;
38 // Adds the header section for a gzip file to the multipart |post_data|.
39 void AddMultipartFileContentHeader(std::string
* post_data
,
40 const std::string
& content_name
) {
41 post_data
->append("--");
42 post_data
->append(kMultipartBoundary
);
43 post_data
->append("\r\nContent-Disposition: form-data; name=\"");
44 post_data
->append(content_name
);
45 post_data
->append("\"; filename=\"");
46 post_data
->append(content_name
+ ".gz");
47 post_data
->append("\"\r\nContent-Type: application/gzip\r\n\r\n");
50 // Adds |compressed_log| to |post_data|.
51 void AddLogData(std::string
* post_data
,
52 const std::vector
<uint8
>& compressed_log
) {
53 AddMultipartFileContentHeader(post_data
, "webrtc_log");
54 post_data
->append(reinterpret_cast<const char*>(&compressed_log
[0]),
55 compressed_log
.size());
56 post_data
->append("\r\n");
59 // Adds the RTP dump data to |post_data|.
60 void AddRtpDumpData(std::string
* post_data
,
61 const std::string
& name
,
62 const std::string
& dump_data
) {
63 AddMultipartFileContentHeader(post_data
, name
);
64 post_data
->append(dump_data
.data(), dump_data
.size());
65 post_data
->append("\r\n");
70 WebRtcLogUploadDoneData::WebRtcLogUploadDoneData() {}
72 WebRtcLogUploadDoneData::~WebRtcLogUploadDoneData() {}
74 WebRtcLogUploader::WebRtcLogUploader()
77 shutting_down_(false) {
78 file_thread_checker_
.DetachFromThread();
81 WebRtcLogUploader::~WebRtcLogUploader() {
82 DCHECK(create_thread_checker_
.CalledOnValidThread());
83 DCHECK(upload_done_data_
.empty());
84 DCHECK(shutting_down_
);
87 void WebRtcLogUploader::OnURLFetchComplete(
88 const net::URLFetcher
* source
) {
89 DCHECK(create_thread_checker_
.CalledOnValidThread());
90 DCHECK(upload_done_data_
.find(source
) != upload_done_data_
.end());
91 DCHECK(!shutting_down_
);
92 int response_code
= source
->GetResponseCode();
93 UploadDoneDataMap::iterator it
= upload_done_data_
.find(source
);
94 if (it
!= upload_done_data_
.end()) {
95 // The log path can be empty here if we failed getting it before. We still
96 // upload the log if that's the case.
97 std::string report_id
;
98 if (response_code
== kHttpResponseOk
&&
99 source
->GetResponseAsString(&report_id
) &&
100 !it
->second
.log_path
.empty()) {
101 // TODO(jiayl): Add the RTP dump records to chrome://webrtc-logs.
102 base::FilePath log_list_path
=
103 WebRtcLogList::GetWebRtcLogListFileForDirectory(it
->second
.log_path
);
104 content::BrowserThread::PostTask(
105 content::BrowserThread::FILE,
107 base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile
,
108 base::Unretained(this),
110 it
->second
.local_log_id
,
113 NotifyUploadDone(response_code
, report_id
, it
->second
);
114 upload_done_data_
.erase(it
);
120 void WebRtcLogUploader::OnURLFetchUploadProgress(
121 const net::URLFetcher
* source
, int64 current
, int64 total
) {
124 bool WebRtcLogUploader::ApplyForStartLogging() {
125 DCHECK(create_thread_checker_
.CalledOnValidThread());
126 if (log_count_
< kLogCountLimit
&& !shutting_down_
) {
133 void WebRtcLogUploader::LoggingStoppedDontUpload() {
134 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
135 base::Bind(&WebRtcLogUploader::DecreaseLogCount
, base::Unretained(this)));
138 void WebRtcLogUploader::LoggingStoppedDoUpload(
139 scoped_ptr
<unsigned char[]> log_buffer
,
141 const std::map
<std::string
, std::string
>& meta_data
,
142 const WebRtcLogUploadDoneData
& upload_done_data
) {
143 DCHECK(file_thread_checker_
.CalledOnValidThread());
144 DCHECK(log_buffer
.get());
145 DCHECK(!upload_done_data
.log_path
.empty());
147 std::vector
<uint8
> compressed_log
;
149 &compressed_log
, reinterpret_cast<uint8
*>(&log_buffer
[0]), length
);
151 std::string local_log_id
;
153 if (base::PathExists(upload_done_data
.log_path
)) {
154 WebRtcLogUtil::DeleteOldWebRtcLogFiles(upload_done_data
.log_path
);
156 local_log_id
= base::DoubleToString(base::Time::Now().ToDoubleT());
157 base::FilePath log_file_path
=
158 upload_done_data
.log_path
.AppendASCII(local_log_id
)
159 .AddExtension(FILE_PATH_LITERAL(".gz"));
160 WriteCompressedLogToFile(compressed_log
, log_file_path
);
162 base::FilePath log_list_path
=
163 WebRtcLogList::GetWebRtcLogListFileForDirectory(
164 upload_done_data
.log_path
);
165 AddLocallyStoredLogInfoToUploadListFile(log_list_path
, local_log_id
);
168 WebRtcLogUploadDoneData upload_done_data_with_log_id
= upload_done_data
;
169 upload_done_data_with_log_id
.local_log_id
= local_log_id
;
171 scoped_ptr
<std::string
> post_data(new std::string());
172 SetupMultipart(post_data
.get(),
174 upload_done_data
.incoming_rtp_dump
,
175 upload_done_data
.outgoing_rtp_dump
,
178 // If a test has set the test string pointer, write to it and skip uploading.
179 // Still fire the upload callback so that we can run an extension API test
180 // using the test framework for that without hanging.
181 // TODO(grunell): Remove this when the api test for this feature is fully
182 // implemented according to the test plan. http://crbug.com/257329.
184 *post_data_
= *post_data
;
185 NotifyUploadDone(kHttpResponseOk
, "", upload_done_data_with_log_id
);
189 content::BrowserThread::PostTask(
190 content::BrowserThread::UI
,
192 base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher
,
193 base::Unretained(this),
194 upload_done_data_with_log_id
,
195 Passed(&post_data
)));
197 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
198 base::Bind(&WebRtcLogUploader::DecreaseLogCount
, base::Unretained(this)));
201 void WebRtcLogUploader::StartShutdown() {
202 DCHECK(create_thread_checker_
.CalledOnValidThread());
203 DCHECK(!shutting_down_
);
205 // Delete all URLFetchers first and clear the upload done map.
206 for (UploadDoneDataMap::iterator it
= upload_done_data_
.begin();
207 it
!= upload_done_data_
.end();
211 upload_done_data_
.clear();
212 shutting_down_
= true;
215 void WebRtcLogUploader::SetupMultipart(
216 std::string
* post_data
,
217 const std::vector
<uint8
>& compressed_log
,
218 const base::FilePath
& incoming_rtp_dump
,
219 const base::FilePath
& outgoing_rtp_dump
,
220 const std::map
<std::string
, std::string
>& meta_data
) {
222 const char product
[] = "Chrome";
223 #elif defined(OS_MACOSX)
224 const char product
[] = "Chrome_Mac";
225 #elif defined(OS_LINUX)
226 #if !defined(ADDRESS_SANITIZER)
227 const char product
[] = "Chrome_Linux";
229 const char product
[] = "Chrome_Linux_ASan";
231 #elif defined(OS_ANDROID)
232 const char product
[] = "Chrome_Android";
233 #elif defined(OS_CHROMEOS)
234 const char product
[] = "Chrome_ChromeOS";
236 #error Platform not supported.
238 net::AddMultipartValueForUpload("prod", product
, kMultipartBoundary
,
240 chrome::VersionInfo version_info
;
241 net::AddMultipartValueForUpload("ver", version_info
.Version() + "-webrtc",
242 kMultipartBoundary
, "", post_data
);
243 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary
,
245 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary
,
248 // Add custom meta data.
249 std::map
<std::string
, std::string
>::const_iterator it
= meta_data
.begin();
250 for (; it
!= meta_data
.end(); ++it
) {
251 net::AddMultipartValueForUpload(it
->first
, it
->second
, kMultipartBoundary
,
255 AddLogData(post_data
, compressed_log
);
257 // Add the rtp dumps if they exist.
258 base::FilePath rtp_dumps
[2] = {incoming_rtp_dump
, outgoing_rtp_dump
};
259 static const char* kRtpDumpNames
[2] = {"rtpdump_recv", "rtpdump_send"};
261 for (size_t i
= 0; i
< 2; ++i
) {
262 if (!rtp_dumps
[i
].empty() && base::PathExists(rtp_dumps
[i
])) {
263 std::string dump_data
;
264 if (base::ReadFileToString(rtp_dumps
[i
], &dump_data
))
265 AddRtpDumpData(post_data
, kRtpDumpNames
[i
], dump_data
);
269 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary
, post_data
);
272 void WebRtcLogUploader::CompressLog(std::vector
<uint8
>* compressed_log
,
275 PartialCircularBuffer
read_pcb(input
, input_size
);
277 z_stream stream
= {0};
278 int result
= deflateInit2(&stream
, Z_DEFAULT_COMPRESSION
, Z_DEFLATED
,
279 // windowBits = 15 is default, 16 is added to
280 // produce a gzip header + trailer.
282 8, // memLevel = 8 is default.
284 DCHECK_EQ(Z_OK
, result
);
286 uint8 intermediate_buffer
[kIntermediateCompressionBufferBytes
] = {0};
287 ResizeForNextOutput(compressed_log
, &stream
);
291 if (stream
.avail_in
== 0) {
292 read
= read_pcb
.Read(&intermediate_buffer
[0],
293 kIntermediateCompressionBufferBytes
);
294 stream
.next_in
= &intermediate_buffer
[0];
295 stream
.avail_in
= read
;
296 if (read
!= kIntermediateCompressionBufferBytes
)
299 result
= deflate(&stream
, Z_SYNC_FLUSH
);
300 DCHECK_EQ(Z_OK
, result
);
301 if (stream
.avail_out
== 0)
302 ResizeForNextOutput(compressed_log
, &stream
);
305 // Ensure we have enough room in the output buffer. Easier to always just do a
306 // resize than looping around and resize if needed.
307 if (stream
.avail_out
< kIntermediateCompressionBufferBytes
)
308 ResizeForNextOutput(compressed_log
, &stream
);
310 result
= deflate(&stream
, Z_FINISH
);
311 DCHECK_EQ(Z_STREAM_END
, result
);
312 result
= deflateEnd(&stream
);
313 DCHECK_EQ(Z_OK
, result
);
315 compressed_log
->resize(compressed_log
->size() - stream
.avail_out
);
318 void WebRtcLogUploader::ResizeForNextOutput(std::vector
<uint8
>* compressed_log
,
320 size_t old_size
= compressed_log
->size() - stream
->avail_out
;
321 compressed_log
->resize(old_size
+ kIntermediateCompressionBufferBytes
);
322 stream
->next_out
= &(*compressed_log
)[old_size
];
323 stream
->avail_out
= kIntermediateCompressionBufferBytes
;
326 void WebRtcLogUploader::CreateAndStartURLFetcher(
327 const WebRtcLogUploadDoneData
& upload_done_data
,
328 scoped_ptr
<std::string
> post_data
) {
329 DCHECK(create_thread_checker_
.CalledOnValidThread());
334 std::string content_type
= kUploadContentType
;
335 content_type
.append("; boundary=");
336 content_type
.append(kMultipartBoundary
);
338 net::URLFetcher
* url_fetcher
=
339 net::URLFetcher::Create(GURL(kUploadURL
), net::URLFetcher::POST
, this);
340 url_fetcher
->SetRequestContext(g_browser_process
->system_request_context());
341 url_fetcher
->SetUploadData(content_type
, *post_data
);
342 url_fetcher
->Start();
343 upload_done_data_
[url_fetcher
] = upload_done_data
;
346 void WebRtcLogUploader::DecreaseLogCount() {
347 DCHECK(create_thread_checker_
.CalledOnValidThread());
351 void WebRtcLogUploader::WriteCompressedLogToFile(
352 const std::vector
<uint8
>& compressed_log
,
353 const base::FilePath
& log_file_path
) {
354 DCHECK(file_thread_checker_
.CalledOnValidThread());
355 DCHECK(!compressed_log
.empty());
356 base::WriteFile(log_file_path
,
357 reinterpret_cast<const char*>(&compressed_log
[0]),
358 compressed_log
.size());
361 void WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile(
362 const base::FilePath
& upload_list_path
,
363 const std::string
& local_log_id
) {
364 DCHECK(file_thread_checker_
.CalledOnValidThread());
365 DCHECK(!upload_list_path
.empty());
366 DCHECK(!local_log_id
.empty());
368 std::string contents
;
370 if (base::PathExists(upload_list_path
)) {
371 if (!base::ReadFileToString(upload_list_path
, &contents
)) {
372 DPLOG(WARNING
) << "Could not read WebRTC log list file.";
376 // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
377 // for the new entry. Each line including the last ends with a '\n', so hit
378 // n will be before line n-1 (from the back).
380 int i
= contents
.size() - 1;
381 for (; i
>= 0 && lf_count
< kLogListLimitLines
; --i
) {
382 if (contents
[i
] == '\n')
385 if (lf_count
>= kLogListLimitLines
) {
386 // + 1 to compensate for the for loop decrease before the conditional
387 // check and + 1 to get the length.
388 contents
.erase(0, i
+ 2);
392 // Write the log ID to the log list file. Leave the upload time and report ID
394 contents
+= ",," + local_log_id
+ '\n';
397 base::WriteFile(upload_list_path
, &contents
[0], contents
.size());
398 if (written
!= static_cast<int>(contents
.size())) {
399 DPLOG(WARNING
) << "Could not write all data to WebRTC log list file: "
404 void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
405 const base::FilePath
& upload_list_path
,
406 const std::string
& local_log_id
,
407 const std::string
& report_id
) {
408 DCHECK(file_thread_checker_
.CalledOnValidThread());
409 DCHECK(!upload_list_path
.empty());
410 DCHECK(!local_log_id
.empty());
411 DCHECK(!report_id
.empty());
413 std::string contents
;
415 if (base::PathExists(upload_list_path
)) {
416 if (!base::ReadFileToString(upload_list_path
, &contents
)) {
417 DPLOG(WARNING
) << "Could not read WebRTC log list file.";
422 // Write the Unix time and report ID to the log list file. We should be able
423 // to find the local log ID, in that case insert the data into the existing
424 // line. Otherwise add it in the end.
425 base::Time time_now
= base::Time::Now();
426 std::string time_now_str
= base::DoubleToString(time_now
.ToDoubleT());
427 size_t pos
= contents
.find(",," + local_log_id
);
428 if (pos
!= std::string::npos
) {
429 contents
.insert(pos
, time_now_str
);
430 contents
.insert(pos
+ time_now_str
.length() + 1, report_id
);
432 contents
+= time_now_str
+ "," + report_id
+ ",\n";
436 base::WriteFile(upload_list_path
, &contents
[0], contents
.size());
437 if (written
!= static_cast<int>(contents
.size())) {
438 DPLOG(WARNING
) << "Could not write all data to WebRTC log list file: "
443 void WebRtcLogUploader::NotifyUploadDone(
445 const std::string
& report_id
,
446 const WebRtcLogUploadDoneData
& upload_done_data
) {
447 content::BrowserThread::PostTask(content::BrowserThread::IO
, FROM_HERE
,
448 base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone
,
449 upload_done_data
.host
));
450 if (!upload_done_data
.callback
.is_null()) {
451 bool success
= response_code
== kHttpResponseOk
;
452 std::string error_message
;
454 error_message
= "Uploading failed, response code: " +
455 base::IntToString(response_code
);
457 content::BrowserThread::PostTask(
458 content::BrowserThread::UI
, FROM_HERE
,
459 base::Bind(upload_done_data
.callback
, success
, report_id
,