Skip to content

Commit a4abe16

Browse files
authored
UCP/GTEST: Unit tests for port_speed event-driven load balancer (#11109)
* UCP/GTEST: Unit tests for port_speed event-driven load balancer * UCP/GTEST: Fixed proto mock test with ibv_query_port_speed
1 parent c8bdca9 commit a4abe16

2 files changed

Lines changed: 205 additions & 19 deletions

File tree

buildlib/tools/coverity.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ run_coverity() {
101101
cov-build --dir $cov_build $MAKEP all
102102
if [ "${ucx_build_type}" == "devel" ]; then
103103
cov-manage-emit --dir $cov_build --tu-pattern "file('.*/test/gtest/common/googletest/*')" delete || :
104+
# Needed to suppress false positives in std::function
105+
COV_OPT="$COV_OPT --checker-option UNINIT_CTOR:ctor_func:swap"
104106
fi
105107
cov-analyze --jobs $parallel_jobs $COV_OPT --disable PARSE_ERROR --security --concurrency --dir $cov_build
106108
nerrors=$(cov-format-errors --dir $cov_build | awk '/Processing [0-9]+ errors?/ { print $2 }')

test/gtest/ucp/test_ucp_proto_mock.cc

Lines changed: 203 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ extern "C" {
1212
#include <uct/base/uct_iface.h>
1313
#include <ucp/proto/proto_debug.h>
1414
#include <ucp/proto/proto_select.inl>
15+
16+
#if HAVE_IB
17+
#include <uct/ib/base/ib_md.h>
18+
#endif
1519
}
1620

1721
class mock_iface {
1822
public:
19-
/* Can't use std::function due to coverity errors */
20-
using iface_attr_func_t = void (*)(uct_iface_attr&);
21-
using perf_attr_func_t = void (*)(uct_perf_attr_t&);
23+
using iface_attr_func_t = std::function<void(uct_iface_attr&)>;
24+
using perf_attr_func_t = std::function<void(uct_perf_attr_t&)>;
2225

23-
mock_iface() : m_tl(nullptr)
26+
mock_iface() : m_tl(nullptr), m_real_md(nullptr)
2427
{
2528
ucs_assert(m_self == nullptr);
2629
m_self = this;
@@ -37,13 +40,12 @@ class mock_iface {
3740
m_mock.cleanup();
3841
}
3942

40-
void add_mock_iface(
41-
const std::string &dev_name = "mock",
42-
iface_attr_func_t cb = [](uct_iface_attr_t &iface_attr) {},
43-
perf_attr_func_t perf_cb = cx7_perf_mock)
43+
void add_mock_iface(const std::string &dev_name = "mock",
44+
iface_attr_func_t cb = [](uct_iface_attr_t &iface_attr) {},
45+
perf_attr_func_t perf_cb = default_perf_mock)
4446
{
45-
m_iface_attrs_funcs[dev_name] = cb;
46-
m_perf_attrs_funcs[dev_name] = perf_cb;
47+
m_iface_attrs_funcs[dev_name] = std::move(cb);
48+
m_perf_attrs_funcs[dev_name] = std::move(perf_cb);
4749
}
4850

4951
void mock_transport(const std::string &tl_name)
@@ -68,6 +70,17 @@ class mock_iface {
6870
FAIL() << "Transport " << tl_name << " not found";
6971
}
7072

73+
#if HAVE_IB
74+
void ib_event(enum ibv_event_type event_type, uint8_t port_num)
75+
{
76+
uct_ib_async_event_t event = {};
77+
event.event_type = event_type;
78+
event.port_num = port_num;
79+
uct_ib_md_t *md = reinterpret_cast<uct_ib_md_t *>(m_self->m_real_md);
80+
uct_ib_handle_async_event(&md->dev, &event);
81+
}
82+
#endif
83+
7184
private:
7285
static ucs_status_t
7386
query_devices_mock(uct_md_h md, uct_tl_device_resource_t **tl_devices_p,
@@ -83,6 +96,7 @@ class mock_iface {
8396
const char *first_dev_name = (*tl_devices_p)[0].name;
8497
if (m_self->m_real_dev_name.empty()) {
8598
m_self->m_real_dev_name = first_dev_name;
99+
m_self->m_real_md = md;
86100
} else if (m_self->m_real_dev_name != first_dev_name) {
87101
*num_tl_devices_p = 0;
88102
return UCS_OK;
@@ -151,21 +165,42 @@ class mock_iface {
151165
static ucs_status_t perf_mock(uct_iface_h iface, uct_perf_attr_t *perf_attr)
152166
{
153167
uct_base_iface_t *base = ucs_derived_of(iface, uct_base_iface_t);
168+
uct_iface_attr_t iface_attr;
169+
ucs_status_t status;
154170

155171
UCS_MOCK_ORIG_FUNC(m_self->m_mock,
156172
&base->internal_ops->iface_estimate_perf, iface,
157173
perf_attr);
158174

175+
if (perf_attr->field_mask & (UCT_PERF_ATTR_FIELD_BANDWIDTH |
176+
UCT_PERF_ATTR_FIELD_PATH_BANDWIDTH)) {
177+
status = iface_query_mock(iface, &iface_attr);
178+
if (status != UCS_OK) {
179+
return status;
180+
}
181+
182+
if (perf_attr->field_mask & UCT_PERF_ATTR_FIELD_BANDWIDTH) {
183+
perf_attr->bandwidth = iface_attr.bandwidth;
184+
}
185+
186+
if (perf_attr->field_mask & UCT_PERF_ATTR_FIELD_PATH_BANDWIDTH) {
187+
perf_attr->path_bandwidth = iface_attr.bandwidth;
188+
}
189+
}
190+
159191
std::string &iface_name = m_self->m_iface_names[base];
160192
auto it = m_self->m_perf_attrs_funcs.find(iface_name);
161193
(it->second)(*perf_attr);
162194
return UCS_OK;
163195
}
164196

165-
static void cx7_perf_mock(uct_perf_attr_t& perf_attr)
197+
static void default_perf_mock(uct_perf_attr_t& perf_attr)
166198
{
167-
perf_attr.path_bandwidth.shared = 0.95 * perf_attr.bandwidth.shared;
168-
perf_attr.path_bandwidth.dedicated = 0;
199+
if (ucs_test_all_flags(perf_attr.field_mask,
200+
UCT_PERF_ATTR_FIELD_BANDWIDTH |
201+
UCT_PERF_ATTR_FIELD_PATH_BANDWIDTH)) {
202+
perf_attr.path_bandwidth = perf_attr.bandwidth;
203+
}
169204
}
170205

171206
/* We have to use singleton to mock C functions */
@@ -177,6 +212,7 @@ class mock_iface {
177212
std::map<std::string, iface_attr_func_t> m_iface_attrs_funcs;
178213
std::map<std::string, perf_attr_func_t> m_perf_attrs_funcs;
179214
std::string m_real_dev_name;
215+
uct_md_h m_real_md;
180216
};
181217

182218
mock_iface *mock_iface::m_self = nullptr;
@@ -554,8 +590,9 @@ class test_ucp_proto_mock : public ucp_test, public mock_iface {
554590
return {rkey, ucp_rkey_destroy};
555591
}
556592

557-
void send_recv_rma_put(size_t size,
558-
ucs_memory_type_t mem_type = UCS_MEMORY_TYPE_HOST)
593+
void send_recv_rma(size_t size, ucp_operation_id_t op_id,
594+
unsigned rkey_cfg_index = 1,
595+
ucs_memory_type_t mem_type = UCS_MEMORY_TYPE_HOST)
559596
{
560597
mem_buffer recv_buf(size, mem_type);
561598
recv_buf.pattern_fill(1);
@@ -568,10 +605,27 @@ class test_ucp_proto_mock : public ucp_test, public mock_iface {
568605

569606
ucp_request_param_t req_param;
570607
req_param.op_attr_mask = 0;
571-
auto sptr = ucp_put_nbx(sender().ep(), send_buf.ptr(), size,
572-
(uint64_t)recv_buf.ptr(), rkey, &req_param);
608+
ucs_status_ptr_t sptr;
609+
if (op_id == UCP_OP_ID_PUT) {
610+
sptr = ucp_put_nbx(sender().ep(), send_buf.ptr(), size,
611+
(uint64_t)recv_buf.ptr(), rkey, &req_param);
612+
} else if (op_id == UCP_OP_ID_GET) {
613+
sptr = ucp_get_nbx(sender().ep(), send_buf.ptr(), size,
614+
(uint64_t)recv_buf.ptr(), rkey, &req_param);
615+
} else {
616+
sptr = nullptr;
617+
FAIL() << "Invalid operation ID: " << op_id;
618+
}
619+
573620
EXPECT_EQ(UCS_OK, request_wait(sptr));
574-
recv_buf.pattern_check(2);
621+
622+
if (op_id == UCP_OP_ID_PUT) {
623+
recv_buf.pattern_check(2);
624+
} else if (op_id == UCP_OP_ID_GET) {
625+
send_buf.pattern_check(1);
626+
}
627+
628+
EXPECT_EQ(rkey->cfg_index, rkey_cfg_index);
575629
}
576630
};
577631

@@ -705,7 +759,7 @@ UCS_TEST_P(test_ucp_proto_mock_rcx, rndv_4_paths,
705759
UCS_TEST_P(test_ucp_proto_mock_rcx, rma_put_2_lanes,
706760
"IB_NUM_PATHS?=1", "MAX_RMA_RAILS=2")
707761
{
708-
send_recv_rma_put(64 * UCS_KBYTE);
762+
send_recv_rma(64 * UCS_KBYTE, UCP_OP_ID_PUT);
709763

710764
ucp_proto_select_key_t key = any_key();
711765
key.param.op_id_flags = UCP_OP_ID_PUT;
@@ -1165,3 +1219,133 @@ UCS_TEST_P(test_ucp_proto_mock_rcx_twins_get_inline_0,
11651219

11661220
UCP_INSTANTIATE_TEST_CASE_TLS(test_ucp_proto_mock_rcx_twins_get_inline_0, rcx,
11671221
"rc_x")
1222+
1223+
1224+
#if HAVE_DECL_IBV_EVENT_PORT_SPEED_CHANGE
1225+
1226+
class test_ucp_proto_mock_rcx_speed_change : public test_ucp_proto_mock {
1227+
public:
1228+
test_ucp_proto_mock_rcx_speed_change()
1229+
{
1230+
mock_transport("rc_mlx5");
1231+
}
1232+
1233+
virtual void init() override
1234+
{
1235+
m_port_speed["mock_0:1"] = 28e9;
1236+
add_mock_iface("mock_0:1", [](uct_iface_attr_t &iface_attr) {
1237+
iface_attr.cap.get.min_zcopy = 0;
1238+
iface_attr.bandwidth.shared = 28e9;
1239+
iface_attr.latency.c = 600e-9;
1240+
iface_attr.latency.m = 1e-9;
1241+
}, [this](uct_perf_attr_t &perf_attr) {
1242+
perf_attr.bandwidth.shared = this->m_port_speed["mock_0:1"];
1243+
perf_attr.path_bandwidth = perf_attr.bandwidth;
1244+
});
1245+
1246+
m_port_speed["mock_1:1"] = 24e9;
1247+
add_mock_iface("mock_1:1", [](uct_iface_attr_t &iface_attr) {
1248+
iface_attr.cap.get.min_zcopy = 0;
1249+
iface_attr.bandwidth.shared = 24e9;
1250+
iface_attr.latency.c = 500e-9;
1251+
iface_attr.latency.m = 1e-9;
1252+
}, [this](uct_perf_attr_t &perf_attr) {
1253+
perf_attr.bandwidth.shared = this->m_port_speed["mock_1:1"];
1254+
perf_attr.path_bandwidth = perf_attr.bandwidth;
1255+
});
1256+
test_ucp_proto_mock::init();
1257+
}
1258+
1259+
void set_port_speed(const std::string &iface_name, double port_speed)
1260+
{
1261+
m_port_speed[iface_name] = port_speed;
1262+
1263+
ib_event(IBV_EVENT_PORT_SPEED_CHANGE, 1);
1264+
while (progress());
1265+
}
1266+
1267+
void test_port_speed(std::function<void(unsigned)> send,
1268+
ucp_operation_id_t op_id)
1269+
{
1270+
// One EP & rkey config created during connection establishment
1271+
ucp_worker_h worker = sender().worker();
1272+
EXPECT_EQ(worker->rkey_config_count, 1);
1273+
EXPECT_EQ(worker->ep_config.length, 1);
1274+
1275+
// New rkey config created during first operation
1276+
send(1);
1277+
EXPECT_EQ(worker->rkey_config_count, 2);
1278+
EXPECT_EQ(worker->ep_config.length, 1);
1279+
1280+
// Existing rkey config is used during second operation
1281+
send(1);
1282+
EXPECT_EQ(worker->rkey_config_count, 2);
1283+
EXPECT_EQ(worker->ep_config.length, 1);
1284+
1285+
ucp_proto_select_key_t key = any_key();
1286+
key.param.op_id_flags = op_id;
1287+
key.param.op_attr = 0;
1288+
1289+
check_rkey_config(sender(), {
1290+
{0, INF, "zero-copy", "47% on rc_mlx5/mock_1:1 and 53% on rc_mlx5/mock_0:1"},
1291+
}, key, 1);
1292+
1293+
// Reduce port_speed of mock_0:1 by 50%, new EP & rkey configs are created
1294+
set_port_speed("mock_0:1", 14e9);
1295+
send(2);
1296+
EXPECT_EQ(worker->rkey_config_count, 3);
1297+
EXPECT_EQ(worker->ep_config.length, 2);
1298+
1299+
// Slightly change port_speed, so that quantized value remains the same
1300+
// This shouldn't affect EP or rkey config
1301+
set_port_speed("mock_0:1", 14.5e9);
1302+
send(2);
1303+
EXPECT_EQ(worker->rkey_config_count, 3);
1304+
EXPECT_EQ(worker->ep_config.length, 2);
1305+
1306+
check_rkey_config(sender(), {
1307+
{0, INF, "zero-copy", "64% on rc_mlx5/mock_1:1 and 36% on rc_mlx5/mock_0:1"},
1308+
}, key, 2);
1309+
1310+
// Reduce port_speed of mock_1:1 to be equal with mock_0:1,
1311+
// new EP & rkey configs are created
1312+
set_port_speed("mock_1:1", 14e9);
1313+
send(3);
1314+
EXPECT_EQ(worker->rkey_config_count, 4);
1315+
EXPECT_EQ(worker->ep_config.length, 3);
1316+
1317+
check_rkey_config(sender(), {
1318+
{0, INF, "zero-copy", "50% on rc_mlx5/mock_1:1 and 50% on rc_mlx5/mock_0:1"},
1319+
}, key, 3);
1320+
1321+
// Reset port_speeds to initial values, should switch to initial configs
1322+
set_port_speed("mock_0:1", 28e9);
1323+
set_port_speed("mock_1:1", 24e9);
1324+
send(1);
1325+
EXPECT_EQ(worker->rkey_config_count, 4);
1326+
EXPECT_EQ(worker->ep_config.length, 3);
1327+
}
1328+
1329+
private:
1330+
std::map<std::string, double> m_port_speed;
1331+
};
1332+
1333+
UCS_TEST_P(test_ucp_proto_mock_rcx_speed_change, rma_put,
1334+
"IB_NUM_PATHS?=1", "MAX_RMA_RAILS=2", "ZCOPY_THRESH=0")
1335+
{
1336+
test_port_speed([this](unsigned rkey_cfg_index) {
1337+
send_recv_rma(64 * UCS_KBYTE, UCP_OP_ID_PUT, rkey_cfg_index);
1338+
}, UCP_OP_ID_PUT);
1339+
}
1340+
1341+
UCS_TEST_P(test_ucp_proto_mock_rcx_speed_change, rma_get,
1342+
"IB_NUM_PATHS?=1", "MAX_RMA_RAILS=2", "ZCOPY_THRESH=0")
1343+
{
1344+
test_port_speed([this](unsigned rkey_cfg_index) {
1345+
send_recv_rma(64 * UCS_KBYTE, UCP_OP_ID_GET, rkey_cfg_index);
1346+
}, UCP_OP_ID_GET);
1347+
}
1348+
1349+
UCP_INSTANTIATE_TEST_CASE_TLS(test_ucp_proto_mock_rcx_speed_change, rcx, "rc_x")
1350+
1351+
#endif // HAVE_DECL_IBV_EVENT_PORT_SPEED_CHANGE

0 commit comments

Comments
 (0)