Merge changes Ic85cab22,I97791a80,I521eb50c

* changes:
  Add support for concurrent data sources.
  Make NetworkTraceHandler thread safe
  Refactor Network Tracing into separate data source
This commit is contained in:
Ryan Zuklie 2023-03-06 19:20:06 +00:00 • committed by Gerrit Code Review
commit dd4cecc907
3 changed files with 137 additions and 55 deletions

View file

@ -55,13 +55,12 @@ void NetworkTraceHandler::InitPerfettoTracing() {
NetworkTraceHandler::RegisterDataSource();
}
NetworkTraceHandler::NetworkTraceHandler()
: NetworkTraceHandler([this](const PacketTrace& pkt) {
NetworkTraceHandler::Trace(
[this, pkt](NetworkTraceHandler::TraceContext ctx) {
Fill(pkt, *ctx.NewTracePacket());
});
}) {}
// static
NetworkTracePoller NetworkTraceHandler::sPoller([](const PacketTrace& pkt) {
NetworkTraceHandler::Trace([pkt](NetworkTraceHandler::TraceContext ctx) {
NetworkTraceHandler::Fill(pkt, *ctx.NewTracePacket());
});
});
void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
const std::string& raw = args.config->network_packet_trace_config_raw();
@ -75,21 +74,27 @@ void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
}
void NetworkTraceHandler::OnStart(const StartArgs&) {
if (!Start()) return;
mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
Loop();
mStarted = sPoller.Start(mPollMs);
}
void NetworkTraceHandler::OnStop(const StopArgs&) {
Stop();
mTaskRunner.reset();
if (mStarted) sPoller.Stop();
mStarted = false;
}
void NetworkTraceHandler::Loop() {
mTaskRunner->PostDelayedTask([this]() { Loop(); }, mPollMs);
ConsumeAll();
void NetworkTracePoller::SchedulePolling() {
// Schedules another run of ourselves to recursively poll periodically.
mTaskRunner->PostDelayedTask(
[this]() {
mMutex.lock();
SchedulePolling();
ConsumeAllLocked();
mMutex.unlock();
},
mPollMs);
}
// static class method
void NetworkTraceHandler::Fill(const PacketTrace& src, TracePacket& dst) {
dst.set_timestamp(src.timestampNs);
auto* event = dst.set_network_packet();
@ -113,9 +118,23 @@ void NetworkTraceHandler::Fill(const PacketTrace& src, TracePacket& dst) {
}
}
bool NetworkTraceHandler::Start() {
bool NetworkTracePoller::Start(uint32_t pollMs) {
ALOGD("Starting datasource");
std::scoped_lock<std::mutex> lock(mMutex);
if (mSessionCount > 0) {
if (mPollMs != pollMs) {
// Nothing technical prevents mPollMs from changing, it's just unclear
// what the right behavior is. Taking the min of active values could poll
// too frequently giving some sessions too much data. Taking the max could
// be too infrequent. For now, do nothing.
ALOGI("poll_ms can't be changed while running, ignoring poll_ms=%d",
pollMs);
}
mSessionCount++;
return true;
}
auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
if (!status.ok()) {
ALOGW("Failed to bind config map: %s", status.error().message().c_str());
@ -136,24 +155,41 @@ bool NetworkTraceHandler::Start() {
return false;
}
// Start a task runner to run ConsumeAll every mPollMs milliseconds.
mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
mPollMs = pollMs;
SchedulePolling();
mSessionCount++;
return true;
}
bool NetworkTraceHandler::Stop() {
bool NetworkTracePoller::Stop() {
ALOGD("Stopping datasource");
std::scoped_lock<std::mutex> lock(mMutex);
if (mSessionCount == 0) return false; // This should never happen
// If this isn't the last session, don't clean up yet.
if (--mSessionCount > 0) return true;
auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
if (!res.ok()) {
ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
return false;
}
mTaskRunner.reset();
mRingBuffer.reset();
return true;
return res.ok();
}
bool NetworkTraceHandler::ConsumeAll() {
bool NetworkTracePoller::ConsumeAll() {
std::scoped_lock<std::mutex> lock(mMutex);
return ConsumeAllLocked();
}
bool NetworkTracePoller::ConsumeAllLocked() {
if (mRingBuffer == nullptr) {
ALOGW("Tracing is not active");
return false;

View file

@ -39,6 +39,9 @@ using ::testing::Test;
namespace android {
namespace bpf {
// Use uint32 max to cause the handler to never Loop. Instead, the tests will
// manually drive things by calling ConsumeAll explicitly.
constexpr uint32_t kNeverPoll = std::numeric_limits<uint32_t>::max();
__be16 bindAndListen(int s) {
sockaddr_in sin = {.sin_family = AF_INET};
@ -83,7 +86,7 @@ struct PacketPrinter {
}
};
class NetworkTraceHandlerTest : public testing::Test {
class NetworkTracePollerTest : public testing::Test {
protected:
void SetUp() {
if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
@ -95,31 +98,49 @@ class NetworkTraceHandlerTest : public testing::Test {
}
};
TEST_F(NetworkTraceHandlerTest, PollWhileInactive) {
NetworkTraceHandler handler([&](const PacketTrace& pkt) {});
TEST_F(NetworkTracePollerTest, PollWhileInactive) {
NetworkTracePoller handler([&](const PacketTrace& pkt) {});
// One succeed after start and before stop.
EXPECT_FALSE(handler.ConsumeAll());
ASSERT_TRUE(handler.Start());
ASSERT_TRUE(handler.Start(kNeverPoll));
EXPECT_TRUE(handler.ConsumeAll());
ASSERT_TRUE(handler.Stop());
EXPECT_FALSE(handler.ConsumeAll());
}
TEST_F(NetworkTraceHandlerTest, TraceTcpSession) {
TEST_F(NetworkTracePollerTest, ConcurrentSessions) {
// Simulate two concurrent sessions (two starts followed by two stops). Check
// that tracing is stopped only after both sessions finish.
NetworkTracePoller handler([&](const PacketTrace& pkt) {});
ASSERT_TRUE(handler.Start(kNeverPoll));
EXPECT_TRUE(handler.ConsumeAll());
ASSERT_TRUE(handler.Start(kNeverPoll));
EXPECT_TRUE(handler.ConsumeAll());
ASSERT_TRUE(handler.Stop());
EXPECT_TRUE(handler.ConsumeAll());
ASSERT_TRUE(handler.Stop());
EXPECT_FALSE(handler.ConsumeAll());
}
TEST_F(NetworkTracePollerTest, TraceTcpSession) {
__be16 server_port = 0;
std::vector<PacketTrace> packets;
// Record all packets with the bound address and current uid. This callback is
// involked only within ConsumeAll, at which point the port should have
// already been filled in and all packets have been processed.
NetworkTraceHandler handler([&](const PacketTrace& pkt) {
NetworkTracePoller handler([&](const PacketTrace& pkt) {
if (pkt.sport != server_port && pkt.dport != server_port) return;
if (pkt.uid != getuid()) return;
packets.push_back(pkt);
});
ASSERT_TRUE(handler.Start());
ASSERT_TRUE(handler.Start(kNeverPoll));
const uint32_t kClientTag = 2468;
const uint32_t kServerTag = 1357;

View file

@ -22,6 +22,7 @@
#include <string>
#include <unordered_map>
#include "android-base/thread_annotations.h"
#include "bpf/BpfMap.h"
#include "bpf/BpfRingbuf.h"
@ -31,6 +32,56 @@
namespace android {
namespace bpf {
// NetworkTracePoller is responsible for interactions with the BPF ring buffer
// including polling. This class is an internal helper for NetworkTraceHandler,
// it is not meant to be used elsewhere.
class NetworkTracePoller {
public:
// Testonly: initialize with a callback capable of intercepting data.
NetworkTracePoller(std::function<void(const PacketTrace&)> callback)
: mCallback(std::move(callback)) {}
// Starts tracing with the given poll interval.
bool Start(uint32_t pollMs) EXCLUDES(mMutex);
// Stops tracing and release any held state.
bool Stop() EXCLUDES(mMutex);
// Consumes all available events from the ringbuffer.
bool ConsumeAll() EXCLUDES(mMutex);
private:
void SchedulePolling() REQUIRES(mMutex);
bool ConsumeAllLocked() REQUIRES(mMutex);
std::mutex mMutex;
// Records the number of successfully started active sessions so that only the
// first active session attempts setup and only the last cleans up. Note that
// the session count will remain zero if Start fails. It is expected that Stop
// will not be called for any trace session where Start fails.
int mSessionCount GUARDED_BY(mMutex);
// How often to poll the ring buffer, defined by the trace config.
uint32_t mPollMs GUARDED_BY(mMutex);
// The function to process PacketTrace, typically a Perfetto sink.
std::function<void(const PacketTrace&)> mCallback GUARDED_BY(mMutex);
// The BPF ring buffer handle.
std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer GUARDED_BY(mMutex);
// The packet tracing config map (really a 1-element array).
BpfMap<uint32_t, bool> mConfigurationMap GUARDED_BY(mMutex);
// This must be the last member, causing it to be the first deleted. If it is
// not, members required for callbacks can be deleted before it's stopped.
std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner GUARDED_BY(mMutex);
};
// NetworkTraceHandler implements the android.network_packets data source. This
// class is registered with Perfetto and is instantiated when tracing starts and
// destroyed when tracing ends. There is one instance per trace session.
class NetworkTraceHandler : public perfetto::DataSource<NetworkTraceHandler> {
public:
// Registers this DataSource.
@ -39,45 +90,19 @@ class NetworkTraceHandler : public perfetto::DataSource<NetworkTraceHandler> {
// Connects to the system Perfetto daemon and registers the trace handler.
static void InitPerfettoTracing();
// Initialize with the default Perfetto callback.
NetworkTraceHandler();
// Testonly: initialize with a callback capable of intercepting data.
NetworkTraceHandler(std::function<void(const PacketTrace&)> callback)
: mCallback(std::move(callback)) {}
// Testonly: standalone functions without perfetto dependency.
bool Start();
bool Stop();
bool ConsumeAll();
// perfetto::DataSource overrides:
void OnSetup(const SetupArgs&) override;
void OnSetup(const SetupArgs& args) override;
void OnStart(const StartArgs&) override;
void OnStop(const StopArgs&) override;
// Convert a PacketTrace into a Perfetto trace packet.
void Fill(const PacketTrace& src,
::perfetto::protos::pbzero::TracePacket& dst);
private:
void Loop();
// Convert a PacketTrace into a Perfetto trace packet.
static void Fill(const PacketTrace& src,
::perfetto::protos::pbzero::TracePacket& dst);
// How often to poll the ring buffer, defined by the trace config.
static NetworkTracePoller sPoller;
uint32_t mPollMs;
// The function to process PacketTrace, typically a Perfetto sink.
std::function<void(const PacketTrace&)> mCallback;
// The BPF ring buffer handle.
std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer;
// The packet tracing config map (really a 1-element array).
BpfMap<uint32_t, bool> mConfigurationMap;
// This must be the last member, causing it to be the first deleted. If it is
// not, members required for callbacks can be deleted before it's stopped.
std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner;
bool mStarted;
};
} // namespace bpf