| # ================================================================================== |
| # Copyright (c) 2020 AT&T Intellectual Property. |
| # Copyright (c) 2020 Nokia |
| # |
| # 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. |
| # ================================================================================== |
| """ |
| Provides classes and methods to define and send metrics as RMR messages to a |
| central collector. Message destination(s) are controlled by the RMR routing table. |
| Message contents must comply with the JSON schema in file metric-schema.json. |
| """ |
| |
| from ctypes import c_void_p |
| import json |
| import time |
| from mdclogpy import Logger |
| from ricxappframe.rmr import rmr |
| from ricxappframe.metric.exceptions import EmptyReport |
| |
| ############## |
| # PRIVATE API |
| ############## |
| |
| mdc_logger = Logger(name=__name__) |
| RETRIES = 4 |
| |
| ############## |
| # PUBLIC API |
| ############## |
| |
| # constants |
| RIC_METRICS = 120 # message type |
| |
| # Publish dict keys as constants for convenience of client code. |
| KEY_REPORTER = "reporter" |
| KEY_GENERATOR = "generator" |
| KEY_TIMESTAMP = "timestamp" |
| KEY_DATA = "data" |
| KEY_DATA_ID = "id" |
| KEY_DATA_TYPE = "type" |
| KEY_DATA_VALUE = "value" |
| |
| |
| class MetricData(dict): |
| """ |
| A single measurement with ID, value and (optionally) type. |
| """ |
| def __init__(self, |
| id: str, |
| value: str, |
| type: str = None): |
| """ |
| Creates a data item with the specified members. |
| |
| Parameters |
| ---------- |
| id: str (required) |
| Metric ID |
| |
| value: str (required) |
| Metric value; e.g., 1. |
| |
| type: str (optional) |
| Metric type; e.g., "counter". |
| """ |
| dict.__init__(self) |
| self[KEY_DATA_ID] = id |
| self[KEY_DATA_VALUE] = value |
| self[KEY_DATA_TYPE] = type |
| |
| |
| class MetricsReport(dict): |
| """ |
| A list of metric data items with identifying information. |
| At init sets the timestamp to the current system time in |
| milliseconds since the Epoch. |
| |
| Parameters |
| ---------- |
| reporter: str (optional) |
| The system that reports the data |
| |
| generator: str (optional) |
| The generator that reports the data |
| |
| items: List of MetricData (optional) |
| The data items for the report |
| """ |
| def __init__(self, |
| reporter: str = None, |
| generator: str = None, |
| items: list = None): |
| """ |
| Creates an object with the specified details and items. |
| """ |
| dict.__init__(self) |
| self[KEY_REPORTER] = reporter |
| self[KEY_GENERATOR] = generator |
| self[KEY_TIMESTAMP] = int(round(time.time() * 1000)) |
| self[KEY_DATA] = [] if items is None else items |
| |
| def add_metric(self, |
| data: MetricData): |
| """ |
| Convenience method that adds a data item to the report. |
| |
| Parameters |
| ---------- |
| data: MetricData |
| A measurement to add to the report |
| """ |
| self[KEY_DATA].append(data) |
| |
| |
| class MetricsManager: |
| """ |
| Provides an API for an Xapp to build and send measurement reports |
| by sending messages via RMR routing to a metrics adapter/collector. |
| |
| Parameters |
| ---------- |
| vctx: ctypes c_void_p (required) |
| Pointer to RMR context obtained by initializing RMR. |
| The context is used to allocate space and send messages. |
| |
| reporter: str (optional) |
| The source of the measurement; e.g., a temperature probe |
| |
| generator: str (optional) |
| The system that collected and sent the measurement; e.g., an environment monitor. |
| """ |
| def __init__(self, |
| vctx: c_void_p, |
| reporter: str = None, |
| generator: str = None): |
| """ |
| Creates a metrics manager. |
| """ |
| self.vctx = vctx |
| self.reporter = reporter |
| self.generator = generator |
| |
| def create_report(self, |
| items: list = None): |
| """ |
| Creates a MetricsReport object with the specified metrics data items. |
| |
| Parameters |
| ---------- |
| items: list (optional) |
| List of MetricData items |
| |
| Returns |
| ------- |
| MetricsReport |
| """ |
| return MetricsReport(self.reporter, self.generator, items) |
| |
| def send_report(self, msg: MetricsReport): |
| """ |
| Serializes the MetricsReport dict to JSON and sends the result via RMR. |
| Raises an exception if the report has no MetricsData items. |
| |
| Parameters |
| ---------- |
| msg: MetricsReport (required) |
| Dictionary with measurement data to encode and send |
| |
| Returns |
| ------- |
| bool |
| True if the send succeeded (possibly with retries), False otherwise |
| """ |
| if KEY_DATA not in msg or len(msg[KEY_DATA]) == 0: |
| raise EmptyReport |
| payload = json.dumps(msg).encode() |
| mdc_logger.debug("send_report: payload is {}".format(payload)) |
| sbuf = rmr.rmr_alloc_msg(vctx=self.vctx, size=len(payload), payload=payload, |
| mtype=RIC_METRICS, gen_transaction_id=True) |
| |
| for _ in range(0, RETRIES): |
| sbuf = rmr.rmr_send_msg(self.vctx, sbuf) |
| post_send_summary = rmr.message_summary(sbuf) |
| mdc_logger.debug("send_report: try {0} result is {1}".format(_, post_send_summary[rmr.RMR_MS_MSG_STATE])) |
| # stop trying if RMR does not indicate retry |
| if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_ERR_RETRY: |
| break |
| |
| rmr.rmr_free_msg(sbuf) |
| if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_OK: |
| mdc_logger.warning("send_report: failed after {} retries".format(RETRIES)) |
| return False |
| |
| return True |
| |
| def send_metrics(self, items: list): |
| """ |
| Convenience method that creates a MetricsReport object with the specified |
| metrics data items and sends it to the metrics adapter/collector. |
| |
| Parameters |
| ---------- |
| items: list (required) |
| List of MetricData items |
| |
| Returns |
| ------- |
| bool |
| True if the send succeeded (possibly with retries), False otherwise |
| """ |
| return self.send_report(self.create_report(items)) |