text: # Copyright 2017-present Open Networking Foundation
  #
  # 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 arrow
  from voltha.protos.device_pb2 import PmConfig, PmGroupConfig
  from voltha.protos.events_pb2 import KpiEvent2, MetricInformation, MetricMetaData, KpiEventType
  from voltha.extensions.kpi.adapter_pm_metrics import AdapterPmMetrics
  from voltha.extensions.omci.omci_entities import \
      EthernetFrameUpstreamPerformanceMonitoringHistoryData, \
      EthernetFrameDownstreamPerformanceMonitoringHistoryData, \
      EthernetFrameExtendedPerformanceMonitoring, \
      EthernetFrameExtendedPerformanceMonitoring64Bit, \
      EthernetPMMonitoringHistoryData, FecPerformanceMonitoringHistoryData, \
      GemPortNetworkCtpMonitoringHistoryData, XgPonTcPerformanceMonitoringHistoryData, \
      XgPonDownstreamPerformanceMonitoringHistoryData, \
      XgPonUpstreamPerformanceMonitoringHistoryData
  
  
  class OnuPmIntervalMetrics(AdapterPmMetrics):
      """
      ONU OMCI PM Interval metrics
  
      These differ from other PM Metrics as they are collected and generated as a
      result of receipt of OMCI get responses on various PM History MEs.  They are
      also always managed as a group with a fixed frequency of 15 minutes.
      """
      ME_ID_INFO = {
          EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id: 'Ethernet_Bridge_Port_History',
          EthernetFrameDownstreamPerformanceMonitoringHistoryData.class_id: 'Ethernet_Bridge_Port_History',
          EthernetFrameExtendedPerformanceMonitoring.class_id: 'Ethernet_Bridge_Port_History',
          EthernetFrameExtendedPerformanceMonitoring64Bit.class_id: 'Ethernet_Bridge_Port_History',
          EthernetPMMonitoringHistoryData.class_id: 'Ethernet_UNI_History',
          FecPerformanceMonitoringHistoryData.class_id: 'FEC_History',
          GemPortNetworkCtpMonitoringHistoryData.class_id: 'GEM_Port_History',
          XgPonTcPerformanceMonitoringHistoryData.class_id: 'xgPON_TC_History',
          XgPonDownstreamPerformanceMonitoringHistoryData.class_id: 'xgPON_Downstream_History',
          XgPonUpstreamPerformanceMonitoringHistoryData.class_id: 'xgPON_Upstream_History'
      }
      ETHERNET_BRIDGE_HISTORY_ENABLED = True
      ETHERNET_UNI_HISTORY_ENABLED = True
      FEC_HISTORY_ENABLED = True
      GEM_PORT_HISTORY_ENABLED = False
      TRANS_CONV_HISTORY_ENABLED = False
      XGPON_DOWNSTREAM_HISTORY = False
      XGPON_UPSTREAM_HISTORY = False
  
      def __init__(self, adapter_agent, device_id, logical_device_id, **kwargs):
          super(OnuPmIntervalMetrics, self).__init__(adapter_agent, device_id, logical_device_id,
                                                     grouped=True, freq_override=False,
                                                     **kwargs)
          ethernet_bridge_history = {
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
              ('parent_class_id', PmConfig.CONTEXT),
              ('parent_entity_id', PmConfig.CONTEXT),
              ('upstream', PmConfig.CONTEXT),
  
              ("drop_events", PmConfig.COUNTER),
              ("octets", PmConfig.COUNTER),
              ("packets", PmConfig.COUNTER),
              ("broadcast_packets", PmConfig.COUNTER),
              ("multicast_packets", PmConfig.COUNTER),
              ("crc_errored_packets", PmConfig.COUNTER),
              ("undersize_packets", PmConfig.COUNTER),
              ("oversize_packets", PmConfig.COUNTER),
              ("64_octets", PmConfig.COUNTER),
              ("65_to_127_octets", PmConfig.COUNTER),
              ("128_to_255_octets", PmConfig.COUNTER),
              ("256_to_511_octets", PmConfig.COUNTER),
              ("512_to_1023_octets", PmConfig.COUNTER),
              ("1024_to_1518_octets", PmConfig.COUNTER)
          }
          self._ethernet_bridge_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                                  for (m, t) in ethernet_bridge_history}
  
          ethernet_uni_history = {   # Ethernet History Data (Class ID 24)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("fcs_errors", PmConfig.COUNTER),
              ("excessive_collision_counter", PmConfig.COUNTER),
              ("late_collision_counter", PmConfig.COUNTER),
              ("frames_too_long", PmConfig.COUNTER),
              ("buffer_overflows_on_rx", PmConfig.COUNTER),
              ("buffer_overflows_on_tx", PmConfig.COUNTER),
              ("single_collision_frame_counter", PmConfig.COUNTER),
              ("multiple_collisions_frame_counter", PmConfig.COUNTER),
              ("sqe_counter", PmConfig.COUNTER),
              ("deferred_tx_counter", PmConfig.COUNTER),
              ("internal_mac_tx_error_counter", PmConfig.COUNTER),
              ("carrier_sense_error_counter", PmConfig.COUNTER),
              ("alignment_error_counter", PmConfig.COUNTER),
              ("internal_mac_rx_error_counter", PmConfig.COUNTER),
          }
          self._ethernet_uni_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                               for (m, t) in ethernet_uni_history}
  
          fec_history = {   # FEC History Data (Class ID 312)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("corrected_bytes", PmConfig.COUNTER),
              ("corrected_code_words", PmConfig.COUNTER),
              ("uncorrectable_code_words", PmConfig.COUNTER),
              ("total_code_words", PmConfig.COUNTER),
              ("fec_seconds", PmConfig.COUNTER),
          }
          self._fec_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                      for (m, t) in fec_history}
  
          gem_port_history = {  # GEM Port Network CTP History Data (Class ID 341)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("transmitted_gem_frames", PmConfig.COUNTER),
              ("received_gem_frames", PmConfig.COUNTER),
              ("received_payload_bytes", PmConfig.COUNTER),
              ("transmitted_payload_bytes", PmConfig.COUNTER),
              ("encryption_key_errors", PmConfig.COUNTER),
          }
          self._gem_port_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                           for (m, t) in gem_port_history}
  
          xgpon_tc_history = {  # XgPon TC History Data (Class ID 344)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("psbd_hec_error_count", PmConfig.COUNTER),
              ("xgtc_hec_error_count", PmConfig.COUNTER),
              ("unknown_profile_count", PmConfig.COUNTER),
              ("transmitted_xgem_frames", PmConfig.COUNTER),
              ("fragment_xgem_frames", PmConfig.COUNTER),
              ("xgem_hec_lost_words_count", PmConfig.COUNTER),
              ("xgem_key_errors", PmConfig.COUNTER),
              ("xgem_hec_error_count", PmConfig.COUNTER),
          }
          self._xgpon_tc_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                           for (m, t) in xgpon_tc_history}
  
          xgpon_downstream_history = {  # XgPon Downstream History Data (Class ID 345)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("ploam_mic_error_count", PmConfig.COUNTER),
              ("downstream_ploam_messages_count", PmConfig.COUNTER),
              ("profile_messages_received", PmConfig.COUNTER),
              ("ranging_time_messages_received", PmConfig.COUNTER),
              ("deactivate_onu_id_messages_received", PmConfig.COUNTER),
              ("disable_serial_number_messages_received", PmConfig.COUNTER),
              ("request_registration_messages_received", PmConfig.COUNTER),
              ("assign_alloc_id_messages_received", PmConfig.COUNTER),
              ("key_control_messages_received", PmConfig.COUNTER),
              ("sleep_allow_messages_received", PmConfig.COUNTER),
              ("baseline_omci_messages_received_count", PmConfig.COUNTER),
              ("extended_omci_messages_received_count", PmConfig.COUNTER),
              ("assign_onu_id_messages_received", PmConfig.COUNTER),
              ("omci_mic_error_count", PmConfig.COUNTER),
          }
          self._xgpon_downstream_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                                   for (m, t) in xgpon_downstream_history}
  
          xgpon_upstream_history = {  # XgPon Upstream History Data (Class ID 346)
              ('class_id', PmConfig.CONTEXT),
              ('entity_id', PmConfig.CONTEXT),
              ("interval_end_time", PmConfig.CONTEXT),
  
              ("upstream_ploam_message_count", PmConfig.COUNTER),
              ("serial_number_onu_message_count", PmConfig.COUNTER),
              ("registration_message_count", PmConfig.COUNTER),
              ("key_report_message_count", PmConfig.COUNTER),
              ("acknowledge_message_count", PmConfig.COUNTER),
              ("sleep_request_message_count", PmConfig.COUNTER),
          }
          self._xgpon_upstream_history_config = {m: PmConfig(name=m, type=t, enabled=True)
                                                 for (m, t) in xgpon_upstream_history}
          self._configs = {
              EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id: self._ethernet_bridge_history_config,
              EthernetFrameDownstreamPerformanceMonitoringHistoryData.class_id: self._ethernet_bridge_history_config,
              EthernetFrameExtendedPerformanceMonitoring.class_id: self._ethernet_bridge_history_config,
              EthernetFrameExtendedPerformanceMonitoring64Bit.class_id: self._ethernet_bridge_history_config,
              EthernetPMMonitoringHistoryData.class_id: self._ethernet_uni_history_config,
              FecPerformanceMonitoringHistoryData.class_id: self._fec_history_config,
              GemPortNetworkCtpMonitoringHistoryData.class_id: self._gem_port_history_config,
              XgPonTcPerformanceMonitoringHistoryData.class_id: self._xgpon_tc_history_config,
              XgPonDownstreamPerformanceMonitoringHistoryData.class_id: self._xgpon_downstream_history_config,
              XgPonUpstreamPerformanceMonitoringHistoryData.class_id: self._xgpon_upstream_history_config
          }
  
      def update(self, pm_config):
          """
          Update the PM Configuration.
  
          For historical PM Intervals, the frequency always zero since the actual collection
          and publishing is provided by the OpenOMCI library
  
          :param pm_config:
          """
          self.log.debug('update')
  
          try:
              if pm_config.grouped:
                  for group in pm_config.groups:
                      group_config = self.pm_group_metrics.get(group.group_name)
                      if group_config is not None and group_config.enabled != group.enabled:
                          group_config.enabled = group.enabled
                          # TODO: For OMCI PM Metrics, tie this into add/remove of the PM Interval ME itself
              else:
                  msg = 'There are on independent OMCI Interval metrics, only group metrics at this time'
                  raise NotImplemented(msg)
  
          except Exception as e:
              self.log.exception('update-failure', e=e)
              raise
  
      def make_proto(self, pm_config=None):
          """
          From the PM Configurations defined in this class's initializer, create
          the PMConfigs protobuf message that defines our PM configuration and
          data.
  
          All ONU PM Interval metrics are grouped metrics that are generated autonmouslly
          from the OpenOMCI Performance Intervals state machine.
  
          :param pm_config (PMConfigs) PM Configuration message to add OpenOMCI config items too
          :return: (PmConfigs) PM Configuration Protobuf message
          """
          assert pm_config is not None
  
          pm_ethernet_bridge_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id],
                                                     group_freq=0,
                                                     enabled=OnuPmIntervalMetrics.ETHERNET_BRIDGE_HISTORY_ENABLED)
          self.pm_group_metrics[pm_ethernet_bridge_history.group_name] = pm_ethernet_bridge_history
  
          for m in sorted(self._ethernet_bridge_history_config):
              pm = self._ethernet_bridge_history_config[m]
              pm_ethernet_bridge_history.metrics.extend([PmConfig(name=pm.name,
                                                                  type=pm.type,
                                                                  enabled=pm.enabled)])
  
          pm_ethernet_uni_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[EthernetPMMonitoringHistoryData.class_id],
                                                  group_freq=0,
                                                  enabled=OnuPmIntervalMetrics.ETHERNET_UNI_HISTORY_ENABLED)
          self.pm_group_metrics[pm_ethernet_uni_history.group_name] = pm_ethernet_uni_history
  
          for m in sorted(self._ethernet_uni_history_config):
              pm = self._ethernet_uni_history_config[m]
              pm_ethernet_uni_history.metrics.extend([PmConfig(name=pm.name,
                                                               type=pm.type,
                                                               enabled=pm.enabled)])
  
          pm_fec_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[FecPerformanceMonitoringHistoryData.class_id],
                                         group_freq=0,
                                         enabled=OnuPmIntervalMetrics.FEC_HISTORY_ENABLED)
          self.pm_group_metrics[pm_fec_history.group_name] = pm_fec_history
  
          for m in sorted(self._fec_history_config):
              pm = self._fec_history_config[m]
              pm_fec_history.metrics.extend([PmConfig(name=pm.name,
                                                      type=pm.type,
                                                      enabled=pm.enabled)])
  
          pm_gem_port_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[GemPortNetworkCtpMonitoringHistoryData.class_id],
                                              group_freq=0,
                                              enabled=OnuPmIntervalMetrics.GEM_PORT_HISTORY_ENABLED)
          self.pm_group_metrics[pm_gem_port_history.group_name] = pm_gem_port_history
  
          for m in sorted(self._gem_port_history_config):
              pm = self._gem_port_history_config[m]
              pm_gem_port_history.metrics.extend([PmConfig(name=pm.name,
                                                           type=pm.type,
                                                           enabled=pm.enabled)])
  
          pm_xgpon_tc_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonTcPerformanceMonitoringHistoryData.class_id],
                                              group_freq=0,
                                              enabled=OnuPmIntervalMetrics.TRANS_CONV_HISTORY_ENABLED)
          self.pm_group_metrics[pm_xgpon_tc_history.group_name] = pm_xgpon_tc_history
  
          for m in sorted(self._xgpon_tc_history_config):
              pm = self._xgpon_tc_history_config[m]
              pm_xgpon_tc_history.metrics.extend([PmConfig(name=pm.name,
                                                           type=pm.type,
                                                           enabled=pm.enabled)])
  
          pm_xgpon_downstream_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonDownstreamPerformanceMonitoringHistoryData.class_id],
                                                      group_freq=0,
                                                      enabled=OnuPmIntervalMetrics.XGPON_DOWNSTREAM_HISTORY)
          self.pm_group_metrics[pm_xgpon_downstream_history.group_name] = pm_xgpon_downstream_history
  
          for m in sorted(self._xgpon_downstream_history_config):
              pm = self._xgpon_downstream_history_config[m]
              pm_xgpon_downstream_history.metrics.extend([PmConfig(name=pm.name,
                                                                   type=pm.type,
                                                                   enabled=pm.enabled)])
  
          pm_xgpon_upstream_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonUpstreamPerformanceMonitoringHistoryData.class_id],
                                                    group_freq=0,
                                                    enabled=OnuPmIntervalMetrics.XGPON_UPSTREAM_HISTORY)
          self.pm_group_metrics[pm_xgpon_upstream_history.group_name] = pm_xgpon_upstream_history
  
          for m in sorted(self._xgpon_upstream_history_config):
              pm = self._xgpon_upstream_history_config[m]
              pm_xgpon_upstream_history.metrics.extend([PmConfig(name=pm.name,
                                                                 type=pm.type,
                                                                 enabled=pm.enabled)])
  
          pm_config.groups.extend([stats for stats in self.pm_group_metrics.itervalues()])
  
          return pm_config
  
      def publish_metrics(self, interval_data):
          """
          Collect the metrics for this ONU PM Interval
  
          :param interval_data: (dict) PM interval dictionary with structure of
                      {
                          'class_id': self._class_id,
                          'entity_id': self._entity_id,
                          'me_name': self._entity.__name__,   # Mostly for debugging...
                          'interval_utc_time': None,
                          # Counters added here as they are retrieved
                      }
  
          :return: (dict) Key/Value of metric data
          """
          self.log.debug('publish-metrics')
  
          try:
              # Locate config
              now = arrow.utcnow()
              class_id = interval_data['class_id']
              config = self._configs.get(class_id)
              group = self.pm_group_metrics.get(OnuPmIntervalMetrics.ME_ID_INFO.get(class_id, ''))
  
              if config is not None and group is not None and group.enabled:
                  # Extract only the metrics we need to publish
                  metrics = dict()
                  context = {
                      'interval_start_time': str(now.replace(minute=int(now.minute / 15) * 15,
                                                             second=0,
                                                             microsecond=0).timestamp)
                  }
                  for metric, config_item in config.items():
                      if config_item.type == PmConfig.CONTEXT and metric in interval_data:
                          context[metric] = str(interval_data[metric])
  
                      elif (config_item.type in (PmConfig.COUNTER, PmConfig.GAUGE, PmConfig.STATE) and
                            metric in interval_data and
                            config_item.enabled):
                          metrics[metric] = interval_data[metric]
  
                  if len(metrics):
                      metadata = MetricMetaData(title=group.group_name,
                                                ts=now.float_timestamp,
                                                logical_device_id=self.logical_device_id,
                                                serial_no=self.serial_number,
                                                device_id=self.device_id,
                                                context=context)
                      slice_data = [MetricInformation(metadata=metadata, metrics=metrics)]
  
                      kpi_event = KpiEvent2(type=KpiEventType.slice,
                                            ts=now.float_timestamp,
                                            slice_data=slice_data)
                      self.adapter_agent.submit_kpis(kpi_event)
  
          except Exception as e:
              self.log.exception('failed-to-submit-kpis', e=e)
  