普通文本  |  387行  |  13.06 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/tracing/tracing_ui.h"

#include <set>
#include <string>
#include <vector>

#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/format_macros.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "content/browser/tracing/grit/tracing_resources.h"
#include "content/browser/tracing/trace_uploader.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tracing_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"

namespace content {
namespace {

const char kUploadURL[] = "https://clients2.google.com/cr/staging_report";

void OnGotCategories(const WebUIDataSource::GotDataCallback& callback,
                     const std::set<std::string>& categorySet) {
  scoped_ptr<base::ListValue> category_list(new base::ListValue());
  for (std::set<std::string>::const_iterator it = categorySet.begin();
       it != categorySet.end(); it++) {
    category_list->AppendString(*it);
  }

  base::RefCountedString* res = new base::RefCountedString();
  base::JSONWriter::Write(category_list.get(), &res->data());
  callback.Run(res);
}

bool GetTracingOptions(const std::string& data64,
                       base::debug::CategoryFilter* category_filter,
                       base::debug::TraceOptions* tracing_options) {
  std::string data;
  if (!base::Base64Decode(data64, &data)) {
    LOG(ERROR) << "Options were not base64 encoded.";
    return false;
  }

  scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data));
  if (!optionsRaw) {
    LOG(ERROR) << "Options were not valid JSON";
    return false;
  }
  base::DictionaryValue* options;
  if (!optionsRaw->GetAsDictionary(&options)) {
    LOG(ERROR) << "Options must be dict";
    return false;
  }

  if (!category_filter) {
    LOG(ERROR) << "category_filter can't be passed as NULL";
    return false;
  }

  if (!tracing_options) {
    LOG(ERROR) << "tracing_options can't be passed as NULL";
    return false;
  }

  bool options_ok = true;
  std::string category_filter_string;
  options_ok &= options->GetString("categoryFilter", &category_filter_string);
  *category_filter = base::debug::CategoryFilter(category_filter_string);

  options_ok &= options->GetBoolean("useSystemTracing",
                                    &tracing_options->enable_systrace);
  options_ok &=
      options->GetBoolean("useSampling", &tracing_options->enable_sampling);

  bool use_continuous_tracing;
  options_ok &=
      options->GetBoolean("useContinuousTracing", &use_continuous_tracing);

  if (use_continuous_tracing)
    tracing_options->record_mode = base::debug::RECORD_CONTINUOUSLY;
  else
    tracing_options->record_mode = base::debug::RECORD_UNTIL_FULL;

  if (!options_ok) {
    LOG(ERROR) << "Malformed options";
    return false;
  }
  return true;
}

void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback);

bool BeginRecording(const std::string& data64,
                    const WebUIDataSource::GotDataCallback& callback) {
  base::debug::CategoryFilter category_filter("");
  base::debug::TraceOptions tracing_options;
  if (!GetTracingOptions(data64, &category_filter, &tracing_options))
    return false;

  return TracingController::GetInstance()->EnableRecording(
      category_filter,
      tracing_options,
      base::Bind(&OnRecordingEnabledAck, callback));
}

void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
  base::RefCountedString* res = new base::RefCountedString();
  callback.Run(res);
}

void OnTraceBufferPercentFullResult(
    const WebUIDataSource::GotDataCallback& callback, float result) {
  std::string str = base::DoubleToString(result);
  callback.Run(base::RefCountedString::TakeString(&str));
}

void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback);

bool EnableMonitoring(const std::string& data64,
                      const WebUIDataSource::GotDataCallback& callback) {
  base::debug::TraceOptions tracing_options;
  base::debug::CategoryFilter category_filter("");
  if (!GetTracingOptions(data64, &category_filter, &tracing_options))
    return false;

  return TracingController::GetInstance()->EnableMonitoring(
      category_filter,
      tracing_options,
      base::Bind(OnMonitoringEnabledAck, callback));
}

void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
  base::RefCountedString* res = new base::RefCountedString();
  callback.Run(res);
}

