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/genids
|
||||
common/utils/T/genids.o
|
||||
artifacts
|
||||
|
||||
@@ -153,12 +153,12 @@ gNBs =
|
||||
};
|
||||
|
||||
////////// AMF parameters:
|
||||
amf_ip_address = ({ ipv4 = "192.168.70.132"; });
|
||||
amf_ip_address = ({ ipv4 = "192.168.71.132"; });
|
||||
|
||||
NETWORK_INTERFACES :
|
||||
{
|
||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.151";
|
||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.151";
|
||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
|
||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
|
||||
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
||||
};
|
||||
|
||||
@@ -172,6 +172,7 @@ MACRLCs = (
|
||||
tr_n_preference = "local_RRC";
|
||||
pusch_TargetSNRx10 = 200;
|
||||
pucch_TargetSNRx10 = 200;
|
||||
min_grant_prb = 1;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -233,7 +234,7 @@ log_config : {
|
||||
};
|
||||
|
||||
e2_agent = {
|
||||
near_ric_ip_addr = "192.168.70.150";
|
||||
near_ric_ip_addr = "192.168.71.21";
|
||||
#sm_dir = "/path/where/the/SMs/are/located/"
|
||||
sm_dir = "/usr/local/lib/flexric/"
|
||||
};
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
Active_gNBs:
|
||||
- gnb-rfsim
|
||||
|
||||
e2_agent:
|
||||
enabled: "yes"
|
||||
near_ric_ip_addr: 192.168.71.21
|
||||
near_ric_port: 36421
|
||||
sm_dir: "/usr/local/lib/flexric/"
|
||||
|
||||
# Asn1_verbosity, choice in: none, info, annoying
|
||||
Asn1_verbosity: none
|
||||
gNBs:
|
||||
@@ -142,13 +149,13 @@ gNBs:
|
||||
GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140
|
||||
GNB_PORT_FOR_S1U: 2152 # Spec 2152
|
||||
|
||||
|
||||
MACRLCs:
|
||||
- num_cc: 1
|
||||
tr_s_preference: local_L1
|
||||
tr_n_preference: local_RRC
|
||||
pusch_TargetSNRx10: 200
|
||||
pucch_TargetSNRx10: 200
|
||||
min_grant_prb: 10
|
||||
|
||||
L1s:
|
||||
- num_cc: 1
|
||||
@@ -195,6 +202,6 @@ log_config:
|
||||
mac_log_level: info
|
||||
rlc_log_level: info
|
||||
pdcp_log_level: info
|
||||
rrc_log_level: info
|
||||
rrc_log_level: debug
|
||||
ngap_log_level: debug
|
||||
f1ap_log_level: debug
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
uicc0 = {
|
||||
imsi = "208990100001100";
|
||||
uicc0 = { # imsi is omitted
|
||||
key = "fec86ba6eb707ed08905757b1bb44b8f";
|
||||
opc= "C42449363BBAD02B66D16BC975D77CC1";
|
||||
dnn= "oai";
|
||||
|
||||
@@ -4,6 +4,7 @@ uicc0:
|
||||
opc: C42449363BBAD02B66D16BC975D77CC1
|
||||
dnn: oai
|
||||
nssai_sst: 1
|
||||
pdu_type: IPV4
|
||||
|
||||
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
||||
|
||||
@@ -24,7 +25,7 @@ channelmod:
|
||||
- model_name: rfsimu_channel_ue0
|
||||
type: AWGN
|
||||
ploss_dB: 20
|
||||
nose_power_dB: -2
|
||||
noise_power_dB: -2
|
||||
forgetfact: 0
|
||||
offset: 0
|
||||
ds_tdl: 0
|
||||
|
||||
@@ -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:
|
||||
general: debug
|
||||
@@ -103,8 +70,8 @@ amf:
|
||||
# This really depends on if we want to keep the "mini" version or not
|
||||
support_features_options:
|
||||
enable_simple_scenario: yes # "no" by default with the normal deployment scenarios with AMF/SMF/UPF/AUSF/UDM/UDR/NRF.
|
||||
# set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF.
|
||||
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
|
||||
# set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF.
|
||||
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
|
||||
enable_nssf: no
|
||||
enable_smf_selection: no
|
||||
relative_capacity: 30
|
||||
+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:
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
container_name: "mysql"
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ./oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ./mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
- ./conf/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ./conf/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
- ./conf/sql.cnf:/etc/mysql/conf.d/custom.cnf
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- TZ=Asia/Tokyo
|
||||
- MYSQL_DATABASE=oai_db
|
||||
- MYSQL_USER=test
|
||||
- MYSQL_PASSWORD=test
|
||||
@@ -19,37 +20,40 @@ services:
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
container_name: "oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.1.9
|
||||
environment:
|
||||
- TZ=Europe/paris
|
||||
- TZ=Asia/Tokyo
|
||||
volumes:
|
||||
- ./mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
- ./conf/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.132
|
||||
|
||||
oai-smf:
|
||||
container_name: "rfsim5g-oai-smf"
|
||||
container_name: "oai-smf"
|
||||
image: oaisoftwarealliance/oai-smf:v2.1.9
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- TZ=Asia/Tokyo
|
||||
volumes:
|
||||
- ./mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
- ./conf/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-amf
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.133
|
||||
|
||||
oai-upf:
|
||||
container_name: "rfsim5g-oai-upf"
|
||||
container_name: "oai-upf"
|
||||
image: oaisoftwarealliance/oai-upf:v2.1.9
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- TZ=Asia/Tokyo
|
||||
volumes:
|
||||
- ./mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
- ./conf/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-smf
|
||||
cap_add:
|
||||
@@ -63,9 +67,10 @@ services:
|
||||
ipv4_address: 192.168.71.134
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.134
|
||||
|
||||
oai-ext-dn:
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-ext-dn
|
||||
container_name: oai-ext-dn
|
||||
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
||||
entrypoint: /bin/bash -c \
|
||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||
@@ -80,16 +85,15 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-gnb:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-gnb
|
||||
image: nakaolab:oct1
|
||||
container_name: oai-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
depends_on:
|
||||
- oai-ext-dn
|
||||
command: ["-c", "/oai-ran/cmake_targets/ran_build/build/nr-softmodem -O /opt/oai-gnb/etc/gnb.yaml -E --rfsim --log_config.global_log_options level,nocolor,time"]
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
@@ -100,21 +104,55 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
|
||||
oai-flexric:
|
||||
image: nakaolab:oct1
|
||||
container_name: oai-flexric
|
||||
command: ["-c", "stdbuf -o0 /oai-ran/openair2/E2AP/flexric/build/examples/ric/nearRT-RIC"]
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.21
|
||||
volumes:
|
||||
- ./conf/flexric.conf:/oai-ran/openair2/E2AP/flexric/flexric.conf
|
||||
- ./conf/flexric.conf:/usr/local/etc/flexric/flexric.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nearRT-RIC"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
|
||||
xapp-chatgpt:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "XAPP_DURATION=20 stdbuf -oL /oai-ran/openair2/E2AP/flexric/build/examples/xApp/c/monitor/xapp_chatgpt"]
|
||||
container_name: xapp-chatgpt
|
||||
environment:
|
||||
- TZ=Asia/Tokyo
|
||||
depends_on:
|
||||
- oai-flexric
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.26
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep -f xapp_chatgpt"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
|
||||
oai-nr-ue1:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue1
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
# environment:
|
||||
# USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
ipv4_address: 192.168.71.151
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
@@ -124,40 +162,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue2:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue2
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001101 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue2
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001101 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.151
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue3:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue3
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001102 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -172,16 +186,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue4:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue4
|
||||
|
||||
oai-nr-ue3:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001102 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue3
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001103 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -196,16 +210,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue5:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue5
|
||||
|
||||
oai-nr-ue4:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001103 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue4
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001104 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -220,16 +234,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue6:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue6
|
||||
|
||||
oai-nr-ue5:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001104 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue5
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001105 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -244,16 +258,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue7:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue7
|
||||
|
||||
oai-nr-ue6:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001105 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue6
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001106 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -268,16 +282,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue8:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue8
|
||||
|
||||
oai-nr-ue7:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001106 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue7
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001107 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -292,16 +306,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue9:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue9
|
||||
|
||||
oai-nr-ue8:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001107 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue8
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001108 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -316,16 +330,16 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-nr-ue10:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue10
|
||||
|
||||
oai-nr-ue9:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001108 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue9
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001109 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
@@ -341,13 +355,60 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue10:
|
||||
image: nakaolab:oct1
|
||||
command: ["-c", "stdbuf -oL /oai-ran/cmake_targets/ran_build/build/nr-uesoftmodem -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001109 -C 3319680000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time"]
|
||||
container_name: oai-nr-ue10
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.160
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# iperf:
|
||||
# image: phlak/iperf
|
||||
# container_name: iperf
|
||||
# ports:
|
||||
# - "5201:5201/tcp"
|
||||
# - "5201:5201/udp"
|
||||
# networks:
|
||||
# traffic_net:
|
||||
# ipv4_address: 192.168.72.130
|
||||
# restart: unless-stopped
|
||||
|
||||
iperf:
|
||||
image: mlabbe/iperf
|
||||
container_name: iperf
|
||||
command: ["-s", "-p 5201"]
|
||||
ports:
|
||||
- "5201:5201/tcp"
|
||||
- "5201:5201/udp"
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.130
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-public-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.71.128/26
|
||||
- subnet: 192.168.71.0/24
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-public"
|
||||
traffic_net:
|
||||
|
||||
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
|
||||
timeout: 5s
|
||||
|
||||
oai-gnb:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-gnb
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
depends_on:
|
||||
- oai-flexric
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.151
|
||||
volumes:
|
||||
- ../../conf_files/gnb.sa.band78.106prb.rfsim.flexric.conf:/opt/oai-gnb/etc/gnb.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-softmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue:
|
||||
oai-nr-ue1:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
@@ -138,7 +118,154 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
name: oai-cn5g-public-net
|
||||
external: true
|
||||
oai-nr-ue3:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue3
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.154
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue4:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue4
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.155
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue5:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue5
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.156
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue6:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue6
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.157
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue7:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue7
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.158
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue8:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue8
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.159
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue9:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue9
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.160
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue10:
|
||||
image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-nr-ue10
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 -C 3619200000 --uicc0.imsi 001010000000004 --rfsimulator.serveraddr 192.168.70.151 --log_config.global_log_options level,nocolor,time
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.70.161
|
||||
volumes:
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -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
|
||||
# Valid for Ubuntu 22.04
|
||||
# Valid for Ubuntu 24.04
|
||||
#
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
@@ -36,7 +36,12 @@ WORKDIR /oai-ran
|
||||
COPY . .
|
||||
|
||||
# build all targets so final targets can be created afterwards
|
||||
RUN /bin/sh oaienv && \
|
||||
RUN \
|
||||
# Mount CCache cache direrctory
|
||||
--mount=type=cache,target=/root/.cache/ccache/ \
|
||||
# Mount CPM package cache
|
||||
--mount=type=cache,target=/root/.cache/cpm/ \
|
||||
/bin/sh oaienv && \
|
||||
cd cmake_targets && \
|
||||
mkdir -p log && \
|
||||
./build_oai -c --ninja \
|
||||
@@ -54,12 +59,13 @@ RUN /bin/sh oaienv && \
|
||||
ldd ran_build/build/*.so
|
||||
|
||||
## Build FlexRIC for SM Models
|
||||
RUN mkdir -p openair2/E2AP/flexric/build && \
|
||||
RUN \
|
||||
# Mount CCache cache direrctory
|
||||
--mount=type=cache,target=/root/.cache/ccache/ \
|
||||
mkdir -p openair2/E2AP/flexric/build && \
|
||||
cd openair2/E2AP/flexric/build && \
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DKPM_VERSION=$KPM_VERSION \
|
||||
-DE2AP_VERSION=$E2AP_VERSION \
|
||||
-DCMAKE_C_COMPILER=gcc-12 \
|
||||
-DCMAKE_CXX_COMPILER=g++-12 .. && \
|
||||
-DE2AP_VERSION=$E2AP_VERSION .. && \
|
||||
ninja && \
|
||||
ninja install
|
||||
@@ -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);
|
||||
uint8_t *buf = test_cond_value->octet_string_value->buf;
|
||||
*sst = buf[0];
|
||||
*sd = malloc(**sd);
|
||||
*sd = malloc(sizeof(uint32_t));
|
||||
**sd = buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
}
|
||||
printf("[E2 AGENT][E2SM-KPM] Condition NSSAI (%d, %x).\n", *sst, (*sd) ? **sd : 0xffffff);
|
||||
|
||||
+1
-1
Submodule openair2/E2AP/flexric updated: df754a8553...0a31810d47
@@ -26,7 +26,6 @@
|
||||
* \email: guido.casati@iis.fraunhofe.de
|
||||
* \version 1.0
|
||||
* @ingroup _mac
|
||||
|
||||
*/
|
||||
|
||||
#include "common/utils/nr/nr_common.h"
|
||||
@@ -708,7 +707,7 @@ static void pf_dl(module_id_t module_id,
|
||||
qsort(UE_sched, sizeofArray(UE_sched), sizeof(UEsched_t), comparator);
|
||||
UEsched_t *iterator = UE_sched;
|
||||
|
||||
const int min_rbSize = 5;
|
||||
const int min_rbSize = 10;
|
||||
|
||||
/* Loop UE_sched to find max coeff and allocate transmission */
|
||||
while (iterator->UE != NULL) {
|
||||
@@ -772,10 +771,10 @@ static void pf_dl(module_id_t module_id,
|
||||
while (rbStart < rbStop && (rballoc_mask[rbStart + bwp_start] & slbitmap))
|
||||
rbStart++;
|
||||
|
||||
uint16_t max_rbSize = 1;
|
||||
uint16_t max_rbSize = 10; // used to be 1
|
||||
|
||||
while (rbStart + max_rbSize <= rbStop && !(rballoc_mask[rbStart + max_rbSize + bwp_start] & slbitmap))
|
||||
max_rbSize++;
|
||||
/* while (rbStart + max_rbSize <= rbStop && !(rballoc_mask[rbStart + max_rbSize + bwp_start] & slbitmap)) */
|
||||
/* max_rbSize++; */
|
||||
|
||||
if (max_rbSize < min_rbSize) {
|
||||
LOG_D(NR_MAC,
|
||||
@@ -907,12 +906,13 @@ nr_pp_impl_dl nr_init_dlsch_preprocessor(int CC_id) {
|
||||
const uint16_t R = nr_get_code_rate_dl(mcs, mcsTableIdx);
|
||||
pf_tbs[mcsTableIdx][mcs] = nr_compute_tbs(Qm,
|
||||
R,
|
||||
1, /* rbSize */
|
||||
1, /* rbSize */
|
||||
10, /* hypothetical number of slots */
|
||||
0, /* N_PRB_DMRS * N_DMRS_SLOT */
|
||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
||||
0 /* tb_scaling */,
|
||||
1 /* nrOfLayers */) >> 3;
|
||||
0, /* N_PRB_DMRS * N_DMRS_SLOT */
|
||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
||||
0 /* tb_scaling */,
|
||||
1 /* nrOfLayers */
|
||||
) >> 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1374,6 +1374,7 @@ void nr_schedule_ue_spec(module_id_t module_id,
|
||||
buf=bufEnd;
|
||||
}
|
||||
|
||||
// mac_stats
|
||||
UE->mac_stats.dl.total_bytes += TBS;
|
||||
UE->mac_stats.dl.current_bytes = TBS;
|
||||
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
|
||||
|
||||
@@ -2151,9 +2151,9 @@ static void pf_ul(module_id_t module_id,
|
||||
while (rbStart < bwpSize && (rballoc_mask[rbStart + bwpStart] & slbitmap))
|
||||
rbStart++;
|
||||
sched_pusch->rbStart = rbStart;
|
||||
uint16_t max_rbSize = 1;
|
||||
while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap))
|
||||
max_rbSize++;
|
||||
uint16_t max_rbSize = 10; // used to be 1
|
||||
/* while (rbStart + max_rbSize < bwpSize && !(rballoc_mask[rbStart + bwpStart + max_rbSize] & slbitmap)) */
|
||||
/* max_rbSize++; */
|
||||
|
||||
if (rbStart + min_rb > bwpSize || max_rbSize < min_rb) {
|
||||
reset_beam_status(&nrmac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, dci_beam.new_beam);
|
||||
|
||||
@@ -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