PNG  IHDR;IDATxܻn0K )(pA 7LeG{ §㻢|ذaÆ 6lذaÆ 6lذaÆ 6lom$^yذag5bÆ 6lذaÆ 6lذa{ 6lذaÆ `}HFkm,mӪôô! x|'ܢ˟;E:9&ᶒ}{v]n&6 h_tڠ͵-ҫZ;Z$.Pkž)!o>}leQfJTu іچ\X=8Rن4`Vwl>nG^is"ms$ui?wbs[m6K4O.4%/bC%t Mז -lG6mrz2s%9s@-k9=)kB5\+͂Zsٲ Rn~GRC wIcIn7jJhۛNCS|j08yiHKֶۛkɈ+;SzL/F*\Ԕ#"5m2[S=gnaPeғL lذaÆ 6l^ḵaÆ 6lذaÆ 6lذa; _ذaÆ 6lذaÆ 6lذaÆ RIENDB` #!/usr/bin/env python3 # -*- encoding: utf-8; py-indent-offset: 4 -*- # +------------------------------------------------------------------+ # | ____ _ _ __ __ _ __ | # | / ___| |__ ___ ___| | __ | \/ | |/ / | # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / | # | | |___| | | | __/ (__| < | | | | . \ | # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ | # | | # | Copyright Mathias Kettner 2016 mk@mathias-kettner.de | # +------------------------------------------------------------------+ # # This file is part of Check_MK. # The official homepage is at http://mathias-kettner.de/check_mk. # # check_mk 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 in version 2. check_mk is distributed # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with- # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. See the GNU General Public License for more de- # tails. You should have received a copy of the GNU General Public # License along with GNU Make; see the file COPYING. If not, write # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301 USA. # Check_MK-Agent-Plugin - php-fpm Status # # By default this plugin tries to detect all locally running php-fpm processes # and to monitor them. If this is not good for your environment you might # create an php_fpm_pools.cfg file in MK_CONFDIR and populate the servers # list to prevent executing the detection mechanism. import configparser import json import re import socket import struct import sys import os from glob import iglob as glob # thanks to Milosz Galazka for sharing this code # https://blog.sleeplessbeastie.eu/2019/04/01/how-to-display-php-fpm-pool-information-using-unix-socket-and-python-script/ class FCGIStatusClient: # FCGI protocol version FCGI_VERSION = 1 # FCGI record types FCGI_BEGIN_REQUEST = 1 FCGI_PARAMS = 4 # FCGI roles FCGI_RESPONDER = 1 # FCGI header length FCGI_HEADER_LENGTH = 8 def __init__(self, socket_path, socket_timeout = 5.0, status_path = "/status" ): # socket_path of type tuple is TCP if type(socket_path) is tuple: self.socket = socket.create_connection(socket_path, socket_timeout) else: self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket.settimeout(socket_timeout) self.socket.connect(socket_path) self.status_path = status_path self.request_id = 1 self.params = { 'SCRIPT_NAME': status_path, 'SCRIPT_FILENAME': status_path, 'QUERY_STRING': 'json', 'REQUEST_METHOD': 'GET', } def close(self): self.socket.close() def define_begin_request(self): fcgi_begin_request = struct.pack("!HB5x", self.FCGI_RESPONDER, 0) fcgi_header = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_BEGIN_REQUEST, self.request_id, len(fcgi_begin_request), 0) self.fcgi_begin_request = fcgi_header + fcgi_begin_request def define_parameters(self): parameters = [] for name, value in self.params.items(): parameters.append(chr(len(name)) + chr(len(value)) + name + value) parameters = ''.join(parameters) parameters_length = len(parameters) parameters_padding_req = parameters_length & 7 parameters_padding = b'\x00' * parameters_padding_req fcgi_header_start = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_PARAMS, self.request_id, parameters_length , parameters_padding_req) fcgi_header_end = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_PARAMS, self.request_id, 0, 0) self.fcgi_params = fcgi_header_start + parameters.encode() + parameters_padding + fcgi_header_end def execute(self): self.status_data = b"" self.socket.send(self.fcgi_begin_request) self.socket.send(self.fcgi_params) while True: header = self.socket.recv(self.FCGI_HEADER_LENGTH) fcgi_version, request_type, request_id, request_length, request_padding = struct.unpack("!BBHHBx", header) if request_type == 3: break elif request_type in (6, 7): raw_status_data = b"" while len(raw_status_data) < request_length: raw_status_data += self.socket.recv(request_length - len(raw_status_data)) self.socket.recv(request_padding) self.status_data += raw_status_data if request_type == 7: raise Exception("Received an error packet: " + self.status_data.decode('ascii')) elif request_type not in (3, 6): raise Exception("Received unexpected packet type: " + str(request_type)) def make_request(self): self.define_begin_request() self.define_parameters() self.execute() self.close() def print_status(self): if hasattr(self, 'status_data'): data = json.loads(self.status_data.split(b"\r\n\r\n", 1)[1]) pool_name = data.pop('pool', None) pm_type = data.pop('process manager', None) for key in data: spaceless_key = "_".join( key.split() ) sys.stdout.write("%s %s %s %s\n" % (pool_name, pm_type, spaceless_key, str(data[key]))) def parse_includes(filename): """ Yield lines in filename, recursively parsing include= lines """ with open(filename) as f: for line in f: if line.strip().startswith('include'): include = line.split('=', 1)[1].strip() for f in glob(include): yield from parse_includes(f) else: yield line def parse_fpm_config(f): """ Parse php-fpm config, yielding dicts with status socket info """ config = configparser.ConfigParser() config.read_file(f) for name in config.sections(): section = config[name] if 'listen' not in section: continue listen = section['listen'] if listen and not listen.startswith('/'): if 'prefix' in section: listen = os.path.join(section['prefix'], listen) else: # we cannot infer the relative listen anchor continue if listen: listen = listen.replace('$pool', name) yield { "socket": listen, "path": section.get('pm.status_path'), } def discover_fpm(): """ Find running php-fpm processes, yielding their config file paths """ pattern = re.compile(r'php-fpm: master process \((.*)\)$') for f in glob('/proc/[0-9]*/cmdline'): with open(f) as fp: cmdline = fp.readline() match = pattern.match(cmdline.strip('\0')) if match: yield match.group(1) sys.stdout.write('<<>>\n') for configfile in discover_fpm(): for fpm_status in parse_fpm_config(parse_includes(configfile)): if not fpm_status.get('path'): continue try: fcgi_client = FCGIStatusClient( socket_path = fpm_status.get('socket'), status_path = fpm_status.get('path', '/status') ) fcgi_client.make_request() fcgi_client.print_status() except Exception as e: sys.stderr.write('Exception (%s): %s\n' % (fpm_status.get('socket'), e))