#! /usr/bin/python # -*- coding: utf-8 -*- # Magic markers - optional - used by installation scripts and # munin-node-configure: # #%# family=auto #%# capabilities=autoconf suggest # import os, psycopg FEATURES = ['pools_cl', 'stats_query', 'stats_requests', 'pools_sv'] class pgbouncer: """ PgBouncer class to get some data out of special SHOW commands """ def __init__(self, user = 'postgres', host = '127.0.0.1', port = 6432): """ pgbouncer instance init """ self.user = user self.host = host self.port = port self.dbname = 'pgbouncer' def get_data(self, command): """ get pgbouncer SHOW data """ alldata = [] psql = 'psql -h %s -p %s -U %s %s -c "SHOW %s;" 2>/dev/null' \ % (self.host, self.port, self.user, self.dbname, command) i = 0 out = os.popen(psql) line = 'stupid init value' while line != '': line = out.readline() i += 1 if i == 1: # header header = [col.strip() for col in line.split('|')] elif i == 2: # skip second line, full of --- continue elif line.strip() != '' and line[0] != '(': cols = [c.strip() for c in line.split('|')] data = {} k = 0 for c in cols: data[header[k]] = c k += 1 alldata.append(data) code = out.close() return alldata def stats(self): """ return stats """ return self.get_data("STATS") def pools(self): """ return pools """ return self.get_data("pools") def databases(self): """ return databases """ return self.get_data("databases") def split_scriptname(scriptname): """ returns (database, mode, field) from scriptname """ # scriptname must be on the form # pgbouncer_dbname_stats_requests # pgbouncer_dbname_pools # # dbname can of course contain _ pos = scriptname.find('pgbouncer_') if pos > -1: exploded = scriptname[pos:].split('_') if exploded[-2] == 'pools': dbname = '_'.join(exploded[1:-2]) mode = 'pools' stat = exploded[-1] if stat not in ('sv', 'cl'): return None elif exploded[-2] == 'stats': dbname = '_'.join(exploded[1:-2]) mode = 'stats' stat = exploded[-1] if stat == 'requests': stat = 'total_requests' elif stat == 'req': stat = 'avg_req' elif stat == 'query': stat = 'avg_query' else: stat = None else: return None return (dbname, mode, stat) return None def get_config_from_env(): """ read os.environ to get current configuration """ import os user, host, port = 'postgres', '127.0.0.1', '6432' if 'user' in os.environ: user = os.environ['user'] if 'host' in os.environ: host = os.environ['host'] if 'port' in os.environ: port = os.environ['port'] return user, host, port def autoconf(): """ munin autoconf """ ret = "no" exitcode = 1 user, host, port = get_config_from_env() try: pgb = pgbouncer(user, host, port) for database in pgb.databases(): if database['name'] != 'pgbouncer': ret = "yes" exitcode = 0 break except: # we print ret whatever, here ret = "no" pass print ret return exitcode def suggest(): """ munin suggest """ user, host, port = get_config_from_env() try: pgb = pgbouncer(user, host, port) for database in pgb.databases(): if database['name'] != 'pgbouncer': for f in FEATURES: print '%s_%s' % (database['name'], f) except: return 1 return 0 def config(scriptname): """ munin plugin configuration """ try: (dbname, mode, stat) = split_scriptname(scriptname) except Exception: return 1 if mode == 'pools' and stat == 'cl': print '''graph_title PgBouncer %s clients graph_category pgbouncer graph_vlabel Client connections graph_scale no active.label active active.min 0 active.max 1024 active.type GAUGE active.draw AREA waiting.label waiting waiting.min 0 waiting.max 1024 waiting.type GAUGE waiting.draw STACK''' % dbname elif mode == 'pools' and stat == 'sv': print '''graph_title PgBouncer %s servers graph_category pgbouncer graph_vlabel Server connections graph_scale no active.label active active.min 0 active.max 120 active.type GAUGE active.draw AREA idle.label idle idle.min 0 idle.max 120 idle.type GAUGE idle.draw STACK used.label used used.min 0 used.max 120 used.type GAUGE used.draw STACK tested.label tested tested.min 0 tested.max 120 tested.type GAUGE tested.draw STACK login.label login login.min 0 login.max 120 login.type GAUGE login.draw STACK''' % dbname elif mode == 'stats' and stat == 'total_requests': print '''graph_title PgBouncer %s %s graph_category pgbouncer graph_vlabel SQL requests pooled graph_args --lower-limit 1 --base 1000 --logarithmic graph_scale no %s.label requests %s.type COUNTER''' % (dbname, stat, stat, stat) elif mode == 'stats' and stat == 'avg_query': print '''graph_title PgBouncer %s %s graph_category pgbouncer graph_vlabel Average query duration (in µs) graph_args --lower-limit 1 --base 1000 --logarithmic graph_scale no %s.label duration %s.type GAUGE''' % (dbname, stat, stat, stat) else: print '%s %s not supported' % (mode, stat) return 1 def main(scriptname): """ returns stats or pools values depending on script name ($0) """ user, host, port = get_config_from_env() pgb = pgbouncer(user, host, port) try: (dbname, mode, stat) = split_scriptname(scriptname) except Exception: return 1 if mode == 'stats': stats = pgb.stats() value = [s[stat] for s in stats if s['database'] == dbname][0] print "%s.value %s" % (stat, value) elif mode == 'pools': pools = pgb.pools() values = [p for p in pools if p['database'] == dbname] if stat == 'cl': s = 'cl_active cl_waiting' elif stat == 'sv': s = 'sv_active sv_idle sv_used sv_tested sv_login' d_stats = {} for v in values: for stat in s.split(): # drop the sv_ or cl_ prefix statname = stat[3:] if statname not in d_stats: d_stats[statname] = int(v[stat]) else: d_stats[statname] += int(v[stat]) for s, v in d_stats.items(): print '%s.value %s' % (s, v) if __name__ == '__main__': import sys if len(sys.argv) == 2: if sys.argv[1] == "autoconf": sys.exit( autoconf() ) elif sys.argv[1] == "suggest": sys.exit( suggest() ) elif sys.argv[1] == "config": sys.exit( config(sys.argv[0]) ) main(sys.argv[0])