Fix cbstats tasks incorrect key
[ep-engine.git] / management / cbstats
1 #!/usr/bin/env python
2
3 import clitool
4 import cli_auth_utils
5 import sys
6 import math
7 import inspect
8 import itertools
9 import mc_bin_client
10 import re
11
12 from collections import defaultdict
13 from operator import itemgetter
14
15 try:
16     import simplejson as json
17 except ImportError:
18     try:
19         import json
20     except ImportError:
21         sys.exit("Error: could not import json module")
22
23 MAGIC_CONVERT_RE=re.compile("(\d+)")
24
25 BIG_VALUE = 2 ** 60
26 SMALL_VALUE = - (2 ** 60)
27
28 output_json = False
29
30 def cmd(f):
31     f = cli_auth_utils.cmd_decorator(f)
32     def g(*args, **kwargs):
33         global output_json
34         output_json = kwargs.pop('json', None)
35         f(*args, **kwargs)
36     return g
37
38 def stats_perform(mc, cmd=''):
39     try:
40         return mc.stats(cmd)
41     except Exception, e:
42         print "Stats '%s' are not available from the requested engine. (%s)"\
43                 % (cmd, e)
44         sys.exit(1)
45
46 def stats_formatter(stats, prefix=" ", cmp=None):
47     if stats:
48         if output_json:
49             for stat, val in stats.items():
50                 stats[stat] = _maybeInt(val)
51                 if isinstance(stats[stat], str):
52                     stats[stat] = _maybeFloat(val)
53             print json.dumps(stats, sort_keys=True, indent=4)
54         else:
55             longest = max((len(x) + 2) for x in stats.keys())
56             for stat, val in sorted(stats.items(), cmp=cmp):
57                 s = stat + ":"
58                 print "%s%s%s" % (prefix, s.ljust(longest), val)
59
60
61 def table_formatter(columns, data, sort_key=None, reverse=False):
62     """Formats data in a top-style table.
63
64     Takes a list of Columns (holding a display name and alignment), and a list
65     of lists representing each row in the table. The data in the rows should be
66     in the same order as the Columns.
67     """
68     column_widths = [len(c) for c in columns]
69     for row in data:
70         for index, item in enumerate(row):
71             column_widths[index] = max([column_widths[index], len(str(item))])
72
73     template = ""
74     for index, column in enumerate(columns[:-1]):
75         align = ">" if column.ralign else "<"
76         template += "{{{0}:{1}{2}}}  ".format(index, align,
77                                               column_widths[index])
78     # Last line is not padded unless right aligned
79     # so only long lines will wrap, not all of them
80     template += ("{{{0}:>{1}}}  ".format(len(columns) - 1, column_widths[-1])
81                 if columns[-1].ralign else ("{" + str(len(columns) - 1) + "}"))
82
83     print template.format(*columns), "\n"
84     for row in sorted(data, key=sort_key, reverse=reverse):
85         print template.format(*row)
86
87 class TaskStat(object):
88     """Represents a stat which must be sorted by a different value than is
89     displayed, i.e. pretty-printed timestamps
90     """
91     def __init__(self, display_value, value):
92         self.display_value = display_value
93         self.value = value
94
95     def __eq__(self, other):
96         return self.value == (other.value if hasattr(other, "value") else other)
97
98     def __lt__(self, other):
99         return self.value < (other.value if hasattr(other, "value") else other)
100
101     # total_ordering decorator unavailable in Python 2.6, otherwise only
102     # __eq__ and one comparision would be necessary
103
104     def __gt__(self, other):
105         return self.value > (other.value if hasattr(other, "value") else other)
106
107     def __str__(self):
108         return self.display_value
109
110 class Column(object):
111     def __init__(self, display_name, invert_sort, ralign):
112         self.display_name = display_name
113         self.invert_sort = invert_sort
114         self.ralign = ralign
115
116     def __str__(self):
117         return self.display_name
118
119     def __len__(self):
120         return len(str(self))
121
122 def ps_time_stat(t):
123     """convenience method for constructing a stat displaying a ps-style
124     timestamp but sorting on the underlying time since epoch.
125     """
126     t = t / 1000
127     return TaskStat(ps_time_label(t), t)
128
129 def tasks_stats_formatter(stats, sort_by=None, reverse=False, *args):
130     """Formats the data from ep_tasks in a top-like display"""
131     if stats:
132         if output_json:
133             stats_formatter(stats)
134         else:
135             cur_time = int(stats.pop("ep_tasks:cur_time"))
136
137             total_tasks = {"Reader":0,
138                            "Writer":0,
139                            "AuxIO":0,
140                            "NonIO":0}
141
142             running_tasks = total_tasks.copy()
143
144             states = ["R", "S", "D"]
145
146
147             tasks = json.loads(stats["ep_tasks:tasks"])
148
149             for task in tasks:
150                 total_tasks[task["type"]]+=1
151
152                 task["waketime_ns"] = (ps_time_stat(
153                                         (task["waketime_ns"] - cur_time))
154                                     if task["waketime_ns"] < BIG_VALUE
155                                     else TaskStat("inf", task["waketime_ns"]))
156
157                 task["total_runtime_ns"] = ps_time_stat(
158                                                    task["total_runtime_ns"])
159
160                 if task["state"] == "RUNNING":
161                     # task is running
162                     task["runtime"] = ps_time_stat(cur_time -
163                                                    task["last_starttime_ns"])
164                     task["waketime_ns"] = ps_time_stat(0)
165                     running_tasks[task["type"]]+=1
166                 else:
167                     task["runtime"] = ps_time_stat(0)
168
169                 task["state"] = task["state"][0]
170
171
172             running_tasks["Total"] = sum(running_tasks.values())
173             total_tasks["Total"] = len(tasks)
174
175             headers = (
176                 "Tasks     Writer Reader AuxIO  NonIO  Total \n" +
177                 "Running   {Writer:<6} {Reader:<6} "
178                 "{AuxIO:<6} {NonIO:<6} {Total:<6}\n"
179                     .format(**running_tasks) +
180                 "All       {Writer:<6} {Reader:<6} "
181                 "{AuxIO:<6} {NonIO:<6} {Total:<6}\n"
182                     .format(**total_tasks)
183             )
184
185             print headers
186
187             table_columns = [
188                     (key, Column(*options)) for key, options in (
189                     # Stat            Display Name, Invert Sort, Right Align
190                     ('tid',              ('TID',      False, True )),
191                     ('priority',         ('Pri',      False, True )),
192                     ('state',            ('St',       False, False)),
193                     ('bucket',           ('Bucket',   False, False)),
194                     ('waketime_ns',      ('SleepFor', True,  True )),
195                     ('runtime',          ('Runtime',  True,  True )),
196                     ('total_runtime_ns', ('TotalRun', True,  True )),
197                     ('type',             ('Type',     False, False)),
198                     ('name',             ('Name',     False, False)),
199                     ('description',      ('Descr.',   False, False)),
200                 )]
201
202             table_column_keys = [x[0] for x in table_columns]
203             table_column_values = [x[1] for x in table_columns]
204
205             table_data = []
206
207             for row in tasks:
208                 table_data.append(tuple(row[key]
209                                         for key in table_column_keys))
210
211             sort_key = None
212             if sort_by is not None:
213                 if isinstance(sort_by, int):
214                     sort_key = itemgetter(sort_by)
215
216                 elif isinstance(sort_by, basestring):
217                     if sort_by.isdigit():
218                         sort_key = itemgetter(int(sort_by))
219                     else:
220                         sort_by = sort_by.lower()
221                         for index, column in enumerate(table_column_values):
222                             if sort_by == column.display_name.lower():
223                                 sort_key = itemgetter(index)
224                                 reverse ^= column.invert_sort
225                                 break
226
227             table_formatter(table_column_values, table_data,
228                             sort_key, reverse=reverse)
229
230
231 def ps_time_label(microseconds):
232     sign = "-" if microseconds < 0 else ""
233
234     centiseconds = abs(microseconds)//10000
235
236     seconds = centiseconds//100
237     centiseconds %= 100
238
239     minutes = seconds//60
240     seconds %= 60
241
242     return "{0}{1}:{2:0>2}.{3:0>2}".format(sign, minutes, seconds, centiseconds)
243
244 def time_label(s):
245     # -(2**64) -> '-inf'
246     # 2**64 -> 'inf'
247     # 0 -> '0'
248     # 4 -> '4us'
249     # 838384 -> '838ms'
250     # 8283852 -> '8s'
251     if s > BIG_VALUE:
252         return 'inf'
253     elif s < SMALL_VALUE:
254         return '-inf'
255     elif s == 0:
256         return '0'
257
258     isNegative = s < 0
259
260     s = abs(s)
261
262     product = 1
263     sizes = (('us', 1), ('ms', 1000), ('s', 1000), ('m', 60))
264     sizeMap = []
265     for l,sz in sizes:
266         product = sz * product
267         sizeMap.insert(0, (l, product))
268     lbl, factor = itertools.dropwhile(lambda x: x[1] > s, sizeMap).next()
269
270     # Give some extra granularity for timings in minutes
271     if lbl == 'm':
272         mins = s / factor
273         secs = (s % factor) / (factor / 60)
274         result = '%d%s:%02ds' % (mins, lbl, secs)
275     else:
276         result = "%d%s" % (s / factor, lbl)
277
278     return ("-" if isNegative else "") + result
279
280 def sec_label(s):
281     return time_label(s * 1000000)
282
283 def size_label(s):
284     if s == 0:
285         return "0"
286     sizes=['', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']
287     e = math.floor(math.log(abs(s), 1024))
288     suffix = sizes[int(e)]
289     return "%d%s" % (s/(1024 ** math.floor(e)), suffix)
290
291 def histograms(mc, raw_stats):
292     # Try to figure out the terminal width.  If we can't, 79 is good
293     def termWidth():
294         try:
295             import fcntl, termios, struct
296             h, w, hp, wp = struct.unpack('HHHH',
297                                          fcntl.ioctl(0, termios.TIOCGWINSZ,
298                                                      struct.pack('HHHH', 0, 0, 0, 0)))
299             return w
300         except:
301             return 79
302
303     special_labels = {'item_alloc_sizes': size_label,
304                       'paged_out_time': sec_label}
305
306     histodata = {}
307     for k, v in raw_stats.items():
308         # Parse out a data point
309         ka = k.split('_')
310         k = '_'.join(ka[0:-1])
311         kstart, kend = [int(x) for x in ka[-1].split(',')]
312
313         # Create a label for the data point
314         label_func = time_label
315         if k.endswith("Size") or k.endswith("Seek"):
316             label_func = size_label
317         elif k in special_labels:
318             label_func = special_labels[k]
319
320         label = "%s - %s" % (label_func(kstart), label_func(kend))
321         
322         if not k in histodata:
323             histodata[k] = []
324         histodata[k].append({'start' : int(kstart),
325                              'end'   : int(kend),
326                              'label' : label,
327                              'lb_fun': label_func,
328                              'value' : int(v)})
329     
330     for name, data_points in histodata.items():
331         max_label_len = max([len(stat['label']) for stat in data_points])
332         widestval = len(str(max([stat['value'] for stat in data_points])))
333         total = sum([stat['value'] for stat in data_points])
334         avg = sum([((x['end'] - x['start']) * x['value']) for x in data_points]) / total
335
336         print " %s (%d total)" % (name, total)
337
338         total_seen = 0
339         for dp in sorted(data_points, key=lambda x: x['start']):
340             total_seen += dp['value']
341             pcnt = (total_seen * 100.0) / total
342             toprint  = "    %s : (%6.02f%%) %s" % \
343                        (dp['label'].ljust(max_label_len), pcnt,
344                        str(dp['value']).rjust(widestval))
345
346             remaining = termWidth() - len(toprint) - 2
347             lpcnt = float(dp['value']) / total
348             print "%s %s" % (toprint, '#' * int(lpcnt * remaining))
349         print "    %s : (%s)" % ("Avg".ljust(max_label_len),
350                                 dp['lb_fun'](avg).rjust(7))
351
352 @cmd
353 def stats_key(mc, key, vb):
354     cmd = "key %s %s" % (key, str(vb))
355     try:
356         vbs = mc.stats(cmd)
357     except mc_bin_client.MemcachedError, e:
358         print e.message
359         sys.exit(1)
360     except Exception, e:
361         print "Stats '%s' are not available from the requested engine." % cmd
362         sys.exit(1)
363
364     if vbs:
365         print "stats for key", key
366         stats_formatter(vbs)
367
368 @cmd
369 def stats_vkey(mc, key, vb):
370     cmd = "vkey %s %s" % (key, str(vb))
371     try:
372         vbs = mc.stats(cmd)
373     except mc_bin_client.MemcachedError, e:
374         print e.message
375         sys.exit(1)
376     except Exception, e:
377         print "Stats '%s' are not available from the requested engine." % cmd
378         sys.exit(1)
379
380     if vbs:
381         print "verification for key", key
382         stats_formatter(vbs)
383
384 @cmd
385 def stats_tap_takeover(mc, vb, name):
386     cmd = "tap-vbtakeover %s %s" % (str(vb), name)
387     stats_formatter(stats_perform(mc, cmd))
388
389 @cmd
390 def stats_dcp_takeover(mc, vb, name):
391     cmd = "dcp-vbtakeover %s %s" % (str(vb), name)
392     stats_formatter(stats_perform(mc, cmd))
393
394 @cmd
395 def stats_all(mc):
396     stats_formatter(stats_perform(mc))
397
398 @cmd
399 def stats_timings(mc):
400     if output_json:
401         print 'Json output not supported for timing stats'
402         return
403     h = stats_perform(mc, 'timings')
404     if h:
405         histograms(mc, h)
406
407 @cmd
408 def stats_tap(mc):
409     stats_formatter(stats_perform(mc, 'tap'))
410
411 @cmd
412 def stats_tapagg(mc):
413     stats_formatter(stats_perform(mc, 'tapagg _'))
414
415 @cmd
416 def stats_dcp(mc):
417     stats_formatter(stats_perform(mc, 'dcp'))
418
419 @cmd
420 def stats_dcpagg(mc):
421     stats_formatter(stats_perform(mc, 'dcpagg :'))
422
423 @cmd
424 def stats_checkpoint(mc, vb=-1):
425     try:
426         vb = int(vb)
427         if vb == -1:
428             cmd = 'checkpoint'
429         else:
430             cmd = "checkpoint %s" % (str(vb))
431         stats_formatter(stats_perform(mc, cmd))
432     except ValueError:
433         print 'Specified vbucket \"%s\" is not valid' % str(vb)
434
435 @cmd
436 def stats_allocator(mc):
437     print stats_perform(mc, 'allocator')['detailed']
438
439 @cmd
440 def stats_slabs(mc):
441     stats_formatter(stats_perform(mc, 'slabs'))
442
443 @cmd
444 def stats_items(mc):
445     stats_formatter(stats_perform(mc, 'items'))
446
447 @cmd
448 def stats_uuid(mc):
449     stats_formatter(stats_perform(mc, 'uuid'))
450
451 @cmd
452 def stats_vbucket(mc):
453     stats_formatter(stats_perform(mc, 'vbucket'))
454
455 @cmd
456 def stats_vbucket_details(mc, vb=-1):
457     try:
458         vb = int(vb)
459         if vb == -1:
460             cmd = 'vbucket-details'
461         else:
462             cmd = "vbucket-details %s" % (str(vb))
463         stats_formatter(stats_perform(mc, cmd))
464     except ValueError:
465         print 'Specified vbucket \"%s\" is not valid' % str(vb)
466
467 @cmd
468 def stats_vbucket_seqno(mc, vb = -1):
469     try:
470         vb = int(vb)
471         if vb == -1:
472             cmd = 'vbucket-seqno'
473         else:
474             cmd = "vbucket-seqno %s" % (str(vb))
475         stats_formatter(stats_perform(mc, cmd))
476     except ValueError:
477         print 'Specified vbucket \"%s\" is not valid' % str(vb)
478 @cmd
479 def stats_failovers(mc, vb = -1):
480     try:
481         vb = int(vb)
482         if vb == -1:
483             cmd = 'failovers'
484         else:
485             cmd = "failovers %s" % (str(vb))
486         stats_formatter(stats_perform(mc, cmd))
487     except ValueError:
488         print 'Specified vbucket \"%s\" is not valid' % str(vb)
489
490 @cmd
491 def stats_prev_vbucket(mc):
492     stats_formatter(stats_perform(mc, 'prev-vbucket'))
493
494 @cmd
495 def stats_memory(mc):
496     stats_formatter(stats_perform(mc, 'memory'))
497
498 @cmd
499 def stats_config(mc):
500     stats_formatter(stats_perform(mc, 'config'))
501
502 @cmd
503 def stats_warmup(mc):
504     stats_formatter(stats_perform(mc, 'warmup'))
505
506 @cmd
507 def stats_info(mc):
508     stats_formatter(stats_perform(mc, 'info'))
509
510 @cmd
511 def stats_workload(mc):
512     stats_formatter(stats_perform(mc, 'workload'))
513
514 @cmd
515 def stats_raw(mc, arg):
516     stats_formatter(stats_perform(mc,arg))
517
518 @cmd
519 def stats_kvstore(mc):
520     stats_formatter(stats_perform(mc, 'kvstore'))
521
522 @cmd
523 def stats_kvtimings(mc):
524     if output_json:
525         print 'Json output not supported for kvtiming stats'
526         return
527     h = stats_perform(mc, 'kvtimings')
528     if h:
529         histograms(mc, h)
530
531 def avg(s):
532     return sum(s) / len(s)
533
534 def _maybeInt(x):
535     try:
536         return int(x)
537     except:
538         return x
539
540 def _maybeFloat(x):
541     try:
542         return float(x)
543     except:
544         return x
545
546 def _magicConvert(s):
547     return [_maybeInt(x) for x in MAGIC_CONVERT_RE.split(s)]
548
549 def magic_cmp(a, b):
550     am = _magicConvert(a[0])
551     bm = _magicConvert(b[0])
552     return cmp(am, bm)
553
554 @cmd
555 def stats_diskinfo(mc, with_detail=None):
556     cmd_str = 'diskinfo'
557     with_detail = with_detail == 'detail'
558     if with_detail:
559         cmd_str = 'diskinfo detail'
560
561     stats_formatter(stats_perform(mc, cmd_str))
562
563 @cmd
564 def stats_hash(mc, with_detail=None):
565     h = stats_perform(mc,'hash')
566     if not h:
567         return
568     with_detail = with_detail == 'detail'
569
570     mins = []
571     maxes = []
572     counts = []
573     for k,v in h.items():
574         if 'max_dep' in k:
575             maxes.append(int(v))
576         if 'min_dep' in k:
577             mins.append(int(v))
578         if ':counted' in k:
579             counts.append(int(v))
580         if ':histo' in k:
581             vb, kbucket = k.split(':')
582             skey = 'summary:' + kbucket
583             h[skey] = int(v) + h.get(skey, 0)
584
585     h['avg_min'] = avg(mins)
586     h['avg_max'] = avg(maxes)
587     h['avg_count'] = avg(counts)
588     h['min_count'] = min(counts)
589     h['max_count'] = max(counts)
590     h['total_counts'] = sum(counts)
591     h['largest_min'] = max(mins)
592     h['largest_max'] = max(maxes)
593
594     toDisplay = h
595     if not with_detail:
596         toDisplay = {}
597         for k in h:
598             if 'vb_' not in k:
599                 toDisplay[k] = h[k]
600
601     stats_formatter(toDisplay, cmp=magic_cmp)
602
603 @cmd
604 def stats_scheduler(mc):
605     if output_json:
606         print 'Json output not supported for scheduler stats'
607         return
608     h = stats_perform(mc, 'scheduler')
609     if h:
610         histograms(mc, h)
611
612 @cmd
613 def stats_runtimes(mc):
614     if output_json:
615         print 'Json output not supported for runtimes stats'
616         return
617     h = stats_perform(mc, 'runtimes')
618     if h:
619         histograms(mc, h)
620
621 @cmd
622 def stats_dispatcher(mc, with_logs='no'):
623     if output_json:
624         print 'Json output not supported for dispatcher stats'
625         return
626     with_logs = with_logs == 'logs'
627     sorig = stats_perform(mc,'dispatcher')
628     if not sorig:
629         return
630     s = {}
631     logs = {}
632     slowlogs = {}
633     for k,v in sorig.items():
634         ak = tuple(k.split(':'))
635         if ak[-1] == 'runtime':
636             v = time_label(int(v))
637
638         dispatcher = ak[0]
639
640         for h in [logs, slowlogs]:
641             if dispatcher not in h:
642                 h[dispatcher] = {}
643
644         if ak[0] not in s:
645             s[dispatcher] = {}
646
647         if ak[1] in ['log', 'slow']:
648             offset = int(ak[2])
649             field = ak[3]
650             h = {'log': logs, 'slow': slowlogs}[ak[1]]
651             if offset not in h[dispatcher]:
652                 h[dispatcher][offset] = {}
653             h[dispatcher][offset][field] = v
654         else:
655             field = ak[1]
656             s[dispatcher][field] = v
657
658     for dispatcher in sorted(s):
659         print " %s" % dispatcher
660         stats_formatter(s[dispatcher], "     ")
661         for l,h in [('Slow jobs', slowlogs), ('Recent jobs', logs)]:
662             if with_logs and h[dispatcher]:
663                 print "     %s:" % l
664                 for offset, fields in sorted(h[dispatcher].items()):
665                     stats_formatter(fields, "        ")
666                     print "        ---------"
667
668 @cmd
669 def stats_tasks(mc, *args):
670     tasks_stats_formatter(stats_perform(mc, 'tasks'), *args)
671
672 @cmd
673 def stats_responses(mc, all=''):
674     resps = json.loads(stats_perform(mc, 'responses')['responses'])
675     c = mc.get_error_map()['errors']
676     d = {}
677     for k, v in resps.iteritems():
678         try:
679             if v > 0 or all:
680                 d[c[int(k, 16)]['name']] = v
681         except KeyError:
682             pass # Ignore it, no matching status code
683
684     stats_formatter(d)
685
686 @cmd
687 def reset(mc):
688     stats_perform(mc, 'reset')
689
690 def main():
691     c = cli_auth_utils.get_authed_clitool()
692
693     c.addCommand('all', stats_all, 'all')
694     c.addCommand('allocator', stats_allocator, 'allocator')
695     c.addCommand('checkpoint', stats_checkpoint, 'checkpoint [vbid]')
696     c.addCommand('config', stats_config, 'config')
697     c.addCommand('diskinfo', stats_diskinfo, 'diskinfo [detail]')
698     c.addCommand('scheduler', stats_scheduler, 'scheduler')
699     c.addCommand('runtimes', stats_runtimes, 'runtimes')
700     c.addCommand('dispatcher', stats_dispatcher, 'dispatcher [logs]')
701     c.addCommand('tasks', stats_tasks, 'tasks [sort column]')
702     c.addCommand('workload', stats_workload, 'workload')
703     c.addCommand('failovers', stats_failovers, 'failovers [vbid]')
704     c.addCommand('hash', stats_hash, 'hash [detail]')
705     c.addCommand('items', stats_items, 'items (memcached bucket only)')
706     c.addCommand('key', stats_key, 'key keyname vbid')
707     c.addCommand('kvstore', stats_kvstore, 'kvstore')
708     c.addCommand('kvtimings', stats_kvtimings, 'kvtimings')
709     c.addCommand('memory', stats_memory, 'memory')
710     c.addCommand('prev-vbucket', stats_prev_vbucket, 'prev-vbucket')
711     c.addCommand('raw', stats_raw, 'raw argument')
712     c.addCommand('reset', reset, 'reset')
713     c.addCommand('responses', stats_responses, 'responses [all]')
714     c.addCommand('slabs', stats_slabs, 'slabs (memcached bucket only)')
715     c.addCommand('tap', stats_tap, 'tap')
716     c.addCommand('tapagg', stats_tapagg, 'tapagg')
717     c.addCommand('tap-vbtakeover', stats_tap_takeover, 'tap-vbtakeover vb name')
718     c.addCommand('dcp', stats_dcp, 'dcp')
719     c.addCommand('dcpagg', stats_dcpagg, 'dcpagg')
720     c.addCommand('dcp-vbtakeover', stats_dcp_takeover, 'dcp-vbtakeover vb name')
721     c.addCommand('timings', stats_timings, 'timings')
722     c.addCommand('vbucket', stats_vbucket, 'vbucket')
723     c.addCommand('vbucket-details', stats_vbucket_details, 'vbucket-details [vbid]')
724     c.addCommand('vbucket-seqno', stats_vbucket_seqno, 'vbucket-seqno [vbid]')
725     c.addCommand('vkey', stats_vkey, 'vkey keyname vbid')
726     c.addCommand('warmup', stats_warmup, 'warmup')
727     c.addCommand('uuid', stats_uuid, 'uuid')
728     c.addFlag('-j', 'json', 'output the results in json format')
729
730     c.execute()
731
732 if __name__ == '__main__':
733     main()