415 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env seiscomp-python
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
############################################################################
 | 
						|
# Copyright (C) GFZ Potsdam                                                #
 | 
						|
# All rights reserved.                                                     #
 | 
						|
#                                                                          #
 | 
						|
# GNU Affero General Public License Usage                                  #
 | 
						|
# This file may be used under the terms of the GNU Affero                  #
 | 
						|
# Public License version 3.0 as published by the Free Software Foundation  #
 | 
						|
# and appearing in the file LICENSE included in the packaging of this      #
 | 
						|
# file. Please review the following information to ensure the GNU Affero   #
 | 
						|
# Public License version 3.0 requirements will be met:                     #
 | 
						|
# https://www.gnu.org/licenses/agpl-3.0.html.                              #
 | 
						|
############################################################################
 | 
						|
 | 
						|
import sys, os, re
 | 
						|
import seiscomp.core, seiscomp.client, seiscomp.logging, seiscomp.system
 | 
						|
 | 
						|
 | 
						|
"""
 | 
						|
Monitor application that connects to the messaging and collects all
 | 
						|
information on the STATUS_GROUP to create an XML file ever N seconds.
 | 
						|
It can furthermore call a configured script to trigger processing of the
 | 
						|
produced XML file.
 | 
						|
"""
 | 
						|
 | 
						|
inputRegEx = re.compile("in\((?P<params>[^\)]*)\)")
 | 
						|
outputRegEx = re.compile("out\((?P<params>[^\)]*)\)")
 | 
						|
 | 
						|
 | 
						|
# Define all units of measure for available system SOH tags. Tags that are
 | 
						|
# not given here are not processed.
 | 
						|
