webrtc_logging_handler_host.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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.
4
5#include "chrome/browser/media/webrtc_logging_handler_host.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/cpu.h"
12#include "base/file_util.h"
13#include "base/logging.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/string_number_conversions.h"
16#include "base/sys_info.h"
17#include "base/time/time.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/chromeos/settings/cros_settings.h"
20#include "chrome/browser/media/webrtc_log_list.h"
21#include "chrome/browser/media/webrtc_log_uploader.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/common/chrome_switches.h"
24#include "chrome/common/media/webrtc_logging_messages.h"
25#include "chrome/common/partial_circular_buffer.h"
26#include "chrome/common/pref_names.h"
27#include "chromeos/settings/cros_settings_names.h"
28#include "content/public/browser/browser_thread.h"
29#include "content/public/browser/content_browser_client.h"
30#include "content/public/browser/gpu_data_manager.h"
31#include "content/public/browser/render_process_host.h"
32#include "gpu/config/gpu_info.h"
33#include "net/base/address_family.h"
34#include "net/url_request/url_request_context_getter.h"
35
36#if defined(OS_LINUX)
37#include "base/linux_util.h"
38#endif
39
40#if defined(OS_MACOSX)
41#include "base/mac/mac_util.h"
42#endif
43
44#if defined(OS_CHROMEOS)
45#include "chromeos/system/statistics_provider.h"
46#endif
47
48using base::IntToString;
49using content::BrowserThread;
50
51
52#if defined(OS_ANDROID)
53const size_t kWebRtcLogSize = 1 * 1024 * 1024;  // 1 MB
54#else
55const size_t kWebRtcLogSize = 6 * 1024 * 1024;  // 6 MB
56#endif
57
58namespace {
59
60const char kLogNotStoppedOrNoLogOpen[] =
61    "Logging not stopped or no log open.";
62
63// For privacy reasons when logging IP addresses. The returned "sensitive
64// string" is for release builds a string with the end stripped away. Last
65// octet for IPv4 and last 80 bits (5 groups) for IPv6. String will be
66// "1.2.3.x" and "1.2.3::" respectively. For debug builds, the string is
67// not stripped.
68std::string IPAddressToSensitiveString(const net::IPAddressNumber& address) {
69#if defined(NDEBUG)
70  std::string sensitive_address;
71  switch (net::GetAddressFamily(address)) {
72    case net::ADDRESS_FAMILY_IPV4: {
73      sensitive_address = net::IPAddressToString(address);
74      size_t find_pos = sensitive_address.rfind('.');
75      if (find_pos == std::string::npos)
76        return std::string();
77      sensitive_address.resize(find_pos);
78      sensitive_address += ".x";
79      break;
80    }
81    case net::ADDRESS_FAMILY_IPV6: {
82      // TODO(grunell): Create a string of format "1:2:3:x:x:x:x:x" to clarify
83      // that the end has been stripped out.
84      net::IPAddressNumber sensitive_address_number = address;
85      sensitive_address_number.resize(net::kIPv6AddressSize - 10);
86      sensitive_address_number.resize(net::kIPv6AddressSize, 0);
87      sensitive_address = net::IPAddressToString(sensitive_address_number);
88      break;
89    }
90    case net::ADDRESS_FAMILY_UNSPECIFIED: {
91      break;
92    }
93  }
94  return sensitive_address;
95#else
96  return net::IPAddressToString(address);
97#endif
98}
99
100void FormatMetaDataAsLogMessage(
101    const MetaDataMap& meta_data,
102    std::string* message) {
103  for (MetaDataMap::const_iterator it = meta_data.begin();
104       it != meta_data.end(); ++it) {
105    *message += it->first + ": " + it->second + '\n';
106  }
107  // Remove last '\n'.
108  message->resize(message->size() - 1);
109}
110
111}  // namespace
112
113WebRtcLoggingHandlerHost::WebRtcLoggingHandlerHost(Profile* profile)
114    : BrowserMessageFilter(WebRtcLoggingMsgStart),
115      profile_(profile),
116      logging_state_(CLOSED),
117      upload_log_on_render_close_(false) {
118  DCHECK(profile_);
119}
120
121WebRtcLoggingHandlerHost::~WebRtcLoggingHandlerHost() {}
122
123void WebRtcLoggingHandlerHost::SetMetaData(
124    const MetaDataMap& meta_data,
125    const GenericDoneCallback& callback) {
126  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
127  DCHECK(!callback.is_null());
128
129  std::string error_message;
130  if (logging_state_ == CLOSED) {
131    meta_data_ = meta_data;
132  } else if (logging_state_ == STARTED) {
133    meta_data_ = meta_data;
134    std::string meta_data_message;
135    FormatMetaDataAsLogMessage(meta_data_, &meta_data_message);
136    LogToCircularBuffer(meta_data_message);
137  } else {
138    error_message = "Meta data must be set before stop or upload.";
139  }
140  bool success = error_message.empty();
141  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
142                                   base::Bind(callback, success,
143                                              error_message));
144}
145
146void WebRtcLoggingHandlerHost::StartLogging(
147    const GenericDoneCallback& callback) {
148  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
149  DCHECK(!callback.is_null());
150
151  start_callback_ = callback;
152  if (logging_state_ != CLOSED) {
153    FireGenericDoneCallback(&start_callback_, false, "A log is already open");
154    return;
155  }
156  logging_state_ = STARTING;
157  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
158      &WebRtcLoggingHandlerHost::StartLoggingIfAllowed, this));
159}
160
161void WebRtcLoggingHandlerHost::StopLogging(
162    const GenericDoneCallback& callback) {
163  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
164  DCHECK(!callback.is_null());
165
166  stop_callback_ = callback;
167  if (logging_state_ != STARTED) {
168    FireGenericDoneCallback(&stop_callback_, false, "Logging not started");
169    return;
170  }
171  logging_state_ = STOPPING;
172  Send(new WebRtcLoggingMsg_StopLogging());
173}
174
175void WebRtcLoggingHandlerHost::UploadLog(const UploadDoneCallback& callback) {
176  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
177  DCHECK(!callback.is_null());
178
179  if (logging_state_ != STOPPED) {
180    if (!callback.is_null()) {
181      content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
182          base::Bind(callback, false, "", kLogNotStoppedOrNoLogOpen));
183    }
184    return;
185  }
186  upload_callback_ = callback;
187  content::BrowserThread::PostTaskAndReplyWithResult(
188      content::BrowserThread::FILE,
189      FROM_HERE,
190      base::Bind(&WebRtcLoggingHandlerHost::GetLogDirectoryAndEnsureExists,
191                 this),
192      base::Bind(&WebRtcLoggingHandlerHost::TriggerUploadLog, this));
193}
194
195void WebRtcLoggingHandlerHost::UploadLogDone() {
196  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
197  logging_state_ = CLOSED;
198}
199
200void WebRtcLoggingHandlerHost::DiscardLog(const GenericDoneCallback& callback) {
201  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
202  DCHECK(!callback.is_null());
203
204  GenericDoneCallback discard_callback = callback;
205  if (logging_state_ != STOPPED) {
206    FireGenericDoneCallback(&discard_callback, false,
207                            kLogNotStoppedOrNoLogOpen);
208    return;
209  }
210  g_browser_process->webrtc_log_uploader()->LoggingStoppedDontUpload();
211  circular_buffer_.reset();
212  log_buffer_.reset();
213  logging_state_ = CLOSED;
214  FireGenericDoneCallback(&discard_callback, true, "");
215}
216
217void WebRtcLoggingHandlerHost::LogMessage(const std::string& message) {
218  BrowserThread::PostTask(
219      BrowserThread::IO,
220      FROM_HERE,
221      base::Bind(
222          &WebRtcLoggingHandlerHost::AddLogMessageFromBrowser,
223          this,
224          WebRtcLoggingMessageData(base::Time::Now(), message)));
225}
226
227void WebRtcLoggingHandlerHost::OnChannelClosing() {
228  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
229  if (logging_state_ == STARTED || logging_state_ == STOPPED) {
230    if (upload_log_on_render_close_) {
231      logging_state_ = STOPPED;
232      logging_started_time_ = base::Time();
233      content::BrowserThread::PostTaskAndReplyWithResult(
234          content::BrowserThread::FILE,
235          FROM_HERE,
236          base::Bind(&WebRtcLoggingHandlerHost::GetLogDirectoryAndEnsureExists,
237                     this),
238          base::Bind(&WebRtcLoggingHandlerHost::TriggerUploadLog, this));
239    } else {
240      g_browser_process->webrtc_log_uploader()->LoggingStoppedDontUpload();
241    }
242  }
243  content::BrowserMessageFilter::OnChannelClosing();
244}
245
246void WebRtcLoggingHandlerHost::OnDestruct() const {
247  BrowserThread::DeleteOnIOThread::Destruct(this);
248}
249
250bool WebRtcLoggingHandlerHost::OnMessageReceived(const IPC::Message& message,
251                                                 bool* message_was_ok) {
252  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
253  bool handled = true;
254  IPC_BEGIN_MESSAGE_MAP_EX(WebRtcLoggingHandlerHost, message, *message_was_ok)
255    IPC_MESSAGE_HANDLER(WebRtcLoggingMsg_AddLogMessages, OnAddLogMessages)
256    IPC_MESSAGE_HANDLER(WebRtcLoggingMsg_LoggingStopped,
257                        OnLoggingStoppedInRenderer)
258    IPC_MESSAGE_UNHANDLED(handled = false)
259  IPC_END_MESSAGE_MAP_EX()
260
261  return handled;
262}
263
264void WebRtcLoggingHandlerHost::AddLogMessageFromBrowser(
265    const WebRtcLoggingMessageData& message) {
266  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
267  if (logging_state_ == STARTED)
268    LogToCircularBuffer(message.Format(logging_started_time_));
269}
270
271void WebRtcLoggingHandlerHost::OnAddLogMessages(
272    const std::vector<WebRtcLoggingMessageData>& messages) {
273  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
274  if (logging_state_ == STARTED || logging_state_ == STOPPING) {
275    for (size_t i = 0; i < messages.size(); ++i) {
276      LogToCircularBuffer(messages[i].Format(logging_started_time_));
277    }
278  }
279}
280
281void WebRtcLoggingHandlerHost::OnLoggingStoppedInRenderer() {
282  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
283  if (logging_state_ != STOPPING) {
284    // If an out-of-order response is received, stop_callback_ may be invalid,
285    // and must not be invoked.
286    DLOG(ERROR) << "OnLoggingStoppedInRenderer invoked in state "
287                << logging_state_;
288    BadMessageReceived();
289    return;
290  }
291  logging_started_time_ = base::Time();
292  logging_state_ = STOPPED;
293  FireGenericDoneCallback(&stop_callback_, true, "");
294}
295
296void WebRtcLoggingHandlerHost::StartLoggingIfAllowed() {
297  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298  if (!g_browser_process->webrtc_log_uploader()->ApplyForStartLogging()) {
299    logging_state_ = CLOSED;
300      FireGenericDoneCallback(
301          &start_callback_, false, "Cannot start, maybe the maximum number of "
302          "simultaneuos logs has been reached.");
303    return;
304  }
305  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
306      &WebRtcLoggingHandlerHost::DoStartLogging, this));
307}
308
309void WebRtcLoggingHandlerHost::DoStartLogging() {
310  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
311
312  log_buffer_.reset(new unsigned char[kWebRtcLogSize]);
313  circular_buffer_.reset(
314    new PartialCircularBuffer(log_buffer_.get(),
315                              kWebRtcLogSize,
316                              kWebRtcLogSize / 2,
317                              false));
318
319  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
320      &WebRtcLoggingHandlerHost::LogInitialInfoOnFileThread, this));
321}
322
323void WebRtcLoggingHandlerHost::LogInitialInfoOnFileThread() {
324  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
325
326  net::NetworkInterfaceList network_list;
327  net::GetNetworkList(&network_list,
328                      net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES);
329
330  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
331      &WebRtcLoggingHandlerHost::LogInitialInfoOnIOThread, this, network_list));
332}
333
334void WebRtcLoggingHandlerHost::LogInitialInfoOnIOThread(
335    const net::NetworkInterfaceList& network_list) {
336  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
337
338  // Log start time (current time). We don't use base/i18n/time_formatting.h
339  // here because we don't want the format of the current locale.
340  base::Time::Exploded now = {0};
341  base::Time::Now().LocalExplode(&now);
342  LogToCircularBuffer(base::StringPrintf(
343      "Start %d-%02d-%02d %02d:%02d:%02d", now.year, now.month,
344      now.day_of_month, now.hour, now.minute, now.second));
345
346  // Write metadata if received before logging started.
347  if (!meta_data_.empty()) {
348    std::string info;
349    FormatMetaDataAsLogMessage(meta_data_, &info);
350    LogToCircularBuffer(info);
351  }
352
353  // OS
354  LogToCircularBuffer(base::SysInfo::OperatingSystemName() + " " +
355                      base::SysInfo::OperatingSystemVersion() + " " +
356                      base::SysInfo::OperatingSystemArchitecture());
357#if defined(OS_LINUX)
358  LogToCircularBuffer("Linux distribution: " + base::GetLinuxDistro());
359#endif
360
361  // CPU
362  base::CPU cpu;
363  LogToCircularBuffer(
364      "Cpu: " + IntToString(cpu.family()) + "." + IntToString(cpu.model()) +
365      "." + IntToString(cpu.stepping()) + ", x" +
366      IntToString(base::SysInfo::NumberOfProcessors()) + ", " +
367      IntToString(base::SysInfo::AmountOfPhysicalMemoryMB()) + "MB");
368  std::string cpu_brand = cpu.cpu_brand();
369  // Workaround for crbug.com/249713.
370  // TODO(grunell): Remove workaround when bug is fixed.
371  size_t null_pos = cpu_brand.find('\0');
372  if (null_pos != std::string::npos)
373    cpu_brand.erase(null_pos);
374  LogToCircularBuffer("Cpu brand: " + cpu_brand);
375
376  // Computer model
377  std::string computer_model = "Not available";
378#if defined(OS_MACOSX)
379  computer_model = base::mac::GetModelIdentifier();
380#elif defined(OS_CHROMEOS)
381  chromeos::system::StatisticsProvider::GetInstance()->
382      GetMachineStatistic(chromeos::system::kHardwareClassKey, &computer_model);
383#endif
384  LogToCircularBuffer("Computer model: " + computer_model);
385
386  // GPU
387  gpu::GPUInfo gpu_info = content::GpuDataManager::GetInstance()->GetGPUInfo();
388  LogToCircularBuffer("Gpu: machine-model='" + gpu_info.machine_model +
389                      "', vendor-id=" + IntToString(gpu_info.gpu.vendor_id) +
390                      ", device-id=" + IntToString(gpu_info.gpu.device_id) +
391                      ", driver-vendor='" + gpu_info.driver_vendor +
392                      "', driver-version=" + gpu_info.driver_version);
393
394  // Network interfaces
395  LogToCircularBuffer("Discovered " + IntToString(network_list.size()) +
396                      " network interfaces:");
397  for (net::NetworkInterfaceList::const_iterator it = network_list.begin();
398       it != network_list.end(); ++it) {
399    LogToCircularBuffer("Name: " + it->friendly_name + ", Address: " +
400                        IPAddressToSensitiveString(it->address));
401  }
402
403  NotifyLoggingStarted();
404}
405
406void WebRtcLoggingHandlerHost::NotifyLoggingStarted() {
407  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
408  Send(new WebRtcLoggingMsg_StartLogging());
409  logging_started_time_ = base::Time::Now();
410  logging_state_ = STARTED;
411  FireGenericDoneCallback(&start_callback_, true, "");
412}
413
414void WebRtcLoggingHandlerHost::LogToCircularBuffer(const std::string& message) {
415  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
416  DCHECK(circular_buffer_.get());
417  circular_buffer_->Write(message.c_str(), message.length());
418  const char eol = '\n';
419  circular_buffer_->Write(&eol, 1);
420}
421
422base::FilePath WebRtcLoggingHandlerHost::GetLogDirectoryAndEnsureExists() {
423  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
424  base::FilePath log_dir_path =
425      WebRtcLogList::GetWebRtcLogDirectoryForProfile(profile_->GetPath());
426  base::File::Error error;
427  if (!base::CreateDirectoryAndGetError(log_dir_path, &error)) {
428    DLOG(ERROR) << "Could not create WebRTC log directory, error: " << error;
429    return base::FilePath();
430  }
431  return log_dir_path;
432}
433
434void WebRtcLoggingHandlerHost::TriggerUploadLog(
435    const base::FilePath& log_directory) {
436  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
437  DCHECK(logging_state_ == STOPPED);
438
439  logging_state_ = UPLOADING;
440  WebRtcLogUploadDoneData upload_done_data;
441
442  upload_done_data.log_path = log_directory;
443  upload_done_data.callback = upload_callback_;
444  upload_done_data.host = this;
445  upload_callback_.Reset();
446
447  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
448      &WebRtcLogUploader::LoggingStoppedDoUpload,
449      base::Unretained(g_browser_process->webrtc_log_uploader()),
450      Passed(&log_buffer_),
451      kWebRtcLogSize,
452      meta_data_,
453      upload_done_data));
454
455  meta_data_.clear();
456  circular_buffer_.reset();
457}
458
459void WebRtcLoggingHandlerHost::FireGenericDoneCallback(
460    GenericDoneCallback* callback, bool success,
461    const std::string& error_message) {
462  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
463  DCHECK(!(*callback).is_null());
464  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
465                                   base::Bind(*callback, success,
466                                              error_message));
467  (*callback).Reset();
468}
469