#!/usr/bin/env python3 ################################################### # # Pandora FMS Autodiscovery plugin. # Checks the status of the services in list and monitors CPU and Memory for each of them. # # (c) A. Kevin Rojas # # Edited in 2023 by Alejandro Sánchez # # TO DO LIST: # - Enable child services detection (Windows) # - Make CPU/Memory usage available for child services (Windows) # ################################################### try: from sys import argv from sys import stderr from sys import exit import psutil from subprocess import Popen from subprocess import PIPE from subprocess import DEVNULL from subprocess import getstatusoutput except ModuleNotFoundError as err: print("{} error: {}. Exiting...".format(argv[0], err), file=stderr) exit(1) module_list = [] VERSION = "1.2" def win_service(servicelist, option=False, memcpu=False): modules_default = [] modules_percentage=[] ## take all services services=psutil.win_service_iter() for service in services: if service.name() in servicelist: serv=service.as_dict() if serv['status']=='running': value=1 else: value=0 ## create module for each service parent = build_module("Service " + str(serv['name']) + " - Status", value,"generic_proc") modules_default +=parent # memory and cpu percentage if memcpu: ## process srv_pid = service.pid() process = psutil.Process(srv_pid) proc_name = process.name() ##cpu value_cpu=process.cpu_percent(interval=0.5) parent = build_module("Service " + str(proc_name) + " - CPU usage", value_cpu,"generic_data") parent[0].update([("unit","%"),("module_parent",str(serv['name']))]) modules_percentage +=parent ##mem value_mem=process.memory_percent() parent = build_module("Service " + str(proc_name) + " - Memory usage", value_mem,"generic_data") parent[0].update([("unit","%"),("module_parent",str(serv['name']))]) modules_percentage +=parent for module in modules_default: print_module(module, 1) if memcpu: for module in modules_percentage: print_module(module, 1) def lnx_service(services_list, memcpu=False): """Creates modules for Linux servers""" modules = [] sysctl = getstatusoutput("command -v systemctl")[0] servic = getstatusoutput("command -v service")[0] for srvc in services_list: status = None if sysctl == 0: ### Systemd available syscall = Popen(["systemctl", "show", "-pLoadState", "-pActiveState", srvc], stdout=PIPE, stdin=DEVNULL, universal_newlines=True) result = syscall.communicate() srvstatus = result[0].strip().lower().split("\n") if srvstatus[0] == "loadstate=not-found": next else: if srvstatus[1] == "activestate=active": modules += build_module("Service " + srvc + " - Status", 1, "generic_proc") status = 1 elif srvstatus[1] == "activestate=inactive": modules += build_module("Service " +srvc+ " - Status", 0, "generic_proc") status = 0 elif sysctl != 0 and servic == 0: ### Systemd not available, switch to service command syscall = Popen(["service", srvc, "status"], stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True) result = syscall.communicate()[0].lower() if "is running" in result: modules += build_module("Service " + srvc + " - Status", 1, "generic_proc") status = 1 elif "is stopped" in result: modules += build_module("Service " +srvc+ " - Status", 0, "generic_proc") status = 0 else: next else: print("No systemd or service commands available. Exiting...", file=stderr) exit() if status: module_list.append(srvc) if memcpu: modules += get_memcpu(srvc, None) for m in modules: print_module(m, 1) def build_module(name, value, module_type, parent=None): #print ("service_module BEGIN "+str(now(0,1))) module = [{ "name" : name , "type" : module_type, "value" : value, "module_parent" : parent, }] #print ("service_module END "+str(now(0,1))) return module def get_memcpu (process, servicename=None): """Creates a module for Memory and CPU for a given process. Returns a list of dictionaries.""" modules = [] if process: if servicename is not None: parentname = servicename else: parentname = process modules += [{ "name" : "Service "+ process + " - Memory usage", "type" : "generic_data", "value" : proc_percentbyname(process)[0], "unit" : "%", "module_parent" : "Service "+ parentname + " - Status", }, {"name" : "Service "+ process + " - CPU usage", "type" : "generic_data", "value" : proc_percentbyname(process)[1], "unit" : "%", "module_parent" : "Service "+ parentname + " - Status", }] return modules def proc_percentbyname(procname): ############# 03/03/2020 """Gets Memory and CPU usage for a given process. Returns a list.""" #print ("proc_percentbyname BEGIN "+str(now(0,1))) procs = [p for p in psutil.process_iter() if procname in p.name().lower()] memory = [] cpu = [] try: for proc in procs: if proc.name() == procname: cpu.append(proc.cpu_percent(interval=0.5)) memory.append(proc.memory_percent()) else: next except psutil.NoSuchProcess: next #print ("proc_percentbyname END "+str(now(0,1))) return [sum(memory),sum(cpu)] ######################################################################################### # print_module function ######################################################################################### def print_module(module, str_flag=False): """Returns module in XML format. Accepts only {dict}.\n + Only works with one module at a time: otherwise iteration is needed. + Module "value" field accepts str type or [list] for datalists. + Use not_print_flag to avoid printing the XML (only populates variables). """ data = dict(module) module_xml = ("\n" "\t\n" "\t" + str(data["type"]) + "\n" ) #### Strip spaces if module not generic_data_string if type(data["type"]) is not str and "string" not in data["type"]: data["value"] = data["value"].strip() if isinstance(data["value"], list): # Checks if value is a list module_xml += "\t\n" for value in data["value"]: if type(value) is dict and "value" in value: module_xml += "\t\n" module_xml += "\t\t\n" if "timestamp" in value: module_xml += "\t\t\n" module_xml += "\t\n" else: module_xml += "\t\n" if "desc" in data: module_xml += "\t\n" if "unit" in data: module_xml += "\t\n" if "interval" in data: module_xml += "\t\n" if "tags" in data: module_xml += "\t" + str(data["tags"]) + "\n" if "module_group" in data: module_xml += "\t" + str(data["module_group"]) + "\n" if "module_parent" in data and data["module_parent"] != None: module_xml += "\t" + str(data["module_parent"]) + "\n" if "min_warning" in data: module_xml += "\t\n" if "max_warning" in data: module_xml += "\t\n" if "min_critical" in data: module_xml += "\t\n" if "max_critical" in data: module_xml += "\t\n" if "str_warning" in data: module_xml += "\t\n" if "str_critical" in data: module_xml += "\t\n" if "critical_inverse" in data: module_xml += "\t\n" if "warning_inverse" in data: module_xml += "\t\n" if "max" in data: module_xml += "\t\n" if "min" in data: module_xml += "\t\n" if "post_process" in data: module_xml += "\t\n" if "disabled" in data: module_xml += "\t\n" if "min_ff_event" in data: module_xml += "\t\n" if "status" in data: module_xml += "\t\n" if "timestamp" in data: module_xml += "\t\n" if "custom_id" in data: module_xml += "\t\n" if "critical_instructions" in data: module_xml += "\t\n" if "warning_instructions" in data: module_xml += "\t\n" if "unknown_instructions" in data: module_xml += "\t\n" if "quiet" in data: module_xml += "\t\n" if "module_ff_interval" in data: module_xml += "\t\n" if "crontab" in data: module_xml += "\t\n" if "min_ff_event_normal" in data: module_xml += "\t\n" if "min_ff_event_warning" in data: module_xml += "\t\n" if "min_ff_event_critical" in data: module_xml += "\t\n" if "ff_type" in data: module_xml += "\t\n" if "ff_timeout" in data: module_xml += "\t\n" if "each_ff" in data: module_xml += "\t\n" if "module_parent_unlink" in data: module_xml += "\t\n" if "global_alerts" in data: for alert in data["alert"]: module_xml += "\t\n" module_xml += "\n" #### Print flag if str_flag is not False: print(module_xml) return (module_xml) ######################################################################################### # MAIN ######################################################################################### def main(): """Checks OS and calls the discover function.""" if psutil.WINDOWS: OS = "Windows" service_list = ["MySQL", "postgresql", "pgsql", "oracle", "MSSQL", "IISADMIN", "apache", "nginx", "W3svc", "NTDS", "Netlogon", "DNS", "MSExchangeADTopology", "MSExchangeServiceHost", "MSExchangeSA", "MSExchangeTransport"] discover(OS, service_list) elif psutil.LINUX: OS = "Linux" service_list = ["httpd", "apache2", "nginx", "ldap", "docker", "postfix", "mysqld", "postgres", "oracle", "mongod"] discover(OS, service_list) else: print("OS not recognized. Exiting...", file=stderr) exit() def discover(osyst, servicelist): """Shows help and triggers the creation of service modules""" if "--usage" in argv: memcpu = True else: memcpu = False if len(argv) > 2 and argv[1] == "--list": servicelist = argv[2].split(",") if osyst == "Windows": win_service(servicelist, False, memcpu) ## False won't get children elif osyst == "Linux": lnx_service(servicelist, memcpu) elif len(argv) > 1 and argv[1] == "--default": if osyst == "Windows": win_service(servicelist, False, memcpu) ## False won't get children elif osyst == "Linux": lnx_service(servicelist, memcpu) else: print("\nPandora FMS Autodiscovery plugin v{}".format(VERSION)) print("Checks the status of the services in list and monitors CPU and Memory for each of them.\n") print("Usage:") print("{} [options] [--usage]".format(argv[0])) print("--help") print("\tPrints this help screen") print("--default") print("\tRuns this tool with default monitoring.") print("\tServices monitored by default for {}:".format(osyst)) print("\t", ", ".join(servicelist)) print("--list \"\"") print("\tReplaces default services for a given list (comma-separated)") if osyst == "Windows": print("\tEach element of the list will be treated as a regexp, but they must be over 2 characters.") print("\tElements under 2 characters will be discarded.") print("--usage") print("\tAdds modules for CPU and Memory usage per service/process (optional, can take some time).\n") ##### RUN #### main()