blob: 625bea0b2e6733638bb5d40211ad9a4149653a7e [file] [log] [blame]
// Copyright 2025 The Fuchsia 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 <fidl/fuchsia.kernel/cpp/fidl.h>
#include <fidl/fuchsia.tracing.controller/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zircon-internal/ktrace.h>
#include <lib/zx/socket.h>
#include <gtest/gtest.h>
#include <trace-reader/reader.h>
namespace {
// Continuously sleep to generate a bunch of scheduler records.
std::thread DoWork(zx::unowned_event e) {
std::thread t{[&]() {
while (true) {
zx_signals_t out;
zx_status_t status = e->wait_one(ZX_EVENT_SIGNALED | ZX_SIGNAL_HANDLE_CLOSED,
zx::deadline_after(zx::msec(1)), &out);
if ((out & ZX_EVENT_SIGNALED) != 0) {
return;
}
if (status == ZX_ERR_PEER_CLOSED) {
return;
}
}
}};
return t;
}
bool WaitForKtrace(const fidl::SyncClient<fuchsia_tracing_controller::Provisioner>& client) {
// Wait for ktrace to attach
for (unsigned retries = 0; retries < 5; retries++) {
fidl::Result<fuchsia_tracing_controller::Provisioner::GetProviders> providers =
client->GetProviders();
if (providers.is_error()) {
return false;
}
if (providers->providers().size() == 1) {
break;
}
sleep(1);
}
return true;
}
// Check that we are able to connect to kernel tracing and actually get data.
TEST(KtraceProviderIntegrationTest, VerifyData) {
zx::result client_end = component::Connect<fuchsia_tracing_controller::Provisioner>();
ASSERT_TRUE(client_end.is_ok());
fidl::SyncClient client{std::move(*client_end)};
ASSERT_TRUE(WaitForKtrace(client));
zx::socket in_socket;
zx::socket outgoing_socket;
ASSERT_EQ(zx::socket::create(0u, &in_socket, &outgoing_socket), ZX_OK);
auto endpoints = fidl::Endpoints<fuchsia_tracing_controller::Session>::Create();
fuchsia_tracing_controller::TraceConfig config{{
// Include lots of trace data so we quickly fill up the buffer
.categories = {{"kernel:meta", "kernel:sched", "kernel:syscall", "kernel:ipc", "kernel:vm",
"kernel:irq"}},
.buffer_size_megabytes_hint = 1,
.buffering_mode = fuchsia_tracing::BufferingMode::kStreaming,
}};
auto init_response =
client->InitializeTracing({std::move(endpoints.server), config, std::move(outgoing_socket)});
ASSERT_TRUE(init_response.is_ok());
fidl::SyncClient controller_client{std::move(endpoints.client)};
zx::event e;
zx::event::create(0, &e);
// Create some background threads to ensure we get context switch events.
std::thread worker1 = DoWork(e.borrow());
std::thread worker2 = DoWork(e.borrow());
std::thread worker3 = DoWork(e.borrow());
std::thread worker4 = DoWork(e.borrow());
controller_client->StartTracing({});
// Read 256K from the socket
std::vector<uint8_t> buffer;
size_t total_read = 0;
bool trace_stopped = false;
std::thread stop_thread;
while (true) {
buffer.resize(total_read + 4096);
size_t bytes_read;
zx_status_t status = in_socket.read(0u, buffer.data() + total_read, 4096, &bytes_read);
if (status == ZX_ERR_PEER_CLOSED) {
buffer.resize(total_read);
break;
}
if (status == ZX_ERR_SHOULD_WAIT) {
zx_status_t result = in_socket.wait_one(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
zx::deadline_after(zx::sec(30)), nullptr);
if (result == ZX_ERR_TIMED_OUT) {
// We traced for 30 seconds, but we may not have had enough data to trigger a buffer flush.
// That's fine, we'll just stop the trace and get what was written.
if (!trace_stopped) {
stop_thread = std::thread{[&]() {
controller_client->StopTracing({{.write_results = true}});
controller_client.TakeClientEnd().reset();
}};
trace_stopped = true;
}
continue;
}
if (result == ZX_ERR_PEER_CLOSED) {
break;
}
ASSERT_EQ(ZX_OK, result);
continue;
}
ASSERT_EQ(ZX_OK, status);
total_read += bytes_read;
if (total_read >= size_t{256} * 1024) {
if (!trace_stopped) {
stop_thread = std::thread{[&]() {
controller_client->StopTracing({{.write_results = true}});
controller_client.TakeClientEnd().reset();
}};
trace_stopped = true;
}
}
}
e.signal(0, ZX_EVENT_SIGNALED);
ASSERT_TRUE(trace_stopped);
stop_thread.join();
worker1.join();
worker2.join();
worker3.join();
worker4.join();
bool found_sched_record = false;
size_t parse_failures = 0;
trace::TraceReader reader(
[&](trace::Record record) {
if (record.type() == trace::RecordType::kScheduler) {
found_sched_record = true;
}
},
[&parse_failures](std::string_view error) {
parse_failures += 1;
FX_LOGS(ERROR) << "TraceReader error: " << error;
});
if (total_read > 0) {
trace::Chunk chunk(reinterpret_cast<const uint64_t*>(buffer.data()), buffer.size() / 8);
reader.ReadRecords(chunk);
}
EXPECT_TRUE(found_sched_record);
EXPECT_EQ(parse_failures, size_t{0});
}
// Ensure we are able to pass kernel:retain to get a previous run's data.
TEST(KtraceProviderIntegrationTest, Retain) {
// We start by manually starting a kernel trace, but not reading the data.
auto tracing_client_end = component::Connect<fuchsia_kernel::TracingResource>();
ASSERT_TRUE(tracing_client_end.is_ok());
auto tracing_result = fidl::SyncClient(std::move(*tracing_client_end))->Get();
ASSERT_TRUE(tracing_result.is_ok());
ASSERT_EQ(ZX_OK,
zx_ktrace_control(tracing_result->resource().get(), KTRACE_ACTION_STOP, 0, nullptr));
ASSERT_EQ(ZX_OK,
zx_ktrace_control(tracing_result->resource().get(), KTRACE_ACTION_REWIND, 0, nullptr));
ASSERT_EQ(ZX_OK, zx_ktrace_control(tracing_result->resource().get(), KTRACE_ACTION_START,
KTRACE_GRP_SYSCALL, nullptr));
// Do syscalls to ensure we get some data
for (size_t i = 0; i < 10000; i++) {
usleep(1);
}
ASSERT_EQ(ZX_OK,
zx_ktrace_control(tracing_result->resource().get(), KTRACE_ACTION_STOP, 0, nullptr));
// Now, we should be able to read out only syscall records using kernel:retain.
zx::result client_end = component::Connect<fuchsia_tracing_controller::Provisioner>();
ASSERT_TRUE(client_end.is_ok());
fidl::SyncClient client{std::move(*client_end)};
ASSERT_TRUE(WaitForKtrace(client));
zx::socket in_socket;
zx::socket outgoing_socket;
ASSERT_EQ(zx::socket::create(0u, &in_socket, &outgoing_socket), ZX_OK);
auto endpoints = fidl::Endpoints<fuchsia_tracing_controller::Session>::Create();
fuchsia_tracing_controller::TraceConfig config{{
.categories = {{"kernel:retain"}},
.buffer_size_megabytes_hint = 1,
.buffering_mode = fuchsia_tracing::BufferingMode::kOneshot,
}};
auto init_response =
client->InitializeTracing({std::move(endpoints.server), config, std::move(outgoing_socket)});
ASSERT_TRUE(init_response.is_ok());
fidl::SyncClient controller_client{std::move(endpoints.client)};
controller_client->StartTracing({});
sleep(1);
std::thread stop_thread{[&]() {
controller_client->StopTracing({{.write_results = true}});
controller_client.TakeClientEnd().reset();
}};
std::vector<uint8_t> buffer;
size_t total_read = 0;
while (true) {
buffer.resize(total_read + 4096);
size_t bytes_read;
zx_status_t status = in_socket.read(0u, buffer.data() + total_read, 4096, &bytes_read);
if (status == ZX_ERR_PEER_CLOSED) {
buffer.resize(total_read);
break;
}
if (status == ZX_ERR_SHOULD_WAIT) {
zx_status_t result = in_socket.wait_one(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
zx::deadline_after(zx::sec(30)), nullptr);
ASSERT_EQ(ZX_OK, result);
continue;
}
ASSERT_EQ(ZX_OK, status);
total_read += bytes_read;
}
stop_thread.join();
bool found_syscall_record = false;
bool found_non_syscall_record = false;
size_t parse_failures = 0;
trace::TraceReader reader(
[&](trace::Record record) {
switch (record.type()) {
case trace::RecordType::kEvent: {
const trace::Record::Event& data = record.GetEvent();
if (data.category == "kernel:syscall") {
found_syscall_record = true;
} else {
FX_LOGS(ERROR) << "Got record of category: " << data.category;
found_non_syscall_record = true;
}
break;
}
case trace::RecordType::kMetadata:
case trace::RecordType::kInitialization:
case trace::RecordType::kString:
case trace::RecordType::kThread:
case trace::RecordType::kKernelObject:
break;
default: {
FX_LOGS(ERROR) << "Got record of type: " << static_cast<uint64_t>(record.type());
found_non_syscall_record = true;
}
}
},
[&parse_failures](std::string_view error) {
parse_failures += 1;
FX_LOGS(ERROR) << "TraceReader error: " << error;
});
if (total_read > 0) {
trace::Chunk chunk(reinterpret_cast<const uint64_t*>(buffer.data()), buffer.size() / 8);
reader.ReadRecords(chunk);
}
EXPECT_TRUE(found_syscall_record);
EXPECT_FALSE(found_non_syscall_record);
EXPECT_EQ(parse_failures, size_t{0});
}
} // namespace