Commit 0436dc8a authored by Michael Spang's avatar Michael Spang Committed by Commit Bot

chromecast: Add tracing agent to collect kernel traces (ftrace)

Add a tracing agent and matching service so that tracing on Cast devices
can collect kernel traces. The implementation is based on the CrOS
tracing agent and debugd service.

This is very barebones at the moment (service only depends on //base) to
keep the size down. We don't have dbus, although we might be able to use
mojo to manage the socket.

BUG=786091

Change-Id: I76157157506628004d812db3f452a486b2962845
Reviewed-on: https://chromium-review.googlesource.com/775557Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Reviewed-by: default avatardsinclair <dsinclair@chromium.org>
Reviewed-by: default avatarLuke Halliwell <halliwell@chromium.org>
Reviewed-by: default avatarAlex Sakhartchouk <alexst@chromium.org>
Commit-Queue: Michael Spang <spang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521036}
parent 5384c653
...@@ -34,6 +34,9 @@ group("all") { ...@@ -34,6 +34,9 @@ group("all") {
if (!is_android) { if (!is_android) {
deps += [ ":cast_shell" ] deps += [ ":cast_shell" ]
} }
if (is_linux) {
deps += [ "//chromecast/tracing" ]
}
if (chromecast_branding != "public") { if (chromecast_branding != "public") {
deps += [ "//chromecast/internal:all" ] deps += [ "//chromecast/internal:all" ]
} }
...@@ -514,7 +517,9 @@ foreach(locale, cast_locales) { ...@@ -514,7 +517,9 @@ foreach(locale, cast_locales) {
] ]
if (chromecast_branding != "public") { if (chromecast_branding != "public") {
sources += [ "$root_gen_dir/chromecast/internal/webui/app_strings_${locale}.pak" ] sources += [
"$root_gen_dir/chromecast/internal/webui/app_strings_${locale}.pak",
]
deps += [ "//chromecast/internal/webui:chromecast_app_strings" ] deps += [ "//chromecast/internal/webui:chromecast_app_strings" ]
if (enable_chromecast_webui) { if (enable_chromecast_webui) {
......
# 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.
import("//chromecast/chromecast.gni")
import("//testing/test.gni")
assert(!is_fuchsia)
cast_source_set("system_tracing_common") {
sources = [
"system_tracing_common.cc",
"system_tracing_common.h",
]
deps = [
"//base",
]
}
cast_executable("traced") {
sources = [
"ftrace.cc",
"ftrace.h",
"tracing_service_main.cc",
]
deps = [
":system_tracing_common",
"//base",
]
}
cast_source_set("system_tracer") {
sources = [
"system_tracer.cc",
"system_tracer.h",
]
public_deps = [
":system_tracing_common",
"//base",
]
}
group("tracing") {
deps = [
":system_tracing_common",
":traced",
]
}
spang@chromium.org
// 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/ftrace.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "base/files/file_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/trace_event/common/trace_event_common.h"
#include "chromecast/tracing/system_tracing_common.h"
namespace chromecast {
namespace tracing {
namespace {
const char kPathTracefs[] = "/sys/kernel/tracing";
const char kTraceFileTracingOn[] = "tracing_on";
const char kTraceFileTraceMarker[] = "trace_marker";
const char kTraceFileSetEvent[] = "set_event";
const char kTraceFileTraceClock[] = "trace_clock";
const char kTraceFileTrace[] = "trace";
const char kTraceFileBufferSizeKb[] = "buffer_size_kb";
const char kBufferSizeKbRunning[] = "7040";
const char kBufferSizeKbIdle[] = "1408";
// TODO(spang): Load these lists from a configuration file.
const char* const kGfxEvents[] = {
"i915:i915_flip_request", "i915:i915_flip_complete",
"i915:i915_gem_object_pwrite", "i915:intel_gpu_freq_change",
"exynos:exynos_flip_request", "exynos:exynos_flip_complete",
"exynos:exynos_page_flip_state", "drm:drm_vblank_event",
};
const char* const kInputEvents[] = {
"irq:irq_threaded_handler_entry", "irq:irq_threaded_handler_exit",
};
const char* const kIrqEvents[] = {
"irq:irq_handler_exit", "irq:irq_handler_entry",
};
const char* const kPowerEvents[] = {
"power:cpu_idle",
"power:cpu_frequency",
"mali:mali_dvfs_set_clock",
"mali:mali_dvfs_set_voltage",
"cpufreq_interactive:cpufreq_interactive_boost",
"cpufreq_interactive:cpufreq_interactive_unboost",
"exynos_busfreq:exynos_busfreq_target_int",
"exynos_busfreq:exynos_busfreq_target_mif",
};
const char* const kSchedEvents[] = {
"sched:sched_switch", "sched:sched_wakeup",
};
const char* const kWorkqEvents[] = {
"workqueue:workqueue_execute_start", "workqueue:workqueue_execute_end",
};
void AddCategoryEvents(const std::string& category,
std::vector<std::string>* events) {
if (category == "gfx") {
std::copy(kGfxEvents, kGfxEvents + arraysize(kGfxEvents),
std::back_inserter(*events));
return;
}
if (category == "input") {
std::copy(kInputEvents, kInputEvents + arraysize(kInputEvents),
std::back_inserter(*events));
return;
}
if (category == TRACE_DISABLED_BY_DEFAULT("irq")) {
std::copy(kIrqEvents, kIrqEvents + arraysize(kIrqEvents),
std::back_inserter(*events));
return;
}
if (category == "power") {
std::copy(kPowerEvents, kPowerEvents + arraysize(kPowerEvents),
std::back_inserter(*events));
return;
}
if (category == "sched") {
std::copy(kSchedEvents, kSchedEvents + arraysize(kSchedEvents),
std::back_inserter(*events));
return;
}
if (category == "workq") {
std::copy(kWorkqEvents, kWorkqEvents + arraysize(kWorkqEvents),
std::back_inserter(*events));
return;
}
LOG(WARNING) << "Unrecognized category: " << category;
}
bool WriteTracingFile(const char* trace_file, base::StringPiece contents) {
base::FilePath path = base::FilePath(kPathTracefs).Append(trace_file);
if (!base::WriteFile(path, contents.data(), contents.size())) {
PLOG(ERROR) << "write: " << path;
return false;
}
return true;
}
bool EnableTraceEvent(base::StringPiece event) {
base::FilePath path = base::FilePath(kPathTracefs).Append(kTraceFileSetEvent);
// Enabling events returns EINVAL if the event does not exist. It is normal
// for driver specific events to be missing when the driver is not built in.
if (!base::AppendToFile(path, event.data(), event.size()) &&
errno != EINVAL) {
PLOG(ERROR) << "write: " << path;
return false;
}
return true;
}
} // namespace
bool IsValidCategory(base::StringPiece str) {
for (size_t i = 0; i < kCategoryCount; ++i) {
base::StringPiece category(kCategories[i]);
if (category == str)
return true;
}
return false;
}
bool StartFtrace(const std::vector<std::string>& categories) {
if (categories.size() == 0) {
LOG(ERROR) << "No categories to enable";
return false;
}
std::vector<std::string> events;
for (const auto& category : categories)
AddCategoryEvents(category, &events);
if (events.size() == 0) {
LOG(ERROR) << "No events to enable";
return false;
}
// Disable tracing and clear events.
if (!WriteTracingFile(kTraceFileTracingOn, "0"))
return false;
if (!WriteTracingFile(kTraceFileSetEvent, "\n"))
return false;
// Use CLOCK_MONOTONIC so that kernel timestamps align with std::steady_clock
// and base::TimeTicks.
if (!WriteTracingFile(kTraceFileTraceClock, "mono"))
return false;
for (const auto& event : events)
EnableTraceEvent(event);
if (!WriteTracingFile(kTraceFileBufferSizeKb, kBufferSizeKbRunning))
return false;
if (!WriteTracingFile(kTraceFileTracingOn, "1"))
return false;
return true;
}
bool WriteFtraceTimeSyncMarker() {
return WriteTracingFile(kTraceFileTraceMarker,
"trace_event_clock_sync: parent_ts=0");
}
bool StopFtrace() {
if (!WriteTracingFile(kTraceFileTracingOn, "0"))
return false;
return true;
}
base::ScopedFD GetFtraceData() {
base::FilePath path = base::FilePath(kPathTracefs).Append(kTraceFileTrace);
base::ScopedFD trace_data(HANDLE_EINTR(
open(path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NONBLOCK)));
if (!trace_data.is_valid())
PLOG(ERROR) << "open: " << path.value();
return trace_data;
}
bool ClearFtrace() {
if (!WriteTracingFile(kTraceFileBufferSizeKb, kBufferSizeKbIdle))
return false;
if (!WriteTracingFile(kTraceFileTrace, "0"))
return false;
return true;
}
} // namespace tracing
} // namespace chromecast
// 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.
#ifndef CHROMECAST_TRACING_FTRACE_H_
#define CHROMECAST_TRACING_FTRACE_H_
#include <string>
#include <vector>
#include "base/files/scoped_file.h"
#include "base/strings/string_piece.h"
namespace chromecast {
namespace tracing {
// Returns true if |category| is valid for system tracing.
bool IsValidCategory(base::StringPiece category);
// Starts ftrace for the specified categories.
//
// This must be paired with StopFtrace() or the kernel will continue tracing
// indefinitely. Returns false if an error occurs writing to tracefs - this
// usually indicates a configuration issue (e.g. tracefs not mounted).
bool StartFtrace(const std::vector<std::string>& categories);
// Writes time synchronization marker.
//
// This is used by trace-viewer to align ftrace clock with userspace
// tracing. Since CLOCK_MONOTONIC is used in both cases, this merely
// writes a zero offset. Call it at the end of tracing right before
// StopFtrace(). Returns false if an error occurs writing to tracefs.
bool WriteFtraceTimeSyncMarker();
// Stops ftrace.
//
// This is safe to call even if tracing isn't started. Returns false if an error
// occurs writing to tracefs.
bool StopFtrace();
// Opens ftrace buffer for reading.
base::ScopedFD GetFtraceData();
// Clears ftrace buffer. Returns false if an error occurs writing to tracefs.
bool ClearFtrace();
} // namespace tracing
} // namespace chromecast
#endif // CHROMECAST_TRACING_FTRACE_H_
// 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_loop.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
// 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.
#ifndef CHROMECAST_TRACING_SYSTEM_TRACER_H_
#define CHROMECAST_TRACING_SYSTEM_TRACER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/message_loop/message_pump_libevent.h"
namespace chromecast {
class SystemTracer {
public:
SystemTracer();
~SystemTracer();
enum class Status {
OK,
KEEP_GOING,
FAIL,
};
using StartTracingCallback = base::OnceCallback<void(Status)>;
using StopTracingCallback =
base::RepeatingCallback<void(Status status, std::string trace_data)>;
// Start system tracing for categories in |categories| (comma separated).
void StartTracing(base::StringPiece categories,
StartTracingCallback callback);
// Stop system tracing.
//
// This will call |callback| on the current thread with the trace data. If
// |status| is Status::KEEP_GOING, another call will be made with additional
// data.
void StopTracing(const StopTracingCallback& callback);
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_;
DISALLOW_COPY_AND_ASSIGN(SystemTracer);
};
} // namespace chromecast
#endif // CHROMECAST_TRACING_SYSTEM_TRACER_H_
// 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_tracing_common.h"
#include <string.h>
#include "base/macros.h"
#include "base/trace_event/common/trace_event_common.h"
namespace chromecast {
namespace tracing {
namespace {
const char kSocketPath[] = "/dev/socket/tracing/tracing";
} // namespace
const char* const kCategories[] = {
"gfx", "input", TRACE_DISABLED_BY_DEFAULT("irq"),
"power", "sched", "workq"};
const size_t kCategoryCount = arraysize(kCategories);
sockaddr_un GetSystemTracingSocketAddress() {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
static_assert(sizeof(kSocketPath) <= sizeof(addr.sun_path),
"Address too long");
strncpy(addr.sun_path, kSocketPath, sizeof(addr.sun_path) - 1);
addr.sun_family = AF_UNIX;
return addr;
}
} // namespace tracing
} // namespace chromecast
// 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.
#ifndef CHROMECAST_TRACING_SYSTEM_TRACING_COMMON_H_
#define CHROMECAST_TRACING_SYSTEM_TRACING_COMMON_H_
#include <sys/socket.h>
#include <sys/un.h>
namespace chromecast {
namespace tracing {
extern const char* const kCategories[];
extern const size_t kCategoryCount;
sockaddr_un GetSystemTracingSocketAddress();
} // namespace tracing
} // namespace chromecast
#endif // CHROMECAST_TRACING_SYSTEM_TRACING_COMMON_H_
// 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 <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <memory>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chromecast/tracing/ftrace.h"
#include "chromecast/tracing/system_tracing_common.h"
namespace chromecast {
namespace tracing {
namespace {
constexpr size_t kMessageSize = 4096;
base::ScopedFD CreateServerSocket() {
struct sockaddr_un addr = GetSystemTracingSocketAddress();
if (unlink(addr.sun_path) != 0 && errno != ENOENT)
PLOG(ERROR) << "unlink: " << addr.sun_path;
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();
}
if (bind(socket_fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
sizeof(addr))) {
PLOG(ERROR) << "bind: " << addr.sun_path;
return base::ScopedFD();
}
if (chmod(addr.sun_path, 0666))
PLOG(WARNING) << "chmod: " << addr.sun_path;
static constexpr int kBacklog = 10;
if (listen(socket_fd.get(), kBacklog)) {
PLOG(ERROR) << "listen: " << addr.sun_path;
return base::ScopedFD();
}
return socket_fd;
}
std::vector<std::string> ParseCategories(base::StringPiece message) {
std::vector<std::string> requested_categories = base::SplitString(
message, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
std::vector<std::string> categories;
for (const auto& category : requested_categories) {
if (IsValidCategory(category))
categories.push_back(category);
else
LOG(WARNING) << "Unrecognized category: " << category;
}
return categories;
}
class TraceCopyTask : public base::MessagePumpLibevent::Watcher {
public:
// Read 64 kB at a time (standard pipe capacity).
static constexpr size_t kCopyBufferSize = 1UL << 16;
enum class Status {
SUCCESS,
FAILURE,
};
TraceCopyTask(base::ScopedFD in_fd,
base::ScopedFD out_fd,
base::OnceCallback<void(Status, size_t)> callback)
: buffer_(new char[kCopyBufferSize]),
in_fd_(std::move(in_fd)),
out_fd_(std::move(out_fd)),
out_watcher_(FROM_HERE),
callback_(std::move(callback)) {}
virtual ~TraceCopyTask() {}
void Start() {
base::MessageLoopForIO::current()->WatchFileDescriptor(
out_fd_.get(), true /* persistent */,
base::MessageLoopForIO::WATCH_WRITE, &out_watcher_, this);
}
// base::MessagePumpLibevent::Watcher:
void OnFileCanReadWithoutBlocking(int fd) override { NOTREACHED(); }
void OnFileCanWriteWithoutBlocking(int fd) override {
DCHECK_EQ(out_fd_.get(), fd);
CopyTraceData();
}
private:
void CopyTraceData() {
for (;;) {
if (read_ == written_) {
total_copied_ += read_;
read_ = written_ = 0;
// Read trace data from debugfs.
ssize_t read_bytes =
HANDLE_EINTR(read(in_fd_.get(), buffer_.get(), kCopyBufferSize));
if (read_bytes == 0) {
// EOF, we're done;
Finish(Status::SUCCESS);
return;
} else if (read_bytes < 0) {
PLOG(ERROR) << "read: trace";
Finish(Status::FAILURE);
return;
}
read_ = read_bytes;
}
// Write trace data to output pipe.
ssize_t written_bytes = HANDLE_EINTR(
write(out_fd_.get(), buffer_.get() + written_, read_ - written_));
if (written_bytes < 0) {
if (errno == EAGAIN)
return; // Wait for more space.
PLOG(ERROR) << "write: pipe";
Finish(Status::FAILURE);
return;
}
written_ += written_bytes;
}
}
void Finish(Status status) {
out_watcher_.StopWatchingFileDescriptor();
in_fd_.reset();
out_fd_.reset();
buffer_.reset();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), status, total_copied_));
}
std::unique_ptr<char[]> buffer_;
size_t read_ = 0;
size_t written_ = 0;
size_t total_copied_ = 0;
// Trace data file.
base::ScopedFD in_fd_;
// Pipe for trace data.
base::ScopedFD out_fd_;
base::MessagePumpLibevent::FileDescriptorWatcher out_watcher_;
// Callback for when copy finishes.
base::OnceCallback<void(Status, size_t)> callback_;
};
class TraceConnection : public base::MessagePumpLibevent::Watcher {
public:
TraceConnection(base::ScopedFD connection_fd, base::OnceClosure callback)
: recv_buffer_(new char[kMessageSize]),
connection_fd_(std::move(connection_fd)),
connection_watcher_(FROM_HERE),
callback_(std::move(callback)),
weak_ptr_factory_(this) {}
virtual ~TraceConnection() {}
void Init() {
base::MessageLoopForIO::current()->WatchFileDescriptor(
connection_fd_.get(), true /* persistent */,
base::MessageLoopForIO::WATCH_READ, &connection_watcher_, this);
}
// base::MessagePumpLibevent::Watcher:
void OnFileCanReadWithoutBlocking(int fd) override {
DCHECK_EQ(connection_fd_.get(), fd);
ReceiveClientMessage();
}
void OnFileCanWriteWithoutBlocking(int fd) override { NOTREACHED(); }
private:
enum class State {
INITIAL, // Waiting for init message
TRACING, // Ftrace started.
COPYING, // Ftrace stopped, copying trace data.
COPY_COMPLETE, // Ftrace stopped, all data written to pipe.
FINISHED, // All done.
};
void ReceiveClientMessage() {
std::vector<base::ScopedFD> fds;
ssize_t bytes = base::UnixDomainSocket::RecvMsg(
connection_fd_.get(), recv_buffer_.get(), kMessageSize, &fds);
if (bytes < 0) {
PLOG(ERROR) << "recvmsg";
Finish();
return;
} else if (bytes == 0) {
LOG(INFO) << "connection closed";
Finish();
} else {
base::StringPiece message(recv_buffer_.get(), bytes);
HandleClientMessage(message);
}
}
void HandleClientMessage(base::StringPiece message) {
if (state_ == State::INITIAL) {
std::vector<std::string> categories = ParseCategories(message);
if (!StartFtrace(categories)) {
LOG(ERROR) << "Failed to start ftrace";
Finish();
return;
}
if (!SendTracePipeToClient()) {
LOG(ERROR) << "Failed to send trace pipe";
Finish();
return;
}
LOG(INFO) << "Started tracing for categories: "
<< base::JoinString(categories, ",");
state_ = State::TRACING;
} else if (state_ == State::TRACING) {
WriteFtraceTimeSyncMarker();
StopFtrace();
base::ScopedFD trace_data = GetFtraceData();
if (!trace_data.is_valid()) {
LOG(ERROR) << "Failed to get trace data";
Finish();
return;
}
LOG(INFO) << "Tracing stopped";
trace_copy_task_ = std::make_unique<TraceCopyTask>(
std::move(trace_data), std::move(trace_pipe_),
base::BindOnce(&TraceConnection::OnFinishedCopying,
weak_ptr_factory_.GetWeakPtr()));
trace_copy_task_->Start();
state_ = State::COPYING;
} else {
LOG(WARNING) << "Unexpected message";
Finish();
return;
}
}
void OnFinishedCopying(TraceCopyTask::Status status, size_t trace_data_size) {
if (status == TraceCopyTask::Status::SUCCESS) {
LOG(INFO) << "Finished tracing (" << trace_data_size << " bytes copied)";
state_ = State::COPY_COMPLETE;
} else {
LOG(INFO) << "I/O error copying trace data";
}
Finish();
}
bool SendTracePipeToClient() {
int pipefd[2] = {-1, -1};
if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)) {
PLOG(ERROR) << "pipe2";
return false;
}
base::ScopedFD read_end(pipefd[0]);
base::ScopedFD write_end(pipefd[1]);
const char response[] = {0};
std::vector<int> send_fds;
send_fds.push_back(read_end.get());
if (!base::UnixDomainSocket::SendMsg(connection_fd_.get(), response,
sizeof(response), send_fds)) {
PLOG(ERROR) << "sendmsg";
return false;
}
trace_pipe_ = std::move(write_end);
return true;
}
void Finish() {
if (state_ != State::COPY_COMPLETE)
LOG(WARNING) << "Ending tracing without sending data";
trace_copy_task_.reset();
state_ = State::FINISHED;
recv_buffer_.reset();
connection_watcher_.StopWatchingFileDescriptor();
connection_fd_.reset();
StopFtrace();
ClearFtrace();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_)));
}
// Tracing state.
State state_ = State::INITIAL;
// Buffer for incoming messages.
std::unique_ptr<char[]> recv_buffer_;
// Client connection.
base::ScopedFD connection_fd_;
base::MessagePumpLibevent::FileDescriptorWatcher connection_watcher_;
// Pipe for trace output.
base::ScopedFD trace_pipe_;
// Task to send all trace output to client via a pipe.
std::unique_ptr<TraceCopyTask> trace_copy_task_;
// Callback for when connection closes.
base::OnceClosure callback_;
base::WeakPtrFactory<TraceConnection> weak_ptr_factory_;
};
class TracingService : public base::MessagePumpLibevent::Watcher {
public:
TracingService()
: server_socket_watcher_(FROM_HERE), weak_ptr_factory_(this) {}
virtual ~TracingService() {}
bool Init() {
server_socket_ = CreateServerSocket();
if (!server_socket_.is_valid())
return false;
base::MessageLoopForIO::current()->WatchFileDescriptor(
server_socket_.get(), true /* persistent */,
base::MessageLoopForIO::WATCH_READ, &server_socket_watcher_, this);
return true;
}
// base::MessagePumpLibevent::Watcher:
void OnFileCanReadWithoutBlocking(int fd) override {
DCHECK_EQ(server_socket_.get(), fd);
AcceptConnection();
}
void OnFileCanWriteWithoutBlocking(int fd) override { NOTREACHED(); }
private:
void AcceptConnection() {
base::ScopedFD connection_fd(accept4(server_socket_.get(), nullptr, nullptr,
SOCK_NONBLOCK | SOCK_CLOEXEC));
if (!connection_fd.is_valid()) {
PLOG(ERROR) << "accept: ";
return;
}
trace_connection_ = std::make_unique<TraceConnection>(
std::move(connection_fd),
base::BindOnce(&TracingService::OnConnectionClosed,
weak_ptr_factory_.GetWeakPtr()));
trace_connection_->Init();
}
void OnConnectionClosed() { trace_connection_.reset(); }
// Socket and watcher for listening socket.
base::ScopedFD server_socket_;
base::MessagePumpLibevent::FileDescriptorWatcher server_socket_watcher_;
// Currently active tracing connection.
// There can only be one; ftrace affects the whole system.
std::unique_ptr<TraceConnection> trace_connection_;
base::WeakPtrFactory<TracingService> weak_ptr_factory_;
};
} // namespace
} // namespace tracing
} // namespace chromecast
int main(int argc, char** argv) {
base::AtExitManager exit_manager;
base::CommandLine::Init(argc, argv);
logging::InitLogging(logging::LoggingSettings());
signal(SIGPIPE, SIG_IGN);
LOG(INFO) << "Starting system tracing service...";
base::MessageLoopForIO message_loop;
chromecast::tracing::TracingService service;
if (!service.Init())
return EXIT_FAILURE;
base::RunLoop run_loop;
run_loop.Run();
return EXIT_SUCCESS;
}
...@@ -1709,6 +1709,17 @@ jumbo_source_set("browser") { ...@@ -1709,6 +1709,17 @@ jumbo_source_set("browser") {
] ]
} }
if (is_chromecast && is_linux) {
sources += [
"tracing/cast_tracing_agent.cc",
"tracing/cast_tracing_agent.h",
]
deps += [ "//chromecast/tracing:system_tracer" ]
defines += [ "CAST_TRACING_AGENT=1" ]
}
if (is_fuchsia) { if (is_fuchsia) {
sources += [ sources += [
"child_process_launcher_helper_fuchsia.cc", "child_process_launcher_helper_fuchsia.cc",
......
...@@ -7,5 +7,8 @@ include_rules = [ ...@@ -7,5 +7,8 @@ include_rules = [
specific_include_rules = { specific_include_rules = {
"tracing_controller_impl\.cc": [ "tracing_controller_impl\.cc": [
"+v8" "+v8"
],
"cast_tracing_agent\.": [
"+chromecast/tracing"
] ]
} }
// 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 "content/browser/tracing/cast_tracing_agent.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/task_scheduler/task_scheduler.h"
#include "base/trace_event/trace_config.h"
#include "chromecast/tracing/system_tracing_common.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/resource_coordinator/public/interfaces/service_constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace content {
namespace {
std::string GetTracingCategories(
const base::trace_event::TraceConfig& trace_config) {
std::vector<base::StringPiece> categories;
for (size_t i = 0; i < chromecast::tracing::kCategoryCount; ++i) {
base::StringPiece category(chromecast::tracing::kCategories[i]);
if (trace_config.category_filter().IsCategoryGroupEnabled(category))
categories.push_back(category);
}
return base::JoinString(categories, ",");
}
} // namespace
CastTracingAgent::CastTracingAgent(service_manager::Connector* connector)
: binding_(this) {
// Connecto to the agent registry interface.
tracing::mojom::AgentRegistryPtr agent_registry;
connector->BindInterface(resource_coordinator::mojom::kServiceName,
&agent_registry);
// Register this agent.
tracing::mojom::AgentPtr agent;
binding_.Bind(mojo::MakeRequest(&agent));
static const char kFtraceLabel[] = "systemTraceEvents";
agent_registry->RegisterAgent(std::move(agent), kFtraceLabel,
tracing::mojom::TraceDataType::STRING,
false /* supports_explicit_clock_sync */);
task_runner_ =
base::TaskScheduler::GetInstance()->CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}
CastTracingAgent::~CastTracingAgent() = default;
// tracing::mojom::Agent. Called by Mojo internals on the UI thread.
void CastTracingAgent::StartTracing(
const std::string& config,
base::TimeTicks coordinator_time,
const Agent::StartTracingCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::trace_event::TraceConfig trace_config(config);
if (!trace_config.IsSystraceEnabled()) {
callback.Run(false /* success */);
return;
}
start_tracing_callback_ = callback;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&CastTracingAgent::StartTracingOnIO,
base::Unretained(this),
base::ThreadTaskRunnerHandle::Get(),
GetTracingCategories(trace_config)));
}
void CastTracingAgent::StopAndFlush(tracing::mojom::RecorderPtr recorder) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
recorder_ = std::move(recorder);
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&CastTracingAgent::StopAndFlushOnIO,
base::Unretained(this),
base::ThreadTaskRunnerHandle::Get()));
}
void CastTracingAgent::RequestClockSyncMarker(
const std::string& sync_id,
const Agent::RequestClockSyncMarkerCallback& callback) {
NOTREACHED();
}
void CastTracingAgent::GetCategories(
const Agent::GetCategoriesCallback& callback) {
callback.Run("" /* categories */);
}
void CastTracingAgent::RequestBufferStatus(
const Agent::RequestBufferStatusCallback& callback) {
callback.Run(0 /* capacity */, 0 /* count */);
}
void CastTracingAgent::StartTracingOnIO(
scoped_refptr<base::TaskRunner> reply_task_runner,
const std::string& categories) {
system_tracer_ = std::make_unique<chromecast::SystemTracer>();
system_tracer_->StartTracing(
categories, base::BindOnce(&CastTracingAgent::FinishStartOnIO,
base::Unretained(this), reply_task_runner));
}
void CastTracingAgent::FinishStartOnIO(
scoped_refptr<base::TaskRunner> reply_task_runner,
chromecast::SystemTracer::Status status) {
reply_task_runner->PostTask(FROM_HERE,
base::BindOnce(&CastTracingAgent::FinishStart,
base::Unretained(this), status));
if (status != chromecast::SystemTracer::Status::OK) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&CastTracingAgent::CleanupOnIO, base::Unretained(this)));
}
}
void CastTracingAgent::FinishStart(chromecast::SystemTracer::Status status) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
start_tracing_callback_.Run(status == chromecast::SystemTracer::Status::OK);
}
void CastTracingAgent::StopAndFlushOnIO(
scoped_refptr<base::TaskRunner> reply_task_runner) {
system_tracer_->StopTracing(
base::BindRepeating(&CastTracingAgent::HandleTraceDataOnIO,
base::Unretained(this), reply_task_runner));
}
void CastTracingAgent::HandleTraceDataOnIO(
scoped_refptr<base::TaskRunner> reply_task_runner,
chromecast::SystemTracer::Status status,
std::string trace_data) {
reply_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&CastTracingAgent::HandleTraceData, base::Unretained(this),
status, std::move(trace_data)));
if (status != chromecast::SystemTracer::Status::KEEP_GOING) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&CastTracingAgent::CleanupOnIO, base::Unretained(this)));
}
}
void CastTracingAgent::HandleTraceData(chromecast::SystemTracer::Status status,
std::string trace_data) {
if (recorder_ && status != chromecast::SystemTracer::Status::FAIL)
recorder_->AddChunk(std::move(trace_data));
if (status != chromecast::SystemTracer::Status::KEEP_GOING)
recorder_.reset();
}
void CastTracingAgent::CleanupOnIO() {
system_tracer_.reset();
}
} // namespace content
// 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.
#ifndef CONTENT_BROWSER_TRACING_CAST_TRACING_AGENT_H_
#define CONTENT_BROWSER_TRACING_CAST_TRACING_AGENT_H_
#include <memory>
#include <string>
#include "chromecast/tracing/system_tracer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/resource_coordinator/public/interfaces/tracing/tracing.mojom.h"
namespace service_manager {
class Connector;
} // namespace service_manager
namespace chromecast {
class SystemTracer;
}
namespace content {
class CastTracingAgent : public tracing::mojom::Agent {
public:
explicit CastTracingAgent(service_manager::Connector* connector);
~CastTracingAgent() override;
private:
// tracing::mojom::Agent. Called by Mojo internals on the UI thread.
void StartTracing(const std::string& config,
base::TimeTicks coordinator_time,
const Agent::StartTracingCallback& callback) override;
void StopAndFlush(tracing::mojom::RecorderPtr recorder) override;
void RequestClockSyncMarker(
const std::string& sync_id,
const Agent::RequestClockSyncMarkerCallback& callback) override;
void GetCategories(const Agent::GetCategoriesCallback& callback) override;
void RequestBufferStatus(
const Agent::RequestBufferStatusCallback& callback) override;
void StartTracingOnIO(scoped_refptr<base::TaskRunner> reply_task_runner,
const std::string& categories);
void FinishStartOnIO(scoped_refptr<base::TaskRunner> reply_task_runner,
chromecast::SystemTracer::Status status);
void FinishStart(chromecast::SystemTracer::Status status);
void StopAndFlushOnIO(scoped_refptr<base::TaskRunner> reply_task_runner);
void HandleTraceDataOnIO(scoped_refptr<base::TaskRunner> reply_task_runner,
chromecast::SystemTracer::Status,
std::string trace_data);
void HandleTraceData(chromecast::SystemTracer::Status status,
std::string trace_data);
void CleanupOnIO();
mojo::Binding<tracing::mojom::Agent> binding_;
Agent::StartTracingCallback start_tracing_callback_;
tracing::mojom::RecorderPtr recorder_;
// Task runner for collecting traces in a worker thread.
scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<chromecast::SystemTracer> system_tracer_;
DISALLOW_COPY_AND_ASSIGN(CastTracingAgent);
};
} // namespace content
#endif // CONTENT_BROWSER_TRACING_CAST_TRACING_AGENT_H_
...@@ -54,6 +54,10 @@ ...@@ -54,6 +54,10 @@
#include "content/browser/tracing/cros_tracing_agent.h" #include "content/browser/tracing/cros_tracing_agent.h"
#endif #endif
#if defined(CAST_TRACING_AGENT)
#include "content/browser/tracing/cast_tracing_agent.h"
#endif
#if defined(OS_WIN) #if defined(OS_WIN)
#include "content/browser/tracing/etw_tracing_agent_win.h" #include "content/browser/tracing/etw_tracing_agent_win.h"
#endif #endif
...@@ -139,6 +143,8 @@ void TracingControllerImpl::AddAgents() { ...@@ -139,6 +143,8 @@ void TracingControllerImpl::AddAgents() {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
agents_.push_back(std::make_unique<CrOSTracingAgent>(connector)); agents_.push_back(std::make_unique<CrOSTracingAgent>(connector));
agents_.push_back(std::make_unique<ArcTracingAgentImpl>(connector)); agents_.push_back(std::make_unique<ArcTracingAgentImpl>(connector));
#elif defined(CAST_TRACING_AGENT)
agents_.push_back(std::make_unique<CastTracingAgent>(connector));
#elif defined(OS_WIN) #elif defined(OS_WIN)
agents_.push_back(std::make_unique<EtwTracingAgent>(connector)); agents_.push_back(std::make_unique<EtwTracingAgent>(connector));
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment