| // 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/allocator/features.h" |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/process/process_iterator.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "base/threading/thread_task_runner_handle.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/profiles/profile.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/memlog_sender_pipe.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_child_process_host_iterator.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/browser/tracing_controller.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" |
| #include "third_party/zlib/zlib.h" |
| |
| #if defined(OS_WIN) |
| #include <io.h> |
| #endif |
| |
| 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_; |
| }; |
| |
| base::trace_event::TraceConfig GetBackgroundTracingConfig() { |
| // Disable all categories other than memory-infra. |
| base::trace_event::TraceConfig trace_config( |
| "-*,disabled-by-default-memory-infra", |
| base::trace_event::RECORD_UNTIL_FULL); |
| |
| // This flag is set by background tracing to filter out undesired events. |
| trace_config.EnableArgumentFilter(); |
| |
| // Trigger a background memory dump exactly once by setting a time-delta |
| // between dumps of 2**29. |
| base::trace_event::TraceConfig::MemoryDumpConfig memory_config; |
| memory_config.allowed_dump_modes.insert( |
| base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND); |
| base::trace_event::TraceConfig::MemoryDumpConfig::Trigger trigger; |
| trigger.min_time_between_dumps_ms = 1 << 30; |
| trigger.level_of_detail = |
| base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND; |
| trigger.trigger_type = base::trace_event::MemoryDumpType::PERIODIC_INTERVAL; |
| memory_config.triggers.clear(); |
| memory_config.triggers.push_back(trigger); |
| trace_config.ResetMemoryDumpConfig(memory_config); |
| return trace_config; |
| } |
| |
| } // namespace |
| |
| namespace profiling { |
| |
| const base::Feature kOOPHeapProfilingFeature{"OOPHeapProfiling", |
| base::FEATURE_DISABLED_BY_DEFAULT}; |
| const char kOOPHeapProfilingFeatureMode[] = "mode"; |
| |
| bool ProfilingProcessHost::has_started_ = false; |
| |
| namespace { |
| |
| // This helper class cleans up initialization boilerplate for the callers who |
| // need to create ProfilingClients bound to various different things. |
| class ProfilingClientBinder { |
| public: |
| // Binds to a non-renderer-child-process' ProfilingClient. |
| explicit ProfilingClientBinder(content::BrowserChildProcessHost* host) |
| : ProfilingClientBinder() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| content::BindInterface(host->GetHost(), std::move(request_)); |
| } |
| |
| // Binds to a renderer's ProfilingClient. |
| explicit ProfilingClientBinder(content::RenderProcessHost* host) |
| : ProfilingClientBinder() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| content::BindInterface(host, std::move(request_)); |
| } |
| |
| // Binds to the local connector to get the browser process' ProfilingClient. |
| explicit ProfilingClientBinder(service_manager::Connector* connector) |
| : ProfilingClientBinder() { |
| connector->BindInterface(content::mojom::kBrowserServiceName, |
| std::move(request_)); |
| } |
| |
| mojom::ProfilingClientPtr take() { return std::move(memlog_client_); } |
| |
| private: |
| ProfilingClientBinder() |
| : memlog_client_(), request_(mojo::MakeRequest(&memlog_client_)) {} |
| |
| mojom::ProfilingClientPtr memlog_client_; |
| mojom::ProfilingClientRequest request_; |
| }; |
| |
| void OnTraceUploadComplete(TraceCrashServiceUploader* uploader, |
| bool success, |
| const std::string& feedback) { |
| 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 file_contents, |
| std::string trigger_name) { |
| // 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("OutOfProcessHeapProfiling.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))); |
| 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::COMPRESSED_UPLOAD, |
| std::move(metadata), |
| content::TraceUploader::UploadProgressCallback(), |
| base::Bind(&OnTraceUploadComplete, base::Owned(uploader))); |
| } |
| |
| } // namespace |
| |
| ProfilingProcessHost::ProfilingProcessHost() |
| : is_registered_(false), |
| background_triggers_(this), |
| mode_(Mode::kNone), |
| always_sample_for_tests_(false) {} |
| |
| 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()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| is_registered_ = true; |
| } |
| |
| void ProfilingProcessHost::Unregister() { |
| DCHECK(is_registered_); |
| Remove(this); |
| } |
| |
| void ProfilingProcessHost::BrowserChildProcessLaunchedAndConnected( |
| const content::ChildProcessData& data) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| // Ensure this is only called for all non-renderer browser child processes |
| // so as not to collide with logic in ProfilingProcessHost::Observe(). |
| DCHECK_NE(data.process_type, content::ProcessType::PROCESS_TYPE_RENDERER); |
| |
| if (!ShouldProfileProcessType(data.process_type)) { |
| return; |
| } |
| |
| StartProfilingNonRendererChild(data); |
| } |
| |
| void ProfilingProcessHost::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| content::RenderProcessHost* host = |
| content::Source<content::RenderProcessHost>(source).ptr(); |
| |
| // NOTIFICATION_RENDERER_PROCESS_CLOSED corresponds to death of an underlying |
| // RenderProcess. NOTIFICATION_RENDERER_PROCESS_TERMINATED corresponds to when |
| // the RenderProcessHost's lifetime is ending. Ideally, we'd only listen to |
| // the former, but if the RenderProcessHost is destroyed before the |
| // RenderProcess, then the former is never sent. |
| if ((type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED || |
| type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED)) { |
| profiled_renderers_.erase(host); |
| } |
| |
| if (type == content::NOTIFICATION_RENDERER_PROCESS_CREATED && |
| ShouldProfileNewRenderer(host)) { |
| StartProfilingRenderer(host); |
| } |
| } |
| |
| bool ProfilingProcessHost::OnMemoryDump( |
| const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) { |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO) |
| ->PostTask(FROM_HERE, |
| base::BindOnce(&ProfilingProcessHost::OnMemoryDumpOnIOThread, |
| base::Unretained(this), args.dump_guid)); |
| return true; |
| } |
| |
| void ProfilingProcessHost::OnMemoryDumpOnIOThread(uint64_t dump_guid) { |
| bool force_prune = TakingTraceForUpload(); |
| const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); |
| bool keep_small_allocations = |
| !force_prune && cmdline->HasSwitch(switches::kMemlogKeepSmallAllocations); |
| |
| profiling_service_->DumpProcessesForTracing( |
| keep_small_allocations, |
| base::BindOnce(&ProfilingProcessHost::OnDumpProcessesForTracingCallback, |
| base::Unretained(this), dump_guid)); |
| } |
| |
| void ProfilingProcessHost::OnDumpProcessesForTracingCallback( |
| uint64_t guid, |
| std::vector<profiling::mojom::SharedBufferWithSizePtr> buffers) { |
| for (auto& buffer_ptr : buffers) { |
| mojo::ScopedSharedBufferHandle& buffer = buffer_ptr->buffer; |
| uint32_t size = buffer_ptr->size; |
| |
| if (!buffer->is_valid()) |
| 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))); |
| |
| // Using the same id merges all of the heap dumps into a single detailed |
| // dump node in the UI. |
| TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_PROCESS_ID( |
| TRACE_EVENT_PHASE_MEMORY_DUMP, |
| base::trace_event::TraceLog::GetCategoryGroupEnabled( |
| base::trace_event::MemoryDumpManager::kTraceCategory), |
| "periodic_interval", trace_event_internal::kGlobalScope, guid, |
| buffer_ptr->pid, kTraceEventNumArgs, kTraceEventArgNames, |
| kTraceEventArgTypes, nullptr /* arg_values */, &wrapper, |
| TRACE_EVENT_FLAG_HAS_ID); |
| } |
| |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, |
| base::Bind(&ProfilingProcessHost::DumpProcessFinishedUIThread, |
| base::Unretained(this))); |
| } |
| |
| void ProfilingProcessHost::DumpProcessFinishedUIThread() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| heap_dump_has_been_added_ = true; |
| if (minimum_time_has_elapsed_ && !finish_tracing_callback_.is_null()) |
| std::move(finish_tracing_callback_).Run(); |
| } |
| |
| void ProfilingProcessHost::OnMinimumTimeHasElapsed() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| minimum_time_has_elapsed_ = true; |
| if (heap_dump_has_been_added_ && !finish_tracing_callback_.is_null()) |
| std::move(finish_tracing_callback_).Run(); |
| } |
| |
| void ProfilingProcessHost::AddClientToProfilingService( |
| profiling::mojom::ProfilingClientPtr client, |
| base::ProcessId pid, |
| profiling::mojom::ProcessType process_type) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| |
| MemlogSenderPipe::PipePair pipes; |
| |
| // Passes the client_for_profiling directly to the profiling process. |
| // The client process can not start sending data until the pipe is ready, |
| // so talking to the client is done in the AddSender completion callback. |
| // |
| // This code doesn't actually hang onto the client_for_browser interface |
| // poiner beyond sending this message to start since there are no other |
| // messages we need to send. |
| profiling_service_->AddProfilingClient( |
| pid, std::move(client), |
| mojo::WrapPlatformFile(pipes.PassSender().release().handle), |
| mojo::WrapPlatformFile(pipes.PassReceiver().release().handle), |
| process_type); |
| } |
| |
| // static |
| ProfilingProcessHost::Mode ProfilingProcessHost::GetCurrentMode() { |
| const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); |
| #if BUILDFLAG(USE_ALLOCATOR_SHIM) |
| if (cmdline->HasSwitch(switches::kMemlog) || |
| base::FeatureList::IsEnabled(kOOPHeapProfilingFeature)) { |
| 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; |
| // Respect the commandline switch above the field trial. |
| if (cmdline->HasSwitch(switches::kMemlog)) { |
| mode = cmdline->GetSwitchValueASCII(switches::kMemlog); |
| } else { |
| mode = base::GetFieldTrialParamValueByFeature( |
| kOOPHeapProfilingFeature, kOOPHeapProfilingFeatureMode); |
| } |
| |
| return ConvertStringToMode(mode); |
| } |
| 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::Mode ProfilingProcessHost::ConvertStringToMode( |
| const std::string& mode) { |
| if (mode == switches::kMemlogModeAll) |
| return Mode::kAll; |
| if (mode == switches::kMemlogModeMinimal) |
| return Mode::kMinimal; |
| if (mode == switches::kMemlogModeBrowser) |
| return Mode::kBrowser; |
| if (mode == switches::kMemlogModeGpu) |
| return Mode::kGpu; |
| if (mode == switches::kMemlogModeRendererSampling) |
| return Mode::kRendererSampling; |
| DLOG(ERROR) << "Unsupported value: \"" << mode << "\" passed to --" |
| << switches::kMemlog; |
| return Mode::kNone; |
| } |
| |
| // 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(); |
| host->metrics_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromHours(24), |
| base::Bind(&ProfilingProcessHost::ReportMetrics, base::Unretained(host))); |
| |
| return host; |
| } |
| |
| // static |
| ProfilingProcessHost* ProfilingProcessHost::GetInstance() { |
| return base::Singleton< |
| ProfilingProcessHost, |
| base::LeakySingletonTraits<ProfilingProcessHost>>::get(); |
| } |
| |
| void ProfilingProcessHost::ConfigureBackgroundProfilingTriggers() { |
| background_triggers_.StartTimer(); |
| } |
| |
| void ProfilingProcessHost::SaveTraceWithHeapDumpToFile( |
| base::FilePath dest, |
| SaveTraceFinishedCallback done, |
| bool stop_immediately_after_heap_dump_for_tests) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| if (!profiling_service_.is_bound()) { |
| DLOG(ERROR) |
| << "Requesting heap dump when profiling process hasn't started."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(done), false)); |
| return; |
| } |
| |
| auto finish_trace_callback = base::BindOnce( |
| [](base::FilePath dest, SaveTraceFinishedCallback done, bool success, |
| std::string trace) { |
| if (!success) { |
| content::BrowserThread::GetTaskRunnerForThread( |
| content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, base::BindOnce(std::move(done), false)); |
| return; |
| } |
| base::PostTaskWithTraits( |
| FROM_HERE, {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)); |
| RequestTraceWithHeapDump(std::move(finish_trace_callback), |
| stop_immediately_after_heap_dump_for_tests); |
| } |
| |
| 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)); |
| if (!connector_) { |
| DLOG(ERROR) |
| << "Requesting process dump when profiling process hasn't started."; |
| return; |
| } |
| |
| // If taking_trace_for_upload_ is already true, then the setter has no effect, |
| // and we should early return. |
| if (SetTakingTraceForUpload(true)) |
| return; |
| |
| // 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( |
| [](ProfilingProcessHost* host, std::string trigger_name, bool success, |
| std::string trace) { |
| UMA_HISTOGRAM_BOOLEAN("OutOfProcessHeapProfiling.RecordTrace.Success", |
| success); |
| host->SetTakingTraceForUpload(false); |
| if (success) { |
| UploadTraceToCrashServer(std::move(trace), std::move(trigger_name)); |
| } |
| }, |
| base::Unretained(this), std::move(trigger_name)); |
| RequestTraceWithHeapDump(std::move(finish_report_callback), false); |
| } |
| |
| void ProfilingProcessHost::RequestTraceWithHeapDump( |
| TraceFinishedCallback callback, |
| bool stop_immediately_after_heap_dump_for_tests) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| if (!connector_) { |
| DLOG(ERROR) |
| << "Requesting heap dump when profiling process hasn't started."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), false, std::string())); |
| return; |
| } |
| |
| bool result = content::TracingController::GetInstance()->StartTracing( |
| GetBackgroundTracingConfig(), base::Closure()); |
| if (!result) { |
| DLOG(ERROR) << "Requesting heap dump when tracing has already started."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), false, std::string())); |
| return; |
| } |
| |
| // Once the trace has stopped, run |callback| on the UI thread. At this point, |
| // ownership of |callback| has been transfered to |finish_sink_callback|. |
| auto finish_sink_callback = base::Bind( |
| [](TraceFinishedCallback callback, |
| std::unique_ptr<const base::DictionaryValue> metadata, |
| base::RefCountedString* in) { |
| std::string result; |
| result.swap(in->data()); |
| content::BrowserThread::GetTaskRunnerForThread( |
| content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), true, |
| std::move(result))); |
| }, |
| base::Passed(std::move(callback))); |
| |
| scoped_refptr<content::TracingController::TraceDataEndpoint> sink = |
| content::TracingController::CreateStringEndpoint( |
| std::move(finish_sink_callback)); |
| base::OnceClosure stop_tracing_closure = base::BindOnce( |
| base::IgnoreResult<bool (content::TracingController::*)( // NOLINT |
| const scoped_refptr<content::TracingController::TraceDataEndpoint>&)>( |
| &content::TracingController::StopTracing), |
| base::Unretained(content::TracingController::GetInstance()), sink); |
| |
| // There is no race condition between setting |
| // |finish_tracing_callback_| and starting tracing, since |
| // the callback is only ever accessed on the UI thread. |
| DCHECK(!finish_tracing_callback_); |
| finish_tracing_callback_ = std::move(stop_tracing_closure); |
| |
| heap_dump_has_been_added_ = false; |
| if (stop_immediately_after_heap_dump_for_tests) { |
| minimum_time_has_elapsed_ = true; |
| } else { |
| minimum_time_has_elapsed_ = false; |
| |
| // Give the MDPs 10 seconds to emit their memory dumps to the trace. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ProfilingProcessHost::OnMinimumTimeHasElapsed, |
| base::Unretained(this)), |
| base::TimeDelta::FromSeconds(10)); |
| } |
| } |
| |
| void ProfilingProcessHost::GetProfiledPids(GetProfiledPidsCallback callback) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ProfilingProcessHost::GetProfiledPidsOnIOThread, |
| base::Unretained(this), std::move(callback))); |
| } |
| |
| void ProfilingProcessHost::GetProfiledPidsOnIOThread( |
| GetProfiledPidsCallback callback) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| if (!profiling_service_.is_bound()) { |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), |
| std::vector<base::ProcessId>())); |
| return; |
| } |
| |
| auto post_result_to_ui_thread = base::BindOnce( |
| [](GetProfiledPidsCallback callback, |
| const std::vector<base::ProcessId>& result) { |
| content::BrowserThread::GetTaskRunnerForThread( |
| content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result)); |
| }, |
| std::move(callback)); |
| profiling_service_->GetProfiledPids(std::move(post_result_to_ui_thread)); |
| } |
| |
| void ProfilingProcessHost::StartProfiling(base::ProcessId pid) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| // The RenderProcessHost iterator must be used on the UI thread. |
| // The BrowserChildProcessHostIterator iterator must be used on the IO thread. |
| for (auto iter = content::RenderProcessHost::AllHostsIterator(); |
| !iter.IsAtEnd(); iter.Advance()) { |
| if (pid == base::GetProcId(iter.GetCurrentValue()->GetHandle())) { |
| StartProfilingRenderer(iter.GetCurrentValue()); |
| return; |
| } |
| } |
| |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ProfilingProcessHost::StartProfilingPidOnIOThread, |
| base::Unretained(this), pid)); |
| } |
| |
| void ProfilingProcessHost::StartProfilingPidOnIOThread(base::ProcessId pid) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| |
| // Check if the request is for the current process. |
| if (pid == base::GetCurrentProcId()) { |
| ProfilingClientBinder client(connector_.get()); |
| AddClientToProfilingService(client.take(), base::Process::Current().Pid(), |
| profiling::mojom::ProcessType::BROWSER); |
| return; |
| } |
| |
| for (content::BrowserChildProcessHostIterator browser_child_iter; |
| !browser_child_iter.Done(); ++browser_child_iter) { |
| const content::ChildProcessData& data = browser_child_iter.GetData(); |
| if (base::GetProcId(data.handle) == pid) { |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, |
| base::BindOnce( |
| &ProfilingProcessHost::StartProfilingNonRendererChild, |
| base::Unretained(this), data)); |
| return; |
| } |
| } |
| |
| DLOG(WARNING) |
| << "Attempt to start profiling failed as no process was found with pid: " |
| << pid; |
| } |
| |
| 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, &profiling_service_); |
| |
| // Start profiling the browser if the mode allows. |
| if (ShouldProfileProcessType(content::ProcessType::PROCESS_TYPE_BROWSER)) { |
| ProfilingClientBinder client(connector_.get()); |
| AddClientToProfilingService(client.take(), base::Process::Current().Pid(), |
| profiling::mojom::ProcessType::BROWSER); |
| } |
| } |
| |
| 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"; |
| content::BrowserThread::GetTaskRunnerForThread(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); |
| |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI) |
| ->PostTask(FROM_HERE, base::BindOnce(std::move(done), |
| written_bytes == trace.size())); |
| } |
| |
| void ProfilingProcessHost::SetMode(Mode mode) { |
| mode_ = mode; |
| } |
| |
| void ProfilingProcessHost::ReportMetrics() { |
| UMA_HISTOGRAM_ENUMERATION("OutOfProcessHeapProfiling.ProfilingMode", mode(), |
| Mode::kCount); |
| } |
| |
| bool ProfilingProcessHost::ShouldProfileProcessType(int process_type) { |
| switch (mode()) { |
| case Mode::kAll: |
| return true; |
| |
| case Mode::kMinimal: |
| return (process_type == content::ProcessType::PROCESS_TYPE_GPU || |
| process_type == content::ProcessType::PROCESS_TYPE_BROWSER); |
| |
| case Mode::kGpu: |
| return process_type == content::ProcessType::PROCESS_TYPE_GPU; |
| |
| case Mode::kBrowser: |
| return process_type == content::ProcessType::PROCESS_TYPE_BROWSER; |
| |
| case Mode::kRendererSampling: |
| // This seems odd because a renderer does get profiled. However, since |
| // the general rule for the whole type is to not profile in this mode, |
| // returning false is appropriate. kRendererSampling has special case |
| // logic elsewhere to enable rendering specifically chosed renderers. |
| return false; |
| |
| case Mode::kNone: |
| return false; |
| |
| case Mode::kCount: |
| // Fall through to hit NOTREACHED() below. |
| {} |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool ProfilingProcessHost::ShouldProfileNewRenderer( |
| content::RenderProcessHost* renderer) const { |
| // Never profile incognito processes. |
| if (Profile::FromBrowserContext(renderer->GetBrowserContext()) |
| ->GetProfileType() == Profile::INCOGNITO_PROFILE) { |
| return false; |
| } |
| |
| if (mode() == Mode::kAll) { |
| return true; |
| } else if (mode() == Mode::kRendererSampling && profiled_renderers_.empty()) { |
| if (always_sample_for_tests_) { |
| return true; |
| } |
| |
| // Sample renderers with a 1/3 probability. |
| return (base::RandUint64() % 100000) < 33333; |
| } |
| |
| return false; |
| } |
| |
| void ProfilingProcessHost::StartProfilingNonRendererChild( |
| const content::ChildProcessData& data) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| profiling::mojom::ProcessType process_type = |
| (data.process_type == content::ProcessType::PROCESS_TYPE_GPU) |
| ? profiling::mojom::ProcessType::GPU |
| : profiling::mojom::ProcessType::OTHER; |
| |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ProfilingProcessHost::StartProfilingNonRendererChildOnIOThread, |
| base::Unretained(this), data.id, base::GetProcId(data.handle), |
| process_type)); |
| } |
| |
| void ProfilingProcessHost::StartProfilingNonRendererChildOnIOThread( |
| int child_process_id, |
| base::ProcessId proc_id, |
| profiling::mojom::ProcessType process_type) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| |
| content::BrowserChildProcessHost* host = |
| content::BrowserChildProcessHost::FromID(child_process_id); |
| if (!host) |
| return; |
| |
| // Tell the child process to start profiling. |
| ProfilingClientBinder client(host); |
| AddClientToProfilingService(client.take(), proc_id, process_type); |
| } |
| |
| void ProfilingProcessHost::StartProfilingRenderer( |
| content::RenderProcessHost* host) { |
| profiled_renderers_.insert(host); |
| |
| // Tell the child process to start profiling. |
| ProfilingClientBinder client(host); |
| |
| content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ProfilingProcessHost::AddClientToProfilingService, |
| base::Unretained(this), client.take(), |
| base::GetProcId(host->GetHandle()), |
| profiling::mojom::ProcessType::RENDERER)); |
| } |
| |
| void ProfilingProcessHost::SetRendererSamplingAlwaysProfileForTest() { |
| always_sample_for_tests_ = true; |
| } |
| |
| bool ProfilingProcessHost::TakingTraceForUpload() { |
| base::AutoLock lock(taking_trace_for_upload_lock_); |
| return taking_trace_for_upload_; |
| } |
| |
| bool ProfilingProcessHost::SetTakingTraceForUpload(bool new_state) { |
| base::AutoLock lock(taking_trace_for_upload_lock_); |
| bool ret = taking_trace_for_upload_; |
| taking_trace_for_upload_ = new_state; |
| return ret; |
| } |
| |
| } // namespace profiling |