void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) {
  base::RefCountedString* res = new base::RefCountedString();
  callback.Run(res);
}

void GetMonitoringStatus(const WebUIDataSource::GotDataCallback& callback) {
  bool is_monitoring;
  base::debug::CategoryFilter category_filter("");
  base::debug::TraceOptions options;
  TracingController::GetInstance()->GetMonitoringStatus(
      &is_monitoring, &category_filter, &options);

  scoped_ptr<base::DictionaryValue>
    monitoring_options(new base::DictionaryValue());
  monitoring_options->SetBoolean("isMonitoring", is_monitoring);
  monitoring_options->SetString("categoryFilter", category_filter.ToString());
  monitoring_options->SetBoolean("useSystemTracing", options.enable_systrace);
  monitoring_options->SetBoolean(
      "useContinuousTracing",
      options.record_mode == base::debug::RECORD_CONTINUOUSLY);
  monitoring_options->SetBoolean("useSampling", options.enable_sampling);

  std::string monitoring_options_json;
  base::JSONWriter::Write(monitoring_options.get(), &monitoring_options_json);

  base::RefCountedString* monitoring_options_base64 =
    new base::RefCountedString();
  base::Base64Encode(monitoring_options_json,
                     &monitoring_options_base64->data());
  callback.Run(monitoring_options_base64);
}

void TracingCallbackWrapper(const WebUIDataSource::GotDataCallback& callback,
                            base::RefCountedString* data) {
  callback.Run(data);
}

bool OnBeginJSONRequest(const std::string& path,
                        const WebUIDataSource::GotDataCallback& callback) {
  if (path == "json/categories") {
    return TracingController::GetInstance()->GetCategories(
        base::Bind(OnGotCategories, callback));
  }

  const char* beginRecordingPath = "json/begin_recording?";
  if (StartsWithASCII(path, beginRecordingPath, true)) {
    std::string data = path.substr(strlen(beginRecordingPath));
    return BeginRecording(data, callback);
  }
  if (path == "json/get_buffer_percent_full") {
    return TracingController::GetInstance()->GetTraceBufferPercentFull(
        base::Bind(OnTraceBufferPercentFullResult, callback));
  }
  if (path == "json/end_recording") {
    return TracingController::GetInstance()->DisableRecording(
        TracingControllerImpl::CreateStringSink(
            base::Bind(TracingCallbackWrapper, callback)));
  }

  const char* enableMonitoringPath = "json/begin_monitoring?";
  if (path.find(enableMonitoringPath) == 0) {
    std::string data = path.substr(strlen(enableMonitoringPath));
    return EnableMonitoring(data, callback);
  }
  if (path == "json/end_monitoring") {
    return TracingController::GetInstance()->DisableMonitoring(
        base::Bind(OnMonitoringDisabled, callback));
  }
  if (path == "json/capture_monitoring") {
    TracingController::GetInstance()->CaptureMonitoringSnapshot(
        TracingControllerImpl::CreateStringSink(
            base::Bind(TracingCallbackWrapper, callback)));
    return true;
  }
  if (path == "json/get_monitoring_status") {
    GetMonitoringStatus(callback);
    return true;
  }

  LOG(ERROR) << "Unhandled request to " << path;
  return false;
}

bool OnTracingRequest(const std::string& path,
                      const WebUIDataSource::GotDataCallback& callback) {
  if (StartsWithASCII(path, "json/", true)) {
    if (!OnBeginJSONRequest(path, callback)) {
      std::string error("##ERROR##");
      callback.Run(base::RefCountedString::TakeString(&error));
    }
    return true;
  }
  return false;
}

}  // namespace


////////////////////////////////////////////////////////////////////////////////
//
// TracingUI
//
////////////////////////////////////////////////////////////////////////////////

