nakaolab oct19
This commit is contained in:
@@ -5,3 +5,4 @@ common/utils/T/T_IDs.h
|
|||||||
common/utils/T/T_messages.txt.h
|
common/utils/T/T_messages.txt.h
|
||||||
common/utils/T/genids
|
common/utils/T/genids
|
||||||
common/utils/T/genids.o
|
common/utils/T/genids.o
|
||||||
|
artifacts
|
||||||
|
|||||||
@@ -153,12 +153,12 @@ gNBs =
|
|||||||
};
|
};
|
||||||
|
|
||||||
////////// AMF parameters:
|
////////// AMF parameters:
|
||||||
amf_ip_address = ({ ipv4 = "192.168.70.132"; });
|
amf_ip_address = ({ ipv4 = "192.168.71.132"; });
|
||||||
|
|
||||||
NETWORK_INTERFACES :
|
NETWORK_INTERFACES :
|
||||||
{
|
{
|
||||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.151";
|
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
|
||||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.151";
|
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
|
||||||
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,6 +172,7 @@ MACRLCs = (
|
|||||||
tr_n_preference = "local_RRC";
|
tr_n_preference = "local_RRC";
|
||||||
pusch_TargetSNRx10 = 200;
|
pusch_TargetSNRx10 = 200;
|
||||||
pucch_TargetSNRx10 = 200;
|
pucch_TargetSNRx10 = 200;
|
||||||
|
min_grant_prb = 1;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ log_config : {
|
|||||||
};
|
};
|
||||||
|
|
||||||
e2_agent = {
|
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 = "/path/where/the/SMs/are/located/"
|
||||||
sm_dir = "/usr/local/lib/flexric/"
|
sm_dir = "/usr/local/lib/flexric/"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
Active_gNBs:
|
Active_gNBs:
|
||||||
- gnb-rfsim
|
- 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, choice in: none, info, annoying
|
||||||
Asn1_verbosity: none
|
Asn1_verbosity: none
|
||||||
gNBs:
|
gNBs:
|
||||||
@@ -142,13 +149,13 @@ gNBs:
|
|||||||
GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140
|
GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140
|
||||||
GNB_PORT_FOR_S1U: 2152 # Spec 2152
|
GNB_PORT_FOR_S1U: 2152 # Spec 2152
|
||||||
|
|
||||||
|
|
||||||
MACRLCs:
|
MACRLCs:
|
||||||
- num_cc: 1
|
- num_cc: 1
|
||||||
tr_s_preference: local_L1
|
tr_s_preference: local_L1
|
||||||
tr_n_preference: local_RRC
|
tr_n_preference: local_RRC
|
||||||
pusch_TargetSNRx10: 200
|
pusch_TargetSNRx10: 200
|
||||||
pucch_TargetSNRx10: 200
|
pucch_TargetSNRx10: 200
|
||||||
|
min_grant_prb: 10
|
||||||
|
|
||||||
L1s:
|
L1s:
|
||||||
- num_cc: 1
|
- num_cc: 1
|
||||||
@@ -195,6 +202,6 @@ log_config:
|
|||||||
mac_log_level: info
|
mac_log_level: info
|
||||||
rlc_log_level: info
|
rlc_log_level: info
|
||||||
pdcp_log_level: info
|
pdcp_log_level: info
|
||||||
rrc_log_level: info
|
rrc_log_level: debug
|
||||||
ngap_log_level: debug
|
ngap_log_level: debug
|
||||||
f1ap_log_level: debug
|
f1ap_log_level: debug
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
uicc0 = {
|
uicc0 = { # imsi is omitted
|
||||||
imsi = "208990100001100";
|
|
||||||
key = "fec86ba6eb707ed08905757b1bb44b8f";
|
key = "fec86ba6eb707ed08905757b1bb44b8f";
|
||||||
opc= "C42449363BBAD02B66D16BC975D77CC1";
|
opc= "C42449363BBAD02B66D16BC975D77CC1";
|
||||||
dnn= "oai";
|
dnn= "oai";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ uicc0:
|
|||||||
opc: C42449363BBAD02B66D16BC975D77CC1
|
opc: C42449363BBAD02B66D16BC975D77CC1
|
||||||
dnn: oai
|
dnn: oai
|
||||||
nssai_sst: 1
|
nssai_sst: 1
|
||||||
|
pdu_type: IPV4
|
||||||
|
|
||||||
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ channelmod:
|
|||||||
- model_name: rfsimu_channel_ue0
|
- model_name: rfsimu_channel_ue0
|
||||||
type: AWGN
|
type: AWGN
|
||||||
ploss_dB: 20
|
ploss_dB: 20
|
||||||
nose_power_dB: -2
|
noise_power_dB: -2
|
||||||
forgetfact: 0
|
forgetfact: 0
|
||||||
offset: 0
|
offset: 0
|
||||||
ds_tdl: 0
|
ds_tdl: 0
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[NEAR-RIC]
|
||||||
|
NEAR_RIC_IP = 192.168.71.21
|
||||||
|
NEAR_RIC_PORT = 36422
|
||||||
|
|
||||||
|
[XAPP]
|
||||||
|
DB_DIR = /tmp/
|
||||||
|
|
||||||
|
[LOG]
|
||||||
|
LEVEL = debug
|
||||||
+2
-35
@@ -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 for all the NFs
|
||||||
log_level:
|
log_level:
|
||||||
general: debug
|
general: debug
|
||||||
@@ -103,8 +70,8 @@ amf:
|
|||||||
# This really depends on if we want to keep the "mini" version or not
|
# This really depends on if we want to keep the "mini" version or not
|
||||||
support_features_options:
|
support_features_options:
|
||||||
enable_simple_scenario: yes # "no" by default with the normal deployment scenarios with AMF/SMF/UPF/AUSF/UDM/UDR/NRF.
|
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.
|
# 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.
|
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
|
||||||
enable_nssf: no
|
enable_nssf: no
|
||||||
enable_smf_selection: no
|
enable_smf_selection: no
|
||||||
relative_capacity: 30
|
relative_capacity: 30
|
||||||
+1
-11
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
|||||||
|
[mysqld]
|
||||||
|
wait_timeout = 15
|
||||||
|
interactive_timeout = 15
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
container_name: "rfsim5g-mysql"
|
container_name: "mysql"
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
- ./conf/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||||
- ./mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
- ./conf/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||||
|
- ./conf/sql.cnf:/etc/mysql/conf.d/custom.cnf
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris
|
- TZ=Asia/Tokyo
|
||||||
- MYSQL_DATABASE=oai_db
|
- MYSQL_DATABASE=oai_db
|
||||||
- MYSQL_USER=test
|
- MYSQL_USER=test
|
||||||
- MYSQL_PASSWORD=test
|
- MYSQL_PASSWORD=test
|
||||||
@@ -19,37 +20,40 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
ipv4_address: 192.168.71.131
|
ipv4_address: 192.168.71.131
|
||||||
|
|
||||||
oai-amf:
|
oai-amf:
|
||||||
container_name: "rfsim5g-oai-amf"
|
container_name: "oai-amf"
|
||||||
image: oaisoftwarealliance/oai-amf:v2.1.9
|
image: oaisoftwarealliance/oai-amf:v2.1.9
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/paris
|
- TZ=Asia/Tokyo
|
||||||
volumes:
|
volumes:
|
||||||
- ./mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
- ./conf/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
networks:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
ipv4_address: 192.168.71.132
|
ipv4_address: 192.168.71.132
|
||||||
|
|
||||||
oai-smf:
|
oai-smf:
|
||||||
container_name: "rfsim5g-oai-smf"
|
container_name: "oai-smf"
|
||||||
image: oaisoftwarealliance/oai-smf:v2.1.9
|
image: oaisoftwarealliance/oai-smf:v2.1.9
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris
|
- TZ=Asia/Tokyo
|
||||||
volumes:
|
volumes:
|
||||||
- ./mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
- ./conf/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||||
depends_on:
|
depends_on:
|
||||||
- oai-amf
|
- oai-amf
|
||||||
networks:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
ipv4_address: 192.168.71.133
|
ipv4_address: 192.168.71.133
|
||||||
|
|
||||||
oai-upf:
|
oai-upf:
|
||||||
container_name: "rfsim5g-oai-upf"
|
container_name: "oai-upf"
|
||||||
image: oaisoftwarealliance/oai-upf:v2.1.9
|
image: oaisoftwarealliance/oai-upf:v2.1.9
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris
|
- TZ=Asia/Tokyo
|
||||||
volumes:
|
volumes:
|
||||||
- ./mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
- ./conf/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||||
depends_on:
|
depends_on:
|
||||||
- oai-smf
|
- oai-smf
|
||||||
cap_add:
|
cap_add:
|
||||||
@@ -63,9 +67,10 @@ services:
|
|||||||
ipv4_address: 192.168.71.134
|
ipv4_address: 192.168.71.134
|
||||||
traffic_net:
|
traffic_net:
|
||||||
ipv4_address: 192.168.72.134
|
ipv4_address: 192.168.72.134
|
||||||
|
|
||||||
oai-ext-dn:
|
oai-ext-dn:
|
||||||
privileged: true
|
privileged: true
|
||||||
container_name: rfsim5g-oai-ext-dn
|
container_name: oai-ext-dn
|
||||||
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
||||||
entrypoint: /bin/bash -c \
|
entrypoint: /bin/bash -c \
|
||||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||||
@@ -80,16 +85,15 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
oai-gnb:
|
oai-gnb:
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
|
image: nakaolab:oct1
|
||||||
container_name: rfsim5g-oai-gnb
|
container_name: oai-gnb
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
environment:
|
|
||||||
USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time
|
|
||||||
ASAN_OPTIONS: detect_leaks=0
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- oai-ext-dn
|
- 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:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
ipv4_address: 192.168.71.140
|
ipv4_address: 192.168.71.140
|
||||||
@@ -100,21 +104,55 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-flexric:
|
||||||
container_name: rfsim5g-oai-nr-ue
|
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:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- NET_RAW # for ping
|
||||||
environment:
|
# 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
|
# 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
ipv4_address: 192.168.71.150
|
ipv4_address: 192.168.71.151
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
volumes:
|
volumes:
|
||||||
@@ -124,40 +162,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
oai-nr-ue2:
|
oai-nr-ue2:
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
image: nakaolab:oct1
|
||||||
container_name: rfsim5g-oai-nr-ue2
|
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:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -172,16 +186,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue4:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue3:
|
||||||
container_name: rfsim5g-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 208990100001102 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue3
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -196,16 +210,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue5:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue4:
|
||||||
container_name: rfsim5g-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 208990100001103 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue4
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -220,16 +234,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue6:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue5:
|
||||||
container_name: rfsim5g-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 208990100001104 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue5
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -244,16 +258,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue7:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue6:
|
||||||
container_name: rfsim5g-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 208990100001105 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue6
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -268,16 +282,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue8:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue7:
|
||||||
container_name: rfsim5g-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 208990100001106 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue7
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -292,16 +306,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue9:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue8:
|
||||||
container_name: rfsim5g-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 208990100001107 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue8
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -316,16 +330,16 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
oai-nr-ue10:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
oai-nr-ue9:
|
||||||
container_name: rfsim5g-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 208990100001108 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||||
|
container_name: oai-nr-ue9
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # for interface bringup
|
- NET_ADMIN # for interface bringup
|
||||||
- NET_RAW # for ping
|
- 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:
|
depends_on:
|
||||||
- oai-gnb
|
- oai-gnb
|
||||||
networks:
|
networks:
|
||||||
@@ -341,13 +355,60 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
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:
|
networks:
|
||||||
public_net:
|
public_net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
name: rfsim5g-oai-public-net
|
name: rfsim5g-oai-public-net
|
||||||
ipam:
|
ipam:
|
||||||
config:
|
config:
|
||||||
- subnet: 192.168.71.128/26
|
- subnet: 192.168.71.0/24
|
||||||
driver_opts:
|
driver_opts:
|
||||||
com.docker.network.bridge.name: "rfsim5g-public"
|
com.docker.network.bridge.name: "rfsim5g-public"
|
||||||
traffic_net:
|
traffic_net:
|
||||||
|
|||||||
Executable
+654
@@ -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 "$@"
|
||||||
@@ -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
|
|
||||||
@@ -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/
|
|
||||||
@@ -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
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB |
@@ -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<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)\]")
|
||||||
|
RNTI_RE = re.compile(r"\bRNTI=(?P<rnti>\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()
|
||||||
@@ -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<ts>.*?)\]
|
||||||
|
\s+UE\s+RNTI=(?P<rnti>\d+)
|
||||||
|
\s+DL_TBS=(?P<dl_tbs>\d+)
|
||||||
|
\s+UL_TBS=(?P<ul_tbs>\d+)
|
||||||
|
\s+DL_SDUs=(?P<dl_sdus>\d+)
|
||||||
|
\s+UL_SDUs=(?P<ul_sdus>\d+)
|
||||||
|
\s+DL_sched_RBs=(?P<dl_sched_rbs>\d+)
|
||||||
|
\s+UL_sched_RBs=(?P<ul_sched_rbs>\d+)
|
||||||
|
\s+DL_PRBs=(?P<dl_prbs>\d+)
|
||||||
|
\s+UL_PRBs=(?P<ul_prbs>\d+)
|
||||||
|
\s+DL_BLER=(?P<dl_bler>[\d.]+)
|
||||||
|
\s+UL_BLER=(?P<ul_bler>[\d.]+)
|
||||||
|
\s+PUSCH_SNR=(?P<pusch_snr>[\d.]+)
|
||||||
|
\s+PUCCH_SNR=(?P<pucch_snr>[\d.]+)
|
||||||
|
\s+WB_CQI=(?P<wb_cqi>\d+)
|
||||||
|
\s+DL_MCS1=(?P<dl_mcs1>\d+)
|
||||||
|
\s+UL_MCS1=(?P<ul_mcs1>\d+)
|
||||||
|
\s+DL_HARQ=(?P<dl_harq>\d+)
|
||||||
|
\s+UL_HARQ=(?P<ul_harq>\d+)
|
||||||
|
\s+BSR=(?P<bsr>\d+)
|
||||||
|
\s+PHR=(?P<phr>\d+)
|
||||||
|
""", re.VERBOSE,
|
||||||
|
)
|
||||||
|
RRC_PATTERNS: List[re.Pattern] = [
|
||||||
|
re.compile(r"\[(?P<ts>[^]]+)\].*RNTI=(?P<rnti>[0-9a-fA-F]+).*RRC[_ ]?MSG=(?P<msg>[A-Za-z_]+)", re.IGNORECASE),
|
||||||
|
re.compile(r"\[(?P<ts>[^]]+)\].*rrc[=:](?P<msg>[A-Za-z_]+).*rnti[=:](?P<rnti>[0-9a-fA-F]+)", re.IGNORECASE),
|
||||||
|
re.compile(r"\[(?P<ts>[^]]+)\].*(?P<msg>RRC[A-Za-z]+).*RNTI=(?P<rnti>[0-9a-fA-F]+)", re.IGNORECASE),
|
||||||
|
re.compile(r"\|\s*(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?).*?RNTI\s*(?P<rnti>[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()
|
||||||
@@ -80,27 +80,7 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
|
||||||
oai-gnb:
|
oai-nr-ue1:
|
||||||
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:
|
|
||||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||||
privileged: true
|
privileged: true
|
||||||
container_name: rfsim5g-oai-nr-ue
|
container_name: rfsim5g-oai-nr-ue
|
||||||
@@ -138,7 +118,154 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
networks:
|
oai-nr-ue3:
|
||||||
public_net:
|
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||||
name: oai-cn5g-public-net
|
privileged: true
|
||||||
external: 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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
#---------------------------------------------------------------------
|
#---------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Dockerfile for the Open-Air-Interface BUILD service
|
# 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 . .
|
COPY . .
|
||||||
|
|
||||||
# build all targets so final targets can be created afterwards
|
# 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 && \
|
cd cmake_targets && \
|
||||||
mkdir -p log && \
|
mkdir -p log && \
|
||||||
./build_oai -c --ninja \
|
./build_oai -c --ninja \
|
||||||
@@ -54,12 +59,13 @@ RUN /bin/sh oaienv && \
|
|||||||
ldd ran_build/build/*.so
|
ldd ran_build/build/*.so
|
||||||
|
|
||||||
## Build FlexRIC for SM Models
|
## 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 && \
|
cd openair2/E2AP/flexric/build && \
|
||||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release \
|
cmake -GNinja -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DKPM_VERSION=$KPM_VERSION \
|
-DKPM_VERSION=$KPM_VERSION \
|
||||||
-DE2AP_VERSION=$E2AP_VERSION \
|
-DE2AP_VERSION=$E2AP_VERSION .. && \
|
||||||
-DCMAKE_C_COMPILER=gcc-12 \
|
|
||||||
-DCMAKE_CXX_COMPILER=g++-12 .. && \
|
|
||||||
ninja && \
|
ninja && \
|
||||||
ninja install
|
ninja install
|
||||||
@@ -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"]
|
||||||
@@ -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);
|
DevAssert(test_cond_value->octet_string_value->len == 4);
|
||||||
uint8_t *buf = test_cond_value->octet_string_value->buf;
|
uint8_t *buf = test_cond_value->octet_string_value->buf;
|
||||||
*sst = buf[0];
|
*sst = buf[0];
|
||||||
*sd = malloc(**sd);
|
*sd = malloc(sizeof(uint32_t));
|
||||||
**sd = buf[1] << 16 | buf[2] << 8 | buf[3];
|
**sd = buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||||
}
|
}
|
||||||
printf("[E2 AGENT][E2SM-KPM] Condition NSSAI (%d, %x).\n", *sst, (*sd) ? **sd : 0xffffff);
|
printf("[E2 AGENT][E2SM-KPM] Condition NSSAI (%d, %x).\n", *sst, (*sd) ? **sd : 0xffffff);
|
||||||
|
|||||||
+1
-1
Submodule openair2/E2AP/flexric updated: df754a8553...0a31810d47
@@ -26,7 +26,6 @@
|
|||||||
* \email: guido.casati@iis.fraunhofe.de
|
* \email: guido.casati@iis.fraunhofe.de
|
||||||
* \version 1.0
|
* \version 1.0
|
||||||
* @ingroup _mac
|
* @ingroup _mac
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common/utils/nr/nr_common.h"
|
#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);
|
qsort(UE_sched, sizeofArray(UE_sched), sizeof(UEsched_t), comparator);
|
||||||
UEsched_t *iterator = UE_sched;
|
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 */
|
/* Loop UE_sched to find max coeff and allocate transmission */
|
||||||
while (iterator->UE != NULL) {
|
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))
|
while (rbStart < rbStop && (rballoc_mask[rbStart + bwp_start] & slbitmap))
|
||||||
rbStart++;
|
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))
|
/* while (rbStart + max_rbSize <= rbStop && !(rballoc_mask[rbStart + max_rbSize + bwp_start] & slbitmap)) */
|
||||||
max_rbSize++;
|
/* max_rbSize++; */
|
||||||
|
|
||||||
if (max_rbSize < min_rbSize) {
|
if (max_rbSize < min_rbSize) {
|
||||||
LOG_D(NR_MAC,
|
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);
|
const uint16_t R = nr_get_code_rate_dl(mcs, mcsTableIdx);
|
||||||
pf_tbs[mcsTableIdx][mcs] = nr_compute_tbs(Qm,
|
pf_tbs[mcsTableIdx][mcs] = nr_compute_tbs(Qm,
|
||||||
R,
|
R,
|
||||||
1, /* rbSize */
|
1, /* rbSize */
|
||||||
10, /* hypothetical number of slots */
|
10, /* hypothetical number of slots */
|
||||||
0, /* N_PRB_DMRS * N_DMRS_SLOT */
|
0, /* N_PRB_DMRS * N_DMRS_SLOT */
|
||||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
0 /* N_PRB_oh, 0 for initialBWP */,
|
||||||
0 /* tb_scaling */,
|
0 /* tb_scaling */,
|
||||||
1 /* nrOfLayers */) >> 3;
|
1 /* nrOfLayers */
|
||||||
|
) >> 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1374,6 +1374,7 @@ void nr_schedule_ue_spec(module_id_t module_id,
|
|||||||
buf=bufEnd;
|
buf=bufEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mac_stats
|
||||||
UE->mac_stats.dl.total_bytes += TBS;
|
UE->mac_stats.dl.total_bytes += TBS;
|
||||||
UE->mac_stats.dl.current_bytes = TBS;
|
UE->mac_stats.dl.current_bytes = TBS;
|
||||||
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
|
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
|
||||||
|
|||||||
@@ -2151,9 +2151,9 @@ static void pf_ul(module_id_t module_id,
|
|||||||
while (rbStart < bwpSize && (rballoc_mask[rbStart + bwpStart] & slbitmap))
|
while (rbStart < bwpSize && (rballoc_mask[rbStart + bwpStart] & slbitmap))
|
||||||
rbStart++;
|
rbStart++;
|
||||||
sched_pusch->rbStart = rbStart;
|
sched_pusch->rbStart = rbStart;
|
||||||
uint16_t max_rbSize = 1;
|
uint16_t max_rbSize = 10; // used to be 1
|
||||||
while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap))
|
/* while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap)) */
|
||||||
max_rbSize++;
|
/* max_rbSize++; */
|
||||||
|
|
||||||
if (rbStart + min_rb > bwpSize || max_rbSize < min_rb) {
|
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);
|
reset_beam_status(&nrmac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, dci_beam.new_beam);
|
||||||
|
|||||||
@@ -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 <<EOF
|
||||||
|
Usage: $0 --profile <name> [--duration <sec>] [--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
|
||||||
Reference in New Issue
Block a user