Skip to content

Commit 2712f9c

Browse files
committed
minimal MVP
Signed-off-by: c8ef <c8ef@outlook.com>
1 parent d585008 commit 2712f9c

19 files changed

Lines changed: 1301 additions & 8 deletions

.github/workflows/bazel.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@ jobs:
6161
run: bazel build //targets/simple_switch_grpc:simple_switch_grpc
6262

6363
- name: Test
64-
run: bazel test //targets/simple_switch_grpc/tests/...
64+
run: |
65+
bazel test //targets/simple_switch/tests/...
66+
bazel test //targets/simple_switch_grpc/tests/...

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ expand_template(
2828
substitutions = {
2929
# disabled
3030
"#cmakedefine BM_DEBUG_ON @BM_DEBUG_ON@": "/* #undef BM_DEBUG_ON */",
31-
"#cmakedefine BM_ELOG_ON @BM_ELOG_ON@": "/* #undef BM_ELOG_ON */",
3231
# enabled
32+
"#cmakedefine BM_ELOG_ON @BM_ELOG_ON@": "#define BM_ELOG_ON 1",
3333
"#cmakedefine BM_ENABLE_MODULES @BM_ENABLE_MODULES@": "#define BM_ENABLE_MODULES 1",
3434
"#cmakedefine BM_HAVE_ALGORITHM @BM_HAVE_ALGORITHM@": "#define BM_HAVE_ALGORITHM 1",
3535
"#cmakedefine BM_HAVE_ARRAY @BM_HAVE_ARRAY@": "#define BM_HAVE_ARRAY 1",

MODULE.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ bazel_dep(name = "rules_cc", version = "0.2.18")
2929
bazel_dep(name = "xxhash", version = "0.8.3.bcr.1")
3030

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

3334
git_override(
3435
module_name = "com_github_p4lang_pi",
3536
commit = "0aa6c69140045b8a74575dbb7818664dd1066504",
3637
init_submodules = True,
3738
remote = "https://github.com/p4lang/PI.git",
3839
)
40+
41+
single_version_override(
42+
module_name = "abseil-cpp",
43+
version = "20250814.1",
44+
)

include/bm/bm_sim/packet_tracer.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <mutex>
2121
#include <string>
2222

23+
#include "event_observer.h"
2324
#include "packet_trace.pb.h"
2425

2526
namespace bm {
@@ -34,6 +35,7 @@ class TraceTreeWrapper {
3435
private:
3536
friend class PacketTraceContext;
3637

38+
std::mutex mutex_;
3739
p4::bm::PacketTrace root_trace_;
3840
};
3941

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

51+
//! Add a trace event. The event is moved into the internal trace.
52+
void add_event(p4::bm::TraceEvent event);
53+
4954
std::shared_ptr<TraceTreeWrapper> get_wrapper() const {
5055
return tree_wrapper_;
5156
}
@@ -56,7 +61,7 @@ class PacketTraceContext {
5661
};
5762

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

79+
//! Attach a fresh trace context to a packet entering the switch.
80+
void attach_trace_ctx(Packet* packet);
81+
82+
void packet_in(const Packet& packet) override;
83+
void packet_out(const Packet& packet) override;
84+
85+
void parser_start(const Packet& packet, const Parser& parser) override;
86+
void parser_done(const Packet& packet, const Parser& parser) override;
87+
void parser_extract(const Packet& packet, header_id_t header) override;
88+
89+
void deparser_start(const Packet& packet, const Deparser& deparser) override;
90+
void deparser_done(const Packet& packet, const Deparser& deparser) override;
91+
void deparser_emit(const Packet& packet, header_id_t header) override;
92+
93+
void checksum_update(const Packet& packet, const Checksum& checksum) override;
94+
95+
void pipeline_start(const Packet& packet, const Pipeline& pipeline) override;
96+
void pipeline_done(const Packet& packet, const Pipeline& pipeline) override;
97+
98+
void condition_eval(const Packet& packet, const Conditional& cond,
99+
bool result) override;
100+
void table_hit(const Packet& packet, const MatchTableAbstract& table,
101+
entry_handle_t handle) override;
102+
void table_miss(const Packet& packet,
103+
const MatchTableAbstract& table) override;
104+
105+
void action_execute(const Packet& packet, const ActionFn& action_fn,
106+
const ActionData& action_data) override;
107+
108+
void config_change() override;
109+
74110
private:
75111
PacketTracer() = default;
76112

src/bm_sim/options_parse.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
#include <bm/bm_sim/P4Objects.h>
1212
#include <bm/bm_sim/event_logger.h>
13+
#include <bm/bm_sim/event_observer.h>
1314
#include <bm/bm_sim/logger.h>
1415
#include <bm/bm_sim/options_parse.h>
16+
#include <bm/bm_sim/packet_tracer.h>
1517
#include <bm/config.h>
1618

1719
#include <cassert>
@@ -109,6 +111,11 @@ OptionsParser::parse(int argc, char *argv[], TargetParserIface *tp,
109111
("nanolog", po::value<std::string>(),
110112
"IPC socket to use for nanomsg pub/sub logs "
111113
"(default: no nanomsg logging")
114+
#ifdef BM_PACKET_TRACE_ON
115+
("trace-dir", po::value<std::string>(),
116+
"Enable structured packet tracing and write textproto traces to the "
117+
"specified directory")
118+
#endif
112119
("log-console",
113120
"Enable logging on stdout")
114121
("log-file", po::value<std::string>(),
@@ -270,6 +277,16 @@ OptionsParser::parse(int argc, char *argv[], TargetParserIface *tp,
270277
#endif
271278
}
272279

280+
#ifdef BM_PACKET_TRACE_ON
281+
if (vm.count("trace-dir")) {
282+
std::string trace_dir = vm["trace-dir"].as<std::string>();
283+
if (!trace_dir.empty()) {
284+
PacketTracer::get()->set_output_dir(trace_dir);
285+
EventObserverRegistry::get()->register_observer(PacketTracer::get());
286+
}
287+
}
288+
#endif
289+
273290
if (vm.count("log-console") && vm.count("log-file")) {
274291
outstream << "Error: --log-console and --log-file are exclusive\n";
275292
exit(1);

src/bm_sim/packet_tracer.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
#ifdef BM_PACKET_TRACE_ON
88

9+
#include <bm/bm_sim/_assert.h>
10+
#include <bm/bm_sim/deparser.h>
911
#include <bm/bm_sim/logger.h>
1012
#include <bm/bm_sim/packet.h>
13+
#include <bm/bm_sim/parser.h>
14+
#include <bm/bm_sim/pipeline.h>
1115
#include <google/protobuf/text_format.h>
1216

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

48+
void PacketTraceContext::add_event(p4::bm::TraceEvent event) {
49+
std::lock_guard<std::mutex> lock(tree_wrapper_->mutex_);
50+
*current_node_->add_events() = std::move(event);
51+
}
52+
4453
// ---------- PacketTracer ----------
4554

4655
void PacketTracer::set_output_dir(const std::string& output_dir) {
@@ -75,6 +84,134 @@ void PacketTracer::flush_trace(const p4::bm::PacketTrace& trace) {
7584
}
7685
}
7786

87+
void PacketTracer::attach_trace_ctx(Packet* packet) {
88+
if (!enabled_) return;
89+
auto ctx = std::make_unique<PacketTraceContext>(packet->get_packet_id(),
90+
packet->get_ingress_port());
91+
packet->set_trace_ctx(std::move(ctx));
92+
}
93+
94+
namespace {
95+
96+
// Add a TraceEvent to the packet's trace context (if present).
97+
void trace_add_event(const Packet& packet, p4::bm::TraceEvent event) {
98+
auto* ctx = packet.get_trace_ctx();
99+
if (ctx) {
100+
ctx->add_event(std::move(event));
101+
}
102+
}
103+
104+
} // namespace
105+
106+
void PacketTracer::packet_in(const Packet& packet) { _BM_UNUSED(packet); }
107+
108+
void PacketTracer::packet_out(const Packet& packet) { _BM_UNUSED(packet); }
109+
110+
void PacketTracer::parser_start(const Packet& packet, const Parser& parser) {
111+
p4::bm::TraceEvent ev;
112+
auto* ps = ev.mutable_pipeline_stage();
113+
ps->set_stage_name(parser.get_name());
114+
ps->set_stage_kind(p4::bm::PipelineStageEvent::PARSER);
115+
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
116+
trace_add_event(packet, std::move(ev));
117+
}
118+
119+
void PacketTracer::parser_done(const Packet& packet, const Parser& parser) {
120+
p4::bm::TraceEvent ev;
121+
auto* ps = ev.mutable_pipeline_stage();
122+
ps->set_stage_name(parser.get_name());
123+
ps->set_stage_kind(p4::bm::PipelineStageEvent::PARSER);
124+
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
125+
trace_add_event(packet, std::move(ev));
126+
}
127+
128+
void PacketTracer::parser_extract(const Packet& packet, header_id_t header) {
129+
_BM_UNUSED(packet);
130+
_BM_UNUSED(header);
131+
}
132+
133+
void PacketTracer::deparser_start(const Packet& packet,
134+
const Deparser& deparser) {
135+
p4::bm::TraceEvent ev;
136+
auto* ps = ev.mutable_pipeline_stage();
137+
ps->set_stage_name(deparser.get_name());
138+
ps->set_stage_kind(p4::bm::PipelineStageEvent::DEPARSER);
139+
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
140+
trace_add_event(packet, std::move(ev));
141+
}
142+
143+
void PacketTracer::deparser_done(const Packet& packet,
144+
const Deparser& deparser) {
145+
p4::bm::TraceEvent ev;
146+
auto* ps = ev.mutable_pipeline_stage();
147+
ps->set_stage_name(deparser.get_name());
148+
ps->set_stage_kind(p4::bm::PipelineStageEvent::DEPARSER);
149+
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
150+
trace_add_event(packet, std::move(ev));
151+
}
152+
153+
void PacketTracer::deparser_emit(const Packet& packet, header_id_t header) {
154+
_BM_UNUSED(packet);
155+
_BM_UNUSED(header);
156+
}
157+
158+
void PacketTracer::checksum_update(const Packet& packet,
159+
const Checksum& checksum) {
160+
_BM_UNUSED(packet);
161+
_BM_UNUSED(checksum);
162+
}
163+
164+
void PacketTracer::pipeline_start(const Packet& packet,
165+
const Pipeline& pipeline) {
166+
p4::bm::TraceEvent ev;
167+
auto* ps = ev.mutable_pipeline_stage();
168+
ps->set_stage_name(pipeline.get_name());
169+
ps->set_stage_kind(p4::bm::PipelineStageEvent::CONTROL);
170+
ps->set_direction(p4::bm::PipelineStageEvent::ENTER);
171+
trace_add_event(packet, std::move(ev));
172+
}
173+
174+
void PacketTracer::pipeline_done(const Packet& packet,
175+
const Pipeline& pipeline) {
176+
p4::bm::TraceEvent ev;
177+
auto* ps = ev.mutable_pipeline_stage();
178+
ps->set_stage_name(pipeline.get_name());
179+
ps->set_stage_kind(p4::bm::PipelineStageEvent::CONTROL);
180+
ps->set_direction(p4::bm::PipelineStageEvent::EXIT);
181+
trace_add_event(packet, std::move(ev));
182+
}
183+
184+
void PacketTracer::condition_eval(const Packet& packet, const Conditional& cond,
185+
bool result) {
186+
_BM_UNUSED(packet);
187+
_BM_UNUSED(cond);
188+
_BM_UNUSED(result);
189+
}
190+
191+
void PacketTracer::table_hit(const Packet& packet,
192+
const MatchTableAbstract& table,
193+
entry_handle_t handle) {
194+
_BM_UNUSED(packet);
195+
_BM_UNUSED(table);
196+
_BM_UNUSED(handle);
197+
}
198+
199+
void PacketTracer::table_miss(const Packet& packet,
200+
const MatchTableAbstract& table) {
201+
_BM_UNUSED(packet);
202+
_BM_UNUSED(table);
203+
}
204+
205+
void PacketTracer::action_execute(const Packet& packet,
206+
const ActionFn& action_fn,
207+
const ActionData& action_data) {
208+
_BM_UNUSED(packet);
209+
_BM_UNUSED(action_fn);
210+
_BM_UNUSED(action_data);
211+
}
212+
213+
void PacketTracer::config_change() {}
214+
78215
} // namespace bm
79216

80217
#endif // BM_PACKET_TRACE_ON

src/bm_sim/switch.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <bm/bm_sim/logger.h>
1717
#include <bm/bm_sim/options_parse.h>
1818
#include <bm/bm_sim/packet.h>
19+
#include <bm/bm_sim/packet_tracer.h>
1920
#include <bm/bm_sim/periodic_task.h>
2021
#include <bm/bm_sim/switch.h>
2122
#include <bm/config.h>
@@ -535,9 +536,13 @@ SwitchWContexts::new_packet_ptr(cxt_id_t cxt_id, port_t ingress_port,
535536
// NOLINTNEXTLINE(whitespace/operators)
536537
PacketBuffer &&buffer) {
537538
std::shared_lock<std::shared_mutex> lock(process_packet_mutex);
538-
return std::unique_ptr<Packet>(new Packet(
539-
cxt_id, ingress_port, id, 0u, ingress_length, std::move(buffer),
540-
phv_source.get()));
539+
auto packet = std::unique_ptr<Packet>(
540+
new Packet(cxt_id, ingress_port, id, 0u, ingress_length,
541+
std::move(buffer), phv_source.get()));
542+
#ifdef BM_PACKET_TRACE_ON
543+
PacketTracer::get()->attach_trace_ctx(packet.get());
544+
#endif
545+
return packet;
541546
}
542547

543548
Packet
@@ -546,8 +551,12 @@ SwitchWContexts::new_packet(cxt_id_t cxt_id, port_t ingress_port,
546551
// NOLINTNEXTLINE(whitespace/operators)
547552
PacketBuffer &&buffer) {
548553
std::shared_lock<std::shared_mutex> lock(process_packet_mutex);
549-
return Packet(cxt_id, ingress_port, id, 0u, ingress_length,
550-
std::move(buffer), phv_source.get());
554+
Packet packet(cxt_id, ingress_port, id, 0u, ingress_length, std::move(buffer),
555+
phv_source.get());
556+
#ifdef BM_PACKET_TRACE_ON
557+
PacketTracer::get()->attach_trace_ctx(&packet);
558+
#endif
559+
return packet;
551560
}
552561

553562
int
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SPDX-FileCopyrightText: 2026 Yuao Ma
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
load("@gutil//gutil:diff_test.bzl", "cmd_diff_test")
6+
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
7+
8+
package(
9+
default_visibility = ["//visibility:public"],
10+
licenses = ["notice"],
11+
)
12+
13+
cc_binary(
14+
name = "test_packet_trace",
15+
testonly = True,
16+
srcs = ["test_packet_trace.cpp"],
17+
deps = [
18+
"//:bm_headers",
19+
"//proto:packet_trace_cc_proto",
20+
"//src/bm_sim:bmsim",
21+
"//targets/simple_switch:simpleswitch",
22+
"@com_google_protobuf//:protobuf",
23+
],
24+
)
25+
26+
# One golden test per P4 pipeline: the runner sends the pipeline's scenario
27+
# packets through an in-process simple_switch and prints the resulting packet
28+
# traces; the output is diffed against the checked-in .expected file.
29+
# To regenerate a golden file:
30+
# bazel run <test target> -- --update
31+
_PACKET_TRACE_GOLDEN_PIPELINES = [
32+
"packet_redirect",
33+
"parser_error",
34+
"queueing",
35+
"recirc",
36+
"truncate",
37+
]
38+
39+
[
40+
cmd_diff_test(
41+
name = "packet_trace_%s_diff_test" % pipeline,
42+
actual_cmd = "$(execpath :test_packet_trace) %s $(location testdata/%s.json)" % (pipeline, pipeline),
43+
data = ["testdata/%s.json" % pipeline],
44+
expected = "testdata/%s.trace" % pipeline,
45+
tools = [":test_packet_trace"],
46+
)
47+
for pipeline in _PACKET_TRACE_GOLDEN_PIPELINES
48+
]

0 commit comments

Comments
 (0)