blob: 2ba3912a354d255c6c4fc4fbae6cfd72424aff72 [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 "chromecast/tracing/system_tracer.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/message_loop/message_pump_libevent.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "base/trace_event/trace_config.h"
#include "chromecast/chromecast_buildflags.h"
#include "chromecast/tracing/system_tracing_common.h"
namespace chromecast {
namespace {
constexpr size_t kBufferSize = 1UL << 16;
base::ScopedFD CreateClientSocket() {
base::ScopedFD socket_fd(
socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
if (!socket_fd.is_valid()) {
PLOG(ERROR) << "socket";
return base::ScopedFD();
}
struct sockaddr_un addr =
chromecast::tracing::GetSystemTracingSocketAddress();
if (connect(socket_fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
sizeof(addr))) {
PLOG(ERROR) << "connect: " << addr.sun_path;
return base::ScopedFD();
}
return socket_fd;
}
class SystemTracerImpl : public SystemTracer {
public:
SystemTracerImpl() : buffer_(new char[kBufferSize]) {}
~SystemTracerImpl() override { Cleanup(); }
void StartTracing(base::StringPiece categories,
StartTracingCallback callback) override;
void StopTracing(const StopTracingCallback& callback) override;
private:
enum class State {
INITIAL, // Not yet started.
STARTING, // Sent start message, waiting for ack.
TRACING, // Tracing, not yet requested stop.
READING, // Trace stopped, reading output.
FINISHED, // All done.
};
void ReceiveStartAckAndTracePipe();
void ReceiveTraceData();
void FailStartTracing();
void FailStopTracing();
void SendPartialTraceData();
void FinishTracing();
void Cleanup();
// Current state of tracing attempt.
State state_ = State::INITIAL;
// Unix socket connection to tracing daemon.
base::ScopedFD connection_fd_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> connection_watcher_;
// Pipe for trace data.
base::ScopedFD trace_pipe_fd_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> trace_pipe_watcher_;
// Read buffer (of size kBufferSize).
std::unique_ptr<char[]> buffer_;
// Callbacks for StartTracing() and StopTracing().
StartTracingCallback start_tracing_callback_;
StopTracingCallback stop_tracing_callback_;
// Trace data.
std::string trace_data_;
};
void SystemTracerImpl::StartTracing(base::StringPiece categories,
StartTracingCallback callback) {
start_tracing_callback_ = std::move(callback);
if (state_ != State::INITIAL) {
FailStartTracing();
return;
}
if (categories.size() == 0) {
// No relevant categories are enabled.
FailStartTracing();
return;
}
connection_fd_ = CreateClientSocket();
if (!connection_fd_.is_valid()) {
FailStartTracing();
return;
}
if (!base::UnixDomainSocket::SendMsg(connection_fd_.get(), categories.data(),
categories.size(), std::vector<int>())) {
PLOG(ERROR) << "sendmsg";
FailStartTracing();
return;
}
connection_watcher_ = base::FileDescriptorWatcher::WatchReadable(
connection_fd_.get(),
base::BindRepeating(&SystemTracerImpl::ReceiveStartAckAndTracePipe,
base::Unretained(this)));
state_ = State::STARTING;
}
void SystemTracerImpl::StopTracing(const StopTracingCallback& callback) {
stop_tracing_callback_ = callback;
if (state_ != State::TRACING) {
FailStopTracing();
return;
}
char stop_tracing_message[] = {0};
if (!base::UnixDomainSocket::SendMsg(
connection_fd_.get(), stop_tracing_message,
sizeof(stop_tracing_message), std::vector<int>())) {
PLOG(ERROR) << "sendmsg";
FailStopTracing();
return;
}
trace_pipe_watcher_ = base::FileDescriptorWatcher::WatchReadable(
trace_pipe_fd_.get(),
base::BindRepeating(&SystemTracerImpl::ReceiveTraceData,
base::Unretained(this)));
state_ = State::READING;
}
void SystemTracerImpl::ReceiveStartAckAndTracePipe() {
DCHECK_EQ(state_, State::STARTING);
std::vector<base::ScopedFD> fds;
ssize_t received = base::UnixDomainSocket::RecvMsg(
connection_fd_.get(), buffer_.get(), kBufferSize, &fds);
if (received == 0) {
LOG(ERROR) << "EOF from server";
FailStartTracing();
return;
}
if (received < 0) {
PLOG(ERROR) << "recvmsg";
FailStartTracing();
return;
}
if (fds.size() != 1) {
LOG(ERROR) << "Start ack missing trace pipe";
FailStartTracing();
return;
}
trace_pipe_fd_ = std::move(fds[0]);
connection_watcher_.reset();
state_ = State::TRACING;
std::move(start_tracing_callback_).Run(Status::OK);
}
void SystemTracerImpl::ReceiveTraceData() {
DCHECK_EQ(state_, State::READING);
for (;;) {
ssize_t bytes =
HANDLE_EINTR(read(trace_pipe_fd_.get(), buffer_.get(), kBufferSize));
if (bytes < 0) {
if (errno == EAGAIN)
return; // Wait for more data.
PLOG(ERROR) << "read: trace";
FailStopTracing();
return;
}
if (bytes == 0) {
FinishTracing();
return;
}
trace_data_.append(buffer_.get(), bytes);
static constexpr size_t kPartialTraceDataSize = 1UL << 20; // 1 MiB
if (trace_data_.size() > kPartialTraceDataSize) {
SendPartialTraceData();
return;
}
}
}
void SystemTracerImpl::FailStartTracing() {
std::move(start_tracing_callback_).Run(Status::FAIL);
Cleanup();
}
void SystemTracerImpl::FailStopTracing() {
stop_tracing_callback_.Run(Status::FAIL, "");
Cleanup();
}
void SystemTracerImpl::SendPartialTraceData() {
DCHECK_EQ(state_, State::READING);
stop_tracing_callback_.Run(Status::KEEP_GOING, std::move(trace_data_));
trace_data_ = "";
}
void SystemTracerImpl::FinishTracing() {
DCHECK_EQ(state_, State::READING);
stop_tracing_callback_.Run(Status::OK, std::move(trace_data_));
Cleanup();
}
void SystemTracerImpl::Cleanup() {
connection_watcher_.reset();
connection_fd_.reset();
trace_pipe_watcher_.reset();
trace_pipe_fd_.reset();
start_tracing_callback_.Reset();
stop_tracing_callback_.Reset();
state_ = State::FINISHED;
}
class FakeSystemTracer : public SystemTracer {
public:
FakeSystemTracer() = default;
~FakeSystemTracer() override = default;
void StartTracing(base::StringPiece categories,
StartTracingCallback callback) override {
std::move(callback).Run(Status::OK);
}
void StopTracing(const StopTracingCallback& callback) override {
std::string trace_data = "# tracer: nop\n";
std::move(callback).Run(Status::OK, std::move(trace_data));
}
};
} // namespace
// static
std::unique_ptr<SystemTracer> SystemTracer::Create() {
#if BUILDFLAG(IS_CAST_DESKTOP_BUILD)
return std::make_unique<FakeSystemTracer>();
#else
return std::make_unique<SystemTracerImpl>();
#endif
}
} // namespace chromecast