Compare commits

...

7 Commits

Author SHA1 Message Date
Ilario FAVERO
71fb81e13a fix conversion routine 2022-09-05 15:17:32 +02:00
Ilario FAVERO
06971bd8e6 fixes further to MR feedback 2022-09-02 16:44:37 +02:00
Ilario FAVERO
48906bf385 new handling of statistics for cpu and protocol via CLI telnet 2022-07-01 17:58:36 +02:00
Ilario FAVERO
5b97df2971 new handling of statistics for cpu and protocol via CLI telnet 2022-07-01 17:58:04 +02:00
Ilario FAVERO
1c0ff9b68d added new display function for cpu statistics that mimics the ones dumped in the file 2022-07-01 17:56:29 +02:00
Ilario FAVERO
fbdc6f0fd0 Changed default behavior for statistics generation. You can disable them via commandline arg --disable-stats 2022-06-13 11:34:05 +02:00
Ilario FAVERO
6c201d6239 new command line arg for enabling stats 2022-06-09 12:53:44 +02:00
12 changed files with 738 additions and 19 deletions

View File

@@ -1212,6 +1212,7 @@ add_library(UTIL
${OPENAIR2_DIR}/UTIL/MATH/oml.c
${OPENAIR2_DIR}/UTIL/OPT/probe.c
${OPENAIR_DIR}/common/utils/threadPool/thread-pool.c
${OPENAIR_DIR}/common/utils/cpustats.c
${OPENAIR_DIR}/common/utils/utils.c
${OPENAIR_DIR}/common/utils/system.c
${OPENAIR_DIR}/common/utils/backtrace.c

319
common/utils/cpustats.c Normal file
View File

@@ -0,0 +1,319 @@
/*
* 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
*/
/*
* This file implements the cpu statistics for Level 1 in gnb and in UE.
* Caveats: Note that we did not merge this development into file telnetsrv_gnb_measurements.c and telnetsrv_5Gue_measurements.c
* belonging to telnetsrv shared library because we needed to provide an API for main programs exectuable without inserting a new
* dependency on the telnetsrv shared library.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <stdbool.h>
#include <errno.h>
#include <linux/sched.h>
#include "common/utils/LOG/log.h"
#include "common/utils/threadPool/thread-pool.h"
#include "cpustats.h"
#include "common/utils/time_meas.h"
#define UEL1_THREADNAME "L1_UE_stats_act"
#define GNBL1_THREADNAME "L1_stats_act"
/* ==== Global variables ==== */
// IPC communication between main thread and the statistics one
static notifiedFIFO_t GNBL1cpustats_fifo, UEL1cpustats_fifo;
static pthread_t UEL1cpustats_threadid = 0;
static pthread_t gNBL1cpustats_threadid = 0;
// data structure for communication between main thread and the statistics one
typedef struct {
unsigned int msgid;
telnet_printfunc_t prnt;
} proto_stats_msg_t;
// PHY_vars_UE_g: declared in openair1/PHY/phy_vars_nr_ue.h and defined in nr-uesoftmodem.c
extern PHY_VARS_NR_UE *** PHY_vars_UE_g;
/* ==== Static functions ==== */
/* return pointer to the global structure that contains the UE data including statistics*/
static PHY_VARS_NR_UE * getglobal_uephy(void) { return PHY_vars_UE_g[0][0]; }
// RC.nb_nr_macrlc_inst in openair2/LAYER2/NR_MAC_gNB/main.c defines the RAN_CONTEXT RC variable that defines how many instances you will have
// ru stands for 'radio unit' data structure
static RU_t *getglobal_ru(void) { return RC.ru[0];}
static void UEL1cpustats_display(telnet_printfunc_t prnt);
static void UEL1cpustats_thread_main(void);
static void gNBL1cpustats_display(telnet_printfunc_t prnt, PHY_VARS_gNB *param);
static void gNBL1cpustats_thread_main (void * param);
/* xxx: these are the same statistics but with different formatting and list of content of those displayed by
* telnetsrv_measurements.c:measurcmd_display_cpumeasures(). you could think about unifying the 2 functions.
* XXX: this implementation should be fixed using a dynamically allocated buffer instead of a plain one
* it is prone to sigSEGV
*/
static void
UEL1cpustats_display(telnet_printfunc_t prnt)
{
/* PHY_vars_UE_g:
* this global variable is allocated by nr-uesoftmodem.c and shared among threads makes the trick of passing statistics data to statistic thread
* that will consume it. There is just one instance of UE created in nr-ue.c
*/
PHY_VARS_NR_UE *ue = getglobal_uephy();
prnt("Display CPU statistics for UE Level 1\n");
char output[TELNET_MAX_MSGLENGTH];
int stroff = 0;
stroff += print_meas_log(&ue->phy_proc_tx , "L1 TX processing" , NULL, NULL, output , TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->ulsch_encoding_stats , "ULSCH encoding" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff );
stroff += print_meas_log(&ue->phy_proc_rx[0] , "L1 RX processing t0" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->phy_proc_rx[1] , "L1 RX processing t1" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->ue_ul_indication_stats , "UL Indication" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->rx_pdsch_stats , "PDSCH receiver" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_decoding_stats[0] , "PDSCH decoding t0" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_decoding_stats[1] , "PDSCH decoding t1" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_deinterleaving_stats , " -> Deinterleive" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_rate_unmatching_stats , " -> Rate Unmatch" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_ldpc_decoding_stats , " -> LDPC Decode" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_unscrambling_stats , "PDSCH unscrambling" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ue->dlsch_rx_pdcch_stats , "PDCCH handling" , NULL, NULL, output + stroff, TELNET_MAX_MSGLENGTH - stroff);
prnt("%s\n", output);
}
static void
UEL1cpustats_thread_main(void)
{
if (pthread_setname_np(pthread_self(), UEL1_THREADNAME))
LOG_E(UTIL, "Error setting name on thread %s: %s\n", UEL1_THREADNAME, strerror(errno));
struct sched_param schedp;
schedp.sched_priority = SCHED_NORMAL;
if (pthread_setschedparam(pthread_self(), SCHED_IDLE, &schedp))
LOG_E(UTIL, "Error setting priority in thread %s: %s\n", UEL1_THREADNAME, strerror(errno));
initNotifiedFIFO(&UEL1cpustats_fifo);
cpumeas(CPUMEAS_ENABLE);
while (1){
/*
* Infinite loop that waits for a message on the FIFO.
* Behind the scenes it uses blocking pthread.h:pthread_cond_wait()
*/
notifiedFIFO_elt_t *msg = pullNotifiedFIFO(&UEL1cpustats_fifo);
proto_stats_msg_t *tsm = (proto_stats_msg_t *)NotifiedFifoData(msg);
switch (tsm->msgid)
{
case TIMESTAT_MSGID_DISPLAY:
if (cpumeas(CPUMEAS_GETSTATE))
UEL1cpustats_display(tsm->prnt);
else
tsm->prnt("ERR: before displaying meaningful stats, you need to activate their generation\n");
break;
case TIMESTAT_MSGID_DISABLE:
cpumeas(CPUMEAS_DISABLE);
UEL1cpustats_threadid = 0;
pthread_exit(NULL);
break;
}
delNotifiedFIFO_elt(msg);
}
LOG_I(UTIL, "Exiting thread %s\n", UEL1_THREADNAME);
}
bool
UEL1cpustats_enable(void)
{
if (UEL1cpustats_threadid){
LOG_E(UTIL, "Thread '%s' is already running. Aborting your request to create a new one\n", UEL1_THREADNAME);
return false;
}
int rt = pthread_create(&UEL1cpustats_threadid, NULL, (void *(*)(void *))UEL1cpustats_thread_main, NULL);
if (rt != 0)
LOG_E(UTIL, "Error creating thread %s: %s\n",UEL1_THREADNAME, strerror(errno));
return (rt != 0) ? false : true;
}
void
UEL1cpustats_disable(void)
{
if (!UEL1cpustats_threadid) {
LOG_W(UTIL, "Thread '%s' is not running. Discarding your request to disable stats\n", UEL1_THREADNAME);
return;
}
notifiedFIFO_elt_t *nfe = newNotifiedFIFO_elt(sizeof(time_stats_msg_t), 0, NULL, NULL);
proto_stats_msg_t *msg = (proto_stats_msg_t *)NotifiedFifoData(nfe);
msg->msgid = TIMESTAT_MSGID_DISABLE;
pushNotifiedFIFO(&UEL1cpustats_fifo, nfe);
}
void
UEL1cpustats_measurcmd_display (telnet_printfunc_t prnt)
{
if (!UEL1cpustats_threadid) {
prnt("Thread '%s' is not running. Aborting your request to display stats, you need first to enable statistics\n", UEL1_THREADNAME);
return;
}
notifiedFIFO_elt_t *nfe = newNotifiedFIFO_elt(sizeof(time_stats_msg_t), 0, NULL, NULL);
proto_stats_msg_t *msg = (proto_stats_msg_t *)NotifiedFifoData(nfe);
msg->prnt = prnt;
msg->msgid = TIMESTAT_MSGID_DISPLAY;
pushNotifiedFIFO(&UEL1cpustats_fifo, nfe);
}
/* Display info from specific instance of gNB and from radio unit */
static void
gNBL1cpustats_display(telnet_printfunc_t prnt, PHY_VARS_gNB *gNB)
{
RU_t *ru = getglobal_ru();
char output[TELNET_MAX_MSGLENGTH];
int stroff = 0;
prnt("%s\n");
// XXX: header will be printed automatically by print_meas_log() just the first time it is called in the program. It means that if you
// use a mechanism like 'loop measur show gnb_L1', you will lose the header after the first iteration.
// You should fix the logic of 'print_meas_log() to avoid it printing any header.
stroff += print_meas_log(&gNB->phy_proc_tx , "L1 Tx processing" , NULL, NULL, output , TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->dlsch_encoding_stats , "DLSCH encoding" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->phy_proc_rx , "L1 Rx processing" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->ul_indication_stats , "UL Indication" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->rx_pusch_stats , "PUSCH inner-receiver", NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->ulsch_decoding_stats , "PUSCH decoding" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&gNB->schedule_response_stats, "Schedule Response" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
if (ru->feprx)
stroff += print_meas_log(&ru->ofdm_demod_stats , "feprx" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
if (ru->feptx_ofdm) {
stroff += print_meas_log(&ru->precoding_stats , "feptx_prec" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ru->txdataF_copy_stats , "txdataF_copy" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ru->ofdm_mod_stats , "feptx_ofdm" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ru->ofdm_total_stats , "feptx_total" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
}
if (ru->fh_north_asynch_in)
stroff += print_meas_log(&ru->rx_fhaul , "rx_fhaul" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ru->tx_fhaul , "tx_fhaul" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
if (ru->fh_north_out) {
stroff += print_meas_log(&ru->compression , "compression" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
stroff += print_meas_log(&ru->transport , "transport" , NULL, NULL, output+stroff, TELNET_MAX_MSGLENGTH - stroff);
}
prnt("%s\n", output);
}
static void
gNBL1cpustats_thread_main(void * param)
{
if (pthread_setname_np(pthread_self(), GNBL1_THREADNAME))
LOG_E(UTIL, "Error setting name on thread %s: %s\n", GNBL1_THREADNAME, strerror(errno));
struct sched_param schedp;
schedp.sched_priority = SCHED_NORMAL;
if (pthread_setschedparam(pthread_self(), SCHED_IDLE, &schedp))
LOG_E(UTIL, "Error setting priority in thread %s: %s\n", GNBL1_THREADNAME, strerror(errno));
initNotifiedFIFO(&GNBL1cpustats_fifo);
cpumeas(CPUMEAS_ENABLE);
while (1){
/*
* Infinite loop that waits for a message on the FIFO.
* Behind the scenes it uses blocking pthread.h:pthread_cond_wait()
*/
notifiedFIFO_elt_t *msg = pullNotifiedFIFO(&GNBL1cpustats_fifo);
proto_stats_msg_t *tsm = (proto_stats_msg_t *)NotifiedFifoData(msg);
switch (tsm->msgid)
{
case TIMESTAT_MSGID_DISPLAY:
if (cpumeas(CPUMEAS_GETSTATE))
gNBL1cpustats_display(tsm->prnt, (PHY_VARS_gNB *)param);
else
tsm->prnt("ERR: before displaying meaningful stats, you need to activate their generation\n");
break;
case TIMESTAT_MSGID_DISABLE:
cpumeas(CPUMEAS_DISABLE);
gNBL1cpustats_threadid = 0;
pthread_exit(NULL);
break;
}
delNotifiedFIFO_elt(msg);
}
LOG_I(UTIL, "Exiting thread '%s'\n", GNBL1_THREADNAME);
}
void
gNBL1cpustats_measurcmd_display (telnet_printfunc_t prnt)
{
if (!gNBL1cpustats_threadid) {
prnt("gNB thread %s is not running. Aborting your request to display stats as you need first to enable statistics\n", GNBL1_THREADNAME);
return;
}
notifiedFIFO_elt_t *nfe = newNotifiedFIFO_elt(sizeof(time_stats_msg_t), 0, NULL, NULL);
proto_stats_msg_t *msg = (proto_stats_msg_t *)NotifiedFifoData(nfe);
msg->prnt = prnt;
msg->msgid = TIMESTAT_MSGID_DISPLAY;
pushNotifiedFIFO(&GNBL1cpustats_fifo, nfe);
}
bool
gNBL1cpustats_enable(PHY_VARS_gNB *gNB)
{
if (gNBL1cpustats_threadid) {
LOG_E(UTIL, "Thread '%s' already running, aborting your request to create a new one\n", GNBL1_THREADNAME);
return false;
}
int rt = pthread_create(&gNBL1cpustats_threadid, NULL, (void *(*)(void *))gNBL1cpustats_thread_main, gNB);
if (rt != 0)
LOG_E(UTIL, "Error creating thread '%s': %s\n", GNBL1_THREADNAME, strerror(errno));
return (rt != 0) ? false : true;
}
void
gNBL1cpustats_disable(void)
{
if (!gNBL1cpustats_threadid) {
LOG_W(UTIL, "Thread '%s' is not running. Discarding your request to disable stats\n", GNBL1_THREADNAME);
return;
}
notifiedFIFO_elt_t *nfe = newNotifiedFIFO_elt(sizeof(time_stats_msg_t), 0, NULL, NULL);
proto_stats_msg_t *msg = (proto_stats_msg_t *)NotifiedFifoData(nfe);
msg->msgid = TIMESTAT_MSGID_DISABLE;
pushNotifiedFIFO(&GNBL1cpustats_fifo, nfe);
}

20
common/utils/cpustats.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef _CPUSTATS_H
#define _CPUSTATS_H
/*
* Activation, deactivation and printing functionalities of cpu statistics for UE and gNB Level 1/PHY indicators
*/
#include "common/utils/telnetsrv/telnetsrv.h"
#include "openair1/PHY/defs_nr_UE.h"
#include "openair1/PHY/defs_gNB.h"
#define UE_STATS_L1 "phycpunew"
void UEL1cpustats_measurcmd_display(telnet_printfunc_t prnt);
bool UEL1cpustats_enable(void);
void UEL1cpustats_disable(void);
#define GNB_STATS_L1 "gnb_L1"
bool gNBL1cpustats_enable(PHY_VARS_gNB *gNB);
void gNBL1cpustats_disable(void);
void gNBL1cpustats_measurcmd_display (telnet_printfunc_t prnt);
#endif

View File

@@ -46,6 +46,7 @@
#include "common/utils/LOG/log.h"
#include "common/config/config_userapi.h"
#include "common/utils/cpustats.h"
#include "telnetsrv_measurements.h"
#include "telnetsrv_ltemeasur_def.h"
#include "telnetsrv_cpumeasur_def.h"
@@ -63,7 +64,9 @@ void measurcmd_display_pdcpcpu(telnet_printfunc_t prnt);
static telnet_measurgroupdef_t nrUEmeasurgroups[] = {
// {"ue", GROUP_LTESTATS,0, measurcmd_display_macstats, {NULL}},
// {"rlc", GROUP_LTESTATS,0, measurcmd_display_rlcstats, {NULL}},
{"phycpu",GROUP_CPUSTATS,0, measurcmd_display_phycpu, {NULL}},
{"phycpu" , GROUP_CPUSTATS, 0, measurcmd_display_phycpu , {NULL}},
{UE_STATS_L1, GROUP_CPUSTATS, 0, UEL1cpustats_measurcmd_display, {NULL}}
// {"maccpu",GROUP_CPUSTATS,0, measurcmd_display_maccpu, {NULL}},
// {"pdcpcpu",GROUP_CPUSTATS,0, measurcmd_display_pdcpcpu, {NULL}},
};

View File

@@ -0,0 +1,275 @@
#define TELNETSERVERCODE
#include <stdbool.h>
#include "telnetsrv.h"
#include "telnetsrv_measurements.h"
#include "telnetsrv_ltemeasur_def.h"
#include "common/utils/cpustats.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
static void gNBL2pstats_measurcmd_display(telnet_printfunc_t prnt);
static char *gNBL2pstats_gethdrline1(char * bufhdr, size_t maxlen);
static char *gNBL2pstats_gethdrline2(char * bufhdr, size_t maxlen);
static const char *scale_number (float n, int width);
/*
* caveats: 5th fiels of nrgNBmeasurgroups[] entries is NULL as we are not yet defining in this module the statistics content to
* be passed from statistics thread. That field was created for future extensibility work on reuniting statistics management.
*/
static telnet_measurgroupdef_t nrgNBmeasurgroups[] = {
{GNB_STATS_L2_MAC, GROUP_LTESTATS, 0, gNBL2pstats_measurcmd_display , {NULL}},
{GNB_STATS_L1 , GROUP_CPUSTATS, 0, gNBL1cpustats_measurcmd_display, {NULL}}
};
const char SPACE_LABEL=' ';
const char MINUS_LABEL='-';
const char * ULSCH_LABEL="[UL sch]";
const char * DLSCH_LABEL="[DL sch]";
int get_measurgroups(telnet_measurgroupdef_t **measurgroups)
{
*measurgroups = nrgNBmeasurgroups;
return sizeof(nrgNBmeasurgroups)/sizeof(telnet_measurgroupdef_t);
}
// magic numbers for the below table
#define TELNETSRV_TBL_HARQ_DL 10
#define TELNETSRV_TBL_HARQ_UL 16
// XXX: table below would need a bit of tuning to understand expected values ranges and avoid massive use of scaling functionality
static const struct itemtable_t{
int width; // field width in ASCII characters
bool tobescaled; // 1(true), 0(false) scaling the value to i.e. Kb, Mb, etc..
char *itemlabel; // the new libproc item enum identifier
enum {TELNETSRV_SCH_NONE = 0, TELNETSRV_SCH_DL, TELNETSRV_SCH_UL} scheduler;
} itemtable[] = {
{2, false, "UE" , TELNETSRV_SCH_NONE}, // UE number
{4, false, "RNTI" , TELNETSRV_SCH_NONE},
{2, false, "PH" , TELNETSRV_SCH_NONE},
{5, false, "PCMax" , TELNETSRV_SCH_NONE},
{7, false, "RSRPavg" , TELNETSRV_SCH_NONE},
{8, false, "RSRPmeas", TELNETSRV_SCH_NONE},
{3, false, "cqi" , TELNETSRV_SCH_NONE},
{2, false, "ri" , TELNETSRV_SCH_NONE},
{5, false, "pmix1" , TELNETSRV_SCH_NONE}, // uint8
{5, false, "pmix2" , TELNETSRV_SCH_NONE}, // uint8
{4, true , "HARQ" , TELNETSRV_SCH_DL }, // special case pointed by TELNETSRV_TBL_HARQ_DL. Given that we do not know how many dl_rounds elements you
// will have,width here is related to each of the dl_rounds. It is similar reasoning for UL below
{4, true , "E" , TELNETSRV_SCH_DL },
{3, false, "dtx" , TELNETSRV_SCH_DL },
{5, true , "bler" , TELNETSRV_SCH_DL }, // float
{3, false, "mcs" , TELNETSRV_SCH_DL },
{6, true , "bdlsch" , TELNETSRV_SCH_UL }, // tot bytes scheduled
{4, true , "HARQ" , TELNETSRV_SCH_UL }, // special case pointed by TELNETSRV_TBL_HARQ_UL. See HARQ for DL above
{4, true , "E" , TELNETSRV_SCH_UL },
{3, false, "dtx" , TELNETSRV_SCH_UL },
{5, true , "bler" , TELNETSRV_SCH_UL }, // float
{3, false, "mcs" , TELNETSRV_SCH_UL },
{6, true , "bulsch" , TELNETSRV_SCH_UL }, // tot bytes scheduled
{6, true , "brxtot" , TELNETSRV_SCH_UL } // tot bytes received
};
static char * gNBL2pstats_gethdrline1(char *bufhdr, size_t maxlen)
{
unsigned int idx;
int bufhdr_idx=0;
int empty_spaces = 0;
int dl_sch = 0;
int ul_sch = 0;
int width = 0;
gNB_MAC_INST *gNB = RC.nrmac[0];
for (idx=0; idx < (sizeof(itemtable)/sizeof(struct itemtable_t)); idx++)
{
if ((bufhdr_idx + itemtable[idx].width + 1) > maxlen)
return NULL;
// handling special case of HARQ configurable numbers of rounds
width = itemtable[idx].width + 1;
if (idx == TELNETSRV_TBL_HARQ_DL)
width = width * gNB->dl_bler.harq_round_max;
else if (idx == TELNETSRV_TBL_HARQ_UL)
width = width * gNB->ul_bler.harq_round_max;
switch (itemtable[idx].scheduler)
{
case TELNETSRV_SCH_NONE:
empty_spaces += width;
memset(bufhdr + bufhdr_idx, SPACE_LABEL, width);
break;
case TELNETSRV_SCH_DL:
dl_sch += width;
memset(bufhdr + bufhdr_idx, MINUS_LABEL, width);
break;
case TELNETSRV_SCH_UL:
ul_sch += width;
memset(bufhdr + bufhdr_idx, MINUS_LABEL, width);
break;
}
bufhdr_idx += width;
}
bufhdr[bufhdr_idx] = '\n';
bufhdr_idx++;
bufhdr[bufhdr_idx] = '\0';
// now, overwite some parts if there is enough space
if (dl_sch > (sizeof(DLSCH_LABEL) + 4 ))
{
bufhdr[empty_spaces + 2] = SPACE_LABEL;
memcpy(bufhdr + empty_spaces + 3, DLSCH_LABEL, strlen(DLSCH_LABEL));
bufhdr[empty_spaces + 3 + sizeof(DLSCH_LABEL)] = SPACE_LABEL;
bufhdr[empty_spaces + dl_sch - 1] = SPACE_LABEL;
}
if (ul_sch > (sizeof(ULSCH_LABEL) + 4 ))
{
bufhdr[empty_spaces + dl_sch + 2] = SPACE_LABEL;
memcpy(bufhdr + empty_spaces + dl_sch + 3, ULSCH_LABEL, strlen(ULSCH_LABEL));
bufhdr[empty_spaces + dl_sch + 3 + sizeof(ULSCH_LABEL)] = SPACE_LABEL;
bufhdr[empty_spaces + dl_sch + ul_sch - 1] = SPACE_LABEL;
}
return bufhdr;
}
static char * gNBL2pstats_gethdrline2(char *bufhdr, size_t maxlen)
{
unsigned int idx;
int printed=0;
int width;
gNB_MAC_INST *gNB = RC.nrmac[0];
for (idx=0; idx < (sizeof(itemtable)/sizeof(struct itemtable_t)); idx++)
{
if ((printed + itemtable[idx].width + 1) > maxlen)
return NULL;
width = itemtable[idx].width;
if (idx == TELNETSRV_TBL_HARQ_DL)
width = (width + 1)*gNB->dl_bler.harq_round_max - 1;
else if (idx == TELNETSRV_TBL_HARQ_UL)
width = (width + 1)*gNB->ul_bler.harq_round_max - 1;
printed += snprintf(bufhdr + printed, maxlen - printed, "%*s ", width, itemtable[idx].itemlabel);
}
bufhdr[printed] = '\n';
printed++;
bufhdr[printed] = '\0';
return bufhdr;
}
static const char *scale_number (float n, int width)
{
static char buf[128];
buf[0] = '\0';
if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, n))
return buf;
char *plabel;
char scale_labels[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 };// E=exabytes are enough, no need for zettabytes and over
for (plabel = scale_labels; *plabel != 0; plabel++)
{
n /= 1000.0;
if (width >= snprintf(buf, sizeof(buf), "%*.1f%c", width - 1, n, *plabel))
return buf;
// let's retry with one decimal less to see if fits.
if (width >= snprintf(buf, sizeof(buf), "%*.0f%c", width - 1, n, *plabel))
return buf;
}
// worst case...
snprintf(buf, sizeof(buf), "%*s" , width, "?");
return buf;
}
/**
* Display UE statistics collected at gNB node related to MAC protocol (OSI Layer 2). This is for the moment limited to one instance of gNB
* This function is meant to be used in combination with 'telnet set loopd 1000' and 'loop' command prepended to the 'measur' one to provide
* refresh of 1000ms of the CLI.
*
* XXX: You should anyway consider about keeping an historic on the screen for these stats, one line per iteration,
* not overwriting like loop command does until a specific command is pressed. One idea is to implement that as option of the loop command,
* that forces to go in append instead of refreshing the position on the screen.
*
* XXX: note that max line length is TELNET_MAX_MSGLENGTH that is equal to 2048 chars. We do not check it yet in this function.
*/
static void gNBL2pstats_measurcmd_display(telnet_printfunc_t prnt)
{
const int gnb_inst = 0;
gNB_MAC_INST *gNB = RC.nrmac[gnb_inst]; // CAVEATS: using global vars as signature of this function can't be change to fit more params.
pthread_mutex_lock(&gNB->UE_info.mutex);
static char buf[1024];
prnt(gNBL2pstats_gethdrline1(buf, sizeof(buf)));
prnt(gNBL2pstats_gethdrline2(buf, sizeof(buf)));
/* loop over all the UEs registered: to each UE will be given a number that is just a progressive counter*/
int uenum = 1;
UE_iterator(gNB->UE_info.list, UE)
{
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_mac_stats_t *stats = &UE->mac_stats;
const int avg_rsrp = stats->num_rsrp_meas > 0 ? stats->cumul_rsrp / stats->num_rsrp_meas : 0;
prnt("%*d" , itemtable[0].width , uenum++);
prnt(" %0*x" , itemtable[1].width , UE->rnti);
prnt(" %*d" , itemtable[2].width , sched_ctrl->ph);
prnt(" %*d" , itemtable[3].width , sched_ctrl->pcmax);
prnt(" %*d" , itemtable[4].width , avg_rsrp);
prnt(" %*d" , itemtable[5].width , stats->num_rsrp_meas);
prnt(" %*d" , itemtable[6].width , UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb);
prnt(" %*d" , itemtable[7].width , UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.ri+1);
prnt(" %*d" , itemtable[8].width , UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1);
prnt(" %*d" , itemtable[9].width , UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2);
prnt(" ");
for (int i=0; i<gNB->dl_bler.harq_round_max; i++){
if (i!=0)
prnt("/");
prnt(scale_number(stats->dl.rounds[i], itemtable[TELNETSRV_TBL_HARQ_DL].width));
}
prnt(" %*"PRIu64"", itemtable[11].width, stats->dl.errors);
prnt(" %*d" , itemtable[12].width, stats->pucch0_DTX);
prnt(" %*.3f" , itemtable[13].width, sched_ctrl->dl_bler_stats.bler);
prnt(" %*d" , itemtable[14].width, sched_ctrl->dl_bler_stats.mcs);
prnt(" ");
prnt(scale_number(stats->dl.total_bytes, itemtable[15].width));
prnt(" ");
for (int i=0; i<gNB->ul_bler.harq_round_max; i++){
if (i!=0)
prnt("/");
prnt(scale_number(stats->ul.rounds[0], itemtable[TELNETSRV_TBL_HARQ_UL].width));
}
prnt(" %*"PRIu64"", itemtable[17].width, stats->ul.errors);
prnt(" %*d" , itemtable[18].width, stats->ulsch_DTX);
prnt(" %*.3f" , itemtable[19].width, sched_ctrl->ul_bler_stats.bler);
prnt(" %*d" , itemtable[20].width, sched_ctrl->ul_bler_stats.mcs);
prnt(" ");
prnt(scale_number(stats->ulsch_total_bytes_scheduled, itemtable[21].width));
prnt(" ");
prnt(scale_number(stats->ul.total_bytes, itemtable[22].width));
prnt("\n");
/*
* XXX-TODO: the values below
* values: stats->dl.lc_bytes[lc_id] for lc_id = 0 ..63;
* values: stats->ul.lc_bytes[lc_id] for lc_id = 0 ..63;
* were included in the previous version of the stats. We will think about including as they are too many
* to be added to one line stats display.
*/
}
pthread_mutex_unlock(&gNB->UE_info.mutex);
/*
* XXX-TODO: also the values below
* print_meas(&gNB->eNB_scheduler, "DL & UL scheduling timing stats", NULL, NULL);
* print_meas(&gNB->schedule_dlsch,"dlsch scheduler",NULL,NULL);
* print_meas(&gNB->rlc_data_req, "rlc_data_req",NULL,NULL);
* print_meas(&gNB->rlc_status_ind,"rlc_status_ind",NULL,NULL);
* were included in the previous version of the stats. They were using print_meas() to display to stderr and should be changed to
* leverage prnt() function. This can be done via using print_meas_log() that prints to buffer, but unfortunately this function will include
* the header of the fields you are printing (and we want to have just one line header, see gNBL2pstats_gethdrline2()), so you probably would
* need to create a new print function i.e. print_meas_lograw().
*/
}

View File

@@ -47,7 +47,7 @@
#include "common/utils/LOG/log.h"
#include "common/config/config_userapi.h"
#include "telnetsrv_measurements.h"
#include "common/utils/cpustats.h"
static char *grouptypes[] = {"ltestats","cpustats"};
static double cpufreq;
@@ -285,6 +285,94 @@ int measurcmd_async(char *buf, int debug, telnet_printfunc_t prnt) {
free(subcmd);
return CMDSTATUS_FOUND;
}
/**
* Parse command "measur enable STATNAME" and take action. STATNAME can be retrieved from 'measur show groups '
*
* @param[in] buf buffer containing the command to be parsed. Expected "<stats name>"
* @param[in] debug if > 0, it print debugging information on the log file for telnet
* @param[in] prnt printing function for telnet CLI
*
* @return 0(success), error code otherwise
*/
int
measurcmd_enablestats(char *buf, int debug, telnet_printfunc_t prnt)
{
char *statname = NULL;
int ret = CMDSTATUS_NOTFOUND;
if (debug > 0)
prnt("measurcmd_enablestats received %s\n", buf);
if (buf == NULL)
return ret;
if (sscanf(buf, "%ms\n", &statname) != 1){
prnt("Error parsing commandline for enable stats: %s\n", strerror(errno));
return ret;
}
if (strcmp (statname, UE_STATS_L1) == 0){
if (UEL1cpustats_enable() ==false)
prnt ("Error when enabling statistics. See log file of UE for exact cause\n");
else
ret = CMDSTATUS_FOUND;
} else if (strcmp (statname, GNB_STATS_L1) == 0){
if (gNBL1cpustats_enable(RC.gNB[0]) == false)
prnt ("Error when enabling statistics. See log file of gNB for exact cause\n");
else
ret = CMDSTATUS_FOUND;
} else if (strcmp (statname, GNB_STATS_L2_MAC) == 0){
prnt ("Nothing to do for this type of statistic. Already activated\n");
ret = CMDSTATUS_FOUND;
} else {
prnt ("ERR: unsupported stat %s\n", statname);
}
return ret;
}
/**
* Parse command "measur disable STATNAME" and take action. STATNAME can be retrieved from 'measur show groups '
*
* @param[in] buf buffer containing the command to be parsed. Expected "<stats name>"
* @param[in] debug if > 0, it print debugging information on the log file for telnet
* @param[in] prnt printing function for telnet CLI
*
* @return 0(success), error code otherwise
*/
int
measurcmd_disablestats(char *buf, int debug, telnet_printfunc_t prnt)
{
char *statname = NULL;
int ret = CMDSTATUS_NOTFOUND;
if (debug > 0)
prnt("measurcmd_disablestats received %s\n", buf);
if (buf == NULL)
return ret;
if (sscanf(buf, "%ms\n", &statname) != 1){
prnt("Error parsing commandline for disable stats: %s\n", strerror(errno));
return ret;
}
if (strcmp (statname, UE_STATS_L1) == 0){
UEL1cpustats_disable();
ret = CMDSTATUS_FOUND;
} else if (strcmp (statname, GNB_STATS_L1) == 0){
gNBL1cpustats_disable();
ret = CMDSTATUS_FOUND;
} else if (strcmp (statname, GNB_STATS_L2_MAC) == 0){
prnt ("Nothing to do for this type of statistic. They are always activated\n");
ret = CMDSTATUS_FOUND;
} else {
prnt ("ERR: unsupported statistic to be disabled: %s\n", statname);
}
return ret;
}
/*-------------------------------------------------------------------------------------*/
/* function called at telnet server init to add the measur command */

View File

@@ -79,16 +79,24 @@ typedef struct mesurgroupdef {
#define MACSTATS_NAME(valptr) #valptr
#define LTEMAC_MEASURGROUP_NAME "ltemac"
#define PHYCPU_MEASURGROUP_NAME "phycpu"
#define GNB_STATS_L2_MAC "gnb_L2"
#ifdef TELNETSRV_MEASURMENTS_MAIN
int measurcmd_show(char *buf, int debug, telnet_printfunc_t prnt);
int measurcmd_cpustats(char *buf, int debug, telnet_printfunc_t prnt);
int measurcmd_async(char *buf, int debug, telnet_printfunc_t prnt);
int measurcmd_enablestats(char *buf, int debug, telnet_printfunc_t prnt);
int measurcmd_disablestats(char *buf, int debug, telnet_printfunc_t prnt);
telnetshell_cmddef_t measur_cmdarray[] = {
{"show", "groups | <group name> | inq" , measurcmd_show},
{"cpustats","[enable | disable]",measurcmd_cpustats},
{"async","[enable | disable]",measurcmd_async},
{"","",NULL}
{"cpustats", "[enable | disable]", measurcmd_cpustats},
{"enable", "<stat name>", measurcmd_enablestats},
{"disable", "<stat name>", measurcmd_disablestats},
{"async", "[enable | disable]", measurcmd_async},
{"", "", NULL}
};
telnetshell_vardef_t measur_vardef[] = {

View File

@@ -87,6 +87,8 @@
#include <openair1/PHY/NR_TRANSPORT/nr_dlsch.h>
#include <PHY/NR_ESTIMATION/nr_ul_estimation.h>
#include "common/utils/cpustats.h"
//#define USRP_DEBUG 1
// Fix per CC openair rf/if device update
// extern openair0_device openair0;
@@ -466,8 +468,12 @@ void init_gNB_Tpool(int inst) {
pushNotifiedFIFO(&gNB->L1_tx_free, msgL1Tx); // to unblock the process in the beginning
}
if ((!get_softmodem_params()->emulate_l1) && (!IS_SOFTMODEM_NOSTATS_BIT))
threadCreate(&proc->L1_stats_thread,nrL1_stats_thread,(void*)gNB,"L1_stats",-1,OAI_PRIORITY_RT_LOW);
if ((!get_softmodem_params()->emulate_l1) && (!IS_SOFTMODEM_NOSTATS_BIT)){
bool ret = gNBL1cpustats_enable(gNB);
if (ret == false)
LOG_E(PHY,"Failure activating statistics thread L1 for gNB instance #%d", inst);
threadCreate(&proc->L1_stats_thread,nrL1_stats_thread,(void*)gNB,"L1_stats",-1,OAI_PRIORITY_RT_LOW);
}
threadCreate(&proc->pthread_tx_reorder, tx_reorder_thread, (void *)gNB, "thread_tx_reorder", -1, OAI_PRIORITY_RT_MAX);

View File

@@ -1043,7 +1043,6 @@ void *ru_thread( void *param ) {
int frame = 1023;
char threadname[40];
int initial_wait=0;
int opp_enabled0 = opp_enabled;
nfapi_nr_config_request_scf_t *cfg = &ru->config;
// set default return value
@@ -1174,10 +1173,9 @@ void *ru_thread( void *param ) {
}
continue;
}
if (proc->frame_rx>=300) {
if (proc->frame_rx>=300)
initial_wait=0;
opp_enabled = opp_enabled0;
}
if (initial_wait == 0 && ru->rx_fhaul.trials > 1000) reset_meas(&ru->rx_fhaul);
proc->timestamp_tx = proc->timestamp_rx;
int sl=proc->tti_tx;

View File

@@ -34,7 +34,7 @@
#include "LAYER2/nr_pdcp/nr_pdcp_entity.h"
#include "SCHED_NR_UE/pucch_uci_ue_nr.h"
#include "openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h"
#include "common/utils/cpustats.h"
/*
* NR SLOT PROCESSING SEQUENCE
*
@@ -1114,6 +1114,8 @@ void init_NR_UE_threads(int nb_inst) {
LOG_I(PHY,"Intializing UE Threads for instance %d (%p,%p)...\n",inst,PHY_vars_UE_g[inst],PHY_vars_UE_g[inst][0]);
threadCreate(&threads[inst], UE_thread, (void *)UE, "UEthread", -1, OAI_PRIORITY_RT_MAX);
if (!IS_SOFTMODEM_NOSTATS_BIT) {
if (UEL1cpustats_enable() == false)
LOG_E(PHY,"Failure activating Statistics thread L1");
pthread_t stat_pthread;
threadCreate(&stat_pthread, nrL1_UE_stats_thread, UE, "L1_UE_stats", -1, OAI_PRIORITY_RT_LOW);
}

View File

@@ -176,12 +176,11 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP,
}
}
if ((slot == 0) && (frame & 127) == 0) {
char stats_output[16384];
stats_output[0] = '\0';
dump_mac_stats(RC.nrmac[module_idP], stats_output, sizeof(stats_output), true);
LOG_I(NR_MAC, "Frame.Slot %d.%d\n%s\n", frame, slot, stats_output);
if ( (!IS_SOFTMODEM_NOSTATS_BIT) && ((slot == 0) && (frame & 127) == 0)) {
char stats_output[16384];
stats_output[0]='\0';
dump_mac_stats(RC.nrmac[module_idP], stats_output, sizeof(stats_output), true);
LOG_I(NR_MAC,"Frame.Slot %d.%d\n%s\n",frame,slot,stats_output);
}
nr_mac_update_timers(module_idP, frame, slot);

View File

@@ -238,7 +238,7 @@ void mac_top_init_gNB(ngran_node_t node_type)
RC.nrmac[i]->pre_processor_ul = nr_init_fr1_ulsch_preprocessor(i, 0);
}
if (!IS_SOFTMODEM_NOSTATS_BIT)
threadCreate(&RC.nrmac[i]->stats_thread, nrmac_stats_thread, (void*)RC.nrmac[i], "MAC_STATS", -1, sched_get_priority_min(SCHED_OAI)+1 );
pthread_create(&RC.nrmac[i]->stats_thread, NULL, nrmac_stats_thread, (void*)RC.nrmac[i]); // this is a printing thread only.
mac_rrc_init(RC.nrmac[i], node_type);
}//END for (i = 0; i < RC.nb_nr_macrlc_inst; i++)