Subversion Repositories saptoolbox

Rev

Rev 1 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#!/usr/bin/python
# -*- coding: utf-8 -*-
# File: available.py
# Author: Tomás Vírseda
# License: The code is licensed under the terms of the  GPL v3
#          so you're free to grab, extend, improve and fork the code
#          as you want
# Description: availability overview of an SAP System

import os
import sys
from datetime import timedelta

from pprint import pprint
from datetime import datetime

class Available:
    def __init__(self, logfile, sapsid='SAP', maxdown=10):
        self.data = {}
        self.stats =  {}
        self.props = {}

        self.props['FILENAME_LOG'] = logfile
        self.props['SHOW_MAX_DOWNTIMES'] = maxdown
        self.stats['SAPSID'] = sapsid

        self.__get_contents()
        self.__group_status()


    def __get_contents(self):
        """
        Convert from available.log raw data to structured data
        """

        LOGFILE = self.props['FILENAME_LOG']

        if not os.path.exists(LOGFILE):
            sys.exit('wrong path to available.log file')

        try:
            flog = open(LOGFILE, 'r')
        except:
            exit('Error opening log file. Check permissions.')

        self.stats['RAW'] = flog.read()
        flog.seek(0) # Back to beguining of file

        lines = flog.readlines()
        serie = []
        for line in lines:
            detail = line.split('\n')[0]
            if detail.split(' ')[0] == 'Unavailable':
                availability, ds, ts, sep, de, te = detail.split(' ')
                availability = False
            else:
                availability, sep, sep, ds, ts, sep, de, te = detail.split(' ')
                availability = True

            sdtraw = ds + " " + ts
            edtraw = de + " " + te
            dstart = datetime.strptime(sdtraw, "%d.%m.%Y %H:%M:%S")
            dend = datetime.strptime(edtraw, "%d.%m.%Y %H:%M:%S")
            registry = [availability, dstart, dend]
            serie.append(registry)
        flog.close()

        self.stats['SERIE'] = serie


    def __group_status(self):
        """
        Group consecutive availability status
        """

        final = []
        n = 0
        for cur_entry in self.stats['SERIE']:
            if n != 0:
                # Add consecutives entries but, if status remains the
                # same, all entries must be grouped keeping the first
                # entry's start date.
                prv_entry = self.stats['SERIE'][n - 1]
                prv_status, prv_start, prv_end = prv_entry
                cur_status, cur_start, cur_end = cur_entry
                same_status = cur_status == prv_status
                if same_status:
                    # save start time of group status before delete
                    prv_start = final[len(final)-1][1]
                    del final[len(final)-1]
                    new_entry = [cur_status, prv_start, cur_end]
                    final.append(new_entry)
                else:
                    # Add first entry
                    final.append(cur_entry)
            else:
                final.append(cur_entry)

            n = n + 1

        self.stats['FINAL'] = final


    def get_stats(self):
        return self.stats


    def get_data(self):
        return self.data


    def get_properties(self):
        return self.props


    def build_report(self):
        """
        Build SAP System Report from available.log contents.
        """

        availability = {}
        availability[False] = {}
        availability[True] = {}

        REPORT = ""
        REPORT += "Report: SAP System '%s' Availability Stats\n" % self.stats['SAPSID']
        REPORT += "Source: %s\n" % self.props['FILENAME_LOG']
        REPORT += "-------------------------------------------\n\n"

        final = self.stats['FINAL']
        self.stats['START'] = start = final[0][1]
        self.stats['END'] = end = final[-1][2]
        self.stats['DELTA'] = delta = end - start
        self.stats['IS_AVAILABLE'] = is_available = final[-1][0]

        self.stats['AVAILABLE'] = {}
        self.stats['AVAILABLE'][True] = {}
        self.stats['AVAILABLE'][False] = {}

        for entry in final:
            is_available = entry[0]
            dstart = entry[1]
            dend = entry[2]
            edelta = dend - dstart
            etotsec = int(edelta.total_seconds())
            availability[is_available]['last'] = dend
            try:
                # Number of seconds in this status
                seconds = availability[is_available]['seconds']
                seconds = seconds + etotsec
                availability[is_available]['seconds'] = seconds
                self.stats['AVAILABLE'][is_available]['SECONDS'] = seconds

                # Update count
                count = availability[is_available]['count']
                count = count + 1
                availability[is_available]['count'] = count
                self.stats['AVAILABLE'][is_available]['COUNT'] = count
            except:
                availability[is_available]['seconds'] = etotsec
                availability[is_available]['count'] = 1
                self.stats['AVAILABLE'][is_available]['SECONDS'] = etotsec
                self.stats['AVAILABLE'][is_available]['COUNT'] = 1

            try:
                # Register entry
                registry = availability[is_available]['registry']
                registry.append(entry)
                availability[is_available]['registry'] = registry
                self.stats['AVAILABLE'][is_available]['REGISTRY'] = registry
            except:
                availability[is_available]['registry'] = []
                self.stats['AVAILABLE'][is_available]['REGISTRY'] = []

        self.stats['LAST_UP'] = lastup = availability[True]['last']
        self.stats['DOWN_COUNT'] = numdowntime = availability[False]['count']
        self.stats['DOWN_SECONDS'] = totdowntime = availability[False]['seconds']
        self.stats['UP_COUNT'] = numuptime = availability[True]['count']
        self.stats['UP_SECONDS'] = totuptime = availability[True]['seconds']

        self.stats['DOWN_PERCENTAJE'] = ptotdowntime = totdowntime * 100 / delta.total_seconds()
        self.stats['UP_PERCENTAJE'] = ptotuptime = totuptime * 100 / delta.total_seconds()
        self.stats['DOWN_LIST'] = downtimes = availability[False]['registry']
        downtimes.reverse()

        REPORT += "First entry recorded: %s\n" % start
        REPORT += " Last entry recorded: %s\n" % end
        REPORT += "        Is Available: %s\n" % is_available
        if not is_available:
            REPORT += "        Last time up: %s\n" % lastup
        REPORT += "        SAP Lifetime: %s\n" % delta
        REPORT += "         Unavailable: %s, %d times down, %.02f%% of lifetime\n" % (str(timedelta(seconds=availability[False]['seconds'])), numdowntime, ptotdowntime)
        REPORT += "           Available: %s, %.02f%% of lifetime\n\n" % (str(timedelta(seconds=availability[True]['seconds'])), ptotuptime)
        REPORT += "Last %d/%d downtimes:\n" % (self.props['SHOW_MAX_DOWNTIMES'], len(downtimes))

        for n in range(len(downtimes)):
            if n < self.props['SHOW_MAX_DOWNTIMES']:
                start = downtimes[n][1]
                end = downtimes[n][2]
                delta = end - start
                duration = str(timedelta(seconds=delta.total_seconds()))
                REPORT += "\t%2d - Down from: %s\t\tto: %s\tDuration: %s\n" % (n + 1, downtimes[n][1], downtimes[n][2], duration)

        REPORT += ""
        REPORT += "\nReport built on %s\n" % datetime.now()
        self.stats['REPORT_TXT'] = REPORT
        return REPORT


if __name__ == "__main__":
    try:
        av = Available(logfile=sys.argv[1], sapsid=sys.argv[2], maxdown=int(sys.argv[3]))
        print(av.build_report())
    except Exception as error:
        raise
        msg = "Error. You must provide a path to available.log, SAPSID and Max downtime times\n"
        msg += "Eg.: python3 available.py /usr/sap/SID/DVEBMGS00/work/available.log DBT 10"
        sys.exit (msg)