import agent_util
import csv
import os
import sys
if sys.version[0] == '3':
from io import StringIO
else:
from StringIO import StringIO
from agent_util import float
textkey_mapping = {}
def get_cmd_location(config):
program_location = ''
if "cassandra_installed_location" in config:
cassandra_installed_location = config["cassandra_installed_location"]
program_location = os.path.join(cassandra_installed_location, 'bin')
cmd = os.path.join(program_location, 'nodetool')
if not os.path.isfile(cmd):
cmd = agent_util.which('nodetool')
else:
cmd = agent_util.which("nodetool", exc=True)
return cmd
def name_to_textkey(name):
textkey = name.strip()
textkey = textkey.replace(" ", "_")
textkey = textkey.replace("(", "").replace(")", "")
textkey_mapping[textkey] = name
return textkey
def get_cfstats(config, keyspace=None, table=None):
cmd = get_cmd_location(config)
cmd += " cfstats"
if keyspace and table:
cmd += " %s.%s" % (keyspace, table)
status, output = agent_util.execute_command(cmd)
if status != 0:
raise Exception(output)
lines = output.split("\n")
data = {}
current_keyspace = None
current_table = None
for line in lines:
if line.startswith("Keyspace:"):
current_keyspace = line.strip().split()[-1]
current_table = None
data[current_keyspace] = {}
data[current_keyspace]["tables"] = {}
continue
if line.strip().startswith("Table:"):
current_table = line.strip().split()[-1]
data[current_keyspace]["tables"][current_table] = {}
continue
# Skip empty lines and "-------" divider lines
if not line.strip(): continue
if not line.replace("-", "").strip(): continue
# Get the key/value pair on the line, get rid of "ms" and convert to a float. Use
# try/except to convert NaN to None
fields = line.strip().split(":")
key = fields[0]
value = fields[1]
key = name_to_textkey(key)
value = value.strip()
value = value.replace("ms", "").strip()
try:
if value.lower() == "nan": raise Exception()
value = float(value)
except:
value = None
if current_table:
data[current_keyspace]["tables"][current_table][key] = value
else:
data[current_keyspace][key] = value
return data
def get_cfhistograms(config, keyspace, table):
cmd = get_cmd_location(config)
cmd += " cfhistograms -- %s %s" % (keyspace, table)
status, output = agent_util.execute_command(cmd)
if status != 0:
raise Exception(output)
lines = output.split("\n")
output = agent_util.StringIO(output)
parsed_output = list(csv.reader(output, delimiter="\t"))
indices = [i for i, x in enumerate(parsed_output) if x == []]
cfhistograms_dict = dict((parsed_output[i-2][0], parsed_output[i-1][0]) for i in indices if i-2 >= 0)
return cfhistograms_dict
def get_tpstats(config):
cmd = get_cmd_location(config)
status, output = agent_util.execute_command(cmd + ' tpstats')
if status != 0: raise Exception(output)
output = agent_util.StringIO(output)
parsed_output = list(csv.reader(output, delimiter="\t"))
header_line = [x.strip() for x in parsed_output[0][0].split(' ') if x]
pool_tasks = []
dropped_message = []
header = []
for values in parsed_output[1:]:
if values:
v = [x.strip() for x in values[0].split(' ') if x]
if len(v) == 2:
if not header:
header = [x.strip() for x in values[0].split(' ') if x]
else: dropped_message.append(dict(zip(header, v)))
else:
pool_tasks.append(dict(zip(header_line, v)))
return pool_tasks, dropped_message
class CassandraPlugin(agent_util.Plugin):
textkey = "cassandra"
label = "Cassandra"
@classmethod
def get_metadata(self, config):
status = agent_util.SUPPORTED
msg = None
program_location = ''
if "cassandra_installed_location" in config:
cassandra_installed_location = config["cassandra_installed_location"]
program_location = os.path.join(cassandra_installed_location,'bin')
# check if cassandra is even installed
installed = agent_util.which('cassandra')
if not installed:
self.log.info("cassandra binary not found")
status = agent_util.UNSUPPORTED
msg = "cassandra binary not found"
return {}
tables = []
keyspaces = []
pool_tasks = []
dropped_message = []
if status is agent_util.SUPPORTED:
try:
cfstats = get_cfstats(config)
keyspaces = cfstats.keys()
for keyspace in cfstats:
for table in cfstats[keyspace]["tables"]:
tables.append(keyspace + "." + table)
pool_tasks, dropped_message = get_tpstats(config)
pool_tasks = [p["Pool Name"] for p in pool_tasks]
dropped_message = [m["Message type"] for m in dropped_message]
except:
status = agent_util.MISCONFIGURED
self.log.error("couldn't get cassandra stats")
msg = "Couldn't get Cassandra stats, make sure cassandra is installed and cassandra configuration file is valid."
tables.sort()
keyspaces.sort()
metadata = {}
# Add keyspace-level metrics
keyspace = keyspaces[0]
for key in cfstats[keyspace]:
if key != "tables":
metadata[key] = {"label": textkey_mapping[key],
"options": keyspaces,
"status": status,
"error_message": msg}
# Add table-level metrics
table = cfstats[keyspace]["tables"].keys()[0]
for key in cfstats[keyspace]["tables"][table].keys():
metadata[key] = {"label": textkey_mapping[key],
"options": tables,
"status": status,
"error_message": msg}
"""
metadata = {
"thread_pool_task.active": {
"label": "Thread pool task: active",
"options": pool_tasks,
"status": status,
"error_message": msg
},
"thread_pool_task.completed": {
"label": "Thread pool task: completed",
"options": pool_tasks,
"status": status,
"error_message": msg
},
"thread_pool_task.blocked": {
"label": "Thread pool task: blocked",
"options": pool_tasks,
"status": status,
"error_message": msg
},
"thread_pool_task.pending": {
"label": "Thread pool task: pending",
"options": pool_tasks,
"status": status,
"error_message": msg
},
"dropped_messages": {
"label": "Dropped Messages",
"options": dropped_message,
"status": status,
"error_message": msg
},
}
"""
return metadata
def check(self, textkey, data, config):
res = 0
if data:
table = ''
if len(data.split('.')) == 2:
keyspace, table = data.split(".")
else:
keyspace = data
if textkey in ["column_count", "row_size", "row_count"]:
cfhistograms_dict = get_cfhistograms(config, keyspace, table)
res = cfhistograms_dict[mapping[textkey]]
if textkey == "column_count":
try: res = res.split(' cells')[0]
except: res = 0
else:
if textkey == "row_size":
try: res = res.split(' bytes: ')[0]
except: res = 0
else:
try: res = res.split(' bytes: ')[1]
except: res = 0
elif "thread_pool_task" in textkey or textkey == "dropped_messages":
pool_tasks, dropped_message = get_tpstats(config)
if "thread_pool_task" in textkey:
for p in pool_tasks:
if p["Pool Name"] == data:
res = p[textkey.split('.')[-1].title()]
break
else:
for p in dropped_message:
if p["Message type"] == data:
res = p["Dropped"]
break
else:
cfstats = get_cfstats(config, keyspace=keyspace, table=table)
if keyspace in cfstats:
if table:
return cfstats[keyspace]["tables"][table].get(textkey)
else:
return cfstats[keyspace].get(textkey)
# Shouldn't get here
return None