#!/usr/bin/env pmpython # Copyright (C) 2022 Red Hat. # Copyright (C) 2020 Ashwin Nayak # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # pylint: disable=bad-whitespace, line-too-long, too-many-return-statements # pylint: disable=broad-except, too-many-branches, too-many-statements, inconsistent-return-statements # pylint: disable=no-name-in-module, too-many-instance-attributes, no-self-use """ Performance Metrics Domain Agent exporting openvswitch metrics. """ import os import json import subprocess from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaInstid from pcp.pmapi import pmUnits from pcp.pmapi import pmContext as PCP from cpmapi import PM_TYPE_STRING, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_64 from cpmapi import PM_SEM_COUNTER, PM_SEM_DISCRETE, PM_SEM_INSTANT from cpmapi import PM_COUNT_ONE, PM_SPACE_BYTE, PM_TIME_SEC, PM_SPACE_KBYTE from cpmapi import PM_ERR_APPVERSION, PM_ERR_PMID from cpmda import PMDA_FETCH_NOVALUES import cpmapi as c_api PMDA_DIR = PCP.pmGetConfig('PCP_PMDAS_DIR') class OpenvswitchPMDA(PMDA): """ PCP openvswitch PMDA """ def __init__(self, name, domain): """ (Constructor) Initialisation - register metrics, callbacks, drop privileges """ PMDA.__init__(self, name, domain) self.verbose = False # True for debugging diagnostics self.switch_info_json = {} self.port_info_json = {} self.flow_json = {} self.interface_info_json = {} self.coverage_json = {} self.switch_names = [] self.port_info_names = [] self.flow_names = [] self.interface_names = [] self.coverage_names = [] self.get_switch_info_json() self.get_port_info_json() self.get_flow_json() self.get_interface_info_json() self.coverage_metrics = [] self.get_coverage_json() self.connect_pmcd() self.switch_indom = self.indom(0) self.switch_instances() self.switch_cluster = 0 self.switch_metrics = [ # Name - type - semantics - units - help [ 'switch.uuid', PM_TYPE_STRING, PM_SEM_DISCRETE, pmUnits(), 'switch id'], # 0 [ 'switch.autoattach', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set:Shortest Path Bridging (SPB) network to automatically attach network devices to individual services in a SPB network'], # 1 [ 'switch.controller', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set:controller'], # 2 [ 'switch.datapath_id', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'datapath_id'], # 3 [ 'switch.datapath_type', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'datapath_type'], # 4 [ 'switch.datapath_version', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'datapath_version'], #5 [ 'switch.external_ids', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map:These values are intended to identify entities external to Open vSwitch with which switch is associated, e.g. the switchs identifier in avirtualization management platform. The Open vSwitch databaseschema specifies well-known key values, but key and value are otherwise arbitrary strings'], # 6 [ 'switch.fail_mode', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set:failmode'], # 7 [ 'switch.flood_vlans', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: flood_vlans'], # 8 [ 'switch.flow_tables', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: flow tables'], # 9 [ 'switch.ipfix', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'The IPFIX Protocol Specification has been designed to be transport protocol independent.'], # 10 [ 'switch.mcast_snooping_enable', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'multicast snooping status'], # 11 [ 'switch.mirrors', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set:packets mirrored'], # 12 [ 'switch.netflow', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'NetFlow is a network protocol developed by Cisco for collecting IP traffic information and monitoring network traffic. By analyzing flow data, a picture of network traffic flow and volume can be built.'], # 14 [ 'switch.other_config', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'other configs to switch'], # 15 [ 'switch.ports', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set:list of ports attached to the switch'], # 16 [ 'switch.protocols', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: protocols ex:openflow12'], # 17 [ 'switch.rstp_enable', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'rapid spanning tree protocol enabled status'], # 18 [ 'switch.rstp_status', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'rapid spanning tree protocol status'], # 19 [ 'switch.sflow', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'sFlow is a sampling technology that meets the key requirements for a network traffic monitoring solution'], # 20 [ 'switch.status', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'status'], # 21 [ 'switch.stp_enable', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'spanning tree protocol'] # 22 ] for item, metric in enumerate(self.switch_metrics): self.add_metric(name + '.' + metric[0], pmdaMetric(self.pmid(self.switch_cluster, item), metric[1], self.switch_indom, metric[2], metric[3]), metric[4], metric[4]) self.port_info_indom = self.indom(1) self.port_info_instances() self.port_info_cluster = 1 self.port_info_metrics = [ # Name - type - semantics - units - help [ 'port_info.rx.pkts', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx:recieved packets'], # 0 [ 'port_info.rx.bytes', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'recieved bytes'], # 1 [ 'port_info.rx.drop', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'recieved packets dropped'], # 2 [ 'port_info.rx.errs', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'shows a total number of packets received with error.'], # 3 [ 'port_info.rx.frame', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'is a number of misaligned frames, i.e. frames with length not divisible by 8.'], # 4 [ 'port_info.rx.over', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'is a number of received packets that experienced fifo overruns, caused by rate at which a buffer gets full and kernel isn’t able to empty it.'], #5 [ 'port_info.rx.crc', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'recieved crc'], # 6 [ 'port_info.tx.pkts', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'total transmitted packets'], # 7 [ 'port_info.tx.bytes', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'total transmitted bytes'], # 8 [ 'port_info.tx.drop', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'transmitted packets dropped'], # 9 [ 'port_info.tx.errs', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'shows a total number of packets transmitted with error.'], # 10 [ 'port_info.tx.coll', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'is a number of transmitted packets that experienced Ethernet collisions.'] # 11 ] for item, metric in enumerate(self.port_info_metrics): self.add_metric(name + '.' + metric[0], pmdaMetric(self.pmid(self.port_info_cluster, item), metric[1], self.port_info_indom, metric[2], metric[3]), metric[4], metric[4]) self.flow_indom = self.indom(2) self.flow_instances() self.flow_cluster = 2 self.flow_metrics = [ # Name - type - semantics - units - help [ 'flow.cookie', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'ovs flow cookie'], # 0 [ 'flow.duration', PM_TYPE_FLOAT, PM_SEM_INSTANT, pmUnits(0,1,0,0,PM_TIME_SEC,0), 'duration the flow has been active'], # 1 [ 'flow.table', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs-flow tables'], # 2 [ 'flow.n_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'number of packets with specific flow'], # 3 [ 'flow.n_bytes', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'packets of specific flow in bytes'], # 4 [ 'flow.actions', PM_TYPE_STRING, PM_SEM_DISCRETE, pmUnits(), 'action to be taken for incoming flow packets'], #5 ] for item, metric in enumerate(self.flow_metrics): self.add_metric(name + '.' + metric[0], pmdaMetric(self.pmid(self.flow_cluster, item), metric[1], self.flow_indom, metric[2], metric[3]), metric[4], metric[4]) self.interface_indom = self.indom(3) self.interface_instances() self.interface_cluster = 3 self.interface_metrics = [ # Name - type - semantics - units - help [ 'interface.uuid', PM_TYPE_STRING, PM_SEM_DISCRETE, pmUnits(), 'interface id'], # 0 [ 'interface.admin_state', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'admin_state'], # 1 [ 'interface.bfd', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: bfd'], # 2 [ 'interface.bfd_status', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: bfd_status'], # 3 [ 'interface.cfm_fault', PM_TYPE_STRING, PM_SEM_DISCRETE, pmUnits(), 'set: cfm_fault'], # 4 [ 'interface.cfm_fault_status', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: cfm_fault_status'], #5 [ 'interface.cfm_flap_count', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: cfm_flap_count '], # 6 [ 'interface.cfm_health', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: cfm_health'], # 7 [ 'interface.cfm_mpid', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: cfm_mpid'], # 8 [ 'interface.cfm_remote_mpids', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: cfm_remote_mpids'], # 9 [ 'interface.cfm_remote_opstate', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: cfm_remote_opstate'], # 10 [ 'interface.duplex', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'duplex'], # 11 [ 'interface.error', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: error'], # 12 [ 'interface.external_ids', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: external_ids'], # 13 [ 'interface.ifindex', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: ifindex'], # 14 [ 'interface.ingress_policing_burst', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'ingress_policing_burst'], # 15 [ 'interface.ingress_policing_kpkts_burst', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'ingress_policing_kpkts_burst'], # 16 [ 'interface.ingress_policing_kpkts_rate', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'ingress_policing_kpkts_rate'], # 17 [ 'interface.ingress_policing_rate', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), 'ingress_policing_rate'], # 18 [ 'interface.lacp_current', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: lacp_current'], # 19 [ 'interface.link_resets', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: link_resets'], # 20 [ 'interface.link_speed', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0), 'set: link_speed'], # 21 [ 'interface.link_state', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: link_state'], # 22 [ 'interface.lldp', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: lldp'], # 23 [ 'interface.mac', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: mac'], # 24 [ 'interface.mac_in_use', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'set: mac_in_use'], # 25 [ 'interface.mtu', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(), 'set: mtu'], # 26 [ 'interface.mtu_request', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(), 'set: mtu_request'], # 27 [ 'interface.ofport', PM_TYPE_64, PM_SEM_DISCRETE, pmUnits(), 'ofport'], # 29 [ 'interface.ofport_request', PM_TYPE_U64, PM_SEM_DISCRETE, pmUnits(), 'set: ofport_request'], # 30 [ 'interface.options', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: options'], # 31 [ 'interface.other_config', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: other_config'], # 32 [ 'interface.statistics', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: statistics'], # 33 [ 'interface.stats.ovs_rx_qos_drops', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_rx_qos_drops'], # 33[0] submetric of statistics column [ 'interface.stats.ovs_tx_failure_drops', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_tx_failure_drops'], # 33[1] [ 'interface.stats.ovs_tx_invalid_hwol_drops', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_tx_invalid_hwol_drops'], # 33[2] [ 'interface.stats.ovs_tx_mtu_exceeded_drops', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_tx_mtu_exceeded_drops'], # 33[3] [ 'interface.stats.ovs_tx_qos_drops', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_tx_qos_drops'], # 33[4] [ 'interface.stats.ovs_tx_retries', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'ovs_tx_retries'], # 33[5] [ 'interface.stats.rx_1024_to_1522_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_1024_to_1522_packets'], # 33[6] [ 'interface.stats.rx_128_to_255_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_128_to_255_packets'], # 33[7] [ 'interface.stats.rx_1523_to_max_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_1523_to_max_packets'], # 33[8] [ 'interface.stats.rx_1_to_64_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_1_to_64_packets'], # 33[9] [ 'interface.stats.rx_256_to_511_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_256_to_511_packets'], # 33[10] [ 'interface.stats.rx_512_to_1023_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_512_to_1023_packets'], # 33[11] [ 'interface.stats.rx_65_to_127_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_65_to_127_packets'], # 33[12] [ 'interface.stats.rx_bytes', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_bytes'], # 33[13] [ 'interface.stats.rx_dropped', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_dropped'], # 33[14] [ 'interface.stats.rx_errors', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_errors'], # 33[15] [ 'interface.stats.rx_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_packets'], # 33[16] [ 'interface.stats.tx_bytes', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'tx_bytes'], # 33[17] [ 'interface.stats.tx_dropped', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'tx_dropped'], # 33[18] [ 'interface.stats.tx_packets', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'tx_packets'], # 33[19] [ 'interface.stats.collisions', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'collisions'], # 33[20] [ 'interface.stats.rx_crc_err', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_crc_err'], # 33[21] [ 'interface.stats.rx_frame_err', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_frame_err'], # 33[22] [ 'interface.stats.rx_missed_errors', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_missed_errors'], # 33[23] [ 'interface.stats.rx_over_err', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'rx_over_err'], # 33[24] [ 'interface.stats.tx_errors', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'tx_errors'], # 33[25] [ 'interface.status', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'map: status'], # 34 [ 'interface.type', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(), 'type'] # 35 ] for item, metric in enumerate(self.interface_metrics): self.add_metric(name + '.' + metric[0], pmdaMetric(self.pmid(self.interface_cluster, item), metric[1], self.interface_indom, metric[2], metric[3]), metric[4], metric[4]) self.coverage_indom = self.indom(4) self.coverage_instances() self.coverage_cluster = 4 for item, metric in enumerate(self.coverage_metrics): self.add_metric(name + '.' + metric[0], pmdaMetric(self.pmid(self.coverage_cluster, item), metric[1], self.coverage_indom, metric[2], metric[3]), metric[4], metric[4]) self.set_fetch_callback(self.openvswitch_fetch_callback) self.set_refresh(self.openvswitch_refresh) self.set_label(self.openvswitch_label) self.set_label_callback(self.openvswitch_label_callback) def get_interface_info_json(self): """ Convert the commandline output to json """ query = ["ovs-vsctl", "--format=json", "list", "interface"] self.interface_info_json = {} self.interface_names = [] try: with open(os.devnull, 'w') as devnull: p = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=devnull) js = json.loads(p.communicate()[0].decode("utf-8")) # reorganize json a bit to convert statistics # value in json output to dictionary for idx in range(len(js["data"])): statistics = js["data"][idx][33] if len(statistics[1]) == 0: js["data"][idx][33] = None else: stats = {} for i in statistics[1]: stats[i[0]] = i[1] js["data"][idx][33][0] = type(stats) js["data"][idx][33][1] = stats self.interface_info_json[str(js["data"][idx][28])] = js["data"][idx] self.interface_names.append(str(js["data"][idx][28])) except Exception as e: self.debug("Failed to get Interface info: %s" % (str(e))) def interface_instances(self): """ set names for openvswitch interface instances """ insts = [] for idx, val in enumerate(self.interface_names): insts.append(pmdaInstid(idx, val)) self.add_indom(pmdaIndom(self.interface_indom, insts)) def get_switch_info_json(self): """ Convert the commandline output to json """ query = ["ovs-vsctl", "--format=json", "list", "bridge"] self.switch_info_json = {} self.switch_names = [] try: with open(os.devnull, 'w') as devnull: p = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=devnull) js = json.loads(p.communicate()[0].decode("utf-8")) # reorganize json a bit for idx in range(len(js["data"])): self.switch_info_json[str(js["data"][idx][13])] = js["data"][idx] self.switch_names.append(str(js["data"][idx][13])) except Exception as e: self.debug("Failed to get switch info: %s" % (str(e))) def switch_instances(self): """ set names for openvswitch switch instances """ insts = [] for idx, val in enumerate(self.switch_names): insts.append(pmdaInstid(idx, val)) self.add_indom(pmdaIndom(self.switch_indom, insts)) def fetch_port_info(self, switch): """ fetches result from command line """ query = ['ovs-ofctl', 'dump-ports', switch] out = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = out.communicate() stdout = stdout.decode("utf-8") return stdout, stderr def get_port_info_json(self): """ Convert the commandline output to json """ self.port_info_json = {} self.port_info_names = [] for val in self.switch_names: stdout, _ = self.fetch_port_info(val) # string manipulation to get required results output = stdout.split('\n') # get number of ports num_ports = int(output[0].split(':')[1].strip().split(' ')[0]) # discard line 1 output = output[1:] for i in range(num_ports): temp = output[2*i]+','+output[2*i+1].strip() # get port name port, temp = temp.split(':')[0].strip(), temp.split(':')[1] temp = temp.split(',') port_vals = [] # put all port values in an array for _,value in enumerate(temp): port_vals.append(value.split('=')[1]) self.port_info_json[val+'::'+port] = port_vals self.port_info_names.append(val+'::'+port) def port_info_instances(self): """ set up ovs switch's port instances""" insts = [] for idx, val in enumerate(self.port_info_names): insts.append(pmdaInstid(idx, val)) self.add_indom(pmdaIndom(self.port_info_indom, insts)) def fetch_flow_info(self, switch): """ fetches result from command line """ query = ['ovs-ofctl','dump-flows', switch] out = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = out.communicate() stdout = stdout.decode("utf-8") return stdout, stderr def get_flow_json(self): """ Convert the commandline output to json """ self.flow_json = {} self.flow_names = [] for val in self.switch_names: stdout, _ = self.fetch_flow_info(val) output = stdout.split('\n') # Remove the excess line output = output[1:] # Remove the empty line if output[-1] == '': output = output[:-1] # Get number of flows per switch num_flows = len(output) # String manipulation to get data for i in range(num_flows): temp = output[i].strip() temp = temp.split(',') temp2 = [] for j in range(5): if j != 1: temp2.append(temp[j].split('=')[1]) else: temp2.append(temp[j].split('=')[1][:-1]) temp2.append(output[i].strip().split(' ')[-1].split('=')[1]) self.flow_json[val+'::'+str(i)] = temp2 self.flow_names.append(val+'::'+str(i)) def flow_instances(self): """ set up ovs switch's port instances""" insts = [] for idx, val in enumerate(self.flow_names): insts.append(pmdaInstid(idx, val)) self.add_indom(pmdaIndom(self.flow_indom, insts)) def fetch_coverage_info(self): """ fetches result from command line """ query = ['ovs-appctl','coverage/show'] out = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = out.communicate() stdout = stdout.decode("utf-8") return stdout, stderr def get_coverage_json(self): """ Convert the commandline output to json """ self.coverage_json = {} self.coverage_names = [] stdout, _ = self.fetch_coverage_info() output = stdout.split('\n') # Remove the excess line output = output[1:] # Remove the empty line if output[-1] == '': output = output[:-1] #Get the number of coverage counters num_counters = len(output) temp_coverage = {} # git grep -l '^COVERAGE_DEFINE(' | \ # xargs sed -n 's/^COVERAGE_DEFINE(\(.*\))\;/"\1",/p' | sort -u #The above command in OVS repo gives the list of coverage counters defined coverage_allow_list = [ "afxdp_cq_empty", "afxdp_cq_skip", "afxdp_fq_full", "afxdp_tx_full", "bridge_reconfigure", "ccmap_expand", "ccmap_shrink", "cmap_expand", "cmap_shrink", "conntrack_full", "conntrack_invalid_tcp_flags", "conntrack_l3csum_err", "conntrack_l4csum_err", "conntrack_long_cleanup", "conntrack_lookup_natted_miss", "conntrack_tcp_seq_chk_bypass", "conntrack_tcp_seq_chk_failed", "datapath_drop_hw_miss_recover", "datapath_drop_invalid_bond", "datapath_drop_invalid_port", "datapath_drop_invalid_tnl_port", "datapath_drop_lock_error", "datapath_drop_meter", "datapath_drop_nsh_decap_error", "datapath_drop_recirc_error", "datapath_drop_rx_invalid_packet", "datapath_drop_sample_error", "datapath_drop_tunnel_pop_error", "datapath_drop_tunnel_push_error", "datapath_drop_upcall_error", "datapath_drop_userspace_action_error", "dpif_destroy", "dpif_execute", "dpif_execute_with_help", "dpif_flow_del", "dpif_flow_flush", "dpif_flow_get", "dpif_flow_put", "dpif_meter_del", "dpif_meter_get", "dpif_meter_set", "dpif_port_add", "dpif_port_del", "dpif_purge", "drop_action_bridge_not_found", "drop_action_congestion", "drop_action_forwarding_disabled", "drop_action_invalid_tunnel_metadata", "drop_action_no_recirculation_context", "drop_action_of_pipeline", "drop_action_recirculation_conflict", "drop_action_recursion_too_deep", "drop_action_stack_too_deep", "drop_action_too_many_mpls_labels", "drop_action_too_many_resubmit", "drop_action_unsupported_packet_type", "dumped_duplicate_flow", "dumped_new_flow", "flow_extract", "handler_duplicate_upcall", "hindex_expand", "hindex_pathological", "hindex_reserve", "hindex_shrink", "hmap_expand", "hmap_pathological", "hmap_reserve", "hmap_shrink", "ipf_l3csum_err", "ipf_stuck_frag_list_purged", "lockfile_error", "lockfile_lock", "lockfile_unlock", "mac_learning_evicted", "mac_learning_expired", "mac_learning_learned", "mac_learning_moved", "mac_learning_static_none_move", "mcast_snooping_expired", "mcast_snooping_learned", "miniflow_extract_ipv4_pkt_len_error", "miniflow_extract_ipv4_pkt_too_short", "miniflow_extract_ipv6_pkt_len_error", "miniflow_extract_ipv6_pkt_too_short", "miniflow_malloc", "netdev_add_router", "netdev_arp_lookup", "netdev_get_ethtool", "netdev_get_hwaddr", "netdev_get_ifindex", "netdev_get_stats", "netdev_push_header_drops", "netdev_received", "netdev_send_prepare_drops", "netdev_sent", "netdev_set_ethtool", "netdev_set_hwaddr", "netdev_set_policing", "netlink_overflow", "netlink_received", "netlink_recv_jumbo", "netlink_sent", "nln_changed", "ofmonitor_pause", "ofmonitor_resume", "ofproto_dpif_expired", "ofproto_flush", "ofproto_packet_out", "ofproto_queue_req", "ofproto_recv_openflow", "ofproto_reinit_ports", "ofproto_update_port", "packet_in_overflow", "poll_create_node", "poll_zero_timeout", "process_start", "pstream_open", "raft_entry_serialize", "rconn_discarded", "rconn_overflow", "rconn_queued", "rconn_sent", "revalidate_missed_dp_flow", "rev_bond", "rev_flow_table", "rev_mac_learning", "rev_mcast_snooping", "rev_port_toggled", "rev_reconfigure", "rev_rstp", "rev_stp", "rtbsd_changed", "seq_change", "stream_open", "txn_aborted", "txn_error", "txn_forward_cancel", "txn_forward_complete", "txn_forward_create", "txn_forward_sent", "txn_incomplete", "txn_not_locked", "txn_success", "txn_try_again", "txn_unchanged", "txn_uncommitted", "unixctl_received", "unixctl_replied", "upcall_flow_limit_hit", "upcall_flow_limit_kill", "upcall_ukey_contention", "upcall_ukey_replace", "util_xalloc", "vconn_open", "vconn_received", "vconn_sent", "vhost_notification", "vhost_tx_contention", "xlate_actions", "xlate_actions_oversize", "xlate_actions_too_many_output", ] for _, metric in enumerate(coverage_allow_list): self.coverage_metrics.append([ 'coverage.' + str(metric), PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0, 0, 1, 0, 0, PM_COUNT_ONE), str(metric), ]) for i in range(num_counters-1): temp = output[i].strip() temp = temp.split() temp_coverage[temp[0]] = int(temp[-1]) self.coverage_json['coverage'] = temp_coverage self.coverage_names.append('coverage') def coverage_instances(self): """ set up ovs switch's port instances""" insts = [] for idx, val in enumerate(self.coverage_names): insts.append(pmdaInstid(idx, val)) self.add_indom(pmdaIndom(self.coverage_indom, insts)) def openvswitch_refresh(self, cluster): """refresh function""" if cluster == self.switch_cluster: self.get_switch_info_json() insts = [] for idx, val in enumerate(self.switch_names): insts.append(pmdaInstid(idx, val)) self.replace_indom(self.switch_indom, insts) if cluster == self.port_info_cluster: self.get_port_info_json() insts = [] for idx, val in enumerate(self.port_info_names): insts.append(pmdaInstid(idx, val)) self.replace_indom(self.port_info_indom, insts) if cluster == self.flow_cluster: self.get_flow_json() insts = [] for idx, val in enumerate(self.flow_names): insts.append(pmdaInstid(idx, val)) self.replace_indom(self.flow_indom, insts) if cluster == self.interface_cluster: self.get_interface_info_json() insts = [] for idx, val in enumerate(self.interface_names): insts.append(pmdaInstid(idx, val)) self.replace_indom(self.interface_indom, insts) if cluster == self.coverage_cluster: self.get_coverage_json() insts = [] for idx, val in enumerate(self.coverage_names): insts.append(pmdaInstid(idx, val)) self.replace_indom(self.coverage_indom, insts) def openvswitch_label(self, ident, target): ''' Return JSONB format labelset for identifier of given type, as a string ''' if target == c_api.PM_LABEL_INDOM: indom = ident if indom == self.switch_indom: ret = '"indom_name":"switch"' elif indom == self.port_info_indom: ret = '"indom_name":"port_info"' elif indom == self.flow_indom: ret = '"indom_name":"flow_info"' elif indom == self.interface_indom: ret = '"indom_name":"interface_info"' elif indom == self.coverage_indom: ret = '"indom_name":"coverage_info"' else: # no labels added for PM_LABEL_{DOMAIN,CLUSTER,ITEM} ret = '' # default is an empty labelset string return '{%s}' % ret def openvswitch_label_callback(self, indom, inst): ''' Return JSONB format labelset for an inst in given indom, as a string ''' if indom == self.switch_indom: ret = '"switch":"%s"' % (self.inst_name_lookup(self.switch_indom, inst)) elif indom == self.port_info_indom: ret = '"port":"%s"' % (self.inst_name_lookup(self.switch_indom, inst).split['::'][1]) elif indom == self.interface_indom: ret = '"interface":"%s"' % (self.inst_name_lookup(self.interface_indom, inst)) elif indom == self.coverage_indom: ret = '"coverage":"%s"' % (self.inst_name_lookup(self.coverage_indom, inst)) else: ret = '' return '{%s}' % ret def openvswitch_fetch_callback(self, cluster, item, inst): """ fetch callback method""" if cluster == self.switch_cluster: if self.switch_info_json is None: return [PMDA_FETCH_NOVALUES] try: switch = self.inst_name_lookup(self.switch_indom,inst) if item == 0: return [str(self.switch_info_json[switch][0][1]), 1] if item == 1: return [str(self.switch_info_json[switch][1][1]), 1] if item == 2: return [str(self.switch_info_json[switch][2][1]), 1] if item == 3: return [str(self.switch_info_json[switch][3]), 1] if item == 4: return [str(self.switch_info_json[switch][4]), 1] if item == 5: return [str(self.switch_info_json[switch][5]), 1] if item == 6: return [str(self.switch_info_json[switch][6][1]), 1] if item == 7: return [str(self.switch_info_json[switch][7][1]), 1] if item == 8: return [str(self.switch_info_json[switch][8][1]), 1] if item == 9: return [str(self.switch_info_json[switch][9][1]), 1] if item == 10: return [str(self.switch_info_json[switch][10][1]), 1] if item == 11: return [str(self.switch_info_json[switch][11]), 1] if item == 12: return [str(self.switch_info_json[switch][12][1]), 1] if item == 13: return [str(self.switch_info_json[switch][14][1]), 1] if item == 14: return [str(self.switch_info_json[switch][15][1]), 1] if item == 15: return [str(self.switch_info_json[switch][16]), 1] if item == 16: return [str(self.switch_info_json[switch][17][1]), 1] if item == 17: return [str(self.switch_info_json[switch][18]), 1] if item == 18: return [str(self.switch_info_json[switch][19][1]), 1] if item == 19: return [str(self.switch_info_json[switch][20][1]), 1] if item == 20: return [str(self.switch_info_json[switch][21][1]), 1] if item == 21: return [str(self.switch_info_json[switch][22]), 1] return [PM_ERR_PMID, 0] except Exception: return [PM_ERR_APPVERSION, 0] if cluster == self.port_info_cluster: if self.port_info_json is None: return [PMDA_FETCH_NOVALUES] try: port = self.inst_name_lookup(self.port_info_indom,inst) if item in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]: return [int(self.port_info_json[port][item]), 1] return [PM_ERR_PMID, 0] except Exception: return [PM_ERR_APPVERSION,0] if cluster == self.flow_cluster: if self.flow_json is None: return [PMDA_FETCH_NOVALUES] try: flow = self.inst_name_lookup(self.flow_indom,inst) if item in [0, 5]: return [str(self.flow_json[flow][item]), 1] if item == 1: return [float(self.flow_json[flow][item]), 1] if item in [2, 3, 4]: return [int(self.flow_json[flow][item]), 1] return [PM_ERR_PMID, 0] except Exception: return [PM_ERR_APPVERSION, 0] if cluster == self.interface_cluster: if self.interface_info_json is None: return [PMDA_FETCH_NOVALUES,0] try: interface = self.inst_name_lookup(self.interface_indom,inst) if item == 0: return [str(self.interface_info_json[interface][item][1]), 1] elif item in [1, 4, 5, 10, 11, 12, 19, 22, 24, 25]: value = self.interface_info_json[interface][item] if not isinstance(value, str): return [PMDA_FETCH_NOVALUES,0] return [self.interface_info_json[interface][item], 1] elif item in [2, 3, 13, 23]: value = self.interface_info_json[interface][item][1] if len(value) == 0: return [PMDA_FETCH_NOVALUES,0] return [str(self.interface_info_json[interface][item][1]), 1] elif item in [6, 7, 8, 9, 14, 20, 21, 26, 27, 15, 16, 17, 18]: value = self.interface_info_json[interface][item] if not isinstance(value, int): return [PMDA_FETCH_NOVALUES,0] return [self.interface_info_json[interface][item], 1] elif item in [28, 29]: value = self.interface_info_json[interface][item+1] if not isinstance(value, int): return [PMDA_FETCH_NOVALUES, 0] return [self.interface_info_json[interface][item+1], 1] elif item in [30, 31]: value = self.interface_info_json[interface][item+1][1] if len(value) == 0: return [PMDA_FETCH_NOVALUES,0] return [str(self.interface_info_json[interface][item+1][1]), 1] elif item == 32: return [str(self.interface_info_json[interface][33][1]), 1] elif item in range(33,59): value = self.interface_info_json[interface][33][1] key = self.interface_metrics[item][4] if key not in value.keys(): return [PMDA_FETCH_NOVALUES,0] return [int(self.interface_info_json[interface][33][1][key]),1] elif item == 59: value = self.interface_info_json[interface][34][1] if len(value) == 0: return [PMDA_FETCH_NOVALUES,0] return [str(self.interface_info_json[interface][34][1]), 1] elif item == 60: value = self.interface_info_json[interface][35] if isinstance(value, str) and len(value) > 0: return [self.interface_info_json[interface][35], 1] return [PMDA_FETCH_NOVALUES, 0] return [PM_ERR_PMID, 0] except Exception: return [PM_ERR_APPVERSION, 0] if cluster == self.coverage_cluster: if self.coverage_json is None: return [PMDA_FETCH_NOVALUES, 0] try: counter = self.inst_name_lookup(self.coverage_indom,inst) value = self.coverage_json[counter] key = self.coverage_metrics[item][4] if key not in value.keys(): return [PMDA_FETCH_NOVALUES, 0] return [self.coverage_json[counter][key], 1] except Exception: return [PM_ERR_APPVERSION, 0] return [PM_ERR_PMID, 0] def debug(self, msg): """ print diagnostic message if verbose logging is enabled """ if self.verbose: self.dbg(msg) if __name__ == '__main__': OpenvswitchPMDA('openvswitch', 126).run()