TracingUI::TracingUI(WebUI* web_ui)
    : WebUIController(web_ui),
      weak_factory_(this) {
  web_ui->RegisterMessageCallback(
        "doUpload",
        base::Bind(&TracingUI::DoUpload, base::Unretained(this)));

  // Set up the chrome://tracing/ source.
  BrowserContext* browser_context =
      web_ui->GetWebContents()->GetBrowserContext();

  WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost);
  source->SetJsonPath("strings.js");
  source->SetDefaultResource(IDR_TRACING_HTML);
  source->AddResourcePath("tracing.js", IDR_TRACING_JS);
  source->SetRequestFilter(base::Bind(OnTracingRequest));
  WebUIDataSource::Add(browser_context, source);
  TracingControllerImpl::GetInstance()->RegisterTracingUI(this);
}

TracingUI::~TracingUI() {
  TracingControllerImpl::GetInstance()->UnregisterTracingUI(this);
}

void TracingUI::OnMonitoringStateChanged(bool is_monitoring) {
  web_ui()->CallJavascriptFunction(
      "onMonitoringStateChanged", base::FundamentalValue(is_monitoring));
}

void TracingUI::DoUpload(const base::ListValue* args) {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  std::string upload_url = kUploadURL;
  if (command_line.HasSwitch(switches::kTraceUploadURL)) {
    upload_url =
        command_line.GetSwitchValueASCII(switches::kTraceUploadURL);
  }
  if (!GURL(upload_url).is_valid()) {
    upload_url.clear();
  }

  if (upload_url.empty()) {
    web_ui()->CallJavascriptFunction("onUploadError",
        base::StringValue("Upload URL empty or invalid"));
    return;
  }

  std::string file_contents;
  if (!args || args->empty() || !args->GetString(0, &file_contents)) {
    web_ui()->CallJavascriptFunction("onUploadError",
                                     base::StringValue("Missing data"));
    return;
  }

  TraceUploader::UploadProgressCallback progress_callback =
      base::Bind(&TracingUI::OnTraceUploadProgress,
      weak_factory_.GetWeakPtr());
  TraceUploader::UploadDoneCallback done_callback =
      base::Bind(&TracingUI::OnTraceUploadComplete,
      weak_factory_.GetWeakPtr());

#if defined(OS_WIN)
  const char product[] = "Chrome";
#elif defined(OS_MACOSX)
  const char product[] = "Chrome_Mac";
#elif defined(OS_LINUX)
  const char product[] = "Chrome_Linux";
#elif defined(OS_ANDROID)
  const char product[] = "Chrome_Android";
#elif defined(OS_CHROMEOS)
  const char product[] = "Chrome_ChromeOS";
#else
#error Platform not supported.
#endif

  // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
  // the part before the "/".
  std::vector<std::string> product_components;
  base::SplitString(content::GetContentClient()->GetProduct(), '/',
                    &product_components);
  DCHECK_EQ(2U, product_components.size());
  std::string version;
  if (product_components.size() == 2U) {
    version = product_components[1];
  } else {
    version = "unknown";
  }

  BrowserContext* browser_context =
      web_ui()->GetWebContents()->GetBrowserContext();
  TraceUploader* uploader = new TraceUploader(
      product, version, upload_url, browser_context->GetRequestContext());

  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
     &TraceUploader::DoUpload,
     base::Unretained(uploader),
     file_contents,
     progress_callback,
     done_callback));
  // TODO(mmandlis): Add support for stopping the upload in progress.
}

void TracingUI::OnTraceUploadProgress(int64 current, int64 total) {
  DCHECK(current <= total);
  int percent = (current / total) * 100;
  web_ui()->CallJavascriptFunction(
        "onUploadProgress",
        base::FundamentalValue(percent),
        base::StringValue(base::StringPrintf("%" PRId64, current)),
        base::StringValue(base::StringPrintf("%" PRId64, total)));
}

void TracingUI::OnTraceUploadComplete(bool success,
                                      const std::string& report_id,
                                      const std::string& error_message) {
  if (success) {
    web_ui()->CallJavascriptFunction("onUploadComplete",
                                     base::StringValue(report_id));
  } else {
    web_ui()->CallJavascriptFunction("onUploadError",
                                     base::StringValue(error_message));
  }
}

}  // namespace content