Tests = {
 | 
						|
    "cpuusage": "%",
 | 
						|
    "clientmemoryusage": "kB",
 | 
						|
    "sentmessages": "cnt",
 | 
						|
    "receivedmessages": "cnt",
 | 
						|
    "messagequeuesize": "cnt",
 | 
						|
    "objectcount": "cnt",
 | 
						|
    "uptime": "s",
 | 
						|
    "dbadds": "row/s",
 | 
						|
    "dbupdates": "row/s",
 | 
						|
    "dbdeletes": "row/s",
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
# Class TestLog to hold the properties of a test. It also creates XML.
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
class TestLog:
 | 
						|
    def __init__(self):
 | 
						|
        self.value = None
 | 
						|
        self.uom = None
 | 
						|
        self.update = None
 | 
						|
 | 
						|
    def toXML(self, f, name):
 | 
						|
        f.write(f'<test name="{name}"')
 | 
						|
        if self.value:
 | 
						|
            try:
 | 
						|
                # Try to convert to float
 | 
						|
                fvalue = float(self.value)
 | 
						|
                if fvalue % 1.0 >= 1e-6:
 | 
						|
                    f.write(f' value="{fvalue:f}"')
 | 
						|
                else:
 | 
						|
                    f.write(' value="%d"' % int(fvalue))
 | 
						|
            except:
 | 
						|
                f.write(f' value="{self.value}"')
 | 
						|
        if self.uom:
 | 
						|
            f.write(f' uom="{self.uom}"')
 | 
						|
        if self.update:
 | 
						|
            f.write(f' updateTime="{self.update}"')
 | 
						|
        f.write("/>")
 | 
						|
 | 
						|
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
# Class ObjectLog to hold the properties of a object log. It also creates
 | 
						|
# XML.
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
class ObjectLog:
 | 
						|
    def __init__(self):
 | 
						|
        self.count = None
 | 
						|
        self.average = None
 | 
						|
        self.timeWindow = None
 | 
						|
        self.last = None
 | 
						|
        self.update = None
 | 
						|
 | 
						|
    def toXML(self, f, name, channel):
 | 
						|
        f.write("<object")
 | 
						|
        if name:
 | 
						|
            f.write(f' name="{name}"')
 | 
						|
        if channel:
 | 
						|
            f.write(f' channel="{channel}"')
 | 
						|
        if not self.count is None:
 | 
						|
            f.write(f' count="{self.count}"')
 | 
						|
        if not self.timeWindow is None:
 | 
						|
            f.write(f' timeWindow="{self.timeWindow}"')
 | 
						|
        if not self.average is None:
 | 
						|
            f.write(f' average="{self.average}"')
 | 
						|
        if self.last:
 | 
						|
            f.write(f' lastTime="{self.last}"')
 | 
						|
        f.write(f' updateTime="{self.update}"')
 | 
						|
        f.write("/>")
 | 
						|
 | 
						|
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
# Class Client that holds all tests and object logs of a particular client
 | 
						|
# (messaging user name).
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
class Client:
 | 
						|
    def __init__(self):
 | 
						|
        self.pid = None
 | 
						|
        self.progname = None
 | 
						|
        self.host = None
 | 
						|
 | 
						|
        self.inputLogs = dict()
 | 
						|
        self.outputLogs = dict()
 | 
						|
        self.tests = dict()
 | 
						|
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    # Update/add (system) tests based on the passed tests dictionary retrieved
 | 
						|
    # from a status message.
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    def updateTests(self, updateTime, tests):
 | 
						|
        for name, value in list(tests.items()):
 | 
						|
            if name == "pid":
 | 
						|
                self.pid = value
 | 
						|
            elif name == "programname":
 | 
						|
                self.progname = value
 | 
						|
            elif name == "hostname":
 | 
						|
                self.host = value
 | 
						|
 | 
						|
            if name not in Tests:
 | 
						|
                continue
 | 
						|
 | 
						|
            # Convert d:h:m:s to seconds
 | 
						|
            if name == "uptime":
 | 
						|
                try:
 | 
						|
                    t = [int(v) for v in value.split(":")]
 | 
						|
                except:
 | 
						|
                    continue
 | 
						|
                if len(t) != 4:
 | 
						|
                    continue
 | 
						|
                value = str(t[0] * 86400 + t[1] * 3600 + t[2] * 60 + t[3])
 | 
						|
 | 
						|
            if name not in self.tests:
 | 
						|
                log = TestLog()
 | 
						|
                log.uom = Tests[name]
 | 
						|
                self.tests[name] = log
 | 
						|
            else:
 | 
						|
                log = self.tests[name]
 | 
						|
            log.value = value
 | 
						|
            log.update = updateTime
 | 
						|
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    # Update/add object logs based on the passed log text. The content is parsed.
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    def updateObjects(self, updateTime, log):
 | 
						|
        # Check input structure
 | 
						|
        v = inputRegEx.search(log)
 | 
						|
        if not v:
 | 
						|
            # Check out structure
 | 
						|
            v = outputRegEx.search(log)
 | 
						|
            if not v:
 | 
						|
                return
 | 
						|
            logs = self.outputLogs
 | 
						|
        else:
 | 
						|
            logs = self.inputLogs
 | 
						|
 | 
						|
        try:
 | 
						|
            tmp = v.group("params").split(",")
 | 
						|
        except:
 | 
						|
            return
 | 
						|
 | 
						|
        params = dict()
 | 
						|
        for p in tmp:
 | 
						|
            try:
 | 
						|
                param, value = p.split(":", 1)
 | 
						|
            except:
 | 
						|
                continue
 | 
						|
            params[param] = value
 | 
						|
 | 
						|
        name = params.get("name", "")
 | 
						|
        channel = params.get("chan", "")
 | 
						|
        if (name, channel) not in logs:
 | 
						|
            logObj = ObjectLog()
 | 
						|
            logs[(name, channel)] = logObj
 | 
						|
        else:
 | 
						|
            logObj = logs[(name, channel)]
 | 
						|
 | 
						|
        logObj.update = updateTime
 | 
						|
        logObj.count = params.get("cnt")
 | 
						|
        logObj.average = params.get("avg")
 | 
						|
        logObj.timeWindow = params.get("tw")
 | 
						|
        logObj.last = params.get("last")
 | 
						|
 | 
						|
    def toXML(self, f, name):
 | 
						|
        f.write(f'<service name="{name}"')
 | 
						|
        if self.host:
 | 
						|
            f.write(f' host="{self.host}"')
 | 
						|
        if self.pid:
 | 
						|
            f.write(f' pid="{self.pid}"')
 | 
						|
        if self.progname:
 | 
						|
            f.write(f' prog="{self.progname}"')
 | 
						|
        f.write(">")
 | 
						|
        for name, log in list(self.tests.items()):
 | 
						|
            log.toXML(f, name)
 | 
						|
        if len(self.inputLogs) > 0:
 | 
						|
            f.write("<input>")
 | 
						|
            for id, log in list(self.inputLogs.items()):
 | 
						|
                log.toXML(f, id[0], id[1])
 | 
						|
            f.write("</input>")
 | 
						|
        if len(self.outputLogs) > 0:
 | 
						|
            f.write("<output>")
 | 
						|
            for id, log in list(self.outputLogs.items()):
 | 
						|
                log.toXML(f, id[0], id[1])
 | 
						|
            f.write("</output>")
 | 
						|
        f.write("</service>")
 | 
						|
 | 
						|
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
# SC3 application class Monitor
 | 
						|
# ----------------------------------------------------------------------------
 | 
						|
class Monitor(seiscomp.client.Application):
 | 
						|
    def __init__(self, argc, argv):
 | 
						|
        seiscomp.client.Application.__init__(self, argc, argv)
 | 
						|
        self.setDatabaseEnabled(False, False)
 | 
						|
        self.setMembershipMessagesEnabled(True)
 | 
						|
        self.addMessagingSubscription(seiscomp.client.Protocol.STATUS_GROUP)
 | 
						|
        self.setMessagingUsername("")
 | 
						|
        self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
 | 
						|
        self._clients = dict()
 | 
						|
        self._outputScript = None
 | 
						|
        self._outputFile = "@LOGDIR@/server.xml"
 | 
						|
        self._outputInterval = 60
 | 
						|
 | 
						|
    def createCommandLineDescription(self):
 | 
						|
        try:
 | 
						|
            self.commandline().addGroup("Output")
 | 
						|
            self.commandline().addStringOption(
 | 
						|
                "Output", "file,o", "Specify the output file to create"
 | 
						|
            )
 | 
						|
            self.commandline().addIntOption(
 | 
						|
                "Output",
 | 
						|
                "interval,i",
 | 
						|
                "Specify the output interval in seconds (default: 60)",
 | 
						|
            )
 | 
						|
            self.commandline().addStringOption(
 | 
						|
                "Output",
 | 
						|
                "script",
 | 
						|
                "Specify an output script to be called after the output file is generated",
 | 
						|
            )
 | 
						|
        except:
 | 
						|
            seiscomp.logging.warning(f"caught unexpected error {sys.exc_info()}")
 | 
						|
        return True
 | 
						|
 | 
						|
    def initConfiguration(self):
 | 
						|
        if not seiscomp.client.Application.initConfiguration(self):
 | 
						|
            return False
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputFile = self.configGetString("monitor.output.file")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputInterval = self.configGetInt("monitor.output.interval")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputScript = self.configGetString("monitor.output.script")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def init(self):
 | 
						|
        if not seiscomp.client.Application.init(self):
 | 
						|
            return False
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputFile = self.commandline().optionString("file")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputInterval = self.commandline().optionInt("interval")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._outputScript = self.commandline().optionString("script")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        self._outputFile = seiscomp.system.Environment.Instance().absolutePath(
 | 
						|
            self._outputFile
 | 
						|
        )
 | 
						|
        seiscomp.logging.info(f"Output file: {self._outputFile}")
 | 
						|
 | 
						|
        if self._outputScript:
 | 
						|
            self._outputScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
						|
                self._outputScript
 | 
						|
            )
 | 
						|
            seiscomp.logging.info(f"Output script: {self._outputScript}")
 | 
						|
 | 
						|
        self._monitor = self.addInputObjectLog(
 | 
						|
            "status", seiscomp.client.Protocol.STATUS_GROUP
 | 
						|
        )
 | 
						|
        self.enableTimer(self._outputInterval)
 | 
						|
        seiscomp.logging.info(
 | 
						|
            "Starting output timer with %d secs" % self._outputInterval
 | 
						|
        )
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def printUsage(self):
 | 
						|
        print(
 | 
						|
            """Usage:
 | 
						|
  scsohlog [options]
 | 
						|
 | 
						|
Connect to the messaging collecting information sent from connected clients"""
 | 
						|
        )
 | 
						|
 | 
						|
        seiscomp.client.Application.printUsage(self)
 | 
						|
 | 
						|
        print(
 | 
						|
            """Examples:
 | 
						|
Create an output XML file every 60 seconds and execute a custom script to process the XML file
 | 
						|
  scsohlog -o stat.xml -i 60 --script process-stat.sh
 | 
						|
"""
 | 
						|
        )
 | 
						|
 | 
						|
    def handleNetworkMessage(self, msg):
 | 
						|
        # A state of health message
 | 
						|
        if msg.type == seiscomp.client.Packet.Status:
 | 
						|
            data = filter(None, msg.payload.split("&"))
 | 
						|
            self.updateStatus(msg.subject, data)
 | 
						|
 | 
						|
        # If a client disconnected, remove it from the list
 | 
						|
        elif msg.type == seiscomp.client.Packet.Disconnected:
 | 
						|
            if msg.subject in self._clients:
 | 
						|
                del self._clients[msg.subject]
 | 
						|
 | 
						|
    def handleDisconnect(self):
 | 
						|
        # If we got disconnected all client states are deleted
 | 
						|
        self._clients = dict()
 | 
						|
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    # Timeout handler called by the Application class.
 | 
						|
    # Write XML to configured output file and trigger configured script.
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    def handleTimeout(self):
 | 
						|
        if self._outputFile == "-":
 | 
						|
            self.toXML(sys.stdout)
 | 
						|
            sys.stdout.write("\n")
 | 
						|
            return
 | 
						|
 | 
						|
        try:
 | 
						|
            f = open(self._outputFile, "w")
 | 
						|
        except:
 | 
						|
            seiscomp.logging.error(
 | 
						|
                f"Unable to create output file: {self._outputFile}"
 | 
						|
            )
 | 
						|
            return
 | 
						|
 | 
						|
        self.toXML(f)
 | 
						|
        f.close()
 | 
						|
 | 
						|
        if self._outputScript:
 | 
						|
            os.system(self._outputScript + " " + self._outputFile)
 | 
						|
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    # Write XML to stream f
 | 
						|
    # ----------------------------------------------------------------------------
 | 
						|
    def toXML(self, f):
 | 
						|
        f.write('<?xml version="1.0" encoding="UTF-8"?>')
 | 
						|
        f.write(f'<server name="seiscomp" host="{self.messagingURL()}">')
 | 
						|
        for name, client in list(self._clients.items()):
 | 
						|
            client.toXML(f, name)
 | 
						|
        f.write("</server>")
 | 
						|
 | 
						|
    def updateStatus(self, name, items):
 | 
						|
        if name not in self._clients:
 | 
						|
            self._clients[name] = Client()
 | 
						|
 | 
						|
        now = seiscomp.core.Time.GMT()
 | 
						|
        client = self._clients[name]
 | 
						|
        self.logObject(self._monitor, now)
 | 
						|
 | 
						|
        params = dict()
 | 
						|
        objs = []
 | 
						|
 | 
						|
        for t in items:
 | 
						|
            try:
 | 
						|
                param, value = t.split("=", 1)
 | 
						|
                params[param] = value
 | 
						|
            except:
 | 
						|
                objs.append(t)
 | 
						|
 | 
						|
        if "time" in params:
 | 
						|
            update = params["time"]
 | 
						|
            del params["time"]
 | 
						|
        else:
 | 
						|
            update = now.iso()
 | 
						|
 | 
						|
        client.updateTests(update, params)
 | 
						|
        for o in objs:
 | 
						|
            client.updateObjects(update, o)
 | 
						|
        # client.toXML(sys.stdout, name)
 | 
						|
 | 
						|
 | 
						|
app = Monitor(len(sys.argv), sys.argv)
 | 
						|
sys.exit(app())
 |