blob: d4e91a04677655c2d29348ec5191dfb60fcf8ff3 [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/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;
}
} // namespace
SystemTracer::SystemTracer() : buffer_(new char[kBufferSize]) {}
SystemTracer::~SystemTracer() {
Cleanup();
}
void SystemTracer::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(&SystemTracer::ReceiveStartAckAndTracePipe,
base::Unretained(this)));
state_ = State::STARTING;
}
void SystemTracer::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(&SystemTracer::ReceiveTraceData,
base::Unretained(this)));
state_ = State::READING;
}
void SystemTracer::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 SystemTracer::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 SystemTracer::FailStartTracing() {
std::move(start_tracing_callback_).Run(Status::FAIL);
Cleanup();
}
void SystemTracer::FailStopTracing() {
stop_tracing_callback_.Run(Status::FAIL, "");
Cleanup();
}
void SystemTracer::SendPartialTraceData() {
DCHECK_EQ(state_, State::READING);
stop_tracing_callback_.Run(Status::KEEP_GOING, std::move(trace_data_));
trace_data_ = "";
}
void SystemTracer::FinishTracing() {
DCHECK_EQ(state_, State::READING);
stop_tracing_callback_.Run(Status::OK, std::move(trace_data_));
Cleanup();
}
void SystemTracer::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;
}
} // namespace chromecast