blob: 68e9d5464f7d036d6b825d4f6870f1c0fd09b01d [file] [log] [blame]
// Copyright 2019 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/perfetto_file_tracer.h"
#include <utility>
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#include "components/tracing/common/trace_startup_config.h"
#include "components/tracing/common/tracing_switches.h"
#include "content/public/browser/tracing_service.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
#include "third_party/perfetto/protos/perfetto/config/trace_config.pb.h"
#if defined(OS_ANDROID)
#include "content/browser/android/tracing_controller_android.h"
#endif
namespace content {
namespace {
constexpr base::TimeDelta kReadBufferIntervalInSeconds =
base::TimeDelta::FromSeconds(1);
class BackgroundDrainer : public mojo::DataPipeDrainer::Client {
public:
BackgroundDrainer() {
base::FilePath output_file =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
switches::kPerfettoOutputFile);
#if defined(OS_ANDROID)
if (output_file.empty()) {
TracingControllerAndroid::GenerateTracingFilePath(&output_file);
VLOG(0) << "Writing to output file: " << output_file.value();
}
#endif
file_.Initialize(output_file,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file_.IsValid()) {
LOG(ERROR) << "Failed to open file: " << output_file;
}
}
void StartDrain(mojo::ScopedDataPipeConsumerHandle consumer_handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
drainer_.reset(new mojo::DataPipeDrainer(this, std::move(consumer_handle)));
}
private:
// mojo::DataPipeDrainer::Client
void OnDataAvailable(const void* data, size_t num_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!file_.IsValid()) {
return;
}
size_t written =
file_.WriteAtCurrentPos(static_cast<const char*>(data), num_bytes);
if (written != num_bytes) {
LOG(ERROR) << "Failed writing to trace output file: wrote " << written
<< " out of " << num_bytes << " bytes.";
}
}
// mojo::DataPipeDrainer::Client
void OnDataComplete() override {}
std::unique_ptr<mojo::DataPipeDrainer> drainer_;
base::File file_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(BackgroundDrainer);
};
} // namespace
// static
bool PerfettoFileTracer::ShouldEnable() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kPerfettoOutputFile);
}
// Use USER_VISIBLE priority for the drainer because BEST_EFFORT tasks are not
// run at startup and we want the trace file to be written soon.
PerfettoFileTracer::PerfettoFileTracer()
: background_drainer_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
GetTracingService().BindConsumerHost(
consumer_host_.BindNewPipeAndPassReceiver());
const auto& chrome_config =
tracing::TraceStartupConfig::GetInstance()->GetTraceConfig();
// The output is always proto based. So, enable privacy filtering if argument
// filter is enabled.
bool privacy_filtering = chrome_config.IsArgumentFilterEnabled();
perfetto::TraceConfig trace_config =
tracing::GetDefaultPerfettoConfig(chrome_config, privacy_filtering);
int duration_in_seconds =
tracing::TraceStartupConfig::GetInstance()->GetStartupDuration();
trace_config.set_duration_ms(duration_in_seconds * 1000);
// We just need a single global trace buffer, for our data.
trace_config.mutable_buffers()->front().set_size_kb(32 * 1024);
consumer_host_->EnableTracing(
tracing_session_host_.BindNewPipeAndPassReceiver(),
receiver_.BindNewPipeAndPassRemote(), std::move(trace_config),
tracing::mojom::TracingClientPriority::kBackground);
receiver_.set_disconnect_handler(base::BindOnce(
&PerfettoFileTracer::OnTracingSessionEnded, base::Unretained(this)));
ReadBuffers();
}
PerfettoFileTracer::~PerfettoFileTracer() = default;
void PerfettoFileTracer::OnTracingEnabled() {}
void PerfettoFileTracer::OnTracingDisabled() {
has_been_disabled_ = true;
}
void PerfettoFileTracer::OnNoMorePackets(bool queued_after_disable) {
DCHECK(consumer_host_);
// If this callback happens as the result of a ReadBuffers() call
// which was queued after tracing was disabled, we know there's
// going to be no more packets coming and we can clean up
// the |background_drainer_|.
if (queued_after_disable) {
consumer_host_.reset();
background_drainer_.Reset();
return;
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PerfettoFileTracer::ReadBuffers,
weak_factory_.GetWeakPtr()),
kReadBufferIntervalInSeconds);
}
void PerfettoFileTracer::ReadBuffers() {
DCHECK(consumer_host_);
mojo::DataPipe data_pipe;
tracing_session_host_->ReadBuffers(
std::move(data_pipe.producer_handle),
base::BindOnce(
[](PerfettoFileTracer* file_tracing, bool queued_after_disable) {
file_tracing->OnNoMorePackets(queued_after_disable);
},
base::Unretained(this), has_been_disabled_));
background_drainer_.Post(FROM_HERE, &BackgroundDrainer::StartDrain,
std::move(data_pipe.consumer_handle));
}
void PerfettoFileTracer::OnTracingSessionEnded() {
receiver_.reset();
tracing_session_host_.reset();
}
} // namespace content