From: deepanshuk Date: Tue, 6 Jul 2021 06:35:30 +0000 (+0530) Subject: First version with ML prediction code X-Git-Tag: 0.0.4~4 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?p=ric-app%2Fqp.git;a=commitdiff_plain;h=a1cb1abac5f5979f507b6760f0862c38c3ba2347 First version with ML prediction code Signed-off-by: deepanshuk Change-Id: Ie7e34b1828adf421276b7924b3b29a63eb1d36a6 --- diff --git a/Dockerfile b/Dockerfile index 402fab7..e7214c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== -FROM python:3.8-alpine - +FROM frolvlad/alpine-miniconda3 +#FROM python:3.7-alpine # RMR setup RUN mkdir -p /opt/route/ # copy rmr files from builder image in lieu of an Alpine package @@ -25,15 +25,16 @@ ENV LD_LIBRARY_PATH /usr/local/lib/:/usr/local/lib64 COPY tests/fixtures/local.rt /opt/route/local.rt ENV RMR_SEED_RT /opt/route/local.rt + # sdl needs gcc RUN apk update && apk add gcc musl-dev # Install COPY setup.py /tmp COPY LICENSE.txt /tmp/ -COPY qp/ /tmp/qp +COPY qp/ /qp RUN pip install /tmp # Run ENV PYTHONUNBUFFERED 1 -CMD run-qp.py +CMD PYTHONPATH=/qp:/usr/lib/python3.7/site-packages/:$PYTHONPATH run-qp.py diff --git a/Dockerfile-Unit-Test b/Dockerfile-Unit-Test index 9f9f104..596ad36 100644 --- a/Dockerfile-Unit-Test +++ b/Dockerfile-Unit-Test @@ -13,7 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== -FROM python:3.8-alpine +FROM frolvlad/alpine-miniconda3 +#FROM python:3.8-alpine # sdl uses hiredis which needs gcc RUN apk update && apk add gcc musl-dev @@ -24,6 +25,14 @@ COPY --from=nexus3.o-ran-sc.org:10002/o-ran-sc/bldr-alpine3-rmr:4.0.5 /usr/local # Upgrade pip, install tox RUN pip install --upgrade pip && pip install tox +# Install dependencies +RUN conda install joblib +RUN conda update -n base -c defaults conda +RUN conda install pandas +RUN pip install schedule +RUN pip install influxdb +RUN conda install -c conda-forge statsmodels + # copies COPY setup.py tox.ini LICENSE.txt /tmp/ COPY qp/ /tmp/qp @@ -32,4 +41,4 @@ RUN pip install /tmp # Run the unit tests WORKDIR /tmp -RUN tox -e code,flake8 +RUN PYTHONPATH=/tmp/qp:/usr/lib/python3.7/site-packages/:$PYTHONPATH tox -e code,flake8 diff --git a/docs/overview.rst b/docs/overview.rst index 736fffe..bb89b06 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -1,6 +1,7 @@ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. SPDX-License-Identifier: CC-BY-4.0 .. Copyright (C) 2020 AT&T Intellectual Property +.. Copyright (C) 2021 HCL Technologies Limited. QoE Predictor Overview ====================== @@ -8,89 +9,15 @@ QoE Predictor Overview QoE Predictor (QP) is an Xapp in the Traffic Steering O-RAN use case, which uses the following Xapps: -#. Traffic Steering, which sends prediction requests to QP Driver. -#. QP Driver, which fetches data from SDL on behalf of traffic steering, - both UE Data and Cell Data, merges that data together, then sends off - the data to the QoE Predictor. +#. Traffic Steering, which sends prediction requests to QP. #. QoE Predictor, which predicts and sends that prediction back to Traffic Steering -#. KPIMONN, which populates SDL in the first place. +#. KPIMONN, which populates UE and Cell metrics into the influxdb. Expected Input -------------- The QP Xapp expects a prediction-request JSON message via RMR with the following structure:: - - { - "predictionUE": "UEId1", - "ueMeasurements" : - { "servingCellId" : "CID2", - "measTimestampUePrbUsage" : TS1, - "measPeriodUePrbUsage" : Int, - "uePrbUsageDL" : Int, - "uePrbUsageUL" : Int, - "measTimestampUePdcpBytes" : TS2, - "measPeriodUePdcpByes" : Int, - "uePdcpBytesDL": Int, - "uePdcpBytesUL" : Int - }, - "cellMeasurements" : [ - { - "cellId" : "CID2", - "measTimestampPrbAvailable" : TS, - "measPeriodPrbAvailable" : Int, - "prbAvailableDL" : Int, - "prbAvailableUL" : Int, - "measTimestampPdcpBytes" : TS, - "measPeriodPdcpBytes" : Int, - "pdcpBytesDL" : 30000000, - "pdcpBytesUL" : 5000000, - "measTimestampRf" : TS, - "measPeriodRf" : Int, - "rfMeasurements" : { - "rsrp": Int, - "rsrq": Int, - "rsSinr": Int - } - }, - { - "cellId" : "CID1", - "measTimestampPrbAvailable" : TS, - "measPeriodPrbAvailable" : Int, - "prbAvailableDL" : Int, - "prbAvailableUL" : Int, - "measTimestampPdcpBytes" : TS, - "measPeriodPdcpBytes" : Int, - "pdcpBytesDL" : 10000000, - "pdcpBytesUL" : 2000000, - "measTimestampRf" : TS, - "measPeriodRf" : Int, - "rfMeasurements" : { - "rsrp": Int, - "rsrq": Int, - "rsSinr": Int - } - }, - { - "cellId" : "CID3", - "measTimestampPrbAvailable" : TS, - "measPeriodPrbAvailable" : Int, - "prbAvailableDL" : Int, - "prbAvailableUL" : Int, - "measTimestampPdcpBytes" : TS, - "measPeriodPdcpBytes" : Int, - "pdcpBytesDL" : 50000000, - "pdcpBytesUL" : 4000000, - "measTimestampRf" : TS, - "measPeriodRf" : Int, - "rfMeasurements" : { - "rsrp": Int, - "rsrq": Int, - "rsSinr": Int - } - } - ] - } - +{"UEPredictionSet": ["Car-1"]} Expected Output --------------- @@ -98,12 +25,10 @@ Expected Output The QP Xapp should send a prediction for both downlink and uplink throughput as a JSON message via RMR with the following structure:: - { - "UEId1": { - "CID1" : [10000000,2000000], - "CID2" : [30000000,5000000], - "CID3" : [50000000,4000000] - } - } - - + {"Car-1":{ + "c6/B2": [12650, 12721], + "c6/N77": [12663, 12739], + "c1/B13": [12576, 12655], + "c7/B13": [12649, 12697], + "c5/B13": [12592, 12688] + }} diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 8fa56ed..79fb288 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -12,7 +12,7 @@ and this project adheres to `Semantic Versioning `__. [0.0.3] - 2020-12-08 -------------------- -* Process incoming message from QP Driver and generate a dynamic mock Prediction message with one cell having better xput +* Removed QP Driver functionaity for QP, Incoming request from TS and Add actual protoype ML prediction code (`RICAPP-46 `_) [0.0.2] - 2020-06-02 -------------------- diff --git a/qp/cell.json.7z b/qp/cell.json.7z new file mode 100644 index 0000000..bc98903 Binary files /dev/null and b/qp/cell.json.7z differ diff --git a/qp/database.py b/qp/database.py new file mode 100644 index 0000000..42083ee --- /dev/null +++ b/qp/database.py @@ -0,0 +1,78 @@ +# ================================================================================== +# Copyright (c) 2020 AT&T Intellectual Property. +# Copyright (c) 2020 HCL Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# ================================================================================== +from influxdb import DataFrameClient +import pandas as pd +import datetime + + +class NoDataError(Exception): + pass + + +class DATABASE(object): + + def __init__(self, dbname, user='root', password='root', host='r4-influxdb.ricplt', port='8086'): + self.client = DataFrameClient(host, port, user, password, dbname) + self.data = None + + def read_data(self, meas='ueMeasReport', limit=100000, cellid=False, ueid=False): + query = """select * from """ + meas + + if cellid: + query += " where nrCellIdentity= '" + cellid + "'" + + if ueid: + query += """ where "ue-id" = \'{}\'""".format(ueid) + query += " ORDER BY DESC LIMIT " + str(limit) + result = self.client.query(query) + try: + if len(result) != 0: + # print("Querying data : " + meas + " : size - " + str(len(result[meas]))) + self.data = result[meas] + self.data['measTimeStampRf'] = self.data.index + else: + raise NoDataError + + except NoDataError: + if cellid: + print('Data not found for ' + meas + ' CellID : '+cellid) + elif ueid: + print('Data not found for ' + meas + ' UEID : '+ueid) + else: + print('Data not found for ' + meas) + pass + + def write_prediction(self, df, meas_name='QP'): + df.index = pd.date_range(start=datetime.datetime.now(), freq='10ms', periods=len(df)) + self.client.write_points(df, meas_name) + + +class DUMMY: + + def __init__(self): + self.ue = pd.DataFrame([[1002, "c2/B13", 8, 69, 65, 113, 0.1, 0.1, "Waiting passenger 9", -882, -959, pd.to_datetime("2021-05-12T07:43:51.652")]], columns=["du-id", "nbCellIdentity", "prb_usage", "rsrp", "rsrq", "rssinr", "throughput", "targetTput", "ue-id", "x", "y", "measTimeStampRf"]) + self.cell = pd.read_csv('qp/dummy.csv') + self.data = None + + def read_data(self, meas='ueMeasReport', limit=100000, cellid=False, ueid=False): + if ueid: + self.data = self.ue.head(limit) + if cellid: + self.data = self.cell.head(limit) + + def write_prediction(self, df, meas_name='QP'): + pass diff --git a/qp/dummy.csv b/qp/dummy.csv new file mode 100644 index 0000000..360ec06 --- /dev/null +++ b/qp/dummy.csv @@ -0,0 +1,101 @@ +availPrbDl,availPrbUl,du-id,measPeriodPdcpBytes,measPeriodPrb,measTimeStampRf,nrCellIdentity,pdcpBytesDl,pdcpBytesUl,throughput,x,y +0,0,1002,10.0,10,2021-06-25T11:42:33.685,c2/B13,4.566447392558995,4.566447392558995,0.44594212817958906,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.695,c2/B13,4.566447392558995,4.566447392558995,0.44594212817958906,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.705,c2/B13,9.132362040900631,9.132362040900631,0.445890102377112,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.715,c2/B13,13.697743446574867,13.697743446574867,0.445838027897874,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.725,c2/B13,18.262591113423376,18.262591113423376,0.44578590496567405,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.735,c2/B13,22.826904547575328,22.826904547575328,0.445733733803901,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.745,c2/B13,27.39068325744312,27.39068325744312,0.445681514635526,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.755,c2/B13,31.953926753718125,31.953926753718125,0.445629247683106,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.765,c2/B13,36.51663454936642,36.51663454936642,0.44557693316877905,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.775,c2/B13,41.07880615962452,41.07880615962452,0.44552457131426704,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.785,c2/B13,45.64044110199503,45.64044110199503,0.44547216234087006,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.795,c2/B13,50.20153889624241,50.20153889624241,0.44541970646947004,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.805,c2/B13,54.76209906438861,54.76209906438861,0.44536720392052703,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.815,c2/B13,59.32212113070874,59.32212113070874,0.445314654914075,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.825,c2/B13,63.88160462172675,63.88160462172675,0.44526205966972804,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.835,c2/B13,68.4405490662111,68.4405490662111,0.44520941840667405,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.845,c2/B13,72.99895399517035,72.99895399517035,0.44515673134367606,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.855,c2/B13,77.55681894184882,77.55681894184882,0.445103998699068,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.865,c2/B13,82.11414344172219,82.11414344172219,0.44505122069075903,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.875,c2/B13,86.67092703249317,86.67092703249317,0.44499839753622805,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.885,c2/B13,91.22716925408702,91.22716925408702,0.444945529452523,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.895,c2/B13,95.78286964864716,95.78286964864716,0.44489261665626406,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.905,c2/B13,100.33802776053082,100.33802776053082,0.444839659363637,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.915,c2/B13,104.89264313630449,104.89264313630449,0.44478665779039706,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.925,c2/B13,109.4467153247396,109.4467153247396,0.444733612151866,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.935,c2/B13,114.000243876808,114.000243876808,0.444680522662929,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.945,c2/B13,118.55322834567752,118.55322834567752,0.44462738953803804,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.955,c2/B13,123.10566828670753,123.10566828670753,0.444574212991211,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.965,c2/B13,127.65756325744442,127.65756325744442,0.444520993236025,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.975,c2/B13,132.20891281761718,132.20891281761718,0.44446773048562105,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.985,c2/B13,136.75971652913285,136.75971652913285,0.44441442495270206,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:33.995,c2/B13,141.30997395607204,141.30997395607204,0.444361076849531,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.005,c2/B13,145.85968466468447,145.85968466468447,0.44430768638793205,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.015,c2/B13,150.40884822338438,150.40884822338438,0.444254253779288,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.025,c2/B13,154.95746420274605,154.95746420274605,0.44420077923453705,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.035,c2/B13,159.50553217549927,159.50553217549927,0.44414726296418106,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.045,c2/B13,164.0530517165248,164.0530517165248,0.444093705178273,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.055,c2/B13,168.60002240284982,168.60002240284982,0.444040106086426,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.065,c2/B13,173.14644381364334,173.14644381364334,0.44398646589780505,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.075,c2/B13,177.69231553021177,177.69231553021177,0.443932784821134,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.085,c2/B13,182.2376371359942,182.2376371359942,0.44387906306468805,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.095,c2/B13,186.78240821655785,186.78240821655785,0.443825300836296,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.105,c2/B13,191.32662835959366,191.32662835959366,0.44377149834334,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.115,c2/B13,195.87029715491147,195.87029715491147,0.443717655792755,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.125,c2/B13,200.4134141944356,200.4134141944356,0.443663773391027,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.135,c2/B13,204.95597907220014,204.95597907220014,0.443609851344193,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.145,c2/B13,209.4979913843444,209.4979913843444,0.44355588985783906,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.155,c2/B13,214.03945072910835,214.03945072910835,0.44350188913710303,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.165,c2/B13,218.58035670682787,218.58035670682787,0.44344784938667103,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.175,c2/B13,223.12070891993022,223.12070891993022,0.443393770810777,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.185,c2/B13,227.66050697292945,227.66050697292945,0.44333965361320404,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.195,c2/B13,232.19975047242164,232.19975047242164,0.443285497997283,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.205,c2/B13,236.73843902708037,236.73843902708037,0.443231304165891,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.215,c2/B13,241.27657224765204,241.27657224765204,0.44317707232145104,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.225,c2/B13,245.81414974695122,245.81414974695122,0.443122802665933,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.235,c2/B13,250.35117113985598,250.35117113985598,0.443068495400854,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.245,c2/B13,254.88763604330327,254.88763604330327,0.443014150727273,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.255,c2/B13,259.42354407628426,259.42354407628426,0.442959768845796,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.265,c2/B13,263.9588948598396,263.9588948598396,0.44290534995657105,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.275,c2/B13,268.49368801705475,268.49368801705475,0.442850894259292,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.285,c2/B13,273.02792317305546,273.02792317305546,0.44279640195319503,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.295,c2/B13,277.5615999550029,277.5615999550029,0.44274187323705805,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.305,c2/B13,282.09471799208916,282.09471799208916,0.44268730830920405,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.315,c2/B13,286.6272769155323,286.6272769155323,0.44263270736749705,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.325,c2/B13,291.15927635857196,291.15927635857196,0.442578070609341,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.335,c2/B13,295.69071595646443,295.69071595646443,0.442523398231685,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.345,c2/B13,300.221595346478,300.221595346478,0.44246869043101406,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.355,c2/B13,304.7519141678884,304.7519141678884,0.44241394740335804,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.365,c2/B13,309.2816720619739,309.2816720619739,0.44235916934428704,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.375,c2/B13,313.8108686720107,313.8108686720107,0.44230435644890803,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.385,c2/B13,318.3395036432683,318.3395036432683,0.44224950891187104,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.395,c2/B13,322.8675766230045,322.8675766230045,0.44219462692736305,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.405,c2/B13,327.39508726046097,327.39508726046097,0.442139710689112,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.415,c2/B13,331.92203520685854,331.92203520685854,0.442084760390384,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.425,c2/B13,336.44842011539214,336.44842011539214,0.44202977622398404,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.435,c2/B13,340.97424164122646,340.97424164122646,0.44197475838225503,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.445,c2/B13,345.4994994414909,345.4994994414909,0.44191970705707806,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.455,c2/B13,350.0241931752752,350.0241931752752,0.441864622439871,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.465,c2/B13,354.5483225036243,354.5483225036243,0.441809504721594,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.475,c2/B13,359.07188708953396,359.07188708953396,0.44175435409273806,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.485,c2/B13,363.59488659794573,363.59488659794573,0.44169917074333703,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.495,c2/B13,368.1173206957424,368.1173206957424,0.44164395486295804,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.505,c2/B13,372.6391890517433,372.6391890517433,0.44158870664070804,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.515,c2/B13,377.16049133669924,377.16049133669924,0.44153342626523,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.525,c2/B13,381.6812272232882,381.6812272232882,0.44147811392470104,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.535,c2/B13,386.2013963861102,386.2013963861102,0.44142276980683803,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.545,c2/B13,390.72099850168286,390.72099850168286,0.44136739409889103,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.555,c2/B13,395.2400332484364,395.2400332484364,0.44131198698765106,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.565,c2/B13,399.7585003067091,399.7585003067091,0.44125654865944103,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.575,c2/B13,404.2763993587423,404.2763993587423,0.44120107930012004,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.585,c2/B13,408.793730088676,408.793730088676,0.44114557909508606,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.595,c2/B13,413.3104921825437,413.3104921825437,0.44109004822927006,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.605,c2/B13,417.826685328268,417.826685328268,0.44103448688714003,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.615,c2/B13,422.34230921565563,422.34230921565563,0.440978895252699,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.625,c2/B13,426.85736353639277,426.85736353639277,0.440923273509486,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.635,c2/B13,431.3718479840403,431.3718479840403,0.44086762184057604,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.645,c2/B13,435.885762254029,435.885762254029,0.44081194042858,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.655,c2/B13,440.39910604365474,440.39910604365474,0.440756229455643,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.665,c2/B13,444.911879052074,444.911879052074,0.44070048910344606,-832.0,-555.0 +0,0,1002,10.0,10,2021-06-25T11:42:34.675,c2/B13,449.42408098029887,449.42408098029887,0.44064471955320705,-832.0,-555.0 diff --git a/qp/insert.py b/qp/insert.py new file mode 100644 index 0000000..ee87711 --- /dev/null +++ b/qp/insert.py @@ -0,0 +1,79 @@ +# ================================================================================== +# Copyright (c) 2020 AT&T Intellectual Property. +# Copyright (c) 2020 HCL Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# ================================================================================== +""" + +This module is temporary which aims to populate cell data into influxDB. This will be depreciated once KPIMON push cell info. into influxDB. + +""" +import pandas as pd +from influxdb import DataFrameClient +import datetime + + +class INSERTDATA: + + def __init__(self): + host = 'r4-influxdb.ricplt' + self.client = DataFrameClient(host, '8086', 'root', 'root') + self.switchdb('UEData') + self.dropmeas('QP') + + def switchdb(self, dbname): + print("Switch database: " + dbname) + self.client.switch_database(dbname) + + def dropmeas(self, measname): + print("DROP MEASUREMENT: " + measname) + self.client.query('DROP MEASUREMENT '+measname) + + +def explode(df): + for col in df.columns: + if isinstance(df.iloc[0][col], list): + df = df.explode(col) + d = df[col].apply(pd.Series) + df[d.columns] = d + df = df.drop(col, axis=1) + return df + + +def jsonToTable(df): + df.index = range(len(df)) + cols = [col for col in df.columns if isinstance(df.iloc[0][col], dict) or isinstance(df.iloc[0][col], list)] + if len(cols) == 0: + return df + for col in cols: + d = explode(pd.DataFrame(df[col], columns=[col])) + d = d.dropna(axis=1, how='all') + df = pd.concat([df, d], axis=1) + df = df.drop(col, axis=1).dropna() + return jsonToTable(df) + + +def time(df): + df.index = pd.date_range(start=datetime.datetime.now(), freq='10ms', periods=len(df)) + df['measTimeStampRf'] = df['measTimeStampRf'].apply(lambda x: str(x)) + return df + + +def populatedb(): + df = pd.read_json('qp/cell.json.gz', lines=True) + df = df[['cellMeasReport']].dropna() + df = jsonToTable(df) + df = time(df) + db = INSERTDATA() + db.client.write_points(df, 'liveCell', batch_size=500, protocol='line') diff --git a/qp/main.py b/qp/main.py index 188e686..002b063 100644 --- a/qp/main.py +++ b/qp/main.py @@ -1,5 +1,6 @@ # ================================================================================== # Copyright (c) 2020 AT&T Intellectual Property. +# Copyright (c) 2020 HCL Technologies Limited. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,20 +15,25 @@ # limitations under the License. # ================================================================================== """ -mock qp module +qp module main -- using Time series ML predictor RMR Messages: - #define TS_QOE_PRED_REQ 30001 + #define TS_UE_LIST 30000 #define TS_QOE_PREDICTION 30002 -30001 is the message type QP receives from the driver; +30000 is the message type QP receives from the TS; sends out type 30002 which should be routed to TS. """ - -import json +import insert import os +import json from mdclogpy import Logger from ricxappframe.xapp_frame import RMRXapp, rmr +from prediction import forecast +from qptrain import train +from database import DATABASE, DUMMY +import warnings +warnings.filterwarnings("ignore") # pylint: disable=invalid-name qp_xapp = None @@ -53,34 +59,60 @@ def qp_default_handler(self, summary, sbuf): def qp_predict_handler(self, summary, sbuf): """ - Function that processes messages for type 30001 - """ - logger.debug("predict handler received message type {}".format(summary[rmr.RMR_MS_MSG_TYPE])) - logger.debug("adding somethign") - logger.debug("message is " + summary[rmr.RMR_MS_PAYLOAD].decode()) - pred_req_msg = json.loads(summary[rmr.RMR_MS_PAYLOAD].decode()) - all_cells = {} - ind = 0 - for ncell in pred_req_msg["CellMeasurements"]: - if (ind == 0): - all_cells[ncell["CellID"]] = [50000, 20000] - else: - all_cells[ncell["CellID"]] = [20000, 10000] - ind += 1 - - pred_msg = {} - pred_msg[pred_req_msg["PredictionUE"]] = all_cells + Function that processes messages for type 30000 + """ + logger.debug("predict handler received payload {}".format(summary[rmr.RMR_MS_PAYLOAD])) + pred_msg = predict(summary[rmr.RMR_MS_PAYLOAD]) self.predict_requests += 1 # we don't use rts here; free this self.rmr_free(sbuf) - # send a mock message based on input - success = self.rmr_send(json.dumps(pred_msg).encode(), 30002) + success = self.rmr_send(pred_msg.encode(), 30002) + logger.debug("Sending message to ts : {}".format(pred_msg)) # For debug purpose if success: logger.debug("predict handler: sent message successfully") else: logger.warning("predict handler: failed to send message") +def nbcells(ue): + """ + Extract neighbor cell id for a given UE + """ + db.read_data(meas='liveUE', limit=1, ueid=ue) + df = db.data + + nbc = df.filter(regex='nbCell').values[0] + return nbc + + +def predict(payload): + """ + Function that forecast the time series + """ + tp = {} + payload = json.loads(payload) + ueid = payload['UEPredictionSet'][0] + + nbc = nbcells(ueid) + for cid in nbc: + mcid = cid.replace('/', '') + db.read_data(meas='liveCell', cellid=cid, limit=11) + if len(db.data) != 0: + inp = db.data + + if not os.path.isfile('qp/' + mcid): + train(db, cid) + + df_f = forecast(inp, mcid, 1) + if df_f is not None: + tp[cid] = df_f.values.tolist()[0] + df_f['cellid'] = cid + db.write_prediction(df_f) + else: + tp[cid] = [None, None] + return json.dumps({ueid: tp}) + + def start(thread=False): """ This is a convenience function that allows this xapp to run in Docker @@ -89,9 +121,15 @@ def start(thread=False): """ logger.debug("QP xApp starting") global qp_xapp + global db + if not thread: + insert.populatedb() # temporory method to popuate db, it will be removed when data will be coming through KPIMON to influxDB + db = DATABASE('UEData') + else: + db = DUMMY() fake_sdl = os.environ.get("USE_FAKE_SDL", None) qp_xapp = RMRXapp(qp_default_handler, rmr_port=4560, post_init=post_init, use_fake_sdl=bool(fake_sdl)) - qp_xapp.register_callback(qp_predict_handler, 30001) + qp_xapp.register_callback(qp_predict_handler, 30000) qp_xapp.run(thread) diff --git a/qp/prediction.py b/qp/prediction.py new file mode 100644 index 0000000..c861ca7 --- /dev/null +++ b/qp/prediction.py @@ -0,0 +1,45 @@ +# ================================================================================== +# Copyright (c) 2020 HCL Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# ================================================================================== +# import pandas as pd +import os +import joblib +import pandas as pd +from qptrain import PROCESS + + +def forecast(data, cid, nobs=1): + """ + forecast the time series using the saved model. + """ + data = data[['pdcpBytesUl', 'pdcpBytesDl']] + ps = PROCESS(data.copy()) + ps.make_stationary() + + if not ps.valid(): + df_f = data.tail(1) + elif os.path.isfile('qp/'+cid): + model = joblib.load('qp/'+cid) + pred = model.forecast(y=ps.data.values, steps=nobs) + + if pred is not None: + df_f = pd.DataFrame(pred, columns=data.columns) + df_f.index = pd.date_range(start=data.index[-1], freq='10ms', periods=len(df_f)) + df_f = df_f[data.columns].astype(int) + df_f = ps.invert_transformation(data, df_f) + else: + return None + df_f = df_f[data.columns].astype(int) + return df_f diff --git a/qp/qptrain.py b/qp/qptrain.py new file mode 100644 index 0000000..68dcb35 --- /dev/null +++ b/qp/qptrain.py @@ -0,0 +1,91 @@ +# ================================================================================== +# Copyright (c) 2020 HCL Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# ================================================================================== + +from statsmodels.tsa.api import VAR +from statsmodels.tsa.stattools import adfuller +import joblib + + +class PROCESS(object): + + def __init__(self, data): + self.diff = 0 + self.data = data + + def adfuller_test(self, series, thresh=0.05, verbose=False): + """ADFuller test for Stationarity of given series and return True or False""" + r = adfuller(series, autolag='AIC') + output = {'test_statistic': round(r[0], 4), 'pvalue': round(r[1], 4), 'n_lags': round(r[2], 4), 'n_obs': r[3]} + p_value = output['pvalue'] + if p_value <= thresh: + return True + else: + return False + + def make_stationary(self): + """ call adfuller_test() to check for stationary + If the column is stationary, perform 1st differencing and return data""" + df = self.data.copy() + res_adf = [] + for name, column in df.iteritems(): + res_adf.append(self.adfuller_test(column)) # Perform ADF test + if not all(res_adf): + self.data = df.diff().dropna() + self.diff += 1 + + def invert_transformation(self, inp, forecast): + """Revert back the differencing to get the forecast to original scale.""" + if self.diff == 0: + return forecast + df = forecast.copy() + columns = inp.columns + for col in columns: + df[col] = inp[col].iloc[-1] + df[col].cumsum() + self.diff = 0 + return df + + def process(self): + """ Filter throughput parameters, call make_stationary() to check for Stationarity time series + """ + df = self.data.copy() + df = df[['pdcpBytesDl', 'pdcpBytesUl']] + self.data = df.loc[:, (df != 0).any(axis=0)] + self.make_stationary() # check for Stationarity and make the Time Series Stationary + + def valid(self): + df = self.data.copy() + df = df.loc[:, (df != 0).any(axis=0)] + if len(df) != 0 and df.shape[1] == 2: + return True + else: + return False + + +def train(db, cid): + """ + Read the input file(based on cell id received from the main program) + call process() to forecast the downlink and uplink of the input cell id + Make a VAR model, call the fit method with the desired lag order. + """ + db.read_data(meas='liveCell', cellid=cid) + md = PROCESS(db.data) + md.process() + if md.valid(): + model = VAR(md.data) # Make a VAR model + model_fit = model.fit(10) # call fit method with lag order + file_name = 'qp/'+cid.replace('/', '') + with open(file_name, 'wb') as f: + joblib.dump(model_fit, f) # Save the model with the cell id name diff --git a/rmr-version.yaml b/rmr-version.yaml index d7b94dd..02723fb 100644 --- a/rmr-version.yaml +++ b/rmr-version.yaml @@ -1,3 +1,3 @@ # CI script installs RMR from PackageCloud using this version --- -version: 4.0.5 +version: 4.7.4 diff --git a/setup.py b/setup.py index eb58e77..9160a86 100644 --- a/setup.py +++ b/setup.py @@ -17,11 +17,11 @@ from setuptools import setup, find_packages setup( name="qp", - version="0.0.2", + version="0.0.3", packages=find_packages(exclude=["tests.*", "tests"]), description="Quality-of-Service Predictor Xapp for Traffic Steering", url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-app/qp", - install_requires=["ricxappframe>=1.1.1,<2.0.0"], + install_requires=["ricxappframe>=1.1.1,<2.0.0", "joblib>=0.3.2", "statsmodels>=0.11.1", "mdclogpy<=1.1.1", "influxdb", "pandas"], entry_points={"console_scripts": ["run-qp.py=qp.main:start"]}, # adds a magical entrypoint for Docker license="Apache 2.0", data_files=[("", ["LICENSE.txt"])], diff --git a/tests/conftest.py b/tests/conftest.py index 79f648d..4c2e6c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ # ================================================================================== # Copyright (c) 2020 AT&T Intellectual Property. +# Copyright (c) 2021 HCL Technologies Limited. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,72 +21,9 @@ import pytest @pytest.fixture def qp_prediction(): - return { - "12345": { - "310-680-200-555001": [50000, 20000], - "310-680-200-555003": [20000, 10000], - "310-680-200-555002": [20000, 10000] - } - } + return {"Car-1": {"c6/B2": [12650, 12721], "c6/N77": [12663, 12739], "c1/B13": [12576, 12655], "c7/B13": [12649, 12697], "c5/B13": [12592, 12688]}} @pytest.fixture -def qpd_to_qp(): - return { - "PredictionUE": "12345", - "UEMeasurements": { - "ServingCellID": "310-680-200-555002", - "MeasTimestampUEPDCPBytes": "2020-03-18 02:23:18.220", - "MeasPeriodUEPDCPBytes": 20, - "UEPDCPBytesDL": 250000, - "UEPDCPBytesUL": 100000, - "MeasTimestampUEPRBUsage": "2020-03-18 02:23:18.220", - "MeasPeriodUEPRBUsage": 20, - "UEPRBUsageDL": 10, - "UEPRBUsageUL": 30, - }, - "CellMeasurements": [ - { - "CellID": "310-680-200-555001", - "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220", - "MeasPeriodPDCPBytes": 20, - "PDCPBytesDL": 2000000, - "PDCPBytesUL": 1200000, - "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220", - "MeasPeriodAvailPRB": 20, - "AvailPRBDL": 30, - "AvailPRBUL": 50, - "MeasTimestampRF": "2020-03-18 02:23:18.210", - "MeasPeriodRF": 40, - "RFMeasurements": {"RSRP": -90, "RSRQ": -13, "RSSINR": -2.5}, - }, - { - "CellID": "310-680-200-555003", - "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220", - "MeasPeriodPDCPBytes": 20, - "PDCPBytesDL": 1900000, - "PDCPBytesUL": 1000000, - "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220", - "MeasPeriodAvailPRB": 20, - "AvailPRBDL": 60, - "AvailPRBUL": 80, - "MeasTimestampRF": "2020-03-18 02:23:18.210", - "MeasPeriodRF": 40, - "RFMeasurements": {"RSRP": -140, "RSRQ": -17, "RSSINR": -6}, - }, - { - "CellID": "310-680-200-555002", - "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220", - "MeasPeriodPDCPBytes": 20, - "PDCPBytesDL": 800000, - "PDCPBytesUL": 400000, - "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220", - "MeasPeriodAvailPRB": 20, - "AvailPRBDL": 30, - "AvailPRBUL": 45, - "MeasTimestampRF": "2020-03-18 02:23:18.210", - "MeasPeriodRF": 40, - "RFMeasurements": {"RSRP": -115, "RSRQ": -16, "RSSINR": -5}, - }, - ], - } +def ts_to_qp(): + return '{"UEPredictionSet": ["Car-1"]}' diff --git a/tests/fixtures/test_local.rt b/tests/fixtures/test_local.rt index 6fcafb5..322b93e 100644 --- a/tests/fixtures/test_local.rt +++ b/tests/fixtures/test_local.rt @@ -1,6 +1,6 @@ # do NOT use localhost, seems unresolved on jenkins VMs newrt|start -mse| 30001 | -1 | 127.0.0.1:4560 # prediction request from QPD to QP -mse| 60001 | -1 | 127.0.0.1:4560 # other message from QPD to QP +mse| 30000 | -1 | 127.0.0.1:4560 # prediction request from TS to QP +mse| 60001 | -1 | 127.0.0.1:4560 # other message from TS to QP mse| 30002 | -1 | 127.0.0.1:4563 # prediction response from QP to TS newrt|end diff --git a/tests/test_qp.py b/tests/test_qp.py index cf3a960..9473bc2 100644 --- a/tests/test_qp.py +++ b/tests/test_qp.py @@ -19,7 +19,7 @@ from contextlib import suppress from qp import main from ricxappframe.xapp_frame import Xapp, RMRXapp -mock_qpd_xapp = None +mock_qp_xapp = None mock_ts_xapp = None """ @@ -43,7 +43,11 @@ def test_init_xapp(monkeypatch): main.start(thread=True) -def test_rmr_flow(monkeypatch, qpd_to_qp, qp_prediction): +def test_predict(monkeypatch, ts_to_qp): + main.predict(ts_to_qp) + + +def test_rmr_flow(monkeypatch, ts_to_qp, qp_prediction): """ this flow mocks out the xapps on both sides of QP. It first stands up a mock ts, then it starts up a mock qp-driver @@ -68,25 +72,20 @@ def test_rmr_flow(monkeypatch, qpd_to_qp, qp_prediction): time.sleep(1) - # define a mock qp driver xapp that sends a message to QP under test - def mock_qpd_entry(self): + # define a mock qp xapp that listens on 4560 + def mock_qp_entry(self): # good traffic steering request - val = json.dumps(qpd_to_qp).encode() - self.rmr_send(val, 30001) + val = json.dumps(ts_to_qp).encode() + self.rmr_send(val, 30000) # should trigger the default handler and do nothing val = json.dumps({"test send 60001": 2}).encode() self.rmr_send(val, 60001) - global mock_qpd_xapp - mock_qpd_xapp = Xapp(entrypoint=mock_qpd_entry, rmr_port=4666, use_fake_sdl=True) - mock_qpd_xapp.run() # this will return since entry isn't a loop - - time.sleep(1) - - assert main.get_stats() == {"PredictRequests": 1} - assert expected_result == qp_prediction + global mock_qp_xapp + mock_qp_xapp = Xapp(entrypoint=mock_qp_entry, rmr_port=4560, use_fake_sdl=True) + mock_qp_xapp.run() # this will return since entry isn't a loop def teardown_module(): @@ -99,6 +98,6 @@ def teardown_module(): with suppress(Exception): mock_ts_xapp.stop() with suppress(Exception): - mock_qpd_xapp.stop() + mock_qp_xapp.stop() with suppress(Exception): main.stop() diff --git a/tox.ini b/tox.ini index 18bedff..c6e9565 100644 --- a/tox.ini +++ b/tox.ini @@ -18,25 +18,28 @@ envlist = code,flake8,docs,docs-linkcheck minversion = 2.0 [testenv:code] -basepython = python3.8 +basepython = python3.7 deps= pytest coverage pytest-cov setenv = LD_LIBRARY_PATH = /usr/local/lib/:/usr/local/lib64 + PYTHONPATH = {toxinidir}:qp:/usr/lib/python3.7/site-packages/ RMR_SEED_RT = tests/fixtures/test_local.rt RMR_ASYNC_CONN = 0 USE_FAKE_SDL = 1 commands = - pytest -v --cov qp --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=70 + pytest -v --cov qp --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=60 coverage xml -i [testenv:flake8] -basepython = python3.8 +basepython = python3.7 skip_install = true deps = flake8 +setenv = + PYTHONPATH = {toxinidir}:qp:/usr/lib/python3.7/site-packages/ commands = flake8 setup.py qp tests [flake8] @@ -51,7 +54,7 @@ commands = sh -c 'pip freeze > requirements.txt' [testenv:docs] whitelist_externals = echo skipsdist = true -basepython = python3.8 +basepython = python3.7 deps = sphinx sphinx-rtd-theme @@ -64,7 +67,7 @@ commands = [testenv:docs-linkcheck] skipsdist = true -basepython = python3.8 +basepython = python3.7 deps = sphinx sphinx-rtd-theme sphinxcontrib-httpdomain diff --git a/xapp-descriptor/config.json b/xapp-descriptor/config.json index 3a5084d..10c2d6a 100644 --- a/xapp-descriptor/config.json +++ b/xapp-descriptor/config.json @@ -1,6 +1,6 @@ { "xapp_name": "qp", - "version": "0.0.2", + "version": "0.0.3", "containers": [ { "name": "qp", @@ -17,7 +17,7 @@ "name": "rmr-data", "container": "qp", "port": 4560, - "rxMessages": ["TS_QOE_PRED_REQ"], + "rxMessages": ["TS_UE_LIST"], "txMessages": ["TS_QOE_PREDICTION"], "policies": [], "description": "rmr receive data port for qp" @@ -34,7 +34,7 @@ "protPort": "tcp:4560", "maxSize": 2072, "numWorkers": 1, - "rxMessages": ["TS_QOE_PRED_REQ"], + "rxMessages": ["TS_UE_LIST"], "txMessages": ["TS_QOE_PREDICTION"], "policies": [] }