2012-12-29 22:10:36 -05:00
|
|
|
#!/usr/bin/python
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Gather Munin statistics and deliver to Carbon for Graphite display."""
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
#from multiprocessing import Pool
|
2012-12-31 10:04:59 -05:00
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import pickle
|
|
|
|
import re
|
2012-12-29 22:10:36 -05:00
|
|
|
import socket
|
2012-12-31 10:04:59 -05:00
|
|
|
import struct
|
|
|
|
import sys
|
|
|
|
import time
|
2012-12-29 22:10:36 -05:00
|
|
|
|
2012-12-31 10:04:59 -05:00
|
|
|
LOGGING_FORMAT = "%(asctime)s : %(levelname)s : %(message)s"
|
|
|
|
HOSTLIST = ["localhost"]
|
|
|
|
RE_LEFTRIGHT = re.compile(r"^(?P<left>\S+)\s+(?P<right>\S+)$")
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
class Munin():
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Munin host object with querying getter functions."""
|
|
|
|
def __init__(self, hostname="localhost", port=4949, args=None):
|
2012-12-29 22:10:36 -05:00
|
|
|
self.hostname = hostname
|
|
|
|
self.port = port
|
2012-12-31 10:04:59 -05:00
|
|
|
self.args = args
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
def connect(self):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Initial connection to Munin host."""
|
|
|
|
try:
|
|
|
|
self._sock = socket.create_connection((self.hostname, self.port),
|
|
|
|
10)
|
|
|
|
except socket.error:
|
|
|
|
logging.exception("Unable to connect to Munin host %s, port: %s",
|
|
|
|
self.hostname, self.port)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2012-12-29 22:10:36 -05:00
|
|
|
self._conn = self._sock.makefile()
|
|
|
|
self.hello_string = self._readline()
|
|
|
|
|
2012-12-31 10:04:59 -05:00
|
|
|
def close_connection(self):
|
|
|
|
"""Close connection to Munin host."""
|
|
|
|
self._sock.close()
|
|
|
|
|
2012-12-29 22:10:36 -05:00
|
|
|
def _readline(self):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Read one line from Munin output, stripping leading/trailing chars."""
|
2012-12-29 22:10:36 -05:00
|
|
|
return self._conn.readline().strip()
|
|
|
|
|
|
|
|
def _iterline(self):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Iterator over Munin output."""
|
2012-12-29 22:10:36 -05:00
|
|
|
while True:
|
|
|
|
current_line = self._readline()
|
2012-12-31 10:04:59 -05:00
|
|
|
logging.debug("Iterating over line: %s", current_line)
|
2012-12-29 22:10:36 -05:00
|
|
|
if not current_line:
|
|
|
|
break
|
|
|
|
if current_line.startswith("#"):
|
|
|
|
continue
|
|
|
|
if current_line == ".":
|
|
|
|
break
|
|
|
|
yield current_line
|
|
|
|
|
|
|
|
def fetch(self, plugin):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Fetch plugin's data fields from Munin."""
|
2012-12-29 22:10:36 -05:00
|
|
|
self._sock.sendall("fetch %s\n" % plugin)
|
2012-12-31 10:04:59 -05:00
|
|
|
response = {}
|
|
|
|
for current_line in self._iterline():
|
|
|
|
# Some munin plugins have more than one space between key and value.
|
|
|
|
full_key_name, key_value = RE_LEFTRIGHT.search(current_line).group(1, 2)
|
|
|
|
key_name = full_key_name.split(".")[0]
|
|
|
|
response[key_name] = key_value
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def list_plugins(self):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Return a list of Munin plugins configured on a node. """
|
2012-12-29 22:10:36 -05:00
|
|
|
self._sock.sendall("list\n")
|
2012-12-31 10:04:59 -05:00
|
|
|
plugin_list = self._readline().split(" ")
|
|
|
|
return plugin_list
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
def get_config(self, plugin):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Get config values for Munin plugin."""
|
2012-12-29 22:10:36 -05:00
|
|
|
self._sock.sendall("config %s\n" % plugin)
|
|
|
|
response = {}
|
2012-12-31 10:04:59 -05:00
|
|
|
|
2012-12-29 22:10:36 -05:00
|
|
|
for current_line in self._iterline():
|
|
|
|
key_name, key_value = current_line.split(" ", 1)
|
|
|
|
if "." in key_name:
|
|
|
|
# Some keys have periods in them.
|
|
|
|
# If so, make their own nested dictionary.
|
|
|
|
key_root, key_leaf = key_name.split(".", 1)
|
|
|
|
if key_root not in response:
|
|
|
|
response[key_root] = {}
|
|
|
|
response[key_root][key_leaf] = key_value
|
|
|
|
else:
|
|
|
|
response[key_name] = key_value
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def process_host_stats(self):
|
2012-12-31 10:04:59 -05:00
|
|
|
"""Process Munin node data, potentially sending to Carbon."""
|
2012-12-29 22:10:36 -05:00
|
|
|
plugins = self.list_plugins()
|
2012-12-31 10:04:59 -05:00
|
|
|
logging.debug("Plugin List: %s", plugins)
|
|
|
|
timestamp = int(time.time())
|
2012-12-29 22:10:36 -05:00
|
|
|
|
2012-12-31 10:04:59 -05:00
|
|
|
for current_plugin in plugins:
|
|
|
|
logging.info("Fetching plugin: %s (Host: %s)",
|
|
|
|
current_plugin, self.hostname)
|
|
|
|
|
|
|
|
plugin_config = self.get_config(current_plugin)
|
|
|
|
logging.debug("Plugin Config: %s", plugin_config)
|
|
|
|
|
|
|
|
plugin_data = self.fetch(current_plugin)
|
|
|
|
logging.debug("Plugin Data: %s", plugin_data)
|
|
|
|
if self.args.carbon:
|
|
|
|
self.send_to_carbon(timestamp, current_plugin, plugin_config, plugin_data)
|
|
|
|
|
|
|
|
def send_to_carbon(self, timestamp, plugin_name, plugin_config, plugin_data):
|
|
|
|
"""Send plugin data to Carbon over Pickle format."""
|
|
|
|
carbon_host, carbon_port = self.args.carbon.split(":")
|
|
|
|
data_list = []
|
|
|
|
logging.info("Creating metrics for plugin %s, timestamp: %d",
|
|
|
|
plugin_name, timestamp)
|
|
|
|
short_hostname = self.hostname.split(".")[0]
|
|
|
|
for data_key in plugin_data:
|
|
|
|
plugin_category = plugin_config["graph_category"]
|
|
|
|
metric = "servers.%s.%s.%s.%s" % (short_hostname, plugin_category, plugin_name, data_key)
|
|
|
|
value = plugin_data[data_key]
|
|
|
|
logging.debug("Creating metric %s, value: %s", metric, value)
|
|
|
|
data_list.append((metric, (timestamp, value)))
|
|
|
|
|
|
|
|
payload = pickle.dumps(data_list)
|
|
|
|
header = struct.pack("!L", len(payload))
|
|
|
|
message = header + payload
|
|
|
|
if self.args.noop:
|
|
|
|
logging.info("NOOP: Not sending data to Carbon")
|
|
|
|
return
|
|
|
|
|
|
|
|
logging.info("Sending plugin %s data to Carbon for host %s.",
|
|
|
|
plugin_name, self.hostname)
|
|
|
|
carbon_sock = socket.create_connection((carbon_host, carbon_port), 10)
|
|
|
|
carbon_sock.sendall(message)
|
|
|
|
carbon_sock.close()
|
|
|
|
logging.info("Finished sending plugin %s data to Carbon for host %s.",
|
|
|
|
plugin_name, self.hostname)
|
|
|
|
|
|
|
|
def parse_args():
|
|
|
|
"""Parse command line arguments."""
|
|
|
|
parser = argparse.ArgumentParser(description="Send Munin statistics to Graphite.")
|
|
|
|
parser.add_argument("--carbon",
|
|
|
|
action="store",
|
|
|
|
help="Carbon hostport (ex: localhost:2003).")
|
|
|
|
parser.add_argument("-n", "--noop",
|
|
|
|
action="store_true",
|
|
|
|
help="Don't actually send Munin data to Carbon.")
|
|
|
|
parser.add_argument("-v", "--verbose",
|
|
|
|
choices=[1, 2, 3],
|
|
|
|
default=2,
|
|
|
|
type=int,
|
|
|
|
help="Verbosity level. 1:ERROR, 2:INFO/Default, 3:DEBUG.")
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
return args
|
2012-12-29 22:10:36 -05:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2012-12-31 10:04:59 -05:00
|
|
|
args = parse_args()
|
|
|
|
if args.verbose == 3:
|
|
|
|
LOGGING_LEVEL = logging.DEBUG
|
|
|
|
elif args.verbose == 1:
|
|
|
|
LOGGING_LEVEL = logging.ERROR
|
|
|
|
else:
|
|
|
|
LOGGING_LEVEL = logging.INFO
|
|
|
|
|
|
|
|
logging.basicConfig(format=LOGGING_FORMAT, level=LOGGING_LEVEL)
|
|
|
|
while True:
|
|
|
|
for current_host in HOSTLIST:
|
|
|
|
start_time = time.time()
|
|
|
|
logging.info("Querying host: %s", current_host)
|
|
|
|
munin_host = Munin(hostname=current_host, args=args)
|
|
|
|
munin_host.connect()
|
|
|
|
munin_host.process_host_stats()
|
|
|
|
munin_host.close_connection()
|
|
|
|
end_time = time.time()
|
|
|
|
elapsed_time = end_time - start_time
|
|
|
|
logging.info("Finished querying host %s (Execution Time: %.2f sec)",
|
|
|
|
current_host, elapsed_time)
|
|
|
|
time.sleep(60)
|