diff --git a/.dockerignore b/.dockerignore index 59051c1a30..1ca068f828 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ common/utils/T/T_IDs.h common/utils/T/T_messages.txt.h common/utils/T/genids common/utils/T/genids.o +artifacts diff --git a/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf b/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf index 12a86c3e64..8be3135296 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf +++ b/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf @@ -153,12 +153,12 @@ gNBs = }; ////////// AMF parameters: - amf_ip_address = ({ ipv4 = "192.168.70.132"; }); + amf_ip_address = ({ ipv4 = "192.168.71.132"; }); NETWORK_INTERFACES : { - GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.151"; - GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.151"; + GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140"; + GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140"; GNB_PORT_FOR_S1U = 2152; # Spec 2152 }; @@ -172,6 +172,7 @@ MACRLCs = ( tr_n_preference = "local_RRC"; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 200; + min_grant_prb = 1; } ); @@ -233,7 +234,7 @@ log_config : { }; e2_agent = { - near_ric_ip_addr = "192.168.70.150"; + near_ric_ip_addr = "192.168.71.21"; #sm_dir = "/path/where/the/SMs/are/located/" sm_dir = "/usr/local/lib/flexric/" }; diff --git a/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.yaml b/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.yaml index 7b75481634..a9257ff2c4 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.yaml +++ b/ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.yaml @@ -1,5 +1,12 @@ Active_gNBs: - gnb-rfsim + +e2_agent: + enabled: "yes" + near_ric_ip_addr: 192.168.71.21 + near_ric_port: 36421 + sm_dir: "/usr/local/lib/flexric/" + # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity: none gNBs: @@ -142,13 +149,13 @@ gNBs: GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140 GNB_PORT_FOR_S1U: 2152 # Spec 2152 - MACRLCs: - num_cc: 1 tr_s_preference: local_L1 tr_n_preference: local_RRC pusch_TargetSNRx10: 200 pucch_TargetSNRx10: 200 + min_grant_prb: 10 L1s: - num_cc: 1 @@ -195,6 +202,6 @@ log_config: mac_log_level: info rlc_log_level: info pdcp_log_level: info - rrc_log_level: info + rrc_log_level: debug ngap_log_level: debug f1ap_log_level: debug diff --git a/ci-scripts/conf_files/nrue.uicc.conf b/ci-scripts/conf_files/nrue.uicc.conf index 5424abe30f..013a105681 100644 --- a/ci-scripts/conf_files/nrue.uicc.conf +++ b/ci-scripts/conf_files/nrue.uicc.conf @@ -1,5 +1,4 @@ -uicc0 = { - imsi = "208990100001100"; +uicc0 = { # imsi is omitted key = "fec86ba6eb707ed08905757b1bb44b8f"; opc= "C42449363BBAD02B66D16BC975D77CC1"; dnn= "oai"; diff --git a/ci-scripts/conf_files/nrue.uicc.yaml b/ci-scripts/conf_files/nrue.uicc.yaml index da3b7e7e19..0d4640833c 100644 --- a/ci-scripts/conf_files/nrue.uicc.yaml +++ b/ci-scripts/conf_files/nrue.uicc.yaml @@ -4,6 +4,7 @@ uicc0: opc: C42449363BBAD02B66D16BC975D77CC1 dnn: oai nssai_sst: 1 + pdu_type: IPV4 thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" @@ -24,7 +25,7 @@ channelmod: - model_name: rfsimu_channel_ue0 type: AWGN ploss_dB: 20 - nose_power_dB: -2 + noise_power_dB: -2 forgetfact: 0 offset: 0 ds_tdl: 0 diff --git a/ci-scripts/yaml_files/5g_rfsimulator/conf/flexric.conf b/ci-scripts/yaml_files/5g_rfsimulator/conf/flexric.conf new file mode 100644 index 0000000000..fda5bb4a7c --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/conf/flexric.conf @@ -0,0 +1,9 @@ +[NEAR-RIC] +NEAR_RIC_IP = 192.168.71.21 +NEAR_RIC_PORT = 36422 + +[XAPP] +DB_DIR = /tmp/ + +[LOG] +LEVEL = debug diff --git a/ci-scripts/yaml_files/5g_rfsimulator/mini_nonrf_config.yaml b/ci-scripts/yaml_files/5g_rfsimulator/conf/mini_nonrf_config.yaml similarity index 65% rename from ci-scripts/yaml_files/5g_rfsimulator/mini_nonrf_config.yaml rename to ci-scripts/yaml_files/5g_rfsimulator/conf/mini_nonrf_config.yaml index 10fb5bcdfd..1dd9cf229a 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator/mini_nonrf_config.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator/conf/mini_nonrf_config.yaml @@ -1,36 +1,3 @@ -################################################################################ -# 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 -################################################################################ - -# OAI CN Configuration File -### This file can be used by all OAI NFs -### Some fields are specific to an NF and will be ignored by other NFs - -## NOTE ON YAML ANCHORS ## -# We use YAML anchors to ease the configuration and to avoid duplicating parts of the configuration. -# This is especially true for the SNSSAIs, as we have to define them for multiple NFs. -# Please note that the use of anchors is not mandatory, and you can define the SNSSAI in each NF yourself. -# You can read more about anchors here: https://yaml.org/spec/1.2.2/#anchors-and-aliases - -############# Common configuration - # Log level for all the NFs log_level: general: debug @@ -103,8 +70,8 @@ amf: # This really depends on if we want to keep the "mini" version or not support_features_options: enable_simple_scenario: yes # "no" by default with the normal deployment scenarios with AMF/SMF/UPF/AUSF/UDM/UDR/NRF. - # set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF. - # There's no NRF in this scenario, SMF info is taken from "nfs" section. + # set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF. + # There's no NRF in this scenario, SMF info is taken from "nfs" section. enable_nssf: no enable_smf_selection: no relative_capacity: 30 diff --git a/ci-scripts/yaml_files/5g_rfsimulator/mysql-healthcheck.sh b/ci-scripts/yaml_files/5g_rfsimulator/conf/mysql-healthcheck.sh similarity index 100% rename from ci-scripts/yaml_files/5g_rfsimulator/mysql-healthcheck.sh rename to ci-scripts/yaml_files/5g_rfsimulator/conf/mysql-healthcheck.sh diff --git a/ci-scripts/yaml_files/5g_rfsimulator/oai_db.sql b/ci-scripts/yaml_files/5g_rfsimulator/conf/oai_db.sql similarity index 58% rename from ci-scripts/yaml_files/5g_rfsimulator/oai_db.sql rename to ci-scripts/yaml_files/5g_rfsimulator/conf/oai_db.sql index ec5e497e88..572a3e950f 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator/oai_db.sql +++ b/ci-scripts/yaml_files/5g_rfsimulator/conf/oai_db.sql @@ -190,7 +190,6 @@ CREATE TABLE `users` ( LOCK TABLES `users` WRITE; /*!40000 ALTER TABLE `users` DISABLE KEYS */; -INSERT INTO `users` VALUES ('20834123456789','380561234567','35609204079300',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,'+�E��ų\0�,IH��H',0,0,00000000000000000096,'Px�X \Z1��x��','^��K�����FeU���'),('20810000001234','33611123456','35609204079299',NULL,'PURGED',120,40000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000281454575616225,'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0','�4�s@���z��~�'),('31002890832150','33638060059','35611302209414',NULL,'PURGED',120,40000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012416,'`�F�݆��D��ϛ���','�4�s@���z��~�'),('001010123456789','33600101789','35609204079298',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'\0 \n \r',1,0,00000000000000000351,'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0','L�*\\�����^��]� '),('208930000000001','33638030001','35609204079301',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208950000000002','33638050002','35609204079502',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000020471,'\0 \n \r','�4�s@���z��~�'),('208950000000003','33638050003','35609204079503',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012343,'\0 \n \r','�4�s@���z��~�'),('208950000000004','33638050004','35609204079504',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000005','33638050005','35609204079505',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000001','33638050001','35609204079501',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208950000000006','33638050006','35609204079506',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000007','33638050007','35609204079507',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208930000000002','33638030002','35609204079302',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208930000000003','33638030003','35609204079303',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208930000000004','33638030004','35609204079304',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208930000000005','33638030005','35609204079305',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208930000000006','33638030006','35609204079306',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208930000000007','33638030007','35609204079307',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000007','33638040007','35609204079407',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000006','33638040006','35609204079406',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000005','33638040005','35609204079405',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000004','33638040004','35609204079404',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000003','33638040003','35609204079403',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000002','33638040002','35609204079402',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208940000000001','33638040001','35609204079401',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'��wq��gzW�Ё��Z]','�4�s@���z��~�'),('208920100001100','33638020001','35609204079201',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001101','33638020001','35609204079201',NULL,'NOT_PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000281044204937234,'\0 \n \r','�$I6;��+f�k�u�|�'),('208920100001102','33638020002','35609204079202',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001103','33638020003','35609204079203',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001104','33638020004','35609204079204',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001105','33638020005','35609204079205',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001106','33638020006','35609204079206',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000000000000006103,'ebd07771ace8677a','�$I6;��+f�k�u�|�'),('208920100001107','33638020007','35609204079207',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001108','33638020008','35609204079208',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001109','33638020009','35609204079209',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208920100001110','33638020010','35609204079210',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208930100001111','33638030011','35609304079211',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208930100001112','33638030012','35609304079212',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006103,'ebd07771ace8677a','�4�s@���z��~�'),('208930100001113','33638030013','35609304079213',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000006263,'�SNܒ�Iv��e�6','�4�s@���z��~�'),('208950000000008','33638050008','35609204079508',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000009','33638050009','35609204079509',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000010','33638050010','35609204079510',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000011','33638050011','35609204079511',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000012','33638050012','35609204079512',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000013','33638050013','35609204079513',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000014','33638050014','35609204079514',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000012215,'56f0261d9d051063','�4�s@���z��~�'),('208950000000015','33638050015','35609204079515',NULL,'PURGED',120,50000000,100000000,47,0000000000,1,'��G?/�Д���� |hb',1,0,00000000000000000000,'3536663032363164','�4�s@���z��~�'),('208920100001118','33638020010','35609204079210',NULL,'NOT_PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000281044204934762,'~?03�u-%�ey�y�','�$I6;��+f�k�u�|�'),('208920100001121','33638020010','35609204079210',NULL,'NOT_PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000281044204935293,'&��@xg�]���\n��Vp','�$I6;��+f�k�u�|�'),('208920100001119','33638020010','35609204079210',NULL,'NOT_PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000281044204935293,'269482407867805d','�$I6;��+f�k�u�|�'),('208920100001120','33638020010','35609204079210',NULL,'NOT_PURGED',120,50000000,100000000,47,0000000000,1,'��k��p~Љu{�K�',1,0,00000281044204935293,'3236393438323430','�$I6;��+f�k�u�|�'); INSERT INTO `users` VALUES ('208990100001100','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); INSERT INTO `users` VALUES ('208990100001101','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); INSERT INTO `users` VALUES ('208990100001102','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); @@ -207,16 +206,6 @@ INSERT INTO `users` VALUES ('208990100001112','1','55000000000000',NULL,'PURGED' INSERT INTO `users` VALUES ('208990100001113','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); INSERT INTO `users` VALUES ('208990100001114','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); INSERT INTO `users` VALUES ('208990100001115','1','55000000000000',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0xfec86ba6eb707ed08905757b1bb44b8f,0,0,0x40,'ebd07771ace8677a',0xc42449363bbad02b66d16bc975d77cc1); -INSERT INTO `users` VALUES ('208950000000031','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000032','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000033','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000034','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000035','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000036','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000037','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000038','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000039','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); -INSERT INTO `users` VALUES ('208950000000040','380561234567','55000000000001',NULL,'PURGED',50,40000000,100000000,47,0000000000,1,0x0C0A34601D4F07677303652C0462535B,0,0,0x40,'ebd07771ace8677a',0x63bfa50ee6523365ff14c1f45f88737d); /*!40000 ALTER TABLE `users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -230,3 +219,4 @@ UNLOCK TABLES; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2016-06-28 11:41:40 + diff --git a/ci-scripts/yaml_files/5g_rfsimulator/conf/sql.cnf b/ci-scripts/yaml_files/5g_rfsimulator/conf/sql.cnf new file mode 100644 index 0000000000..ddba2223c0 --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/conf/sql.cnf @@ -0,0 +1,3 @@ +[mysqld] +wait_timeout = 15 +interactive_timeout = 15 \ No newline at end of file diff --git a/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml index 4733737b50..02c3b1ab7b 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml @@ -1,12 +1,13 @@ services: mysql: - container_name: "rfsim5g-mysql" + container_name: "mysql" image: mysql:8.0 volumes: - - ./oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql - - ./mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh + - ./conf/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql + - ./conf/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh + - ./conf/sql.cnf:/etc/mysql/conf.d/custom.cnf environment: - - TZ=Europe/Paris + - TZ=Asia/Tokyo - MYSQL_DATABASE=oai_db - MYSQL_USER=test - MYSQL_PASSWORD=test @@ -19,37 +20,40 @@ services: networks: public_net: ipv4_address: 192.168.71.131 + oai-amf: - container_name: "rfsim5g-oai-amf" + container_name: "oai-amf" image: oaisoftwarealliance/oai-amf:v2.1.9 environment: - - TZ=Europe/paris + - TZ=Asia/Tokyo volumes: - - ./mini_nonrf_config.yaml:/openair-amf/etc/config.yaml + - ./conf/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml depends_on: - mysql networks: public_net: ipv4_address: 192.168.71.132 + oai-smf: - container_name: "rfsim5g-oai-smf" + container_name: "oai-smf" image: oaisoftwarealliance/oai-smf:v2.1.9 environment: - - TZ=Europe/Paris + - TZ=Asia/Tokyo volumes: - - ./mini_nonrf_config.yaml:/openair-smf/etc/config.yaml + - ./conf/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml depends_on: - oai-amf networks: public_net: ipv4_address: 192.168.71.133 + oai-upf: - container_name: "rfsim5g-oai-upf" + container_name: "oai-upf" image: oaisoftwarealliance/oai-upf:v2.1.9 environment: - - TZ=Europe/Paris + - TZ=Asia/Tokyo volumes: - - ./mini_nonrf_config.yaml:/openair-upf/etc/config.yaml + - ./conf/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml depends_on: - oai-smf cap_add: @@ -63,9 +67,10 @@ services: ipv4_address: 192.168.71.134 traffic_net: ipv4_address: 192.168.72.134 + oai-ext-dn: privileged: true - container_name: rfsim5g-oai-ext-dn + container_name: oai-ext-dn image: oaisoftwarealliance/trf-gen-cn5g:focal entrypoint: /bin/bash -c \ "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\ @@ -80,16 +85,15 @@ services: interval: 10s timeout: 5s retries: 5 + oai-gnb: - image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} - container_name: rfsim5g-oai-gnb + image: nakaolab:oct1 + container_name: oai-gnb cap_drop: - ALL - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time - ASAN_OPTIONS: detect_leaks=0 depends_on: - oai-ext-dn + command: ["-c", "/oai-ran/cmake_targets/ran_build/build/nr-softmodem -O /opt/oai-gnb/etc/gnb.yaml -E --rfsim --log_config.global_log_options level,nocolor,time"] networks: public_net: ipv4_address: 192.168.71.140 @@ -100,21 +104,55 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue + + oai-flexric: + image: nakaolab:oct1 + container_name: oai-flexric + command: ["-c", "stdbuf -o0 /oai-ran/openair2/E2AP/flexric/build/examples/ric/nearRT-RIC"] + networks: + public_net: + ipv4_address: 192.168.71.21 + volumes: + - ./conf/flexric.conf:/oai-ran/openair2/E2AP/flexric/flexric.conf + - ./conf/flexric.conf:/usr/local/etc/flexric/flexric.conf + healthcheck: + test: /bin/bash -c "pgrep nearRT-RIC" + interval: 10s + timeout: 5s + + xapp-chatgpt: + image: nakaolab:oct1 + command: ["-c", "XAPP_DURATION=20 stdbuf -oL /oai-ran/openair2/E2AP/flexric/build/examples/xApp/c/monitor/xapp_chatgpt"] + container_name: xapp-chatgpt + environment: + - TZ=Asia/Tokyo + depends_on: + - oai-flexric + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.71.26 + healthcheck: + test: /bin/bash -c "pgrep -f xapp_chatgpt" + interval: 10s + timeout: 5s + + oai-nr-ue1: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue1 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time + # environment: + # USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: public_net: - ipv4_address: 192.168.71.150 + ipv4_address: 192.168.71.151 devices: - /dev/net/tun:/dev/net/tun volumes: @@ -124,40 +162,16 @@ services: interval: 10s timeout: 5s retries: 5 + oai-nr-ue2: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue2 + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001101 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue2 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001101 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time - depends_on: - - oai-gnb - networks: - public_net: - ipv4_address: 192.168.71.151 - devices: - - /dev/net/tun:/dev/net/tun - volumes: - - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf - healthcheck: - test: /bin/bash -c "pgrep nr-uesoftmodem" - interval: 10s - timeout: 5s - retries: 5 - oai-nr-ue3: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue3 - cap_drop: - - ALL - cap_add: - - NET_ADMIN # for interface bringup - - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001102 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -172,16 +186,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue4: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue4 + + oai-nr-ue3: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001102 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue3 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001103 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -196,16 +210,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue5: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue5 + + oai-nr-ue4: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001103 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue4 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001104 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -220,16 +234,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue6: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue6 + + oai-nr-ue5: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001104 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue5 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001105 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -244,16 +258,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue7: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue7 + + oai-nr-ue6: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001105 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue6 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001106 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -268,16 +282,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue8: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue8 + + oai-nr-ue7: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001106 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue7 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001107 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -292,16 +306,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue9: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue9 + + oai-nr-ue8: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001107 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue8 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001108 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -316,16 +330,16 @@ services: interval: 10s timeout: 5s retries: 5 - oai-nr-ue10: - image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} - container_name: rfsim5g-oai-nr-ue10 + + oai-nr-ue9: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001108 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue9 cap_drop: - ALL cap_add: - NET_ADMIN # for interface bringup - NET_RAW # for ping - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001109 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: @@ -341,13 +355,60 @@ services: timeout: 5s retries: 5 + oai-nr-ue10: + image: nakaolab:oct1 + command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001109 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"] + container_name: oai-nr-ue10 + cap_drop: + - ALL + cap_add: + - NET_ADMIN # for interface bringup + - NET_RAW # for ping + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.71.160 + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + # iperf: + # image: phlak/iperf + # container_name: iperf + # ports: + # - "5201:5201/tcp" + # - "5201:5201/udp" + # networks: + # traffic_net: + # ipv4_address: 192.168.72.130 + # restart: unless-stopped + + iperf: + image: mlabbe/iperf + container_name: iperf + command: ["-s", "-p 5201"] + ports: + - "5201:5201/tcp" + - "5201:5201/udp" + networks: + public_net: + ipv4_address: 192.168.71.130 + restart: unless-stopped + networks: public_net: driver: bridge name: rfsim5g-oai-public-net ipam: config: - - subnet: 192.168.71.128/26 + - subnet: 192.168.71.0/24 driver_opts: com.docker.network.bridge.name: "rfsim5g-public" traffic_net: diff --git a/ci-scripts/yaml_files/5g_rfsimulator/exp.sh b/ci-scripts/yaml_files/5g_rfsimulator/exp.sh new file mode 100755 index 0000000000..baee1a0127 --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/exp.sh @@ -0,0 +1,654 @@ +#!/bin/bash +# Examples: +# ./exp-35.sh +# UE_MIN=2 UE_MAX=12 UE_PEAK_HOUR=14 UE_NOISE_FRAC=0.15 ./exp-35.sh +# TEST_UE=oai-nr-ue1 TEST_PROFILE=browsing TEST_DURATION_SEC=120 ./exp-35.sh + +set -euo pipefail + + +# ---------------------------[ Random Burst Mode ]---------------------------- +# UE scaler mode: 'random' (rapid random targets) or 'diurnal' (legacy cosine) +UE_SCALER_MODE="${UE_SCALER_MODE:-random}" +# Interval (sec) for scaler ticks +UE_SCALER_INTERVAL_SEC="${UE_SCALER_INTERVAL_SEC:-30}" + +# Rapid/random system-level bursts: every few minutes trigger a short, global burst +# that nudges all UEs to start/extend a session at once. UE-side script watches +# /tmp/system_burst and reacts (see traffic_runner --profile random). +RANDOM_BURST_MODE="${RANDOM_BURST_MODE:-1}" # 1=enabled, 0=disabled +BURST_MIN_GAP_SEC="${BURST_MIN_GAP_SEC:-180}" # 3 minutes +BURST_MAX_GAP_SEC="${BURST_MAX_GAP_SEC:-600}" # 10 minutes +BURST_MARK_FILE="${BURST_MARK_FILE:-/tmp/system_burst}" +# -------------------------------[ Config ]------------------------------------ + +ARTIFACT_DIR="/home/nakaolab-go/OAI/research-main/artifacts/" +mkdir -p "$ARTIFACT_DIR" + +POWER_LOG_FILE="${POWER_LOG_FILE:-$ARTIFACT_DIR/power_usage.csv}" +CPU_PROXY_LOG_FILE="${CPU_PROXY_LOG_FILE:-$ARTIFACT_DIR/cpu_proxy.csv}" +RRC_SINCE_STATE="${RRC_SINCE_STATE:-$ARTIFACT_DIR/.rrc_since}" + +# xApp (mac stats) container + path inside container +MAC_STATS_CONTAINER="${MAC_STATS_CONTAINER:-xapp-chatgpt}" +MAC_STATS_FILE_IN_CONTAINER="${MAC_STATS_FILE_IN_CONTAINER:-/tmp/mac_stats.log}" + +# Intervals (seconds) +RRC_LOG_INTERVAL_SEC="${RRC_LOG_INTERVAL_SEC:-10}" +POWER_LOG_INTERVAL_SEC="${POWER_LOG_INTERVAL_SEC:-5}" +CPU_PROXY_INTERVAL_SEC="${CPU_PROXY_INTERVAL_SEC:-5}" +UE_SCALER_INTERVAL_SEC="${UE_SCALER_INTERVAL_SEC:-60}" + +# Docker compose helper +DC_BIN=(docker compose) +dc() { command "${DC_BIN[@]}" "$@"; } +dc down -v + +# Service names (override if your compose uses different ones) +AMF_SERVICE="${AMF_SERVICE:-oai-amf}" +UPF_SERVICE="${UPF_SERVICE:-oai-upf}" +GNB_SERVICE="${GNB_SERVICE:-oai-gnb}" +XAPP_SERVICE="${XAPP_SERVICE:-xapp-chatgpt}" + +# UE service name pattern (for discovery) +UE_NAME_REGEX="${UE_NAME_REGEX:-^oai-nr-ue}" + +# Test controls +TEST_UE="${TEST_UE:-}" # e.g. oai-nr-ue1 +TEST_PROFILES="${TEST_PROFILES:-}" # profile name +TEST_DURATION_SEC="${TEST_DURATION_SEC:-0}" # 0 = run indefinitely + +# Diurnal scaling controls (normal mode only; ignored if TEST_UE is set) +UE_MIN="${UE_MIN:-1}" # Minimum number of UEs to keep connected +# UE_MAX="${UE_MAX:-}" # Max UEs; default = total UEs in compose +UE_MAX=10 +UE_PEAK_HOUR="${UE_PEAK_HOUR:-17}" # Local hour with peak load (0..23) +UE_NOISE_FRAC="${UE_NOISE_FRAC:-0.15}" # ±fractional noise applied to target + +# -------------------------[ Power Model (mW) ]-------------------------------- + +declare -A PWR_MW_PROFILE=( + [idle]=60 # screen on, modem camped, no transfers + [iot_chatty]=150 # tiny bursts, long sleeps + [browsing]=380 # short transfers with think-time + [voip]=520 # steady uplink/downlink small packets + [video]=850 # continuous DL; 720–1080p + [gaming]=950 # bursty DL + UL keepalives + [bulk_download]=1100 # sustained UL/DL at MCS ceiling +) + +# -------------------------------[ Utils ]------------------------------------- + +ts_now() { date +%s; } +ts_iso() { date -Is; } +ts_tag() { date +%Y%m%d_%H%M%S; } +log() { echo "[$(ts_iso)] $*"; } + +exec_safe() { + local svc="$1"; shift + dc exec -T "$svc" bash -lc "$*" +} + +# -----------------------------[ Discovery ]----------------------------------- + +list_all_services() { dc config --services; } + +get_all_ue_services() { list_all_services | grep -E "${UE_NAME_REGEX}" || true; } + +get_core_services() { + # everything except UEs, gNB, xApp + list_all_services | grep -Ev "(${UE_NAME_REGEX}|^${GNB_SERVICE}$|^${XAPP_SERVICE}$)" || true +} + +get_running_ues() { + # Running UEs by service name (include TEST_UE name if custom) + if [[ -n "${TEST_UE:-}" ]]; then + dc ps --services --filter "status=running" \ + | grep -E "${UE_NAME_REGEX}|^${TEST_UE}$" || true + else + dc ps --services --filter "status=running" \ + | grep -E "${UE_NAME_REGEX}" || true + fi +} + +# -------------------------[ Readiness helpers ]------------------------------- + +svc_cid() { dc ps -q "$1"; } # container id for a service + +wait_healthy() { + local svc="$1" timeout="${2:-60}" + local cid; cid="$(svc_cid "$svc")" + if [[ -z "$cid" ]]; then + log "wait_healthy: service '$svc' not running." + return 1 + fi + local health + health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$cid" 2>/dev/null || true)" + if [[ -z "$health" ]]; then + log "wait_healthy: no healthcheck for '$svc'; skipping." + return 0 + fi + local start; start="$(ts_now)" + while :; do + health="$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "")" + if [[ "$health" == "healthy" ]]; then + log "$svc is healthy." + return 0 + fi + if (( $(ts_now) - start > timeout )); then + log "wait_healthy timeout for '$svc' (last='$health')." + return 1 + fi + sleep 1 + done +} + +wait_log() { + local svc="$1" pat="$2" timeout="${3:-60}" + local cid; cid="$(svc_cid "$svc")" + if [[ -z "$cid" ]]; then + log "wait_log: service '$svc' not running." + return 1 + fi + local start; start="$(ts_now)" + while :; do + if dc logs "$svc" --since=2m 2>&1 | grep -Eiq -- "$pat"; then + log "wait_log: detected '$pat' in '$svc'." + return 0 + fi + if (( $(ts_now) - start > timeout )); then + log "wait_log timeout for '$svc' waiting '$pat'." + return 1 + fi + sleep 1 + done +} + +get_container_ip() { + local cid; cid="$(svc_cid "$1")" + [[ -z "$cid" ]] && return 1 + docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$cid" +} + +# --------------------------[ Staged bring-up ]-------------------------------- + +start_services_list() { + [[ $# -eq 0 ]] && return 0 + dc up -d "$@" +} + +start_ue_list() { + [[ $# -eq 0 ]] && return 0 + for svc in "$@"; do + log "Starting: $svc" + dc up -d "$svc" + sleep 10 + done +} + +start_in_stages() { + # 1) CORE + mapfile -t core_svcs < <(get_core_services) + if [[ ${#core_svcs[@]} -gt 0 ]]; then + start_services_list "${core_svcs[@]}" + else + log "No CORE services to start." + exit 1 + fi + + # Ensure AMF is ready + wait_healthy "${AMF_SERVICE}" 15 + wait_healthy "${UPF_SERVICE}" 15 + exec_safe "${UPF_SERVICE}" 'bash -lc " + GW=192.168.72.129 + DEV=\$(ip -4 route get \$GW 2>/dev/null | awk '\''/ dev /{for(i=1;i<=NF;i++) if(\$i==\"dev\"){print \$(i+1); exit}}'\''); DEV=\${DEV:-eth0} + ip -4 route replace \$GW/32 dev \$DEV + ip -4 route replace default via \$GW dev \$DEV onlink + "' + wait_healthy oai-flexric 15 + + # 2) gNB + start_services_list "${GNB_SERVICE}" + wait_log "${GNB_SERVICE}" "Received NGSetupResponse from AMF" 20 + + wait_log "${GNB_SERVICE}" "Transaction ID E2 SETUP-REQUEST 0 E2 SETUP-RESPONSE 0" 15 + + # 3) xApp + start_services_list "${XAPP_SERVICE}" + wait_healthy "${XAPP_SERVICE}" 15 + + # 4) UE(s) + # if [[ -n "${TEST_UE:-}" ]]; then + # # Allow multiple UEs in TEST_UE (comma and/or space separated) + # IFS=', ' read -r -a _ue_list <<< "${TEST_UE}" + # start_services_list "${_ue_list[@]}" + # for ue in "${_ue_list[@]}"; do + # [[ -z "$ue" ]] && continue + # if wait_oai_tun "$ue" 20; then + # exec_safe "$ue" 'ip route replace default dev oaitun_ue1' || true + # run_traffic_profile "$ue" "random" "0" || true + # else + # exit 1 + # fi + # done + # else + # # Initial diurnal target, then enforce UE count + # local target; if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then target="$(calc_target_ue_count)"; else target="$(random_target_ue_count)"; fi + # log "Initial diurnal target UE count: ${target}" + # ensure_ue_count "$target" + # fi +} + +# -------------------------[ Diurnal UE scaling ]------------------------------ + +calc_target_ue_count() { + local total_ues="${UE_MAX:-}" + if [[ -z "$total_ues" ]]; then + total_ues="$(get_all_ue_services | wc -l | tr -d ' ')" + fi + local min="${UE_MIN:-1}" + if (( total_ues < min )); then total_ues="$min"; fi + + local H M + H="$(date +%H)"; M="$(date +%M)" + + # Compute target via cosine wave (peak at UE_PEAK_HOUR), then add noise and clamp + awk -v min="$min" -v max="$total_ues" -v peak="${UE_PEAK_HOUR:-14}" \ + -v noise="${UE_NOISE_FRAC:-0.15}" -v H="$H" -v M="$M" 'BEGIN{ + # time-of-day in days [0,1) + t = (H*60.0 + M)/1440.0 + ph = peak/24.0 + norm = (cos(2*3.141592653589793*(t - ph)) + 1)/2 # 0..1 + base = min + norm*(max-min) + + srand(); u = (rand()*2.0 - 1.0) * noise # [-noise, +noise] + val = base * (1.0 + u) + + if (val < min) val = min + if (val > max) val = max + + # round to nearest integer + printf("%.0f\n", val) + }' +} + +wait_oaitun() { + local ue="$1" timeout="${2:-20}" + for ((i=1; i<=timeout; i++)); do + if exec_safe "$ue" 'ip link show oaitun_ue1 >/dev/null 2>&1'; then + return 0 + fi + sleep 1 + done + return 1 +} + +ensure_ue_count() { + local target="$1" + + # Build arrays + mapfile -t all_ues < <(get_all_ue_services) + mapfile -t running < <(get_running_ues) + + # Current and desired counts + local cur="${#running[@]}" + + if (( cur == target )); then + log "UE scaler: already at target (${target})." + return 0 + fi + + # Set helpers for membership tests + declare -A IS_RUNNING=() + for u in "${running[@]}"; do IS_RUNNING["$u"]=1; done + + # Determine non-running UEs + declare -a not_running=() + for u in "${all_ues[@]}"; do + [[ -z "${IS_RUNNING[$u]:-}" ]] && not_running+=("$u") + done + + # Start more UEs if needed + if (( cur < target )); then + local need=$(( target - cur )) + log "UE scaler: need +${need} UEs (cur=${cur}, target=${target})." + # Choose candidates (prefer random if shuf available) + local to_start=() + if command -v shuf >/dev/null 2>&1; then + mapfile -t to_start < <(printf '%s\n' "${not_running[@]}" | shuf -n "$need" || true) + else + to_start=("${not_running[@]:0:$need}") + fi + if (( ${#to_start[@]} > 0 )); then + start_ue_list "${to_start[@]}" + # kick traffic + (best-effort) readiness + for ue in "${to_start[@]}"; do + if wait_oaitun "$ue" 20; then + exec_safe "$ue" 'ip route replace default dev oaitun_ue1' + run_traffic_profile "$ue" "random" "0" + else + dc restart "$ue" + to_start+="$ue" + fi + done + fi + return 0 + fi + + # Otherwise stop some UEs to shrink + local drop=$(( cur - target )) + log "UE scaler: need -${drop} UEs (cur=${cur}, target=${target})." + local to_stop=() + if command -v shuf >/dev/null 2>&1; then + mapfile -t to_stop < <(printf '%s\n' "${running[@]}" | shuf -n "$drop" || true) + else + to_stop=("${running[@]:0:$drop}") + fi + for ue in "${to_stop[@]}"; do + log "Stopping UE ${ue}" + dc stop "$ue" || true + dc rm -f "$ue" || true + done +} + +# --------------------------[ UE traffic helpers ]----------------------------- + +pick_profile() { + local r=$((RANDOM % 100)) + if (( r < 30 )); then echo browsing # 45% + elif (( r < 70 )); then echo video # 35% + elif (( r < 90 )); then echo bulk_download # 12% + else echo iot_chatty # 8% + fi +} + +run_traffic_profile() { + local ue="$1" profile="$2" duration="${3:-0}" + exec_safe "$ue" "echo 'traffic runner ready for $ue. profile: $profile'" + exec_safe "$ue" "bash -lc ' + if [ ! -f /tmp/traffic_profile.lock ]; then + prof=\$1; dur=\$2 + /usr/local/bin/traffic_runner --profile \$prof --duration \$dur >/tmp/traffic_runner.log 2>&1 & + printf \"%s %s\n\" \"\$prof\" \"\$dur\" > /tmp/traffic_profile.lock + fi + ' -- $profile $duration" +} + +# --------------------------[ RRC / MAC scraping ]----------------------------- + +rotate_mac_capture() { + local tag="${1:-}" + [[ -z "$tag" ]] && tag="$(ts_tag)" + if dc ps --services --filter "status=running" | grep -q "^${MAC_STATS_CONTAINER}$"; then + log "Rotating mac_stats from ${MAC_STATS_CONTAINER}" + dc cp "${MAC_STATS_CONTAINER}:${MAC_STATS_FILE_IN_CONTAINER}" \ + "${ARTIFACT_DIR}/mac_stats_${tag}.log" || true + exec_safe "$MAC_STATS_CONTAINER" "> '${MAC_STATS_FILE_IN_CONTAINER}'" + else + log "mac_stats container ${MAC_STATS_CONTAINER} not running; skipping rotate" + fi +} + +_since_arg_from_file() { + local f="$1" + local val + val="$(tr -d '[]\r\n' < "$f" 2>/dev/null || true)" + if [[ -n "$val" ]]; then + printf -- '--since=%s' "$val" + else + printf -- '--since=2m' + fi +} + +dump_gnb_rrc_logs() { + local tag="$1" + dc logs "${GNB_SERVICE}" 2>&1 | grep -E "NR_RRC" \ + > "${ARTIFACT_DIR}/gnb_rrc_${tag}.log" || true +} + +# -----------------------------[ Power / CPU ]--------------------------------- + +init_power_logs() { + if [[ ! -s "$POWER_LOG_FILE" ]]; then + echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$POWER_LOG_FILE" + fi + if [[ ! -s "$CPU_PROXY_LOG_FILE" ]]; then + echo "epoch,container,cpu_percent,mem_usage" > "$CPU_PROXY_LOG_FILE" + fi +} + +profile_total_mw() { + local profile="$1" + echo "${PWR_MW_PROFILE[$profile]:-0}" +} + +sample_power_once() { + local epoch="$(ts_now)" + local dt_s="${POWER_LOG_INTERVAL_SEC}" + + while IFS= read -r ue; do + [[ -z "$ue" ]] && continue + local lock + lock="$(exec_safe "$ue" 'bash -lc "cat /tmp/traffic_profile.lock 2>/dev/null || true"')" + local picked_profile picked_duration + picked_profile="$(awk "{print \$1}" <<<"$lock")" + picked_duration="$(awk "{print \$2}" <<<"$lock")" + + if [[ -z "$picked_profile" ]]; then + picked_profile="${TEST_PROFILE:-idle}" + fi + + while [[ "$picked_profile" == -* ]]; do + picked_profile="${picked_profile#-}" + done + picked_profile="${picked_profile,,}" + + local p_mw + p_mw="$(profile_total_mw "$picked_profile")" + : "${p_mw:=0}" + + local energy_mJ=$(( p_mw * dt_s )) + + echo "${epoch},${ue},${picked_profile},${p_mw},${dt_s},${energy_mJ}" >> "$POWER_LOG_FILE" + done < <(get_running_ues) +} + +# --- CPU proxy: query docker stats by container ID and map back to service name --- +cpu_proxy_once() { + local epoch="$(ts_now)" + + # Build list of UE service names (same as container names) + declare -a SVC_LIST=() + while IFS= read -r svc; do + [[ -z "$svc" ]] && continue + SVC_LIST+=("$svc") + done < <(get_running_ues) + + # Query docker stats by NAME and key 'seen' by NAME + local fmt='{{.Name}},{{.CPUPerc}},{{.MemUsage}}' + declare -A SEEN=() + + while IFS=',' read -r name cpu mem; do + [[ -z "$name" ]] && continue + name="${name#/}" + SEEN["$name"]=1 + cpu="${cpu%%%}" # drop trailing '%' + echo "${epoch},${name},${cpu},${mem}" >> "$CPU_PROXY_LOG_FILE" + done < <(docker stats --no-stream --format "$fmt" "${SVC_LIST[@]}" 2>/dev/null || true) + + # Any container absent from docker stats output → record zeros once + local svc + for svc in "${SVC_LIST[@]}"; do + if [[ -z "${SEEN[$svc]:-}" ]]; then + echo "${epoch},${svc},0,0" >> "$CPU_PROXY_LOG_FILE" + fi + done +} + +rotate_csv_if_any() { + local src="$1" prefix="$2" tag="${3:-}" + [[ -z "$tag" ]] && tag="$(ts_tag)" + if [[ -s "$src" ]]; then + mv "$src" "${ARTIFACT_DIR}/${prefix}_${tag}.csv" || true + case "$prefix" in + power_usage) echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$src" ;; + cpu_proxy) echo "epoch,container,cpu_percent,mem_usage" > "$src" ;; + esac + fi +} + +finalize_and_rotate_artifacts() { + local tag; tag="$(ts_tag)" + rotate_mac_capture "$tag" || true + dump_gnb_rrc_logs "$tag" || true + rotate_csv_if_any "$POWER_LOG_FILE" "power_usage" "$tag" + rotate_csv_if_any "$CPU_PROXY_LOG_FILE" "cpu_proxy" "$tag" +} + +# -------------------------------[ Signals ]----------------------------------- + +__SHUTTING_DOWN=0 +on_interrupt() { + if [[ "${__SHUTTING_DOWN}" -eq 1 ]]; then return; fi + __SHUTTING_DOWN=1 + log "Signal received (INT/TERM); taking final samples and rotating artifacts…" + # One last sample tick + sample_power_once || true + cpu_proxy_once || true + # Rotate artifacts and clean up + finalize_and_rotate_artifacts + cleanup + # 130 is the conventional exit code for SIGINT + exit 130 +} + +# --------------------------------[ Cleanup ]---------------------------------- + +cleanup() { + # Default: stop only UEs; keep infra up for inspection + log "Cleanup: stopping UE containers only" + while IFS= read -r ue; do + [[ -z "$ue" ]] && continue + dc stop "$ue" || true + dc rm -f "$ue" || true + done < <(dc ps --services | grep -E "${UE_NAME_REGEX}" || true) +} + +# ---------------------------------[ Main ]------------------------------------ + + +trigger_system_burst() { + mapfile -t running < <(get_running_ues) + if (( ${#running[@]} == 0 )); then + return 0 + fi + log "Triggering system-wide burst for ${#running[@]} UE(s)…" + for ue in "${running[@]}"; do + exec_safe "$ue" "bash -lc 'date +%s > ${BURST_MARK_FILE}'" || true + done +} + +random_target_ue_count() { + local total_ues="${UE_MAX:-}" + if [[ -z "$total_ues" ]]; then + total_ues="$(get_all_ue_services | wc -l | tr -d ' ')" + fi + local min="${UE_MIN:-1}" + if (( total_ues < min )); then total_ues="$min"; fi + local val + if command -v shuf >/dev/null 2>&1; then + val="$(shuf -i "${min}-${total_ues}" -n 1)" + else + local span=$(( total_ues - min + 1 )) + val=$(( min + (RANDOM % span) )) + fi + echo "$val" +} +main() { + trap on_interrupt INT TERM + + init_power_logs + + # Staged bring-up with readiness checks for both normal and TEST modes + start_in_stages + + # Initialize next system-burst time + if (( RANDOM_BURST_MODE )); then + local next_burst_ts + next_burst_ts="$(($(ts_now) + BURST_MIN_GAP_SEC + RANDOM % (BURST_MAX_GAP_SEC - BURST_MIN_GAP_SEC + 1)))" + fi + + # Kick off traffic workload(s) + if [[ -n "${TEST_UE:-}" ]]; then + log "TEST UE(S) ENABLED" + # Support multiple UEs (comma/space/newline separated) + read -r -a _ue_list <<< "$(tr ',\n' ' ' <<< "${TEST_UE}")" + read -r -a _ue_profiles <<< "$(tr ',\n' ' ' <<< "${TEST_PROFILES}")" + start_ue_list "${_ue_list[@]}" + for i in "${!_ue_list[@]}"; do + ue="${_ue_list[$i]}" + prof="${_ue_profiles[$i]}" + [[ -z "$ue" ]] && continue + if wait_oaitun "$ue" 20; then + exec_safe "$ue" "ip route replace default dev oaitun_ue1" + run_traffic_profile "$ue" "$prof" + else + exit 1 + fi + done + fi + + # Optional finite duration for TEST mode + if [[ -n "${TEST_UE:-}" && "${TEST_DURATION_SEC:-0}" -gt 0 ]]; then + RUN_STOP_TS=$(( $(ts_now) + TEST_DURATION_SEC )) + fi + + # Main sampling & scaling loop + local last_rrc="$(( $(ts_now) - RRC_LOG_INTERVAL_SEC ))" + local last_power="$(( $(ts_now) - POWER_LOG_INTERVAL_SEC ))" + local last_cpu="$(( $(ts_now) - CPU_PROXY_INTERVAL_SEC ))" + local last_scale="$(( $(ts_now) - UE_SCALER_INTERVAL_SEC ))" + + log "Entering main loop (sampling + diurnal scaler)…" + while :; do + local now; now="$(ts_now)" + # System-wide random burst scheduler + if (( RANDOM_BURST_MODE )); then + if (( now >= next_burst_ts )); then + trigger_system_burst || true + next_burst_ts="$(( now + BURST_MIN_GAP_SEC + RANDOM % (BURST_MAX_GAP_SEC - BURST_MIN_GAP_SEC + 1) ))" + fi + fi + + + if (( now - last_power >= POWER_LOG_INTERVAL_SEC )); then + sample_power_once || true + last_power="$now" + fi + + if (( now - last_cpu >= CPU_PROXY_INTERVAL_SEC )); then + cpu_proxy_once || true + last_cpu="$now" + fi + + # Re-evaluate diurnal target (normal mode only) + if [[ -z "${TEST_UE:-}" ]] && (( now - last_scale >= UE_SCALER_INTERVAL_SEC )); then + local target; if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then target="$(calc_target_ue_count)"; else target="$(random_target_ue_count)"; fi + log "Diurnal scaler tick: target = ${target}" + ensure_ue_count "$target" + last_scale="$now" + fi + + # TEST duration stop gate + if [[ -n "${RUN_STOP_TS:-}" && "$now" -ge "${RUN_STOP_TS}" ]]; then + log "TEST run reached duration (${TEST_DURATION_SEC}s); finalizing artifacts…" + finalize_and_rotate_artifacts + cleanup + exit 0 + fi + + sleep 1 + done +} + +main "$@" diff --git a/ci-scripts/yaml_files/5g_rfsimulator/local-override-ue-gdb.yaml b/ci-scripts/yaml_files/5g_rfsimulator/local-override-ue-gdb.yaml deleted file mode 100644 index 0b2b60210a..0000000000 --- a/ci-scripts/yaml_files/5g_rfsimulator/local-override-ue-gdb.yaml +++ /dev/null @@ -1,6 +0,0 @@ -services: - oai-nr-ue: - privileged: true - stdin_open: true - tty: true - command: gdb --args /opt/oai-nr-ue/bin/nr-uesoftmodem -O /opt/oai-nr-ue/etc/nr-ue.conf -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3619200000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time diff --git a/ci-scripts/yaml_files/5g_rfsimulator/local-override.yaml b/ci-scripts/yaml_files/5g_rfsimulator/local-override.yaml deleted file mode 100644 index 40d9b7ecc9..0000000000 --- a/ci-scripts/yaml_files/5g_rfsimulator/local-override.yaml +++ /dev/null @@ -1,27 +0,0 @@ -include: - - ../local_common_overrides/build_images.yaml - - ../local_common_overrides/rebuild_nr_softmodems.yaml -services: - oai-gnb: - privileged: true - depends_on: - rebuild-nr-softmodems: - condition: service_completed_successfully - volumes: - - ../../../cmake_targets/ran_build/build/nr-softmodem:/opt/oai-gnb/bin/nr-softmodem - - ../../../cmake_targets/ran_build/build/librfsimulator.so:/usr/local/lib/librfsimulator.so - oai-nr-ue: - privileged: true - image: ue-local - depends_on: - build-ran-build: - condition: service_completed_successfully - restart: true - oai-gnb: - condition: service_started - rebuild-nr-softmodems: - condition: service_completed_successfully - volumes: - - ../../../cmake_targets/ran_build/build/nr-uesoftmodem:/opt/oai-nr-ue/bin/nr-uesoftmodem - - ../../../cmake_targets/ran_build/build/librfsimulator.so:/usr/local/lib/librfsimulator.so - - ../../../crashdumps/:/cores/ diff --git a/ci-scripts/yaml_files/5g_rfsimulator/memo.txt b/ci-scripts/yaml_files/5g_rfsimulator/memo.txt new file mode 100644 index 0000000000..55c22e92bc --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/memo.txt @@ -0,0 +1,44 @@ +docker build -f docker/Dockerfile.gnb_e2 -t nakaolab:sep1 . + +O-RU and O-DU are connected via the Fronthaul (FH) interface. +FH traffic is logically diviced into 4 planes: +1. Control Plane which transports real-time slot configuration control messages +2. User Plane which transports IQ samples data (amplitude and phase) +3. Synchronization Plane transports timing information messages +4. Management Plane carries non-real-time management operation messages + +C/U Plane messages are exchanged using the eCPRI protocol. + +The energy consumption of RU can be modeled as +P = P_0 + P_RF + P_BB + P_PAM +where +P_0 : baseline components in the RU (fabric, ethernet, PCIe, etc.) +P_RF : a-to-d/d-to-a converters (ADC/DAC) and the freq up/down converters +P_BB : core digital processor (eFPGA) and the other low-PHY processing units + (e.g. beamforming) for each antenna chain +P_PAM : power amplifier module (PAM) at each antenna chain + +The paper claims that components other than PAM are rather static (?) +-> I guess the PAM is controlled by RF. Thus P_RF is the minimum amount of power + required to run RF. + +P_PAM = P_(PAM, 0) * P_(PAM, c)(r) * E_PAM * n_ant +where +P_(PAM, 0) : baseline power consumption that depends on the n of antennas, + ganging elements, and the maximum EIRP per antenna + * EIRP (Effective Isotropic Radiated Power) is the total power radiated + by an antenna in the direction of its maximum gain, referenced to an ideal isotropic radiator. +P_(PAM, c)(r) : a factor that depends on _the amount of subcarriers to be amplified_, + which in turn depends on the n of RBs (r) carried therein +E_PAM : captures the efficiency of the amplifier to convert DC power into signal power gain +n_ant : n of radio chains (with ONE amplifier per radio chain) + +> This observation motivates the principle behind the solution we propose in this paper: + consolidate data into as few symbol periods as possible, using more radio resources per period instead. +Since keeping PAM awake consumes more energy, it is better to send data in short bursts, where each burst +utilizes more spectrum resources + + + +MIMO Muting + diff --git a/ci-scripts/yaml_files/5g_rfsimulator/oai-end-to-end.jpg b/ci-scripts/yaml_files/5g_rfsimulator/oai-end-to-end.jpg deleted file mode 100644 index f454a016f6..0000000000 Binary files a/ci-scripts/yaml_files/5g_rfsimulator/oai-end-to-end.jpg and /dev/null differ diff --git a/ci-scripts/yaml_files/5g_rfsimulator/plots.py b/ci-scripts/yaml_files/5g_rfsimulator/plots.py new file mode 100644 index 0000000000..325e9bf54e --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/plots.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +UE Count Plotter +---------------- +Parses log lines like: +[2025-09-08 21:33:40.789676] UE RNTI=44512 ... +Groups entries into 1-second slots (by wall-clock time) and plots the number of +unique UEs (by RNTI) per slot for the entire file. + +Usage: + python ue_count_plot.py /path/to/log.txt [--save output.png] [--no-show] + +Notes: +- Uses matplotlib for a single-line plot. +- Does not set any colors or styles. +- For very large logs, this streams line-by-line with O(N) memory in the number + of distinct seconds encountered (each holds a set of RNTIs). +""" +import argparse +import datetime as dt +import re +import sys +from collections import defaultdict, OrderedDict +from typing import Dict, Set, Iterable, Tuple + +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + + +TIMESTAMP_RE = re.compile(r"\[(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)\]") +RNTI_RE = re.compile(r"\bRNTI=(?P\d+)\b") + + +def parse_lines(lines: Iterable[str]) -> Dict[dt.datetime, Set[int]]: + """ + Parse lines and bucket unique RNTIs per second. + + Returns: + dict mapping second (datetime truncated to seconds) -> set of RNTIs + """ + buckets: Dict[dt.datetime, Set[int]] = defaultdict(set) + + for line in lines: + ts_m = TIMESTAMP_RE.search(line) + rnti_m = RNTI_RE.search(line) + if not ts_m or not rnti_m: + # Skip lines that don't fit the schema + continue + + ts_str = ts_m.group("ts") + # Parse timestamp with or without fractional seconds + # Accept both "%Y-%m-%d %H:%M:%S" and with "%f" + if "." in ts_str: + ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S.%f") + else: + ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S") + + # Truncate to whole-second wall time + ts_minute = ts.replace(second=0, microsecond=0) + rnti = int(rnti_m.group("rnti")) + buckets[ts_minute].add(rnti) + return buckets + + +def sort_and_fill(buckets: Dict[dt.datetime, Set[int]]) -> Tuple[list, list]: + """ + Sorts buckets by time and fills missing seconds with zero counts. + + Returns: + times (list[datetime]), counts (list[int]) + """ + if not buckets: + return [], [] + + # Sort keys + sorted_times = sorted(buckets.keys()) + start = sorted_times[0] + end = sorted_times[-1] + + all_times = [] + counts = [] + + cur = start + one_sec = dt.timedelta(minutes=1) + while cur <= end: + all_times.append(cur) + counts.append(len(buckets.get(cur, set()))) + cur += one_sec + + return all_times, counts + + +def plot_counts(times: list, counts: list, title: str = "Unique UEs per minute"): + """ + Render a simple matplotlib line plot (no specific colors/styles set). + """ + fig, ax = plt.subplots(figsize=(10, 4)) + ax.plot(times, counts, linestyle="-") + ax.set_title(title) + ax.set_xlabel("Time (s)") + ax.set_ylabel("Unique UEs") + ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S")) + fig.autofmt_xdate() + plt.tight_layout() + return fig, ax + + +def main(): + parser = argparse.ArgumentParser(description="Plot unique UEs (by RNTI) per minute from a log file.") + parser.add_argument("--save", help="Optional path to save the plot image (e.g., output.png)") + parser.add_argument("--no-show", action="store_true", help="Do not display the plot window") + args = parser.parse_args() + + # Read lines + try: + lines = open("artifacts/mac_stats_20250909_231855.log", "r", encoding="utf-8") + except OSError as e: + print(f"Error opening {args.logfile}: {e}", file=sys.stderr) + sys.exit(1) + + buckets = parse_lines(lines) + if lines is not sys.stdin: + lines.close() + + times, counts = sort_and_fill(buckets) + if not times: + print("No valid entries found to plot.", file=sys.stderr) + sys.exit(2) + + fig, _ = plot_counts(times, counts) + + if args.save: + try: + fig.savefig(args.save, dpi=150, bbox_inches="tight") + print(f"Saved plot to: {args.save}") + except Exception as e: + print(f"Failed to save figure: {e}", file=sys.stderr) + + if not args.no_show: + plt.show() + + +if __name__ == "__main__": + main() diff --git a/ci-scripts/yaml_files/5g_rfsimulator/prediction.py b/ci-scripts/yaml_files/5g_rfsimulator/prediction.py new file mode 100644 index 0000000000..3fd9c971b7 --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator/prediction.py @@ -0,0 +1,772 @@ +from __future__ import annotations + +""" +Memory-safe, drop-in refined version of prediction.py +- Streams very large log files (no giant raw DataFrames) +- Computes observed flags without large Python sets +- Zero-copy feature audit (mask features on the fly) + +You can run this directly. Key differences from the original are marked with +"STREAMING" and "ZERO-COPY" comments. +""" + +import glob, os, re +from collections import defaultdict +from dataclasses import dataclass +from datetime import datetime +from pathlib import Path +from typing import List, Tuple, Dict, Optional + +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import torch +import torch.nn as nn +from torch.utils.data import Dataset, DataLoader + +# ---------------------------- +# Utils +# ---------------------------- + +def set_seed(seed=42): + import random + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) +set_seed(42) + + +def ensure_dir(p: Path): + p.mkdir(parents=True, exist_ok=True) + return p + + +def _run_lengths(mask: np.ndarray): + """Return number of runs and their lengths (consecutive True segments).""" + if mask.sum() == 0: + return 0, np.array([], dtype=int) + m = mask.astype(bool) + dm = np.diff(np.concatenate(([False], m, [False])).astype(np.int8)) + starts = np.where(dm == 1)[0] + ends = np.where(dm == -1)[0] + lens = ends - starts + return len(lens), lens + + +def report_underpredictions( + timestamps: pd.Series, + actual: np.ndarray, + pred: np.ndarray, + out_csv_path +): + """ + Save a per-minute CSV of under-predictions (pred < actual) and + return a dict of summary metrics. + """ + actual = actual.astype("float64") + pred = pred.astype("float64") + + finite = np.isfinite(actual) & np.isfinite(pred) + if finite.sum() == 0: + return { + "n_pred_points": 0, "n_under": 0, "under_rate": np.nan, + "mae_under": np.nan, "sum_under": np.nan, "max_under": np.nan, + "p95_under": np.nan, "n_runs": 0, "avg_run": np.nan, "max_run": 0 + } + + under = finite & (pred < actual) + gap = (actual - pred) # positive when under + rel_gap = np.divide(gap, np.maximum(actual, 1e-9)) + + # summary metrics + n_pred = int(finite.sum()) + n_under = int(under.sum()) + under_rate = n_under / n_pred + if n_under > 0: + mae_under = float(gap[under].mean()) + sum_under = float(gap[under].sum()) + max_under = float(gap[under].max()) + p95_under = float(np.percentile(gap[under], 95)) + runs, lens = _run_lengths(under) + avg_run = float(lens.mean()) if runs > 0 else np.nan + max_run = int(lens.max()) if runs > 0 else 0 + else: + mae_under = sum_under = max_under = p95_under = np.nan + runs = 0; avg_run = np.nan; max_run = 0 + + # write detailed CSV (only the under-predicted rows) + df = pd.DataFrame({ + "timestamp": np.asarray(timestamps)[finite], + "actual": actual[finite], + "pred": pred[finite], + "under": (pred[finite] < actual[finite]), + "gap": gap[finite], + "rel_gap": rel_gap[finite], + }) + df_under = df[df["under"]].drop(columns=["under"]) + df_under.to_csv(out_csv_path, index=False) + + return { + "n_pred_points": n_pred, + "n_under": n_under, + "under_rate": under_rate, + "mae_under": mae_under, + "sum_under": sum_under, + "max_under": max_under, + "p95_under": p95_under, + "n_runs": runs, + "avg_run": avg_run, + "max_run": max_run, + } + +# ---------------------------- +# Config +# ---------------------------- +# NOTE: fixed missing comma in DATE_STRS +DATE_STRS = ["20250828", "20250829"] # train/eval days handled independently +LOG_DIR = "artifacts" +RESULT_DIR = Path("results") + +SEQ_LEN = 45 # history length (minutes) +PRED_LEN = 10 # forecast horizon (minutes) +BATCH = 256 +EPOCHS_FIRST = 4 # per-target epochs on the first day (you can raise) +EPOCHS_NEXT = 2 # per-target epochs on subsequent days +LR = 1e-3 + +ADD_TIME_OF_DAY = True # add sin/cos time-of-day features + +# === Feature selection === +ENABLE_FEATURE_SELECTION = True +AUDIT_DAY = DATE_STRS[0] +FS_EPOCHS = max(2, min(3, EPOCHS_FIRST)) # quick audit training +FS_IMPROVE_THRESH = 0.01 # drop if masking improves val loss by >=1% +FS_NOISE_BAND = 0.002 # ignore micro-changes (<0.2%) + +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +# Model targets +TARGETS = ["num_ues", "traffic_load"] + +# Columns we aim to have per minute (some may be zero if not present) +FOCUS_COLS = [ + "num_ues","traffic_load","dl_prbs","ul_prbs","dl_sched_rbs","ul_sched_rbs", + "wb_cqi","dl_mcs1","ul_mcs1","pusch_snr","pucch_snr", + "dl_bler","ul_bler","bsr","phr", + "rrc_total","rrc_unique_rnti","rrc_setup_cpl","rrc_release", + "rrc_recfg","rrc_recfg_cpl","rrc_resume","rrc_paging" +] + +# ---------------------------- +# Regex / Parsing +# ---------------------------- +LOG_PATTERN_FULL = re.compile( + r"""\[ + (?P.*?)\] + \s+UE\s+RNTI=(?P\d+) + \s+DL_TBS=(?P\d+) + \s+UL_TBS=(?P\d+) + \s+DL_SDUs=(?P\d+) + \s+UL_SDUs=(?P\d+) + \s+DL_sched_RBs=(?P\d+) + \s+UL_sched_RBs=(?P\d+) + \s+DL_PRBs=(?P\d+) + \s+UL_PRBs=(?P\d+) + \s+DL_BLER=(?P[\d.]+) + \s+UL_BLER=(?P[\d.]+) + \s+PUSCH_SNR=(?P[\d.]+) + \s+PUCCH_SNR=(?P[\d.]+) + \s+WB_CQI=(?P\d+) + \s+DL_MCS1=(?P\d+) + \s+UL_MCS1=(?P\d+) + \s+DL_HARQ=(?P\d+) + \s+UL_HARQ=(?P\d+) + \s+BSR=(?P\d+) + \s+PHR=(?P\d+) + """, re.VERBOSE, +) +RRC_PATTERNS: List[re.Pattern] = [ + re.compile(r"\[(?P[^]]+)\].*RNTI=(?P[0-9a-fA-F]+).*RRC[_ ]?MSG=(?P[A-Za-z_]+)", re.IGNORECASE), + re.compile(r"\[(?P[^]]+)\].*rrc[=:](?P[A-Za-z_]+).*rnti[=:](?P[0-9a-fA-F]+)", re.IGNORECASE), + re.compile(r"\[(?P[^]]+)\].*(?PRRC[A-Za-z]+).*RNTI=(?P[0-9a-fA-F]+)", re.IGNORECASE), + re.compile(r"\|\s*(?P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?).*?RNTI\s*(?P[0-9a-fA-F]+)", re.IGNORECASE), +] + +# ---------------------------- +# STREAMING: Per-minute aggregators without raw-frame concatenation +# ---------------------------- +C_RNTI_MIN, C_RNTI_MAX = 0x003D, 0xFFFC + +@dataclass +class _MACAgg: + dl_sdus_sum: float = 0.0; ul_sdus_sum: float = 0.0 + dl_sched_rbs_sum: float = 0.0; ul_sched_rbs_sum: float = 0.0 + dl_prbs_sum: float = 0.0; ul_prbs_sum: float = 0.0 + dl_bler_sum: float = 0.0; ul_bler_sum: float = 0.0 + pusch_snr_sum: float = 0.0; pucch_snr_sum: float = 0.0 + wb_cqi_sum: float = 0.0; dl_mcs1_sum: float = 0.0; ul_mcs1_sum: float = 0.0 + dl_harq_sum: float = 0.0; ul_harq_sum: float = 0.0 + bsr_sum: float = 0.0; phr_sum: float = 0.0 + count: int = 0 + dl_bytes: int = 0; ul_bytes: int = 0 + ues: Optional[set] = None + + +def _floor_min(ts: datetime) -> pd.Timestamp: + return pd.Timestamp(ts).floor("min") + + +def stream_mac_minutes(files: List[str]) -> pd.DataFrame: + if not files: return pd.DataFrame() + acc: Dict[pd.Timestamp, _MACAgg] = {} + + for path in files: + with open(path, "r", errors="ignore") as f: + for line in f: + m = LOG_PATTERN_FULL.search(line) + if not m: continue + d = m.groupdict() + try: + ts = datetime.fromisoformat(d["ts"]) # may raise + except Exception: + continue + t = _floor_min(ts) + A = acc.get(t) + if A is None: + A = _MACAgg(ues=set()) + acc[t] = A + # unique UE when real RNTI with any traffic + try: + rnti_val = int(d["rnti"]) # MAC logs use decimal here + except Exception: + rnti_val = -1 + dl_tbs = int(d["dl_tbs"]); ul_tbs = int(d["ul_tbs"]) + if (C_RNTI_MIN <= rnti_val <= C_RNTI_MAX) and (dl_tbs > 0 or ul_tbs > 0): + A.ues.add(rnti_val) + + # sums & bytes + A.dl_bytes += dl_tbs; A.ul_bytes += ul_tbs + A.dl_sdus_sum += int(d["dl_sdus"]); A.ul_sdus_sum += int(d["ul_sdus"]) + A.dl_sched_rbs_sum += int(d["dl_sched_rbs"]); A.ul_sched_rbs_sum += int(d["ul_sched_rbs"]) + A.dl_prbs_sum += int(d["dl_prbs"]); A.ul_prbs_sum += int(d["ul_prbs"]) + A.dl_bler_sum += float(d["dl_bler"]); A.ul_bler_sum += float(d["ul_bler"]) + A.pusch_snr_sum += float(d["pusch_snr"]); A.pucch_snr_sum += float(d["pucch_snr"]) + A.wb_cqi_sum += int(d["wb_cqi"]); A.dl_mcs1_sum += int(d["dl_mcs1"]); A.ul_mcs1_sum += int(d["ul_mcs1"]) + A.dl_harq_sum += int(d["dl_harq"]); A.ul_harq_sum += int(d["ul_harq"]) + A.bsr_sum += int(d["bsr"]); A.phr_sum += int(d["phr"]) + A.count += 1 + + if not acc: return pd.DataFrame() + + rows = [] + for t in sorted(acc.keys()): + A = acc[t] + c = max(1, A.count) + row = { + "timestamp": t, + "num_ues": len(A.ues) if A.ues is not None else 0, + "dl_bytes": A.dl_bytes, + "ul_bytes": A.ul_bytes, + "dl_sdus": A.dl_sdus_sum / c, + "ul_sdus": A.ul_sdus_sum / c, + "dl_sched_rbs": A.dl_sched_rbs_sum / c, + "ul_sched_rbs": A.ul_sched_rbs_sum / c, + "dl_prbs": A.dl_prbs_sum / c, + "ul_prbs": A.ul_prbs_sum / c, + "dl_bler": A.dl_bler_sum / c, + "ul_bler": A.ul_bler_sum / c, + "pusch_snr": A.pusch_snr_sum / c, + "pucch_snr": A.pucch_snr_sum / c, + "wb_cqi": A.wb_cqi_sum / c, + "dl_mcs1": A.dl_mcs1_sum / c, + "ul_mcs1": A.ul_mcs1_sum / c, + "dl_harq": A.dl_harq_sum / c, + "ul_harq": A.ul_harq_sum / c, + "bsr": A.bsr_sum / c, + "phr": A.phr_sum / c, + "traffic_load": A.dl_bytes + A.ul_bytes, + "mac_present": True, + } + rows.append(row) + out = pd.DataFrame(rows) + out.sort_values("timestamp", inplace=True) + return out + + +RRC_FAM_COLS = ["rrc_conn_req","rrc_setup_cpl","rrc_recfg","rrc_recfg_cpl","rrc_reestab", + "rrc_release","rrc_resume","rrc_suspend","rrc_paging","rrc_other"] + +def canonical_rrc_family(msg: str | None) -> str: + if not msg: return "rrc_other" + m = msg.lower() + if "connectionrequest" in m or "connrequest" in m or "setupreq" in m: return "rrc_conn_req" + if "connectionsetupcomplete" in m or "setupcomplete" in m: return "rrc_setup_cpl" + if "reconfigurationcomplete" in m or "reconfigcomplete" in m: return "rrc_recfg_cpl" + if "reconfiguration" in m or "reconfig" in m: return "rrc_recfg" + if "reestablish" in m: return "rrc_reestab" + if "release" in m: return "rrc_release" + if "pag" in m: return "rrc_paging" + if "resume" in m: return "rrc_resume" + if "suspend" in m: return "rrc_suspend" + return "rrc_other" + + +def stream_rrc_minutes(files: List[str]) -> pd.DataFrame: + if not files: return pd.DataFrame() + total = defaultdict(int) + unique = defaultdict(set) + fam = {c: defaultdict(int) for c in RRC_FAM_COLS} + + for path in files: + with open(path, "r", errors="ignore") as f: + for line in f: + ts = rnti = msg = None + for pat in RRC_PATTERNS: + m = pat.search(line) + if m: + gd = m.groupdict(); ts = gd.get("ts"); rnti = gd.get("rnti"); msg = gd.get("msg"); break + if not ts: continue + ts = ts.rstrip("Z") + try: + t = _floor_min(datetime.fromisoformat(ts)) + except ValueError: + continue + total[t] += 1 + fam_key = canonical_rrc_family(msg) + fam[fam_key][t] += 1 + # unique per minute if we can parse rnti + rid = None + if rnti is not None: + try: + rid = int(rnti, 16) if any(c.isalpha() for c in rnti) else int(rnti, 10) + except ValueError: + pass + if rid is not None: + unique[t].add(rid) + + if not total: return pd.DataFrame() + minutes = sorted(total.keys()) + data = { + "timestamp": minutes, + "rrc_total": [total[t] for t in minutes], + "rrc_unique_rnti": [len(unique[t]) if t in unique else 0 for t in minutes], + "rrc_present": [True] * len(minutes), + } + for c in RRC_FAM_COLS: + data[c] = [fam[c][t] if t in fam[c] else 0 for t in minutes] + out = pd.DataFrame(data) + return out + +# ---------------------------- +# Per-minute features (compat helpers kept for reference) +# ---------------------------- + +def full_minute_index(start, end): + return pd.date_range(start.floor("min"), end.ceil("min"), freq="1min") + + +def add_time_of_day_feats(df: pd.DataFrame) -> pd.DataFrame: + if not ADD_TIME_OF_DAY: return df + out = df.copy() + m = out["timestamp"].dt.hour * 60 + out["timestamp"].dt.minute + out["tod_sin"] = np.sin(2*np.pi*m/1440.0).astype("float32") + out["tod_cos"] = np.cos(2*np.pi*m/1440.0).astype("float32") + return out + +# ---------------------------- +# STREAMING: Memory-safe build_day_features +# ---------------------------- + +def build_day_features(day: str) -> pd.DataFrame: + mac_files = glob.glob(os.path.join(LOG_DIR, f"mac_stats_{day}.log")) \ + + glob.glob(os.path.join(LOG_DIR, f"mac_stats_{day}_*.log")) + rrc_files = glob.glob(os.path.join(LOG_DIR, f"rrc_messages_{day}.log")) \ + + glob.glob(os.path.join(LOG_DIR, f"rrc_messages_{day}_*.log")) + mac_feat = stream_mac_minutes(mac_files) + rrc_feat = stream_rrc_minutes(rrc_files) + if mac_feat.empty and rrc_feat.empty: + raise SystemExit(f"[{day}] no parsable lines found") + + # build the full per-minute index from whichever side exists + frames = [df for df in (mac_feat, rrc_feat) if not df.empty] + t_start = min(df["timestamp"].min() for df in frames) + t_end = max(df["timestamp"].max() for df in frames) + idx = pd.date_range(t_start.floor("min"), t_end.ceil("min"), freq="1min") + + # --- MAC side: reindex or fabricate skeleton if empty + if not mac_feat.empty: + mac_feat = (mac_feat.set_index("timestamp") + .reindex(idx).fillna(0.0) + .rename_axis("timestamp").reset_index()) + mac_feat["mac_present"] = True + else: + mac_feat = pd.DataFrame({"timestamp": idx}) + mac_feat["mac_present"] = False # no MAC rows this minute + + # --- RRC side: reindex or fabricate skeleton if empty + if not rrc_feat.empty: + rrc_feat = (rrc_feat.set_index("timestamp") + .reindex(idx).fillna(0.0) + .rename_axis("timestamp").reset_index()) + rrc_feat["rrc_present"] = True + else: + rrc_feat = pd.DataFrame({"timestamp": idx}) + # minimal set the rest of the code expects: + rrc_feat["rrc_total"] = 0.0 + rrc_feat["rrc_unique_rnti"] = 0.0 + for c in RRC_FAM_COLS: + rrc_feat[c] = 0.0 + rrc_feat["rrc_present"] = False + + # merge & flags + df = (mac_feat.merge(rrc_feat, on="timestamp", how="left") + .fillna(0.0).reset_index(drop=True)) + present_cols = [] + if "mac_present" in df: present_cols.append(df["mac_present"].astype(bool)) + if "rrc_present" in df: present_cols.append(df["rrc_present"].astype(bool)) + df["observed"] = False if not present_cols else np.logical_or.reduce(present_cols) + + # time-of-day & compact dtypes + if ADD_TIME_OF_DAY: + m = df["timestamp"].dt.hour * 60 + df["timestamp"].dt.minute + df["tod_sin"] = np.sin(2*np.pi*m/1440.0).astype("float32") + df["tod_cos"] = np.cos(2*np.pi*m/1440.0).astype("float32") + float64_cols = df.select_dtypes(include=["float64"]).columns + if len(float64_cols): df[float64_cols] = df[float64_cols].astype("float32") + return df + +# ---------------------------- +# Sequence dataset (streaming) +# ---------------------------- + +def valid_t_indices(observed: np.ndarray, seq_len: int, pred_len: int) -> np.ndarray: + """Return t indices where [t-seq_len, t+pred_len-1] are all observed=True.""" + obs = observed.astype(np.uint8) + w = seq_len + pred_len + s = pd.Series(obs).rolling(w, min_periods=w).sum().to_numpy() + idx = [] + for i in range(w - 1, len(obs)): + if s[i] == w: + t = i - pred_len + 1 + if t >= seq_len: + idx.append(t) + return np.array(idx, dtype=np.int64) + + +class TSWindowDataset(Dataset): + def __init__(self, values_norm: np.ndarray, t_indices: np.ndarray, seq_len: int, pred_len: int, tgt_idx: int): + self.V = values_norm + self.t_idx = t_indices + self.seq = seq_len + self.h = pred_len + self.tgt = tgt_idx + def __len__(self): return len(self.t_idx) + def __getitem__(self, i): + t = self.t_idx[i] + x = self.V[t - self.seq : t, :] + y = self.V[t : t + self.h, self.tgt] + return torch.from_numpy(x), torch.from_numpy(y) + +# ---------------------------- +# LSTM model +# ---------------------------- +class Seq2PointLSTM(nn.Module): + def __init__(self, input_size, hidden=128, layers=1, pred_len=PRED_LEN, dropout=0.0): + super().__init__() + self.lstm = nn.LSTM(input_size, hidden, layers, batch_first=True, dropout=dropout if layers>1 else 0.0) + self.fc = nn.Linear(hidden, pred_len) + def forward(self, x): + enc, _ = self.lstm(x) + return self.fc(enc[:, -1, :]) + +# ---------------------------- +# Train / Forecast per day +# ---------------------------- + +def prepare_day_matrix(day_df: pd.DataFrame, feat_cols_override: List[str] | None = None) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, list]: + """Return values_norm, mu, sigma, observed, feature_names in a fixed order.""" + base_cols = FOCUS_COLS + (["tod_sin","tod_cos"] if ADD_TIME_OF_DAY else []) + feat_cols = feat_cols_override if feat_cols_override is not None else base_cols + + # ensure presence; if missing, create zeros + df = day_df.copy() + for c in feat_cols: + if c not in df.columns: + df[c] = 0.0 + + Xdf = df[feat_cols].astype("float32").copy() + observed = df["observed"].to_numpy() + + obs_mask = observed.astype(bool) + mu = Xdf[obs_mask].mean(0).to_numpy(dtype=np.float32) + sigma = Xdf[obs_mask].std(0).to_numpy(dtype=np.float32) + 1e-8 + values_norm = ((Xdf.to_numpy(dtype=np.float32) - mu) / sigma).astype(np.float32) + return values_norm, mu, sigma, observed.astype(np.bool_), feat_cols + + +def build_dataloaders(values_norm: np.ndarray, observed: np.ndarray, feat_cols: list, target: str): + tgt_idx = feat_cols.index(target) + t_idx_all = valid_t_indices(observed, SEQ_LEN, PRED_LEN) + if len(t_idx_all) < 100: + raise RuntimeError(f"Not enough valid windows for {target} on this day.") + split = int(0.9 * len(t_idx_all)) + t_tr, t_va = t_idx_all[:split], t_idx_all[split:] + ds_tr = TSWindowDataset(values_norm, t_tr, SEQ_LEN, PRED_LEN, tgt_idx) + ds_va = TSWindowDataset(values_norm, t_va, SEQ_LEN, PRED_LEN, tgt_idx) + dl_tr = DataLoader(ds_tr, batch_size=BATCH, shuffle=True, num_workers=0, pin_memory=True) + dl_va = DataLoader(ds_va, batch_size=BATCH, shuffle=False, num_workers=0, pin_memory=True) + return dl_tr, dl_va, tgt_idx + + +def eval_val_loss(model: nn.Module, dl_va: DataLoader) -> float: + crit = nn.MSELoss() + was_training = model.training # remember mode + model.eval() + with torch.no_grad(): + tot = 0.0; n = 0 + for xb, yb in dl_va: + xb = xb.to(DEVICE); yb = yb.to(DEVICE) + pred = model(xb) + tot += crit(pred, yb).item() * xb.size(0); n += xb.size(0) + if was_training: + model.train(True) # restore mode + return tot / max(1, n) + + +def train_one_day(model: nn.Module, dl_tr: DataLoader, dl_va: DataLoader, epochs: int) -> float: + opt = torch.optim.Adam(model.parameters(), lr=LR) + crit = nn.MSELoss() + best_va = float("inf") + + for _ in range(epochs): + model.train(True) + for xb, yb in dl_tr: + xb = xb.to(DEVICE); yb = yb.to(DEVICE) + opt.zero_grad() + pred = model(xb) + loss = crit(pred, yb) + loss.backward() + opt.step() + + va = eval_val_loss(model, dl_va) + best_va = min(best_va, va) + + return best_va + + +def rolling_forecast_day(model: nn.Module, values_norm: np.ndarray, observed: np.ndarray, + feat_cols: list, target: str, mu: np.ndarray, sigma: np.ndarray) -> np.ndarray: + tgt_idx = feat_cols.index(target) + N = values_norm.shape[0] + merged = np.full(N, np.nan, dtype=np.float32) + counts = np.zeros(N, dtype=np.float32) + + t_idx_all = valid_t_indices(observed, SEQ_LEN, PRED_LEN) + model.eval() + with torch.no_grad(): + for t in t_idx_all: + x = torch.from_numpy(values_norm[t-SEQ_LEN:t, :]).unsqueeze(0).to(DEVICE) + yhat = model(x).squeeze(0).cpu().numpy() # normalized + yhat_dn = yhat * sigma[tgt_idx] + mu[tgt_idx] + for j, v in enumerate(yhat_dn): + i = t + j + if i < N: + if np.isnan(merged[i]): merged[i] = 0.0 + merged[i] += v; counts[i] += 1.0 + with np.errstate(invalid="ignore", divide="ignore"): + merged = np.divide(merged, counts, out=merged, where=counts > 0) + return merged + +# ---------------------------- +# ZERO-COPY: Feature selection (mask-on-the-fly) +# ---------------------------- + +def eval_val_loss_masked(model: nn.Module, dl_va: DataLoader, col_to_zero: Optional[int]) -> float: + crit = nn.MSELoss() + was_training = model.training + model.eval() + tot = 0.0; n = 0 + with torch.no_grad(): + for xb, yb in dl_va: + if col_to_zero is not None: + xb = xb.clone() + xb[:, :, col_to_zero] = 0.0 + xb = xb.to(DEVICE); yb = yb.to(DEVICE) + pred = model(xb) + tot += crit(pred, yb).item() * xb.size(0); n += xb.size(0) + if was_training: + model.train(True) + return tot / max(1, n) + + +def feature_audit_and_select(day_df: pd.DataFrame, target: str, base_cols: List[str]) -> Tuple[List[str], Dict[str, float]]: + """ + Memory-safe audit: + - Train once on AUDIT_DAY (fast) + - For each feature, evaluate validation while zeroing that column on-the-fly + (no full matrix copies) + Returns (kept_features, deltas) where deltas[c] = (masked - base)/base. + """ + print(f"[{AUDIT_DAY}] auditing features for target={target}...") + values_norm, mu, sigma, observed, feat_cols = prepare_day_matrix(day_df, feat_cols_override=base_cols) + dl_tr, dl_va, _ = build_dataloaders(values_norm, observed, feat_cols, target) + + # Baseline model + model = Seq2PointLSTM(input_size=len(feat_cols), hidden=128, layers=1, pred_len=PRED_LEN).to(DEVICE) + base_va = train_one_day(model, dl_tr, dl_va, epochs=FS_EPOCHS) + print(f"[{AUDIT_DAY}] baseline val MSE (audit) ~ {base_va:.6f}") + + deltas: Dict[str, float] = {} + for i, col in enumerate(feat_cols): + if col == target: + deltas[col] = 0.0 + continue + va_mask = eval_val_loss_masked(model, dl_va, i) + delta = (va_mask - base_va) / max(1e-12, base_va) + deltas[col] = float(delta) + + harmful = [c for c, d in deltas.items() if d <= -FS_IMPROVE_THRESH and c != target] + ignored = [c for c, d in deltas.items() if abs(d) < FS_NOISE_BAND and c != target] + if harmful: + print(f"[{AUDIT_DAY}] dropping harmful features (improve ≥{FS_IMPROVE_THRESH*100:.1f}%): {harmful}") + if ignored: + print(f"[{AUDIT_DAY}] (near-zero impact features): {ignored}") + + kept = [c for c in feat_cols if (c == target or c not in harmful)] + print(f"[{AUDIT_DAY}] kept {len(kept)}/{len(feat_cols)} features") + + top_help = sorted([(c, d) for c, d in deltas.items() if d < 0], key=lambda x: x[1])[:8] + top_hurt = sorted([(c, d) for c, d in deltas.items() if d > 0], key=lambda x: x[1], reverse=True)[:8] + def fmt(entries): return ", ".join([f"{c}:{d*100:+.1f}%" for c,d in entries]) if entries else "-" + print(f"[{AUDIT_DAY}] best removals (↓val): {fmt(top_help)}") + print(f"[{AUDIT_DAY}] worst removals (↑val): {fmt(top_hurt)}") + + return kept, deltas + +# ---------------------------- +# Orchestration +# ---------------------------- + +def run_pipeline_for_target(target: str, feat_cols: List[str], run_tag: str, day_tables: Dict[str, pd.DataFrame]) -> Dict[str, float]: + """ + Train across days (inheriting weights). Save plots & underpred CSVs. + Return dict of per-day best validation losses. + """ + print(f"\n=== [{run_tag}] Target: {target} with {len(feat_cols)} features ===") + run_dir = ensure_dir(RESULT_DIR / run_tag) + val_losses: Dict[str, float] = {} + model = None + + for k, day in enumerate(DATE_STRS): + print(f"[{day}] preparing data...") + df_day = day_tables[day] + values_norm, mu, sigma, observed, used_cols = prepare_day_matrix(df_day, feat_cols_override=feat_cols) + input_size = values_norm.shape[1] + + if model is None: + model = Seq2PointLSTM(input_size=input_size, hidden=128, layers=1, pred_len=PRED_LEN).to(DEVICE) + + dl_tr, dl_va, _ = build_dataloaders(values_norm, observed, used_cols, target) + epochs = EPOCHS_FIRST if k == 0 else EPOCHS_NEXT + print(f"[{day}] training ({epochs} epochs)...") + va = train_one_day(model, dl_tr, dl_va, epochs) + val_losses[day] = va + print(f"[{day}] best val MSE ~ {va:.6f}") + + print(f"[{day}] forecasting...") + pred = rolling_forecast_day(model, values_norm, observed, used_cols, target, mu, sigma) + actual = df_day[target].astype("float32").to_numpy() + + # Under-prediction report + csv_path = run_dir / f"{target}_underpred_{day}.csv" + metrics = report_underpredictions( + timestamps=df_day["timestamp"], + actual=actual, + pred=pred, + out_csv_path=csv_path + ) + print( + f"[{day}] {target} under-predictions: " + f"{metrics['n_under']}/{metrics['n_pred_points']} " + f"({metrics['under_rate']*100:.1f}%) | " + f"MAE_under={metrics['mae_under']:.3f}, " + f"sum_under={metrics['sum_under']:.3f}, " + f"max_under={metrics['max_under']:.3f}, " + f"p95_under={metrics['p95_under']:.3f}, " + f"runs={metrics['n_runs']}, avg_run={metrics['avg_run']:.2f} min, " + f"max_run={metrics['max_run']} min" + ) + print("saved under-prediction CSV →", csv_path) + + # Plot with under-prediction highlighting + plt.figure(figsize=(12,4)) + plt.plot(actual, label=f"actual {target}", lw=1) + if np.isfinite(pred).any(): + plt.plot(pred, label=f"forecast {target}", lw=2) + finite = np.isfinite(actual) & np.isfinite(pred) + under = finite & (pred < actual) + if under.any(): + x = np.arange(len(actual)) + pred_f = np.where(np.isfinite(pred), pred, actual) + plt.fill_between(x, pred_f, actual, where=under, alpha=0.15, color="red", label="under-predicted") + plt.title(f"{target} – {PRED_LEN}-min horizon (LSTM, day {day}) [{run_tag}]") + plt.xlabel("minutes (index within day)") + plt.ylabel(target); plt.grid(True); plt.legend() + outp = run_dir / f"{target}_forecast_{day}_lstm.png" + plt.tight_layout(); plt.savefig(outp); plt.close() + print("saved", outp) + + return val_losses + + +def summarize_comparison(target: str, losses_base: Dict[str, float], losses_pruned: Dict[str, float]): + days = sorted(losses_base.keys()) + print(f"\n=== Comparison (target={target}) ===") + for d in days: + b, p = losses_base[d], losses_pruned[d] + delta = (p - b) / max(1e-12, b) + print(f"{d}: base={b:.6f} | pruned={p:.6f} | Δ={delta*100:+.2f}%") + b_mean = np.mean([losses_base[d] for d in days]) + p_mean = np.mean([losses_pruned[d] for d in days]) + d_mean = (p_mean - b_mean) / max(1e-12, b_mean) + print(f"Avg: base={b_mean:.6f} | pruned={p_mean:.6f} | Δ={d_mean*100:+.2f}%") + + +def main(): + ensure_dir(RESULT_DIR) + # Build per-day feature tables once (independently per day) + day_tables: Dict[str, pd.DataFrame] = {d: build_day_features(d) for d in DATE_STRS} + + base_cols = FOCUS_COLS + (["tod_sin","tod_cos"] if ADD_TIME_OF_DAY else []) + + for target in TARGETS: + # 1) Baseline full-feature run + losses_base = run_pipeline_for_target(target, feat_cols=base_cols, run_tag="baseline", day_tables=day_tables) + + # 2) Audit and select (on AUDIT_DAY) then re-run with pruned set + if ENABLE_FEATURE_SELECTION: + kept_cols = base_cols + kept_map = {} + try: + kept_cols, deltas = feature_audit_and_select(day_tables[AUDIT_DAY], target, base_cols) + kept_map = deltas + except Exception as e: + print(f"[WARN] feature audit failed ({e}); proceeding without pruning.") + losses_pruned = run_pipeline_for_target(target, feat_cols=kept_cols, run_tag="pruned", day_tables=day_tables) + + # 3) Summarize before/after + summarize_comparison(target, losses_base, losses_pruned) + + # Save audit table + audit_path = ensure_dir(RESULT_DIR / "pruned") / f"{target}_feature_audit_{AUDIT_DAY}.csv" + if kept_map: + pd.DataFrame( + [{"feature": c, "delta_val_pct": 100.0 * kept_map[c], "dropped": (c not in kept_cols)} for c in kept_map] + ).sort_values("delta_val_pct").to_csv(audit_path, index=False) + print("saved feature audit →", audit_path) + else: + print("[INFO] Feature selection disabled; only baseline run executed.") + +if __name__ == "__main__": + main() diff --git a/ci-scripts/yaml_files/5g_rfsimulator_flexric/docker-compose.yml b/ci-scripts/yaml_files/5g_rfsimulator_flexric/docker-compose.yml index 6b9771e1c1..2e4766e747 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_flexric/docker-compose.yml +++ b/ci-scripts/yaml_files/5g_rfsimulator_flexric/docker-compose.yml @@ -80,27 +80,7 @@ services: interval: 10s timeout: 5s - oai-gnb: - image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} - privileged: true - container_name: rfsim5g-oai-gnb - environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time - ASAN_OPTIONS: detect_leaks=0 - depends_on: - - oai-flexric - networks: - public_net: - ipv4_address: 192.168.70.151 - volumes: - - ../../conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf:/opt/oai-gnb/etc/gnb.conf - healthcheck: - test: /bin/bash -c "pgrep nr-softmodem" - interval: 10s - timeout: 5s - retries: 5 - - oai-nr-ue: + oai-nr-ue1: image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} privileged: true container_name: rfsim5g-oai-nr-ue @@ -138,7 +118,154 @@ services: timeout: 5s retries: 5 -networks: - public_net: - name: oai-cn5g-public-net - external: true + oai-nr-ue3: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue3 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.154 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue4: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue4 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.155 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue5: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue5 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.156 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue6: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue6 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.157 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue7: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue7 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.158 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue8: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue8 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.159 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue9: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue9 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.160 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue10: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + privileged: true + container_name: rfsim5g-oai-nr-ue10 + environment: + USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.70.161 + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 diff --git a/docker/Dockerfile.base.ubuntu b/docker/Dockerfile.base.ubuntu new file mode 100644 index 0000000000..2928040331 --- /dev/null +++ b/docker/Dockerfile.base.ubuntu @@ -0,0 +1,51 @@ +FROM ubuntu:noble AS ran-base +ARG NEEDED_GIT_PROXY +ARG TARGETARCH +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Asia/Tokyo +ENV BUILD_UHD_FROM_SOURCE=True +ENV UHD_VERSION=4.4.0.0 + +RUN apt-get update && \ + apt-get upgrade --yes && \ + apt-get install --yes \ + #gcc needed for build_oai + build-essential \ + ccache \ + psmisc \ + git \ + gcc-12 \ + g++-12 \ + xxd \ + libpcre2-dev \ + python3-dev \ + bison \ + flex \ + m4 \ + # python3-pip for conf template generation + python3-pip \ + wget && \ + # --break-system-packages allows externally managed environment in pip + pip3 install --ignore-installed pyyaml --break-system-packages + +ENV TINI_VERSION=v0.19.0 +RUN wget https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} -O /tini +RUN chmod +x /tini + +RUN /bin/bash -c "if [[ -v NEEDED_GIT_PROXY ]]; then git config --global http.proxy $NEEDED_GIT_PROXY; fi" + +WORKDIR /oai-ran/cmake_targets/tools +COPY cmake_targets/tools/build_helper \ + cmake_targets/tools/uhd-4.x-tdd-patch.diff \ + ./ + +WORKDIR /oai-ran/cmake_targets +COPY cmake_targets/build_oai . + +WORKDIR /oai-ran +COPY oaienv . + +RUN /bin/sh oaienv && \ + cd cmake_targets && \ + mkdir -p log && \ + ./build_oai -I -w USRP --install-optional-packages diff --git a/docker/Dockerfile.base.ubuntu22 b/docker/Dockerfile.base.ubuntu22 deleted file mode 100644 index 60e2aad8ab..0000000000 --- a/docker/Dockerfile.base.ubuntu22 +++ /dev/null @@ -1,84 +0,0 @@ -#/* -# * 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 -# */ -#--------------------------------------------------------------------- -# -# Dockerfile for the Open-Air-Interface BUILD service -# Valid for Ubuntu 22.04 -# -#--------------------------------------------------------------------- - - -FROM ubuntu:jammy AS ran-base -ARG NEEDED_GIT_PROXY -ARG TARGETARCH -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=Europe/Paris -ENV BUILD_UHD_FROM_SOURCE=True -ENV UHD_VERSION=4.4.0.0 - -#install developers pkg/repo -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get upgrade --yes && \ - DEBIAN_FRONTEND=noninteractive apt-get install --yes \ - #gcc needed for build_oai - build-essential \ - psmisc \ - git \ - #use gcc-12 to avoid problems of default gcc-11 in FlexRIC - gcc-12 \ - g++-12 \ - xxd \ - libpcre2-dev \ - python3-dev \ - bison \ - flex \ - m4 \ - # python3-pip for conf template generation - python3-pip && \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 --slave /usr/bin/g++ g++ /usr/bin/g++-12 && \ - pip3 install --ignore-installed pyyaml - -# Add "Tini - A tiny but valid init for containers", https://github.com/krallin/tini -# it will be copied into target containers, to print exit numbers and handle signals properly -ENV TINI_VERSION=v0.19.0 -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} /tini -RUN chmod +x /tini - -# In some network environments, GIT proxy is required -RUN /bin/bash -c "if [[ -v NEEDED_GIT_PROXY ]]; then git config --global http.proxy $NEEDED_GIT_PROXY; fi" - -# Copying only the needed files to create ran-base -WORKDIR /oai-ran/cmake_targets/tools -COPY cmake_targets/tools/build_helper \ - cmake_targets/tools/uhd-4.x-tdd-patch.diff \ - ./ - -WORKDIR /oai-ran/cmake_targets -COPY cmake_targets/build_oai . - -WORKDIR /oai-ran -COPY oaienv . - -#run build_oai -I to get the builder image -RUN /bin/sh oaienv && \ - cd cmake_targets && \ - mkdir -p log && \ - ./build_oai -I -w USRP --install-optional-packages diff --git a/docker/Dockerfile.build.ubuntu22 b/docker/Dockerfile.build.ubuntu similarity index 85% rename from docker/Dockerfile.build.ubuntu22 rename to docker/Dockerfile.build.ubuntu index a3c2417b33..b2cc9c6888 100644 --- a/docker/Dockerfile.build.ubuntu22 +++ b/docker/Dockerfile.build.ubuntu @@ -21,7 +21,7 @@ #--------------------------------------------------------------------- # # Dockerfile for the Open-Air-Interface BUILD service -# Valid for Ubuntu 22.04 +# Valid for Ubuntu 24.04 # #--------------------------------------------------------------------- @@ -36,7 +36,12 @@ WORKDIR /oai-ran COPY . . # build all targets so final targets can be created afterwards -RUN /bin/sh oaienv && \ +RUN \ + # Mount CCache cache direrctory + --mount=type=cache,target=/root/.cache/ccache/ \ + # Mount CPM package cache + --mount=type=cache,target=/root/.cache/cpm/ \ + /bin/sh oaienv && \ cd cmake_targets && \ mkdir -p log && \ ./build_oai -c --ninja \ @@ -54,12 +59,13 @@ RUN /bin/sh oaienv && \ ldd ran_build/build/*.so ## Build FlexRIC for SM Models -RUN mkdir -p openair2/E2AP/flexric/build && \ +RUN \ + # Mount CCache cache direrctory + --mount=type=cache,target=/root/.cache/ccache/ \ + mkdir -p openair2/E2AP/flexric/build && \ cd openair2/E2AP/flexric/build && \ cmake -GNinja -DCMAKE_BUILD_TYPE=Release \ -DKPM_VERSION=$KPM_VERSION \ - -DE2AP_VERSION=$E2AP_VERSION \ - -DCMAKE_C_COMPILER=gcc-12 \ - -DCMAKE_CXX_COMPILER=g++-12 .. && \ + -DE2AP_VERSION=$E2AP_VERSION .. && \ ninja && \ ninja install diff --git a/docker/Dockerfile.nakaolab.oct1 b/docker/Dockerfile.nakaolab.oct1 new file mode 100644 index 0000000000..3a525b008b --- /dev/null +++ b/docker/Dockerfile.nakaolab.oct1 @@ -0,0 +1,66 @@ +FROM ran-base:latest AS ran-build +ARG BUILD_OPTION + +ARG E2AP_VERSION=E2AP_V2 +ARG KPM_VERSION=KPM_V2_03 + +RUN rm -Rf /oai-ran +WORKDIR /oai-ran +COPY . . + +# build all targets so final targets can be created afterwards +RUN /bin/sh oaienv && \ + cd cmake_targets && \ + mkdir -p log && \ + ./build_oai -c --ninja \ + --gNB --RU --nrUE \ + --build-lib "telnetsrv enbscope uescope nrscope" \ + -w USRP -t Ethernet \ + --build-e2 --cmake-opt -DKPM_VERSION=$KPM_VERSION --cmake-opt -DE2AP_VERSION=$E2AP_VERSION \ + --noavx512 \ + --build-tool-opt -k10 \ + --cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \ + echo "---- ldd on executables ----" && \ + ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \ + echo "---- ldd on shared libraries ----" && \ + ldd ran_build/build/*.so + +RUN rm -rf openair2/E2AP/flexric/build && \ + mkdir -p openair2/E2AP/flexric/build && \ + cd openair2/E2AP/flexric/build && \ + cmake -DCMAKE_BUILD_TYPE=Release \ + -DKPM_VERSION=KPM_V2_03 \ + -DE2AP_VERSION=E2AP_V2 \ + -DCMAKE_C_COMPILER=gcc-12 \ + -DCMAKE_CXX_COMPILER=g++-12 .. && \ + make -j8 && make install + +RUN set -eux; \ + apt-get update; \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates curl wget iproute2 iputils-ping dnsutils jq \ + iperf iperf3 ffmpeg \ + nghttp2-client || true; \ + # Ubuntu vs Debian name differences (fallback for nghttp2 client tools) + if ! command -v h2load >/dev/null 2>&1; then \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \ + fi; \ + # Try to install sipp from apt; if unavailable, build from source + if apt-cache show sipp >/dev/null 2>&1; then \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sipp; \ + else \ + echo "sipp not in apt, building from source..."; \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential cmake git libncurses5-dev libpcap-dev libssl-dev libpcre3-dev; \ + git clone --depth=1 https://github.com/SIPp/sipp.git /tmp/sipp; \ + cd /tmp/sipp; cmake -DENABLE_SSL=1 -DENABLE_PCAP=1 -DCMAKE_BUILD_TYPE=Release .; \ + make -j"$(nproc)"; make install; strip /usr/local/bin/sipp || true; \ + rm -rf /tmp/sipp; \ + fi; \ + rm -rf /var/lib/apt/lists/* + +# Traffic runner +COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner +RUN chmod +x /usr/local/bin/traffic_runner + +ENTRYPOINT ["/bin/bash"] \ No newline at end of file diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c index 4468cdf0b9..ea5c683a37 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c @@ -170,7 +170,7 @@ static void capture_sst_sd(test_cond_value_t* test_cond_value, uint8_t *sst, uin DevAssert(test_cond_value->octet_string_value->len == 4); uint8_t *buf = test_cond_value->octet_string_value->buf; *sst = buf[0]; - *sd = malloc(**sd); + *sd = malloc(sizeof(uint32_t)); **sd = buf[1] << 16 | buf[2] << 8 | buf[3]; } printf("[E2 AGENT][E2SM-KPM] Condition NSSAI (%d, %x).\n", *sst, (*sd) ? **sd : 0xffffff); diff --git a/openair2/E2AP/flexric b/openair2/E2AP/flexric index df754a8553..0a31810d47 160000 --- a/openair2/E2AP/flexric +++ b/openair2/E2AP/flexric @@ -1 +1 @@ -Subproject commit df754a85537f6db255b5330b8d83eda6a2742f25 +Subproject commit 0a31810d47c22f76e539faef6cb43ec6764156a7 diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index cb3c1bf801..4328fd1ac2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -26,7 +26,6 @@ * \email: guido.casati@iis.fraunhofe.de * \version 1.0 * @ingroup _mac - */ #include "common/utils/nr/nr_common.h" @@ -708,7 +707,7 @@ static void pf_dl(module_id_t module_id, qsort(UE_sched, sizeofArray(UE_sched), sizeof(UEsched_t), comparator); UEsched_t *iterator = UE_sched; - const int min_rbSize = 5; + const int min_rbSize = 10; /* Loop UE_sched to find max coeff and allocate transmission */ while (iterator->UE != NULL) { @@ -772,10 +771,10 @@ static void pf_dl(module_id_t module_id, while (rbStart < rbStop && (rballoc_mask[rbStart + bwp_start] & slbitmap)) rbStart++; - uint16_t max_rbSize = 1; + uint16_t max_rbSize = 10; // used to be 1 - while (rbStart + max_rbSize <= rbStop && !(rballoc_mask[rbStart + max_rbSize + bwp_start] & slbitmap)) - max_rbSize++; + /* while (rbStart + max_rbSize <= rbStop && !(rballoc_mask[rbStart + max_rbSize + bwp_start] & slbitmap)) */ + /* max_rbSize++; */ if (max_rbSize < min_rbSize) { LOG_D(NR_MAC, @@ -907,12 +906,13 @@ nr_pp_impl_dl nr_init_dlsch_preprocessor(int CC_id) { const uint16_t R = nr_get_code_rate_dl(mcs, mcsTableIdx); pf_tbs[mcsTableIdx][mcs] = nr_compute_tbs(Qm, R, - 1, /* rbSize */ + 1, /* rbSize */ 10, /* hypothetical number of slots */ - 0, /* N_PRB_DMRS * N_DMRS_SLOT */ - 0 /* N_PRB_oh, 0 for initialBWP */, - 0 /* tb_scaling */, - 1 /* nrOfLayers */) >> 3; + 0, /* N_PRB_DMRS * N_DMRS_SLOT */ + 0 /* N_PRB_oh, 0 for initialBWP */, + 0 /* tb_scaling */, + 1 /* nrOfLayers */ + ) >> 3; } } @@ -1374,6 +1374,7 @@ void nr_schedule_ue_spec(module_id_t module_id, buf=bufEnd; } + // mac_stats UE->mac_stats.dl.total_bytes += TBS; UE->mac_stats.dl.current_bytes = TBS; UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 3fa16f6a51..2561b618f2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2151,9 +2151,9 @@ static void pf_ul(module_id_t module_id, while (rbStart < bwpSize && (rballoc_mask[rbStart + bwpStart] & slbitmap)) rbStart++; sched_pusch->rbStart = rbStart; - uint16_t max_rbSize = 1; - while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap)) - max_rbSize++; + uint16_t max_rbSize = 10; // used to be 1 + /* while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap)) */ + /* max_rbSize++; */ if (rbStart + min_rb > bwpSize || max_rbSize < min_rb) { reset_beam_status(&nrmac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, dci_beam.new_beam); diff --git a/tools/scripts/traffic_runner.sh b/tools/scripts/traffic_runner.sh new file mode 100644 index 0000000000..c18c18f562 --- /dev/null +++ b/tools/scripts/traffic_runner.sh @@ -0,0 +1,233 @@ +#!/bin/bash +# Iperf-exclusive traffic generator for OAI UE containers. +# Profiles: browsing, video, bulk_download, voip, iot_chatty, gaming, idle +# Usage: +# traffic_runner --profile browsing --duration 0 --iperf-server 10.0.0.1:5201 +# +set -euo pipefail + +# Clear exp.sh lock on exit so sessions can restart later +trap 'rm -f /tmp/traffic_profile.lock' EXIT + +DEFAULT_IPERF_PORT="${DEFAULT_IPERF_PORT:-5201}" +TRAFFIC_NET_GATEWAY="${TRAFFIC_NET_GATEWAY:-192.168.71.130}" + +if [[ -z "${IPERF_SERVER:-}" ]]; then + # Candidate hostnames (Docker DNS on the same network) then the gateway IP + IPERF_HOST_CANDIDATES=${IPERF_HOST_CANDIDATES:-"iperf iperf3 iperf-server ${TRAFFIC_NET_GATEWAY}"} + for h in $IPERF_HOST_CANDIDATES; do + if getent hosts "$h" >/dev/null 2>&1; then + IPERF_SERVER="${h}:${DEFAULT_IPERF_PORT}" + break + fi + # Accept numeric IPv4 even if DNS not present + if [[ "$h" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + IPERF_SERVER="${h}:${DEFAULT_IPERF_PORT}" + break + fi + done +fi + +# Final check +[[ -n "${IPERF_SERVER:-}" ]] || die "No iperf server found. Set --iperf-server or connect a server to rfsim5g-oai-traffic-net." +IPERF_HOST="${IPERF_SERVER%:*}" +IPERF_PORT="${IPERF_SERVER#*:}" +[[ "$IPERF_PORT" =~ ^[0-9]+$ ]] || die "Invalid iperf port in $IPERF_SERVER" + +PROFILE="" +DURATION=0 +LOG_FILE="/tmp/traffic_runner.log" + +log() { printf '[%(%Y-%m-%dT%H:%M:%S%z)T] %s\n' -1 "$*" | tee -a "$LOG_FILE" >&2; } +die() { log "ERROR: $*"; exit 2; } + +# Parse args +while [[ $# -gt 0 ]]; do + case "$1" in + --profile) PROFILE="$2"; shift 2;; + --duration) DURATION="$2"; shift 2;; + --iperf-server) IPERF_SERVER="$2"; shift 2;; + --help|-h) cat < [--duration ] [--iperf-server host:port] +Profiles: browsing, video, bulk_download, voip, iot_chatty, gaming, idle +If duration is 0 or omitted, run indefinitely. +EOF + exit 0;; + *) die "Unknown arg: $1";; + esac +done + +[[ -n "$PROFILE" ]] || die "--profile is required" +[[ -n "$IPERF_SERVER" ]] || die "--iperf-server or IPERF_SERVER env is required (e.g., 192.168.0.2:5201)" + +IPERF_HOST="${IPERF_SERVER%:*}" +IPERF_PORT="${IPERF_SERVER#*:}" +[[ "$IPERF_PORT" =~ ^[0-9]+$ ]] || die "Invalid iperf port in $IPERF_SERVER" + +# Helpers +run_for_duration() { + # $1: command string to eval in a loop + # uses global DURATION + local start now elapsed + start=$(date +%s) + while :; do + now=$(date +%s) + elapsed=$(( now - start )) + if (( DURATION > 0 && elapsed >= DURATION )); then break; fi + eval "$1" + done +} + +# iperf helpers +iperf_tcp_dl() { iperf3 -c "$IPERF_HOST" -p "$IPERF_PORT" -R -t "$1" -J >/dev/null 2>&1 || true; } +iperf_tcp_ul() { iperf3 -c "$IPERF_HOST" -p "$IPERF_PORT" -t "$1" -J >/dev/null 2>&1 || true; } + +iperf_udp_dl() { iperf3 -c "$IPERF_HOST" -p "$IPERF_PORT" -R -u -b "$2" -t "$1" -J >/dev/null 2>&1 || true; } +iperf_udp_ul() { iperf3 -c "$IPERF_HOST" -p "$IPERF_PORT" -u -b "$2" -t "$1" -J >/dev/null 2>&1 || true; } + +# Profiles +do_browsing() { + log "Browsing (TCP bursts, short think-time) to $IPERF_SERVER" + run_for_duration ' + dur=$(( 1 + RANDOM % 3 )) # 1-3s burst + iperf_tcp_dl "$dur" + sleep "0.$((100 + RANDOM % 600))" # 0.1-0.7s think + ' +} + +do_video() { + log "Video (FHD video 5 Mbps sustained) to $IPERF_SERVER" + run_for_duration ' + iperf3 -c "'"$IPERF_HOST"'" -p "'"$IPERF_PORT"'" -R -u -b 5M -t 30 -J >/dev/null 2>&1 + sleep 1 + ' +} + +do_bulk_download() { + # Heavy TCP downlink with multiple streams + log "Bulk download (TCP DL 8 parallel, long sustained) to $IPERF_SERVER" + run_for_duration ' + iperf3 -c "'"$IPERF_HOST"'" -p "'"$IPERF_PORT"'" -R -P 8 -t 20 -w 1M -J >/dev/null 2>&1 + sleep 1 + ' +} + +do_voip() { + log "VoIP (UDP bi-di ~100–200 kbps) to $IPERF_SERVER" + run_for_duration ' + iperf_udp_ul 5 120K & + iperf_udp_dl 5 120K & + wait + sleep 0.5 + ' +} + +do_iot_chatty() { + log "IoT chatty (UDP UL ~50–80 kbps bursts) to $IPERF_SERVER" + run_for_duration ' + iperf_udp_ul 2 60K + sleep $(( 3 + RANDOM % 7 )) + ' +} + +do_gaming() { + log "Gaming (UDP bi-di ~1–3 Mbps bursts) to $IPERF_SERVER" + run_for_duration ' + rate_ul=$(( 1 + RANDOM % 3 ))M + rate_dl=$(( 1 + RANDOM % 3 ))M + iperf_udp_ul 5 "$rate_ul" & + iperf_udp_dl 5 "$rate_dl" & + wait + sleep 0.5 + ' +} + +do_idle() { + log "Idle" + run_for_duration "sleep 2" +} + + + +# ------------- Randomized pattern (bursts of 30s..minutes) ------------------ + +# Defaults can be overridden via env: +# MIN_ACTIVE_SEC / MAX_ACTIVE_SEC — each session duration (default 30..180) +# MIN_IDLE_SEC / MAX_IDLE_SEC — wait between sessions (default 5..60) +# BURST_FILE — path watched for system-wide bursts +# BURST_MAX_AGE_SEC — consider burst file "fresh" within N sec +MIN_ACTIVE_SEC="${MIN_ACTIVE_SEC:-30}" +MAX_ACTIVE_SEC="${MAX_ACTIVE_SEC:-180}" +MIN_IDLE_SEC="${MIN_IDLE_SEC:-5}" +MAX_IDLE_SEC="${MAX_IDLE_SEC:-60}" +BURST_FILE="${BURST_FILE:-/tmp/system_burst}" +BURST_MAX_AGE_SEC="${BURST_MAX_AGE_SEC:-45}" + +_pick_random_profile() { + # Weighted pick to feel realistic; adjust as desired + local r=$((RANDOM % 100)) + if (( r < 40 )); then echo browsing # 40% + elif (( r < 55 )); then echo voip # 15% + elif (( r < 70 )); then echo video # 15% + elif (( r < 85 )); then echo gaming # 15% + else echo bulk_download # 15% + fi +} + +_run_profile_for() { + local name="$1" seconds="$2" + DURATION="$seconds" + case "$name" in + browsing) do_browsing;; + video) do_video;; + bulk_download) do_bulk_download;; + voip) do_voip;; + iot_chatty) do_iot_chatty;; + gaming) do_gaming;; + idle) do_idle;; + *) do_browsing;; + esac +} + +_is_burst_now() { + # return 0 if a fresh burst marker exists + [[ -f "$BURST_FILE" ]] || return 1 + local now mtime age + now=$(date +%s) + mtime=$(stat -c %Y "$BURST_FILE" 2>/dev/null || echo 0) + age=$(( now - mtime )) + (( age >= 0 && age <= BURST_MAX_AGE_SEC )) +} + +do_random() { + log "Random pattern: independent per-UE bursts to $IPERF_SERVER (session: ${MIN_ACTIVE_SEC}-${MAX_ACTIVE_SEC}s; idle: ${MIN_IDLE_SEC}-${MAX_IDLE_SEC}s)" + while :; do + local prof dur idle + prof="$(_pick_random_profile)" + dur=$(( MIN_ACTIVE_SEC + RANDOM % (MAX_ACTIVE_SEC - MIN_ACTIVE_SEC + 1) )) + + if _is_burst_now; then + # If a system burst is signaled, start immediately and slightly extend session + idle=0 + dur=$(( dur + 20 + RANDOM % 40 )) # +20..60s + else + idle=$(( MIN_IDLE_SEC + RANDOM % (MAX_IDLE_SEC - MIN_IDLE_SEC + 1) )) + fi + + log "Random session: profile=${prof} duration=${dur}s (then idle ${idle}s)" + _run_profile_for "$prof" "$dur" || true + sleep "$idle" + done +} + +case "$PROFILE" in + browsing) do_browsing;; + video) do_video;; + bulk_download) do_bulk_download;; + voip) do_voip;; + iot_chatty) do_iot_chatty;; + gaming) do_gaming;; + idle) do_idle;; + random) do_random;; + *) die "Unknown profile: $PROFILE";; +esac