Skip to content

Commit 07afeb7

Browse files
author
Jeff Tharp
committed
formating cleanup, colored output
1 parent 9bd6933 commit 07afeb7

File tree

1 file changed

+53
-21
lines changed

1 file changed

+53
-21
lines changed

elasticstat.py

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
from elasticsearch import Elasticsearch
1111

12-
# cluster_name status shards pri relo init unassign pending_tasks timestamp
13-
CLUSTER_TEMPLATE = """{cluster_name:33} {status:6} {active_shards:>6} {active_primary_shards:>4} {relocating_shards:>4} {initializing_shards:>4} {unassigned_shards:>8} {number_of_pending_tasks:>13} {timestamp:8}"""
12+
CLUSTER_TEMPLATE = """{cluster_name:33} {status:6} {active_shards:>6} {active_primary_shards:>4} {relocating_shards:>4} {initializing_shards:>4} {unassigned_shards:>8} {number_of_pending_tasks:>13} {timestamp:8}"""
1413
CLUSTER_HEADINGS = {}
1514
CLUSTER_HEADINGS["cluster_name"] = "cluster"
1615
CLUSTER_HEADINGS["status"] = "status"
@@ -22,7 +21,6 @@
2221
CLUSTER_HEADINGS["number_of_pending_tasks"] = "pending tasks"
2322
CLUSTER_HEADINGS["timestamp"] = "time"
2423

25-
# node_name role load_avg mem% heap% old sz old gc young gc
2624
NODES_TEMPLATE = {}
2725
NODES_TEMPLATE['general'] = """{name:24} {role:<6}"""
2826
NODES_TEMPLATE['os'] = """{load_avg:>18} {used_mem:>4}"""
@@ -31,7 +29,7 @@
3129
NODES_TEMPLATE['fielddata'] = """{fielddata:^7}"""
3230
NODES_TEMPLATE['connections'] = """{http_conn:>6} {transport_conn:>6}"""
3331
NODES_TEMPLATE['data_nodes'] = """{merge_time:>8} {store_throttle:>8} {docs}"""
34-
NODES_FAILED_TEMPLATE = """{name:24} {role:<6} (No data received, node may have left cluster)"""
32+
NODES_FAILED_TEMPLATE = """{name:24} {role:<6} (No data received, node may have left cluster)"""
3533
NODE_HEADINGS = {}
3634
NODE_HEADINGS["name"] = "nodes"
3735
NODE_HEADINGS["role"] = "role"
@@ -61,10 +59,21 @@ def error(self, message):
6159
self.print_help()
6260
sys.exit(2)
6361

62+
class ESColors:
63+
"""ANSI escape codes for color output"""
64+
END = '\033[00m'
65+
RED = '\033[0;31m'
66+
GREEN = '\033[0;32m'
67+
YELLOW = '\033[0;33m'
68+
GRAY = '\033[1;30m'
69+
WHITE = '\033[1;37m'
70+
6471
class Elasticstat:
6572
"""Elasticstat"""
6673

67-
def __init__(self, host, port, username, password, delay_interval, categories, threadpools):
74+
STATUS_COLOR = {'red': ESColors.RED, 'green': ESColors.GREEN, 'yellow': ESColors.YELLOW}
75+
76+
def __init__(self, host, port, username, password, delay_interval, categories, threadpools, no_color):
6877

