A testsuite and a benchmark for RLC AM mode
This commit is contained in:
@@ -767,8 +767,10 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity,
|
||||
nr_rlc_pdu_decoder_init(&decoder, buffer, size);
|
||||
dc = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
||||
|
||||
if (dc == 0)
|
||||
if (dc == 0) {
|
||||
LOG_D(RLC, "RLC received control PDU\n");
|
||||
return process_control_pdu(entity, buffer, size);
|
||||
}
|
||||
|
||||
/* data PDU */
|
||||
p = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
||||
@@ -839,6 +841,7 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity,
|
||||
entity->rx_list, pdu);
|
||||
|
||||
/* do reception actions (38.322 5.2.3.2.3) */
|
||||
LOG_D(RLC, "RLC received PDU sn %d so %d is_first %d is_last %d data_size = %d \n", sn, so, is_first, is_last, data_size);
|
||||
reception_actions(entity, pdu);
|
||||
|
||||
if (p) {
|
||||
@@ -852,15 +855,14 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity,
|
||||
entity->status_triggered = 1;
|
||||
if (!(sn_compare_rx(entity, sn, entity->rx_highest_status) < 0 ||
|
||||
sn_compare_rx(entity, sn, v) >= 0)) {
|
||||
LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n",
|
||||
__FILE__, __LINE__, __FUNCTION__);
|
||||
LOG_D(RLC, "warning: STATUS triggerered but should be delayed according to specs\n");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
|
||||
LOG_W(RLC, "RX error decoding PDU, discarding\n");
|
||||
goto discard;
|
||||
|
||||
discard:
|
||||
@@ -978,10 +980,7 @@ static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
|
||||
pdu_header_size = compute_pdu_header_size(entity, sdu);
|
||||
|
||||
next = calloc(1, sizeof(nr_rlc_sdu_segment_t));
|
||||
if (next == NULL) {
|
||||
LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
|
||||
exit(1);
|
||||
}
|
||||
AssertFatal(next != NULL, "out of memory\n");
|
||||
*next = *sdu;
|
||||
|
||||
over_size = pdu_header_size + sdu->size - pdu_size;
|
||||
@@ -994,7 +993,6 @@ static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
|
||||
next->size = over_size;
|
||||
next->so = sdu->so + sdu->size;
|
||||
next->is_first = 0;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -1594,6 +1592,8 @@ static int generate_retx_pdu(nr_rlc_entity_am_t *entity, char *buffer,
|
||||
entity->force_poll = 0;
|
||||
}
|
||||
|
||||
LOG_D(RLC, "RLC TX: sending sdu sn = %d is_first = %d, is_last = %d size = %d\n", sdu->sdu->sn, sdu->is_first, sdu->is_last, size);
|
||||
|
||||
int ret_size = serialize_sdu(entity, sdu, buffer, size, p);
|
||||
entity->common.stats.txpdu_pkts++;
|
||||
entity->common.stats.txpdu_bytes += ret_size;
|
||||
@@ -1612,11 +1612,14 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
||||
int p;
|
||||
|
||||
/* sn out of window (that is: we have window stalling)? do nothing */
|
||||
if (is_window_stalling(entity))
|
||||
if (is_window_stalling(entity)) {
|
||||
LOG_D(RLC, "Abort transmit due to window stall\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (entity->tx_list == NULL)
|
||||
if (entity->tx_list == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sdu = entity->tx_list;
|
||||
|
||||
@@ -1645,6 +1648,7 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
||||
|
||||
/* segment if necessary */
|
||||
if (pdu_size > size) {
|
||||
LOG_D(RLC, "Segmentation (header %d + data %d) / (%d)\n", pdu_header_size, size - pdu_header_size, pdu_size - pdu_header_size);
|
||||
nr_rlc_sdu_segment_t *next_sdu;
|
||||
next_sdu = resegment(sdu, entity, size);
|
||||
/* put the second SDU back at the head of the TX list */
|
||||
@@ -1693,7 +1697,7 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
||||
entity->common.stats.txpdu_pkts++;
|
||||
entity->common.stats.txpdu_bytes += ret_size;
|
||||
|
||||
/* No need to 'zero' time-of-arrival;
|
||||
/* No need to 'zero' time-of-arrival;
|
||||
Segmented packets do need to be duplicated in time-sensitive use cases */
|
||||
if (entity->common.avg_time_is_on) {
|
||||
uint64_t time_now = time_average_now();
|
||||
@@ -1701,6 +1705,14 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
||||
time_average_add(entity->common.txsdu_avg_time_to_tx, time_now, waited_time);
|
||||
}
|
||||
|
||||
LOG_D(RLC,
|
||||
"RLC TX: sending sdu sn = %d is_first = %d, is_last = %d header size = %d data size = %d\n",
|
||||
sdu->sdu->sn,
|
||||
sdu->is_first,
|
||||
sdu->is_last,
|
||||
pdu_header_size,
|
||||
size - pdu_header_size);
|
||||
|
||||
return ret_size;
|
||||
// return serialize_sdu(entity, sdu, buffer, size, p);
|
||||
}
|
||||
@@ -1730,14 +1742,18 @@ int nr_rlc_entity_am_generate_pdu(nr_rlc_entity_t *_entity,
|
||||
|
||||
if (status_to_report(entity)) {
|
||||
ret = generate_status(entity, buffer, size);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
LOG_D(RLC, "RLC transmit status pdu PDU\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity->retransmit_list != NULL) {
|
||||
ret = generate_retx_pdu(entity, buffer, size);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
LOG_D(RLC, "RLC retransmit PDU\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return generate_tx_pdu(entity, buffer, size);
|
||||
@@ -1756,11 +1772,7 @@ void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
|
||||
|
||||
entity->common.stats.rxsdu_pkts++;
|
||||
|
||||
if (size > NR_SDU_MAX) {
|
||||
LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
|
||||
__FILE__, __LINE__, __FUNCTION__, size);
|
||||
exit(1);
|
||||
}
|
||||
AssertFatal(size <= NR_SDU_MAX, "Fatal: SDU size too big (%d bytes)\n", size);
|
||||
|
||||
/* log SDUs rejected, at most once per second */
|
||||
if (entity->sdu_rejected != 0
|
||||
|
||||
@@ -12,3 +12,13 @@ add_test(NAME nr_rlc_tests
|
||||
COMMAND exec_nr_rlc_test.sh ${CMAKE_CURRENT_BINARY_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_executable(test_nr_rlc_am_entity test_nr_rlc_am_entity.cpp)
|
||||
target_link_libraries(test_nr_rlc_am_entity PRIVATE nr_rlc_core GTest::gtest minimal_lib)
|
||||
add_dependencies(tests test_nr_rlc_am_entity)
|
||||
add_test(NAME test_nr_rlc_am_entity COMMAND ./test_nr_rlc_am_entity)
|
||||
|
||||
add_executable(benchmark_nr_rlc_am_entity benchmark_nr_rlc_am_entity.cpp)
|
||||
target_link_libraries(benchmark_nr_rlc_am_entity PRIVATE benchmark::benchmark nr_rlc_core UTIL ${T_LIB} minimal_lib)
|
||||
add_dependencies(tests benchmark_nr_rlc_am_entity)
|
||||
add_test(NAME benchmark_nr_rlc_am_entity
|
||||
COMMAND ./benchmark_nr_rlc_am_entity)
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
#include "benchmark/benchmark.h"
|
||||
extern "C" {
|
||||
#include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h"
|
||||
#include "openair2/LAYER2/nr_rlc/nr_rlc_entity.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
#include "common/config/config_load_configmodule.h"
|
||||
extern configmodule_interface_t *uniqCfg;
|
||||
}
|
||||
#include <iostream>
|
||||
|
||||
void deliver_sdu(void *deliver_sdu_data, struct nr_rlc_entity_t *entity, char *buf, int size)
|
||||
{
|
||||
}
|
||||
void sdu_successful_delivery(void *sdu_successful_delivery_data, struct nr_rlc_entity_t *entity, int sdu_id)
|
||||
{
|
||||
}
|
||||
void max_retx_reached(void *max_retx_reached_data, struct nr_rlc_entity_t *entity)
|
||||
{
|
||||
}
|
||||
|
||||
static void BM_nr_rlc_am_entity(benchmark::State &state)
|
||||
{
|
||||
nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(10000,
|
||||
10000,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
5,
|
||||
35,
|
||||
0,
|
||||
4,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
|
||||
nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(10000,
|
||||
10000,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
5,
|
||||
35,
|
||||
0,
|
||||
4,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
|
||||
char message[] = "Message to the other RLC AM entity.";
|
||||
for (auto _ : state) {
|
||||
char pdu_buf[40];
|
||||
for (int i = 0; i < state.range(0); i++) {
|
||||
tx_entity->recv_sdu(tx_entity, message, sizeof(message), i);
|
||||
int size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
if (size != 0) {
|
||||
// 10% packet loss
|
||||
if (i % 10 != 0)
|
||||
rx_entity->recv_pdu(rx_entity, pdu_buf, size);
|
||||
}
|
||||
// Allow the lists to build up before sending messages in the other direction
|
||||
if (i % 100 == 0) {
|
||||
size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
if (size != 0) {
|
||||
// No packet lost in the other direction
|
||||
tx_entity->recv_pdu(tx_entity, pdu_buf, size);
|
||||
}
|
||||
}
|
||||
tx_entity->set_time(tx_entity, i + 1);
|
||||
rx_entity->set_time(tx_entity, i + 1);
|
||||
}
|
||||
}
|
||||
tx_entity->delete_entity(tx_entity);
|
||||
rx_entity->delete_entity(rx_entity);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_nr_rlc_am_entity)->RangeMultiplier(4)->Range(100, 20000);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
logInit();
|
||||
uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
g_log->log_component[RLC].level = -1;
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
extern "C" {
|
||||
#include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h"
|
||||
#include "openair2/LAYER2/nr_rlc/nr_rlc_entity.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
#include "common/config/config_load_configmodule.h"
|
||||
extern configmodule_interface_t *uniqCfg;
|
||||
}
|
||||
#include <iostream>
|
||||
|
||||
int sdu_delivered_count = 0;
|
||||
int sdu_acked_count = 0;
|
||||
void deliver_sdu(void *deliver_sdu_data, struct nr_rlc_entity_t *entity, char *buf, int size)
|
||||
{
|
||||
sdu_delivered_count++;
|
||||
char payload[300];
|
||||
ASSERT_LE(size, sizeof(payload));
|
||||
snprintf(payload, size, "%s", buf);
|
||||
std::cout << "Delivered sdu: " << payload << std::endl;
|
||||
}
|
||||
|
||||
void sdu_successful_delivery(void *sdu_successful_delivery_data, struct nr_rlc_entity_t *entity, int sdu_id)
|
||||
{
|
||||
sdu_acked_count++;
|
||||
std::cout << "SDU " << sdu_id << " acked" << std::endl;
|
||||
}
|
||||
|
||||
void max_retx_reached(void *max_retx_reached_data, struct nr_rlc_entity_t *entity)
|
||||
{
|
||||
}
|
||||
|
||||
TEST(nr_rlc_am_entity, test_init)
|
||||
{
|
||||
nr_rlc_entity_t *entity = new_nr_rlc_entity_am(100,
|
||||
100,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
45,
|
||||
35,
|
||||
0,
|
||||
-1,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
char buf[30];
|
||||
EXPECT_EQ(entity->generate_pdu(entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS";
|
||||
|
||||
entity->delete_entity(entity);
|
||||
}
|
||||
|
||||
TEST(nr_rlc_am_entity, test_segmentation_reassembly)
|
||||
{
|
||||
nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(100,
|
||||
100,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
45,
|
||||
35,
|
||||
0,
|
||||
-1,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
|
||||
nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(100,
|
||||
100,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
45,
|
||||
35,
|
||||
0,
|
||||
-1,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
char buf[30] = {0};
|
||||
snprintf(buf, sizeof(buf), "%s", "Message");
|
||||
EXPECT_EQ(tx_entity->generate_pdu(tx_entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS";
|
||||
|
||||
// Higher layer IF -> deliver PDCP SDU
|
||||
tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 0);
|
||||
sdu_delivered_count = 0;
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
if (sdu_delivered_count == 1) {
|
||||
break;
|
||||
}
|
||||
int size = tx_entity->generate_pdu(tx_entity, buf, 10);
|
||||
EXPECT_GT(size, 0);
|
||||
rx_entity->recv_pdu(rx_entity, buf, size);
|
||||
}
|
||||
EXPECT_EQ(sdu_delivered_count, 1);
|
||||
sdu_acked_count = 0;
|
||||
int size = rx_entity->generate_pdu(rx_entity, buf, 30);
|
||||
tx_entity->recv_pdu(tx_entity, buf, size);
|
||||
EXPECT_EQ(sdu_acked_count, 1);
|
||||
|
||||
tx_entity->delete_entity(tx_entity);
|
||||
rx_entity->delete_entity(rx_entity);
|
||||
}
|
||||
|
||||
TEST(nr_rlc_am_entity, test_ack_out_of_order)
|
||||
{
|
||||
nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(100,
|
||||
100,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
5,
|
||||
35,
|
||||
0,
|
||||
4,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
|
||||
nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(100,
|
||||
100,
|
||||
deliver_sdu,
|
||||
NULL,
|
||||
sdu_successful_delivery,
|
||||
NULL,
|
||||
max_retx_reached,
|
||||
NULL,
|
||||
5,
|
||||
35,
|
||||
0,
|
||||
4,
|
||||
-1,
|
||||
8,
|
||||
12);
|
||||
|
||||
char buf[30] = {0};
|
||||
EXPECT_EQ(tx_entity->generate_pdu(tx_entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS";
|
||||
// Higher layer IF -> deliver PDCP SDU
|
||||
// Generate 4 PDCP SDUS
|
||||
snprintf(buf, sizeof(buf), "%s", "Message 1");
|
||||
tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 0);
|
||||
snprintf(buf, sizeof(buf), "%s", "Message 2");
|
||||
tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 1);
|
||||
snprintf(buf, sizeof(buf), "%s", "Message 3");
|
||||
tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 2);
|
||||
|
||||
sdu_delivered_count = 0;
|
||||
sdu_acked_count = 0;
|
||||
char pdu_buf[40];
|
||||
uint64_t time = 0;
|
||||
for (auto i = 0; i < 3; i++) {
|
||||
int size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
EXPECT_GT(size, 0);
|
||||
// Do not deliver PDU 2 to RX entity
|
||||
if (i != 1) {
|
||||
rx_entity->recv_pdu(rx_entity, pdu_buf, size);
|
||||
}
|
||||
tx_entity->set_time(tx_entity, time);
|
||||
rx_entity->set_time(tx_entity, time);
|
||||
time++;
|
||||
}
|
||||
EXPECT_EQ(sdu_delivered_count, 2) << "Expected 2 out of 3 SDUs delivered";
|
||||
|
||||
int size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
EXPECT_GT(size, 0);
|
||||
tx_entity->recv_pdu(tx_entity, pdu_buf, size);
|
||||
|
||||
// Triggers t-poll-retransmit and retransmission of the lost PDU
|
||||
for (auto i = 0; i < 5; i++) {
|
||||
tx_entity->set_time(tx_entity, time);
|
||||
rx_entity->set_time(tx_entity, time);
|
||||
size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
if (size != 0)
|
||||
rx_entity->recv_pdu(rx_entity, pdu_buf, size);
|
||||
size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf));
|
||||
if (size != 0)
|
||||
tx_entity->recv_pdu(tx_entity, pdu_buf, size);
|
||||
time++;
|
||||
}
|
||||
EXPECT_EQ(sdu_acked_count, 3);
|
||||
EXPECT_EQ(sdu_delivered_count, 3);
|
||||
|
||||
tx_entity->delete_entity(tx_entity);
|
||||
rx_entity->delete_entity(rx_entity);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
logInit();
|
||||
uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
g_log->log_component[RLC].level = OAILOG_TRACE;
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user