blob: efc1e4f19c4c22d3a815dc06d81feb0cd74e504d [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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/trace_event/trace_log.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 "components/heap_profiling/supervisor.h"
#include "components/services/heap_profiling/public/cpp/controller.h"
#include "components/services/heap_profiling/public/cpp/settings.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/zlib/zlib.h"
#if defined(OS_WIN)
#include <io.h>
#endif
namespace heap_profiling {
namespace {
// Slow-report trigger configuration.
// see: content/browser/tracing/background_tracing_config_impl.cc
const char kConfigsKey[] = "configs";
const char kConfigModeKey[] = "mode";
const char kConfigScenarioName[] = "scenario_name";
const char kConfigCategoryKey[] = "category";
const char kConfigCategoryMemlog[] = "MEMLOG";
const char kOOPHeapProfilingUploadUrl[] = "upload_url";
void OnTraceUploadComplete(TraceCrashServiceUploader* uploader,
bool success,
const std::string& feedback) {
UMA_HISTOGRAM_BOOLEAN("HeapProfiling.UploadTrace.Success", success);
if (!success) {
LOG(ERROR) << "Cannot upload trace file: " << feedback;
return;
}
// The reports is successfully sent. Reports the crash-id to ease debugging.
LOG(WARNING) << "slow-reports sent: '" << feedback << '"';
}
void UploadTraceToCrashServer(std::string upload_url,
std::string file_contents,
std::string trigger_name,
uint32_t sampling_rate) {
// Traces has been observed as small as 4k. Seems likely to be a bug. To
// account for all potentially too-small traces, we set the lower bounds to
// 512 bytes. The upper bounds is set to 300MB as an extra-high threshold,
// just in case something goes wrong.
UMA_HISTOGRAM_CUSTOM_COUNTS("HeapProfiling.UploadTrace.Size",
file_contents.size(), 512, 300 * 1024 * 1024, 50);
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)));
rules_list.GetList().push_back(std::move(rule));
std::string sampling_mode = base::StringPrintf("SAMPLING_%u", sampling_rate);
base::Value configs(base::Value::Type::DICTIONARY);
configs.SetKey(kConfigModeKey, base::Value(sampling_mode));
configs.SetKey(kConfigCategoryKey, base::Value(kConfigCategoryMemlog));
configs.SetKey(kConfigsKey, std::move(rules_list));
std::unique_ptr<base::DictionaryValue> metadata =
std::make_unique<base::DictionaryValue>();
metadata->SetKey("config", std::move(configs));
metadata->SetKey(kConfigScenarioName, base::Value("MEMLOG"));
TraceCrashServiceUploader* uploader = new TraceCrashServiceUploader(
g_browser_process->shared_url_loader_factory());
if (!upload_url.empty())
uploader->SetUploadURL(upload_url);
uploader->DoUpload(file_contents, content::TraceUploader::COMPRESSED_UPLOAD,
std::move(metadata),
content::TraceUploader::UploadProgressCallback(),
base::Bind(&OnTraceUploadComplete, base::Owned(uploader)));
}
} // namespace
ProfilingProcessHost::ProfilingProcessHost() : background_triggers_(this) {
const std::string upload_url = base::GetFieldTrialParamValueByFeature(
kOOPHeapProfilingFeature, kOOPHeapProfilingUploadUrl);
if (GURL(upload_url).is_valid())
upload_url_ = upload_url;
}
ProfilingProcessHost::~ProfilingProcessHost() = default;
void ProfilingProcessHost::Start() {
// Only enable automatic uploads when the Finch experiment is enabled.
// Developers can still manually upload via chrome://memory-internals.
if (IsBackgroundHeapProfilingEnabled())
background_triggers_.StartTimer();
metrics_timer_.Start(
FROM_HERE, base::TimeDelta::FromHours(24),
base::Bind(&ProfilingProcessHost::ReportMetrics, base::Unretained(this)));
}
// static
ProfilingProcessHost* ProfilingProcessHost::GetInstance() {
return base::Singleton<
ProfilingProcessHost,
base::LeakySingletonTraits<ProfilingProcessHost>>::get();
}
void ProfilingProcessHost::SaveTraceWithHeapDumpToFile(
base::FilePath dest,
SaveTraceFinishedCallback done,
bool stop_immediately_after_heap_dump_for_tests) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
auto finish_trace_callback = base::BindOnce(
[](base::FilePath dest, SaveTraceFinishedCallback done, bool success,
std::string trace) {
if (!success) {
base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})
->PostTask(FROM_HERE, base::BindOnce(std::move(done), false));
return;
}
base::PostTask(
FROM_HERE,
{base::ThreadPool(), base::TaskPriority::USER_VISIBLE,
base::MayBlock()},
base::BindOnce(
&ProfilingProcessHost::SaveTraceToFileOnBlockingThread,
base::Unretained(ProfilingProcessHost::GetInstance()),
std::move(dest), std::move(trace), std::move(done)));
},
std::move(dest), std::move(done));
Supervisor::GetInstance()->RequestTraceWithHeapDump(
std::move(finish_trace_callback), /*anonymize=*/false);
}
void ProfilingProcessHost::RequestProcessReport(std::string trigger_name) {
// https://crbug.com/753218: Add e2e tests for this code path.
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// It's safe to pass a raw pointer for ProfilingProcessHost because it's a
// singleton that's never destroyed.
auto finish_report_callback = base::BindOnce(
[](std::string upload_url, std::string trigger_name,
uint32_t sampling_rate, bool success, std::string trace) {
UMA_HISTOGRAM_BOOLEAN("HeapProfiling.RecordTrace.Success", success);
if (success) {
UploadTraceToCrashServer(std::move(upload_url), std::move(trace),
std::move(trigger_name), sampling_rate);
}
},
upload_url_, std::move(trigger_name),
Supervisor::GetInstance()->GetSamplingRate());
Supervisor::GetInstance()->RequestTraceWithHeapDump(
std::move(finish_report_callback), true /* anonymize */);
}
void ProfilingProcessHost::SaveTraceToFileOnBlockingThread(
base::FilePath dest,
std::string trace,
SaveTraceFinishedCallback done) {
base::File file(dest,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
// Pass ownership of the underlying fd/HANDLE to zlib.
base::PlatformFile platform_file = file.TakePlatformFile();
#if defined(OS_WIN)
// The underlying handle |platform_file| is also closed when |fd| is closed.
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(platform_file), 0);
#else
int fd = platform_file;
#endif
gzFile gz_file = gzdopen(fd, "w");
if (!gz_file) {
DLOG(ERROR) << "Cannot compress trace file";
base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})
->PostTask(FROM_HERE, base::BindOnce(std::move(done), false));
return;
}
size_t written_bytes = gzwrite(gz_file, trace.c_str(), trace.size());
gzclose(gz_file);
base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})
->PostTask(FROM_HERE, base::BindOnce(std::move(done),
written_bytes == trace.size()));
}
void ProfilingProcessHost::ReportMetrics() {
UMA_HISTOGRAM_ENUMERATION("HeapProfiling.ProfilingMode",
Supervisor::GetInstance()->GetMode(), Mode::kCount);
}
} // namespace heap_profiling