nakaolab oct19
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user