Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ jobs:
run: bazel build //targets/simple_switch_grpc:simple_switch_grpc

- name: Test
run: bazel test //targets/simple_switch_grpc/tests/...
run: |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My one request is also to add test support for cmake here. Bazel-only is not great for the wider community

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this PR is already huge I suggest this could be done in follow up PRs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to merge this only when it is complete. This PR only changes 19 files, most of which are test files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. But I'm not familiar with all these build systems so it might take a while.

bazel test //targets/simple_switch/tests/...
bazel test //targets/simple_switch_grpc/tests/...
2 changes: 1 addition & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ expand_template(
substitutions = {
# disabled
"#cmakedefine BM_DEBUG_ON @BM_DEBUG_ON@": "/* #undef BM_DEBUG_ON */",
"#cmakedefine BM_ELOG_ON @BM_ELOG_ON@": "/* #undef BM_ELOG_ON */",
# enabled
"#cmakedefine BM_ELOG_ON @BM_ELOG_ON@": "#define BM_ELOG_ON 1",
"#cmakedefine BM_ENABLE_MODULES @BM_ENABLE_MODULES@": "#define BM_ENABLE_MODULES 1",
"#cmakedefine BM_HAVE_ALGORITHM @BM_HAVE_ALGORITHM@": "#define BM_HAVE_ALGORITHM 1",
"#cmakedefine BM_HAVE_ARRAY @BM_HAVE_ARRAY@": "#define BM_HAVE_ARRAY 1",
Expand Down
6 changes: 6 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ bazel_dep(name = "rules_cc", version = "0.2.18")
bazel_dep(name = "xxhash", version = "0.8.3.bcr.1")

bazel_dep(name = "googletest", version = "1.17.0.bcr.2", dev_dependency = True)
bazel_dep(name = "gutil", version = "20260309.0", dev_dependency = True)

git_override(
module_name = "com_github_p4lang_pi",
commit = "0aa6c69140045b8a74575dbb7818664dd1066504",
init_submodules = True,
remote = "https://github.com/p4lang/PI.git",
)

single_version_override(
module_name = "abseil-cpp",
version = "20250814.1",
)
38 changes: 37 additions & 1 deletion include/bm/bm_sim/packet_tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <mutex>
#include <string>

#include "event_observer.h"
#include "packet_trace.pb.h"

namespace bm {
Expand All @@ -34,6 +35,7 @@ class TraceTreeWrapper {
private:
friend class PacketTraceContext;

std::mutex mutex_;
p4::bm::PacketTrace root_trace_;
};

Expand All @@ -46,6 +48,9 @@ class PacketTraceContext {
PacketTraceContext(std::shared_ptr<TraceTreeWrapper> tree_wrapper,
p4::bm::TraceTree* node);

//! Add a trace event. The event is moved into the internal trace.
void add_event(p4::bm::TraceEvent event);

std::shared_ptr<TraceTreeWrapper> get_wrapper() const {
return tree_wrapper_;
}
Expand All @@ -56,7 +61,7 @@ class PacketTraceContext {
};

//! Protobuf trace backend.
class PacketTracer {
class PacketTracer : public EventObserverIface {
public:
static PacketTracer* get() {
static PacketTracer instance;
Expand All @@ -71,6 +76,37 @@ class PacketTracer {
//! Thread-safe: each call writes to a unique file keyed by packet_id.
void flush_trace(const p4::bm::PacketTrace& trace);

//! Attach a fresh trace context to a packet entering the switch.
void attach_trace_ctx(Packet* packet);

void packet_in(const Packet& packet) override;
void packet_out(const Packet& packet) override;

void parser_start(const Packet& packet, const Parser& parser) override;
void parser_done(const Packet& packet, const Parser& parser) override;
void parser_extract(const Packet& packet, header_id_t header) override;

void deparser_start(const Packet& packet, const Deparser& deparser) override;
void deparser_done(const Packet& packet, const Deparser& deparser) override;
void deparser_emit(const Packet& packet, header_id_t header) override;

void checksum_update(const Packet& packet, const Checksum& checksum) override;

void pipeline_start(const Packet& packet, const Pipeline& pipeline) override;
void pipeline_done(const Packet& packet, const Pipeline& pipeline) override;

void condition_eval(const Packet& packet, const Conditional& cond,
bool result) override;
void table_hit(const Packet& packet, const MatchTableAbstract& table,
entry_handle_t handle) override;
void table_miss(const Packet& packet,
const MatchTableAbstract& table) override;

void action_execute(const Packet& packet, const ActionFn& action_fn,
const ActionData& action_data) override;

void config_change() override;

private:
PacketTracer() = default;

Expand Down
17 changes: 17 additions & 0 deletions src/bm_sim/options_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

#include <bm/bm_sim/P4Objects.h>
#include <bm/bm_sim/event_logger.h>
#include <bm/bm_sim/event_observer.h>
#include <bm/bm_sim/logger.h>
#include <bm/bm_sim/options_parse.h>
#include <bm/bm_sim/packet_tracer.h>
#include <bm/config.h>

#include <cassert>
Expand Down Expand Up @@ -109,6 +111,11 @@ OptionsParser::parse(int argc, char *argv[], TargetParserIface *tp,
("nanolog", po::value<std::string>(),
"IPC socket to use for nanomsg pub/sub logs "
"(default: no nanomsg logging")
#ifdef BM_PACKET_TRACE_ON
("trace-dir", po::value<std::string>(),
"Enable structured packet tracing and write textproto traces to the "
"specified directory")
#endif
("log-console",
"Enable logging on stdout")
("log-file", po::value<std::string>(),
Expand Down Expand Up @@ -270,6 +277,16 @@ OptionsParser::parse(int argc, char *argv[], TargetParserIface *tp,
#endif
}

#ifdef BM_PACKET_TRACE_ON
if (vm.count("trace-dir")) {
std::string trace_dir = vm["trace-dir"].as<std::string>();
if (!trace_dir.empty()) {
PacketTracer::get()->set_output_dir(trace_dir);
EventObserverRegistry::get()->register_observer(PacketTracer::get());
}
}
#endif

if (vm.count("log-console") && vm.count("log-file")) {
outstream << "Error: --log-console and --log-file are exclusive\n";
exit(1);
Expand Down
137 changes: 137 additions & 0 deletions src/bm_sim/packet_tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

#ifdef BM_PACKET_TRACE_ON

#include <bm/bm_sim/_assert.h>
#include <bm/bm_sim/deparser.h>
#include <bm/bm_sim/logger.h>
#include <bm/bm_sim/packet.h>
#include <bm/bm_sim/parser.h>
#include <bm/bm_sim/pipeline.h>
#include <google/protobuf/text_format.h>

#include <filesystem>
Expand Down Expand Up @@ -41,6 +45,11 @@ PacketTraceContext::PacketTraceContext(
std::shared_ptr<TraceTreeWrapper> tree_wrapper, p4::bm::TraceTree* node)
: tree_wrapper_(std::move(tree_wrapper)), current_node_(node) {}

void PacketTraceContext::add_event(p4::bm::TraceEvent event) {
std::lock_guard<std::mutex> lock(tree_wrapper_->mutex_);
*current_node_->add_events() = std::move(event);
}

// ---------- PacketTracer ----------

void PacketTracer::set_output_dir(const std::string& output_dir) {
Expand Down Expand Up @@ -75,6 +84,134 @@ void PacketTracer::flush_trace(const p4::bm::PacketTrace& trace) {
}
}

void PacketTracer::attach_trace_ctx(Packet* packet) {
if (!enabled_) return;
auto ctx = std::make_unique<PacketTraceContext>(packet->get_packet_id(),
packet->get_ingress_port());
packet->set_trace_ctx(std::move(ctx));
}

namespace {

// Add a TraceEvent to the packet's trace context (if present).
void trace_add_event(const Packet& packet, p4::bm::TraceEvent event) {
PacketTraceContext* ctx = packet.get_trace_ctx();
if (ctx) {
ctx->add_event(std::move(event));
}
}

} // namespace

void PacketTracer::packet_in(const Packet& packet) { _BM_UNUSED(packet); }
Comment thread
c8ef marked this conversation as resolved.

void PacketTracer::packet_out(const Packet& packet) { _BM_UNUSED(packet); }

void PacketTracer::parser_start(const Packet& packet, const Parser& parser) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(parser.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::PARSER);
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::parser_done(const Packet& packet, const Parser& parser) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(parser.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::PARSER);
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::parser_extract(const Packet& packet, header_id_t header) {
_BM_UNUSED(packet);
_BM_UNUSED(header);
}

void PacketTracer::deparser_start(const Packet& packet,
const Deparser& deparser) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(deparser.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::DEPARSER);
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::deparser_done(const Packet& packet,
const Deparser& deparser) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(deparser.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::DEPARSER);
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::deparser_emit(const Packet& packet, header_id_t header) {
_BM_UNUSED(packet);
_BM_UNUSED(header);
}

void PacketTracer::checksum_update(const Packet& packet,
const Checksum& checksum) {
_BM_UNUSED(packet);
_BM_UNUSED(checksum);
}

void PacketTracer::pipeline_start(const Packet& packet,
const Pipeline& pipeline) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(pipeline.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::CONTROL);
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::pipeline_done(const Packet& packet,
const Pipeline& pipeline) {
p4::bm::TraceEvent ev;
p4::bm::PipelineStageEvent* ps = ev.mutable_pipeline_stage();
ps->set_stage_name(pipeline.get_name());
ps->set_stage_kind(p4::bm::PipelineStageEvent::CONTROL);
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
trace_add_event(packet, std::move(ev));
}

void PacketTracer::condition_eval(const Packet& packet, const Conditional& cond,
bool result) {
_BM_UNUSED(packet);
_BM_UNUSED(cond);
_BM_UNUSED(result);
}

void PacketTracer::table_hit(const Packet& packet,
const MatchTableAbstract& table,
entry_handle_t handle) {
_BM_UNUSED(packet);
_BM_UNUSED(table);
_BM_UNUSED(handle);
}

void PacketTracer::table_miss(const Packet& packet,
const MatchTableAbstract& table) {
_BM_UNUSED(packet);
_BM_UNUSED(table);
}

void PacketTracer::action_execute(const Packet& packet,
const ActionFn& action_fn,
const ActionData& action_data) {
_BM_UNUSED(packet);
_BM_UNUSED(action_fn);
_BM_UNUSED(action_data);
}

void PacketTracer::config_change() {}

} // namespace bm

#endif // BM_PACKET_TRACE_ON
19 changes: 14 additions & 5 deletions src/bm_sim/switch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <bm/bm_sim/logger.h>
#include <bm/bm_sim/options_parse.h>
#include <bm/bm_sim/packet.h>
#include <bm/bm_sim/packet_tracer.h>
#include <bm/bm_sim/periodic_task.h>
#include <bm/bm_sim/switch.h>
#include <bm/config.h>
Expand Down Expand Up @@ -535,9 +536,13 @@ SwitchWContexts::new_packet_ptr(cxt_id_t cxt_id, port_t ingress_port,
// NOLINTNEXTLINE(whitespace/operators)
PacketBuffer &&buffer) {
std::shared_lock<std::shared_mutex> lock(process_packet_mutex);
return std::unique_ptr<Packet>(new Packet(
cxt_id, ingress_port, id, 0u, ingress_length, std::move(buffer),
phv_source.get()));
auto packet = std::unique_ptr<Packet>(
new Packet(cxt_id, ingress_port, id, 0u, ingress_length,
std::move(buffer), phv_source.get()));
#ifdef BM_PACKET_TRACE_ON
PacketTracer::get()->attach_trace_ctx(packet.get());
#endif
return packet;
}

Packet
Expand All @@ -546,8 +551,12 @@ SwitchWContexts::new_packet(cxt_id_t cxt_id, port_t ingress_port,
// NOLINTNEXTLINE(whitespace/operators)
PacketBuffer &&buffer) {
std::shared_lock<std::shared_mutex> lock(process_packet_mutex);
return Packet(cxt_id, ingress_port, id, 0u, ingress_length,
std::move(buffer), phv_source.get());
Packet packet(cxt_id, ingress_port, id, 0u, ingress_length, std::move(buffer),
phv_source.get());
#ifdef BM_PACKET_TRACE_ON
PacketTracer::get()->attach_trace_ctx(&packet);
#endif
return packet;
}

int
Expand Down
48 changes: 48 additions & 0 deletions targets/simple_switch/tests/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SPDX-FileCopyrightText: 2026 Yuao Ma
#
# SPDX-License-Identifier: Apache-2.0

load("@gutil//gutil:diff_test.bzl", "cmd_diff_test")
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")

package(
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)

cc_binary(
name = "test_packet_trace",
testonly = True,
srcs = ["test_packet_trace.cpp"],
deps = [
"//:bm_headers",
"//proto:packet_trace_cc_proto",
"//src/bm_sim:bmsim",
"//targets/simple_switch:simpleswitch",
"@com_google_protobuf//:protobuf",
],
)

# One golden test per P4 pipeline: the runner sends the pipeline's scenario
# packets through an in-process simple_switch and prints the resulting packet
# traces; the output is diffed against the checked-in testdata/<pipeline>.trace
# file. To regenerate a golden file:
# bazel run <test target> -- --update
_PACKET_TRACE_GOLDEN_PIPELINES = [
"packet_redirect",
"parser_error",
"queueing",
"recirc",
"truncate",
]

[
cmd_diff_test(
name = "packet_trace_%s_diff_test" % pipeline,
actual_cmd = "$(execpath :test_packet_trace) %s $(location testdata/%s.json)" % (pipeline, pipeline),
data = ["testdata/%s.json" % pipeline],
expected = "testdata/%s.trace" % pipeline,
tools = [":test_packet_trace"],
)
for pipeline in _PACKET_TRACE_GOLDEN_PIPELINES
]
Loading
Loading