blob: fd0b7e7ab411ce0c6edf019294eccb0ef5169540 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/profiling_host/profiling_process_host.h"
#include "base/allocator/features.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/process/process_iterator.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/tracing/crash_service_uploader.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/profiling/constants.mojom.h"
#include "chrome/common/profiling/profiling_constants.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/bind_interface_helpers.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/service_manager_connection.h"
#include "content/public/common/service_names.mojom.h"
#include "mojo/edk/embedder/platform_channel_pair.h"
#include "mojo/edk/embedder/scoped_platform_handle.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace {
// A wrapper classes that allows a string to be exported as JSON in a trace
// event.
class StringWrapper : public base::trace_event::ConvertableToTraceFormat {
public:
explicit StringWrapper(std::string string) : json_(std::move(string)) {}
void AppendAsTraceFormat(std::string* out) const override {
out->append(json_);
}
std::string json_;
};
} // namespace
namespace profiling {
bool ProfilingProcessHost::has_started_ = false;
namespace {
const size_t kMaxTraceSizeUploadInBytes = 10 * 1024 * 1024;
const char kNoTriggerName[] = "";
void OnTraceUploadComplete(TraceCrashServiceUploader* uploader,
bool success,
const std::string& feedback);
void UploadTraceToCrashServer(base::FilePath file_path,
std::string trigger_name) {
std::string file_contents;
if (!base::ReadFileToStringWithMaxSize(file_path, &file_contents,
kMaxTraceSizeUploadInBytes)) {
DLOG(ERROR) << "Cannot read trace file contents.";
return;
}
base::Value rules_list(base::Value::Type::LIST);
base::Value rule(base::Value::Type::DICTIONARY);
rule.SetKey("rule", base::Value("MEMLOG"));
rule.SetKey("trigger_name", base::Value(std::move(trigger_name)));
rule.SetKey("category", base::Value("BENCHMARK_MEMORY_HEAVY"));
rules_list.GetList().push_back(std::move(rule));
base::Value configs(base::Value::Type::DICTIONARY);
configs.SetKey("mode", base::Value("REACTIVE_TRACING_MODE"));
configs.SetKey("category", base::Value("MEMLOG"));
configs.SetKey("configs", std::move(rules_list));
std::unique_ptr<base::DictionaryValue> metadata =
base::MakeUnique<base::DictionaryValue>();
metadata->SetKey("config", std::move(configs));
TraceCrashServiceUploader* uploader = new TraceCrashServiceUploader(
g_browser_process->system_request_context());
uploader->DoUpload(file_contents, content::TraceUploader::UNCOMPRESSED_UPLOAD,
std::move(metadata),
content::TraceUploader::UploadProgressCallback(),
base::Bind(&OnTraceUploadComplete, base::Owned(uploader)));
}
void OnTraceUploadComplete(TraceCrashServiceUploader* uploader,
bool success,
const std::string& feedback) {
if (!success) {
LOG(ERROR) << "Cannot upload trace file: " << feedback;
return;
}
}
} // namespace
ProfilingProcessHost::ProfilingProcessHost()
: is_registered_(false), background_triggers_(this) {}
ProfilingProcessHost::~ProfilingProcessHost() {
if (is_registered_)
Unregister();
}
void ProfilingProcessHost::Register() {
DCHECK(!is_registered_);
Add(this);
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
is_registered_ = true;
}
void ProfilingProcessHost::Unregister() {
DCHECK(is_registered_);
Remove(this);
}
void ProfilingProcessHost::BrowserChildProcessLaunchedAndConnected(
const content::ChildProcessData& data) {
// In minimal mode, only profile the GPU process.
if (mode_ == Mode::kMinimal &&
data.process_type != content::ProcessType::PROCESS_TYPE_GPU) {
return;
}
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(
FROM_HERE,
base::BindOnce(
&ProfilingProcessHost::BrowserChildProcessLaunchedAndConnected,
base::Unretained(this), data));
return;
}
content::BrowserChildProcessHost* host =
content::BrowserChildProcessHost::FromID(data.id);
if (!host)
return;
// Tell the child process to start profiling.
profiling::mojom::MemlogClientPtr memlog_client;
profiling::mojom::MemlogClientRequest request =
mojo::MakeRequest(&memlog_client);
BindInterface(host->GetHost(), std::move(request));
base::ProcessId pid = base::GetProcId(data.handle);
LOG(ERROR) << "start profiling: " << pid;
SendPipeToProfilingService(std::move(memlog_client), pid);
}
void ProfilingProcessHost::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
// Ignore newly launched renderer if only profiling a minimal set of
// processes.
if (mode_ == Mode::kMinimal)
return;
if (type != content::NOTIFICATION_RENDERER_PROCESS_CREATED)
return;
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(FROM_HERE, base::BindOnce(&ProfilingProcessHost::Observe,
base::Unretained(this), type,
source, details));
return;
}
// Tell the child process to start profiling.
content::RenderProcessHost* host =
content::Source<content::RenderProcessHost>(source).ptr();
profiling::mojom::MemlogClientPtr memlog_client;
profiling::mojom::MemlogClientRequest request =
mojo::MakeRequest(&memlog_client);
content::BindInterface(host, std::move(request));
base::ProcessId pid = base::GetProcId(host->GetHandle());
SendPipeToProfilingService(std::move(memlog_client), pid);
}
bool ProfilingProcessHost::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
// Attempt to dump all processes. Some of these processes will not be profiled
// [e.g. utility processes, including the profiling process]. The profiling
// process will gracefully handle these failures.
DCHECK_NE(GetCurrentMode(), Mode::kNone);
base::ProcessIterator process_iter(NULL);
while (const base::ProcessEntry* process_entry =
process_iter.NextProcessEntry()) {
memlog_->DumpProcessForTracing(
process_entry->pid(),
base::BindOnce(&ProfilingProcessHost::OnDumpProcessForTracingCallback,
base::Unretained(this)));
}
return true;
}
void ProfilingProcessHost::OnDumpProcessForTracingCallback(
mojo::ScopedSharedBufferHandle buffer,
uint32_t size) {
if (!buffer->is_valid()) {
// The profiling process host will have logged error messages indicating the
// nature of the problem.
return;
}
mojo::ScopedSharedBufferMapping mapping = buffer->Map(size);
if (!mapping) {
DLOG(ERROR) << "Failed to map buffer";
return;
}
const char* char_buffer = static_cast<const char*>(mapping.get());
std::string json(char_buffer, char_buffer + size);
const int kTraceEventNumArgs = 1;
const char* const kTraceEventArgNames[] = {"dumps"};
const unsigned char kTraceEventArgTypes[] = {TRACE_VALUE_TYPE_CONVERTABLE};
std::unique_ptr<base::trace_event::ConvertableToTraceFormat> wrapper(
new StringWrapper(std::move(json)));
TRACE_EVENT_API_ADD_TRACE_EVENT(
TRACE_EVENT_PHASE_MEMORY_DUMP,
base::trace_event::TraceLog::GetCategoryGroupEnabled(
base::trace_event::MemoryDumpManager::kTraceCategory),
"periodic_interval", trace_event_internal::kGlobalScope, 0x0,
kTraceEventNumArgs, kTraceEventArgNames, kTraceEventArgTypes,
nullptr /* arg_values */, &wrapper, TRACE_EVENT_FLAG_HAS_ID);
}
void ProfilingProcessHost::SendPipeToProfilingService(
profiling::mojom::MemlogClientPtr memlog_client,
base::ProcessId pid) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
// Writes to the data_channel must be atomic to ensure that the profiling
// process can demux the messages. We accomplish this by making writes
// synchronous, and protecting the write() itself with a Lock.
mojo::edk::PlatformChannelPair data_channel(true /* client_is_blocking */);
memlog_->AddSender(
pid,
mojo::WrapPlatformFile(data_channel.PassServerHandle().release().handle),
base::BindOnce(&ProfilingProcessHost::SendPipeToClientProcess,
base::Unretained(this), std::move(memlog_client),
mojo::WrapPlatformFile(
data_channel.PassClientHandle().release().handle)));
}
void ProfilingProcessHost::SendPipeToClientProcess(
profiling::mojom::MemlogClientPtr memlog_client,
mojo::ScopedHandle handle) {
memlog_client->StartProfiling(std::move(handle));
}
// static
ProfilingProcessHost::Mode ProfilingProcessHost::GetCurrentMode() {
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
#if BUILDFLAG(USE_ALLOCATOR_SHIM)
if (cmdline->HasSwitch(switches::kMemlog)) {
if (cmdline->HasSwitch(switches::kEnableHeapProfiling)) {
// PartitionAlloc doesn't support chained allocation hooks so we can't
// run both heap profilers at the same time.
LOG(ERROR) << "--" << switches::kEnableHeapProfiling
<< " specified with --" << switches::kMemlog
<< "which are not compatible. Memlog will be disabled.";
return Mode::kNone;
}
std::string mode = cmdline->GetSwitchValueASCII(switches::kMemlog);
if (mode == switches::kMemlogModeAll)
return Mode::kAll;
if (mode == switches::kMemlogModeMinimal)
return Mode::kMinimal;
DLOG(ERROR) << "Unsupported value: \"" << mode << "\" passed to --"
<< switches::kMemlog;
}
return Mode::kNone;
#else
LOG_IF(ERROR, cmdline->HasSwitch(switches::kMemlog))
<< "--" << switches::kMemlog
<< " specified but it will have no effect because the use_allocator_shim "
<< "is not available in this build.";
return Mode::kNone;
#endif
}
// static
ProfilingProcessHost* ProfilingProcessHost::Start(
content::ServiceManagerConnection* connection,
Mode mode) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
CHECK(!has_started_);
has_started_ = true;
ProfilingProcessHost* host = GetInstance();
host->SetMode(mode);
host->Register();
host->MakeConnector(connection);
host->LaunchAsService();
host->ConfigureBackgroundProfilingTriggers();
return host;
}
// static
ProfilingProcessHost* ProfilingProcessHost::GetInstance() {
return base::Singleton<
ProfilingProcessHost,
base::LeakySingletonTraits<ProfilingProcessHost>>::get();
}
void ProfilingProcessHost::ConfigureBackgroundProfilingTriggers() {
background_triggers_.StartTimer();
}
void ProfilingProcessHost::RequestProcessDump(base::ProcessId pid,
const base::FilePath& dest,
base::OnceClosure done) {
if (!connector_) {
DLOG(ERROR)
<< "Requesting process dump when profiling process hasn't started.";
return;
}
const bool kNoUpload = false;
base::PostTaskWithTraits(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&ProfilingProcessHost::GetOutputFileOnBlockingThread,
base::Unretained(this), pid, dest, kNoTriggerName,
kNoUpload, std::move(done)));
}
void ProfilingProcessHost::RequestProcessReport(base::ProcessId pid,
std::string trigger_name) {
if (!connector_) {
DLOG(ERROR)
<< "Requesting process dump when profiling process hasn't started.";
return;
}
base::FilePath output_path;
if (!CreateTemporaryFile(&output_path)) {
DLOG(ERROR) << "Cannot create temporary file for memory dump.";
return;
}
const bool kUpload = true;
base::PostTaskWithTraits(
FROM_HERE, {base::TaskPriority::BACKGROUND, base::MayBlock()},
base::BindOnce(&ProfilingProcessHost::GetOutputFileOnBlockingThread,
base::Unretained(this), pid, std::move(output_path),
std::move(trigger_name), kUpload, base::OnceClosure()));
}
void ProfilingProcessHost::MakeConnector(
content::ServiceManagerConnection* connection) {
connector_ = connection->GetConnector()->Clone();
}
void ProfilingProcessHost::LaunchAsService() {
// May get called on different threads, we need to be on the IO thread to
// work.
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(FROM_HERE,
base::BindOnce(&ProfilingProcessHost::LaunchAsService,
base::Unretained(this)));
return;
}
// No need to unregister since ProfilingProcessHost is never destroyed.
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "OutOfProcessHeapProfilingDumpProvider",
content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO));
// Bind to the memlog service. This will start it if it hasn't started
// already.
connector_->BindInterface(mojom::kServiceName, &memlog_);
// Tell the current process to start profiling.
profiling::mojom::MemlogClientPtr memlog_client;
profiling::mojom::MemlogClientRequest request =
mojo::MakeRequest(&memlog_client);
connector_->BindInterface(content::mojom::kBrowserServiceName,
mojo::MakeRequest(&memlog_client));
base::ProcessId pid = base::Process::Current().Pid();
SendPipeToProfilingService(std::move(memlog_client), pid);
}
void ProfilingProcessHost::GetOutputFileOnBlockingThread(
base::ProcessId pid,
const base::FilePath& dest,
std::string trigger_name,
bool upload,
base::OnceClosure done) {
base::File file(dest,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::BindOnce(&ProfilingProcessHost::HandleDumpProcessOnIOThread,
base::Unretained(this), pid, std::move(dest),
std::move(file), std::move(trigger_name), upload,
std::move(done)));
}
void ProfilingProcessHost::HandleDumpProcessOnIOThread(base::ProcessId pid,
base::FilePath file_path,
base::File file,
std::string trigger_name,
bool upload,
base::OnceClosure done) {
mojo::ScopedHandle handle = mojo::WrapPlatformFile(file.TakePlatformFile());
memlog_->DumpProcess(
pid, std::move(handle), GetMetadataJSONForTrace(),
base::BindOnce(&ProfilingProcessHost::OnProcessDumpComplete,
base::Unretained(this), std::move(file_path),
std::move(trigger_name), upload, std::move(done)));
}
void ProfilingProcessHost::OnProcessDumpComplete(base::FilePath file_path,
std::string trigger_name,
bool upload,
base::OnceClosure done,
bool success) {
base::ScopedClosureRunner done_runner(std::move(done));
if (!success) {
DLOG(ERROR) << "Cannot dump process.";
// On any errors, the requested trace output file is deleted.
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(base::IgnoreResult(&base::DeleteFile), file_path,
false));
return;
}
if (upload) {
UploadTraceToCrashServer(file_path, trigger_name);
// Uploaded file is a temporary file and must be deleted.
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(base::IgnoreResult(&base::DeleteFile), file_path,
false));
}
}
void ProfilingProcessHost::SetMode(Mode mode) {
mode_ = mode;
}
std::unique_ptr<base::DictionaryValue>
ProfilingProcessHost::GetMetadataJSONForTrace() {
std::unique_ptr<base::DictionaryValue> metadata_dict(
new base::DictionaryValue);
metadata_dict->SetKey(
"product-version",
base::Value(version_info::GetProductNameAndVersionForUserAgent()));
metadata_dict->SetKey("user-agent", base::Value(GetUserAgent()));
metadata_dict->SetKey("os-name",
base::Value(base::SysInfo::OperatingSystemName()));
metadata_dict->SetKey(
"command_line",
base::Value(
base::CommandLine::ForCurrentProcess()->GetCommandLineString()));
metadata_dict->SetKey(
"os-arch", base::Value(base::SysInfo::OperatingSystemArchitecture()));
return metadata_dict;
}
} // namespace profiling