OpServ-Monitoring/opserv-backend

View on GitHub
app/gathering/measuring/psutil_source.py

Summary

Maintainability
D
1 day
Test Coverage
'''
    This module contains a wrapper class to create a nice fitting interface to the psutil module
'''

import logging
import time

from gathering.measuring.MeasuringSource import MeasuringSource
from misc.constants import Operating_System
from misc.standalone_helper import import_if_exists

log = logging.getLogger("opserv.gathering.psutil")

NOTIMPLEMENTED_NUMERICAL = 0
NOTIMPLEMENTED_TEXT = ""


class PsUtilWrap(MeasuringSource):
    '''
        MeasuringSource adaption to the psutil interface
    '''

    _supported_os = [Operating_System.macos,
                     Operating_System.windows, Operating_System.linux]

    _supported_comps = {
        "cpu": {
            "usage",
            "temperature"
        },
        "cpucore": {
            "usage",
            "temperature"
        },
        "memory": {
            "total",
            "free",
            "used"
        },
        "process": {
            "cpuusage",
            "memusage",
            "name"
        },
        "partition": {
            "total",
            "free",
            "used"
        },
        "network": {
            "info",
            "receivepersec",
            "transmitpersec"
        },
        "system": {
            "cpucores",
            "partitions",
            "processes",
            "networks"
        }
    }

    def __init__(self):
        self._init_complete = False
        self.psutil = None

        # Network specific fields
        # This contains the latest saved receive and sent bytes data
        # Using this format:
        # {
        #    "wlan1" : {"bytes": 5050, "timestamp": 1231245345.323}
        # }
        # wlan1 is the network interface name as received by the get_network_interfaces function
        # bytes is the numer of bytes got from the system
        # timestamp is the time of the measurement in seconds
        self.net_last_receive_data = {}
        self.net_last_sent_data = {}

        # List of Process objects to successfully gather cpu_percent values
        # See psutil.Process().cpu_percent() documentation for more info
        self.process_dict = {}

        self.init()

    def init(self):
        '''
            Initializes the measuring source (opening hardware connections etc.)
            If initialization is successful, it will return True
            If errors occured, the return value will be False
        '''

        self.psutil = import_if_exists("psutil")

        if not self.psutil:
            return False

        self._init_complete = True
        return True

    def deinit(self):
        '''
            De-Initializes the measuring source, removing connections etc.
            Returns True if deinit was successfull, False if it errord
        '''
        self._init_complete = False
        return True

    def get_measurement(self, component, metric, args):
        '''
            Retrieves a measurement from the measuring source
            given the component, metric and optionally arguments
        '''
        if not self._init_complete:
            log.error("Tried to capture measurement without initialization")
            return None

        if not self.can_measure(component, metric):
            raise NotImplementedError

        result = None
        if component == "cpu":
            args = int(args)
            if metric == "usage":
                result = self.psutil.cpu_percent(percpu=False)
            elif metric == "temperature":
                for sensor in self.psutil.sensors_temperatures()["coretemp"]:
                    if sensor.label == "Physical id 0":
                        result = sensor.current
        elif component == "cpucore":
            args = int(args)
            if metric == "usage":
                result = self.psutil.cpu_percent(percpu=True)[args]
            elif metric == "temperature":
                for sensor in self.psutil.sensors_temperatures()["coretemp"]:
                    if sensor.label == "Core {}".format(args):
                        result = sensor.current
        elif component == "memory":
            result = self.measure_memory(metric)

        elif component == "process":
            args = int(args.split(":")[0])
            result = self.measure_process(metric, args)

        elif component == "partition":
            result = self.measure_partition(metric, args)
        elif component == "network":
            result = self.measure_network(metric, args)

        elif component == "system":
            result = self.get_system_data(metric)

        return result

    def measure_memory(self, metric):
        '''
            Returns a measurement of the desired system memories metrics
        '''
        if metric == "total":
            return self.psutil.virtual_memory().total
        elif metric == "free":
            return self.psutil.virtual_memory().available
        elif metric == "used":
            return self.psutil.virtual_memory().used

    def measure_process(self, metric, args):
        '''
            Returns a measurement of the desired process and metric
        '''
        if args in self.process_dict:
            requested_process = self.process_dict[args]
        else:
            requested_process = self.psutil.Process(args)
            self.process_dict[args] = requested_process
        if metric == "cpuusage":
            return requested_process.cpu_percent()
        elif metric == "memusage":
            return requested_process.memory_info().rss
        elif metric == "name":
            return requested_process.name()

    def measure_partition(self, metric, args):
        '''
            Returns a measurement of the desired partition and metric
        '''
        try:
            if metric == "total":
                return self.psutil.disk_usage(args).total
            elif metric == "free":
                return self.psutil.disk_usage(args).free
            elif metric == "used":
                return self.psutil.disk_usage(args).used
        except OSError as err:
            log.error(err)
            log.error("Path does not exist: %s", str(args))

    def measure_network(self, metric, args):
        '''
            Returns a measurement of the desired network interface and metric
        '''
        try:
            if metric == "info":
                network_info = ""
                try:
                    network_info += str(self.psutil.net_if_stats()[args])
                except KeyError as err:
                    log.error(err)
                    log.error("Network Info couldnt be fetched with net_if_stats")
                try:
                    network_info += str(self.psutil.net_if_addrs()[args])
                except KeyError as err:
                    log.error(err)
                    log.error("Network Info couldnt be fetched with net_if_addrs")
                return network_info
            elif metric == "receivepersec":
                # Check if there is already an entry for this netif
                # If, then check if the bytes went down since last TimeoutError
                # If the bytes went not down, calculate the diff per second
                return self.net_calc_bytesper_sec(True, args)
            elif metric == "transmitpersec":
                return self.net_calc_bytesper_sec(False, args)
        except FileNotFoundError as err:
            log.error(err)
            log.error(
                "The following network interface couldnt be found %s", str(args))

    def net_calc_bytesper_sec(self, received, name):
        '''
           Uses the last received data fields to calculate either the receive or sent
           bytes per second. Returns zero if it couldn't be determined
           Also can raise the FileNotFoundError, if the interface can not be found
        '''
        new_bytes = self.net_get_bytes(received, name)
        new_time = time.time()
        bytes_per_second = 0
        last = {
            "bytes": 0
        }
        # Get specifc data for received and sent
        if received and name in self.net_last_receive_data:
            last = self.net_last_receive_data[name]
        elif not received and name in self.net_last_sent_data:
            last = self.net_last_sent_data[name]

        # Try to calculate bytes per second
        if last["bytes"] <= new_bytes and last["bytes"] != 0:
            byte_diff = new_bytes - last["bytes"]
            time_diff = new_time - last["timestamp"]
            bytes_per_second = byte_diff / time_diff

        # Save data to thei specific dict entries
        if received:
            self.net_last_receive_data[name] = {
                "bytes": new_bytes,
                "timestamp": new_time
            }
        else:
            self.net_last_sent_data[name] = {
                "bytes": new_bytes,
                "timestamp": new_time
            }
        return bytes_per_second

    def net_get_bytes(self, received, name):
        '''
            Gets either the received or sent (depending on the parameter) bytes from
            the specified network interface
        '''
        networks_list = self.psutil.net_io_counters(pernic=True)
        if name in networks_list:
            if received:
                return getattr(networks_list[name], "bytes_recv")
            else:
                return getattr(networks_list[name], "bytes_sent")
        else:
            raise FileNotFoundError

    def get_system_data(self, metric):
        '''
            Returns a measurement of the specified basic system metric
        '''
        if metric == "cpucores":
            return self.get_core_list()
        if metric == "partitions":
            return self.get_partition_list()
        if metric == "processes":
            return self.get_process_list()
        if metric == "networks":
            return self.get_network_interfaces()

    def get_network_interfaces(self):
        '''
            Gets the names of all currently available network interfaces
            and returns them in an array
        '''
        if not self.psutil:
            return []
        try:
            detailed_interfaces = self.psutil.net_if_stats()
        except LookupError as error:
            log.error(error)
            log.error("Couldn't get network interfaces")
            detailed_interfaces = {}
        simple_interfaces = []
        for interface in detailed_interfaces:
            simple_interfaces.append(interface)
        return simple_interfaces

    def get_core_list(self):
        '''
            Returns a list of all the available cpu cores
            E.g. 4 Cores: [0,1,2,3]
        '''
        result = []
        try:
            result = list(range(self.psutil.cpu_count()))
        except Exception as error:
            log.error("Couldn't get corelist")
            log.error(error)
        return result

    def get_partition_list(self):
        '''
            Returns a list of all the available partitions
            which are basically the mount locations /mnt/SDA1, C:/
        '''
        result = []
        try:
            part_tuples = self.psutil.disk_partitions()
            for part in part_tuples:
                result.append(part.mountpoint)
        except Exception as err:
            log.error("Couldn't get partition list")
            log.error(err)
        return result

    def get_process_list(self):
        '''
            Returns a list of all the currently active processes
            using their PIDs
        '''
        result = []
        try:
            for p in self.psutil.process_iter():
                # p.pid has a weird type that cannot be converted directly to str
                result.append(str(int(p.pid)) + ":" + str(p.name()))
        except Exception as err:
            log.error("Couldn't get processlist")
            log.error(err)
        return result