6978
self.sleep_interval = delay_interval
7079
self.node_counters = {}
@@ -77,6 +86,7 @@ def __init__(self, host, port, username, password, delay_interval, categories, t
7786
self.new_nodes = [] # used to track new nodes that join the cluster
7887
self.active_master = ""
7988
self.threadpools = threadpools
89+
self.no_color = no_color
8090

8191
# categories for display
8292
if categories == 'all':
@@ -98,6 +108,12 @@ def __init__(self, host, port, username, password, delay_interval, categories, t
98108

99109
self.es_client = Elasticsearch([host_dict])
100110

111+
def colorize(self, msg, color):
112+
if self.no_color == True:
113+
return(msg)
114+
else:
115+
return(color + msg + ESColors.END)
116+
101117
def thetime(self):
102118
return datetime.datetime.now().strftime("%H:%M:%S")
103119

@@ -213,9 +229,10 @@ def process_node_threads(self, role, node_id, node):
213229
return(" ".join(thread_segments))
214230

215231
def process_node_fielddata(self, role, node_id, node):
216-
return(self.get_fd_stats(node_id,
217-
node['indices']['fielddata']['evictions'],
218-
node['breakers']['fielddata']['tripped']))
232+
fielddata = self.get_fd_stats(node_id,
233+
node['indices']['fielddata']['evictions'],
234+
node['breakers']['fielddata']['tripped'])
235+
return(NODES_TEMPLATE['fielddata'].format(fielddata=fielddata))
219236

220237
def process_node_connections(self, role, node_id, node):
221238
processed_node_conns = {}
@@ -229,12 +246,16 @@ def process_node_data_nodes(self, role, node_id, node):
229246
if role in ['DATA', 'ALL']:
230247
processed_node_dn['merge_time'] = node['indices']['merges']['total_time']
231248
processed_node_dn['store_throttle'] = node['indices']['store']['throttle_time']
232-
processed_node_dn['docs'] = "{0}|{1}".format(node['indices']['docs']['count'],
233-
node['indices']['docs']['deleted'])
249+
doc_count = node['indices']['docs']['count']
250+
deleted_count = node['indices']['docs']['deleted']
251+
if deleted_count > 0:
252+
processed_node_dn['docs'] = "{0}|{1}".format(doc_count, deleted_count)
253+
else:
254+
processed_node_dn['docs'] = str(doc_count)
234255
else:
235256
processed_node_dn['merge_time'] = "-"
236257
processed_node_dn['store_throttle'] = "-"
237-
processed_node_dn['docs'] = "-|-"
258+
processed_node_dn['docs'] = "-"
238259
return(NODES_TEMPLATE['data_nodes'].format(**processed_node_dn))
239260

240261
def process_node(self, role, node_id, node):
@@ -254,24 +275,27 @@ def process_role(self, role, nodes_stats):
254275
new_nodes_by_name = {nodes_stats['nodes'][id]['name']: id for id in self.new_nodes}
255276
if failed_node_name in new_nodes_by_name:
256277
# ...found it! Remove the old node_id, we've already added the new node_id at this point
257-
new_node_id = new_nodes_by_name[failed_node_name]
258-
self.new_nodes.remove(new_node_id) # So we don't flag this as a new node visually
278+
new_node_id = new_nodes_by_name[failed_node_name]
259279
self.nodes_list.remove(node_id)
260280
self.node_names.pop(node_id)
261281
self.nodes_by_role[role].remove(node_id)
262282
else:
263283
failed_node = {}
264-
failed_node['name'] = "-" + failed_node_name
284+
failed_node['name'] = failed_node_name + '-'
265285
failed_node['role'] = "({0})".format(role) # Role it had when we last saw this node in the cluster
266-
print NODES_FAILED_TEMPLATE.format(**failed_node)
286+
print self.colorize(NODES_FAILED_TEMPLATE.format(**failed_node), ESColors.GRAY)
267287
continue
268288
# make sure node's role hasn't changed
269289
current_role = self.get_role(nodes_stats['nodes'][node_id]['attributes'])
270290
if current_role != role:
271291
# Role changed, update lists so output will be correct on next iteration
272292
self.nodes_by_role.setdefault(current_role, []).append(node_id) # add to new role
273293
self.nodes_by_role[role].remove(node_id) # remove from current role
274-
print self.process_node(current_role, node_id, nodes_stats['nodes'][node_id])
294+
row = self.process_node(current_role, node_id, nodes_stats['nodes'][node_id])
295+
if node_id in self.new_nodes:
296+
print self.colorize(row, ESColors.WHITE)
297+
else:
298+
print row
275299

276300
def get_threads_headings(self):
277301
thread_segments = []
@@ -305,8 +329,10 @@ def print_stats(self):
305329

306330
# Print cluster health
307331
cluster_health['timestamp'] = self.thetime()
308-
print self.cluster_headings
309-
print CLUSTER_TEMPLATE.format(**cluster_health)
332+
status = cluster_health['status']
333+
#cluster_health['status'] = self.colorize(status, self.STATUS_COLOR[status])
334+
print self.colorize(self.cluster_headings, ESColors.GRAY)
335+
print self.colorize(CLUSTER_TEMPLATE.format(**cluster_health), self.STATUS_COLOR[status])
310336
print "" # space for readability
311337

312338
# Nodes can join and leave cluster with each iteration -- in order to report on nodes
@@ -331,7 +357,7 @@ def print_stats(self):
331357
self.nodes_by_role.setdefault(node_role, []).append(node_id)
332358

333359
# Print node stats
334-
print self.node_headings
360+
print self.colorize(self.node_headings, ESColors.GRAY)
335361
for role in self.nodes_by_role:
336362
self.process_role(role, nodes_stats)
337363
print "" # space out each run for readability
@@ -370,13 +396,19 @@ def main():
370396
metavar='CATEGORY',
371397
nargs='+',
372398
help='Statistic categories to show')
373-
parser.add_argument('-T',
399+
parser.add_argument('-t',
374400
'--threadpools',
375401
dest='threadpools',
376402
default=DEFAULT_THREAD_POOLS,
377403
metavar='THREADPOOL',
378404
nargs='+',
379405
help='Thread pools to show')
406+
parser.add_argument('-C',
407+
'--no-color',
408+
dest='no_color',
409+
action='store_true',
410+
default=False,
411+
help='Display without color output')
380412
parser.add_argument('delay_interval',
381413
default='1',
382414
nargs='?',
@@ -387,7 +419,7 @@ def main():
387419
args = parser.parse_args()
388420

389421
signal.signal(signal.SIGINT, lambda signum, frame: sys.exit())
390-
elasticstat = Elasticstat(args.hostlist, args.port, args.username, args.password, args.delay_interval, args.categories, args.threadpools)
422+
elasticstat = Elasticstat(args.hostlist, args.port, args.username, args.password, args.delay_interval, args.categories, args.threadpools, args.no_color)
391423
elasticstat.format_headings()
392424
elasticstat.print_stats()
393425

0 commit comments

Comments
 (0)