xref: /3.0.3-GA/ep-engine/management/cbstats (revision a8b244a2)
1#!/usr/bin/env python
2
3import clitool
4import sys
5import math
6import itertools
7import mc_bin_client
8import re
9import json
10
11MAGIC_CONVERT_RE=re.compile("(\d+)")
12
13BIG_VALUE = 2 ** 60
14SMALL_VALUE = - (2 ** 60)
15
16output_json = False
17
18def cmd(f):
19    """Decorate a function with code to authenticate based on 1-2
20    arguments past the normal number of arguments."""
21
22    def g(*args, **kwargs):
23        global output_json
24        mc = args[0]
25        n = f.func_code.co_argcount
26        if len(args) > n:
27            print "Too many args, given %s, but expected a maximum of %s"\
28                    % (list(args[1:]), n - 1)
29            sys.exit(1)
30
31        bucket = kwargs.get('bucketName', None)
32        password = kwargs.get('password', None) or ""
33        output_json = kwargs.get('json', None)
34
35        if bucket:
36            try:
37                mc.sasl_auth_plain(bucket, password)
38            except mc_bin_client.MemcachedError:
39                print "Authentication error for %s" % bucket
40                sys.exit(1)
41
42        if kwargs.get('allBuckets', None):
43            buckets = mc.stats('bucket')
44            for bucket in buckets.iterkeys():
45                print '*' * 78
46                print bucket
47                print
48                mc.bucket_select(bucket)
49                f(*args[:n])
50        else:
51            f(*args[:n])
52
53    return g
54
55def stats_perform(mc, cmd=''):
56    try:
57        return mc.stats(cmd)
58    except Exception, e:
59        print "Stats '%s' are not available from the requested engine. (%s)"\
60                % (cmd, e)
61
62def stats_formatter(stats, prefix=" ", cmp=None):
63    if stats:
64        if output_json:
65            for stat, val in stats.items():
66                stats[stat] = _maybeInt(val)
67                if isinstance(stats[stat], str):
68                    stats[stat] = _maybeFloat(val)
69            print json.dumps(stats, sort_keys=True, indent=4)
70        else:
71            longest = max((len(x) + 2) for x in stats.keys())
72            for stat, val in sorted(stats.items(), cmp=cmp):
73                s = stat + ":"
74                print "%s%s%s" % (prefix, s.ljust(longest), val)
75
76def time_label(s):
77    # -(2**64) -> '-inf'
78    # 2**64 -> 'inf'
79    # 0 -> '0'
80    # 4 -> '4us'
81    # 838384 -> '838ms'
82    # 8283852 -> '8s'
83    if s > BIG_VALUE:
84        return 'inf'
85    elif s < SMALL_VALUE:
86        return '-inf'
87    elif s == 0:
88        return '0'
89    product = 1
90    sizes = (('us', 1), ('ms', 1000), ('s', 1000), ('m', 60))
91    sizeMap = []
92    for l,sz in sizes:
93        product = sz * product
94        sizeMap.insert(0, (l, product))
95    lbl, factor = itertools.dropwhile(lambda x: x[1] > s, sizeMap).next()
96
97    # Give some extra granularity for timings in minutes
98    if lbl == 'm':
99        mins = s / factor
100        secs = (s % factor) / (factor / 60)
101        return '%d%s:%02ds' % (mins, lbl, secs)
102    return "%d%s" % (s / factor, lbl)
103
104def sec_label(s):
105    return time_label(s * 1000000)
106
107def size_label(s):
108    if s == 0:
109        return "0"
110    sizes=['', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']
111    e = math.floor(math.log(abs(s), 1024))
112    suffix = sizes[int(e)]
113    return "%d%s" % (s/(1024 ** math.floor(e)), suffix)
114
115@cmd
116
117def histograms(mc, raw_stats):
118    # Try to figure out the terminal width.  If we can't, 79 is good
119    def termWidth():
120        try:
121            import fcntl, termios, struct
122            h, w, hp, wp = struct.unpack('HHHH',
123                                         fcntl.ioctl(0, termios.TIOCGWINSZ,
124                                                     struct.pack('HHHH', 0, 0, 0, 0)))
125            return w
126        except:
127            return 79
128
129    special_labels = {'item_alloc_sizes': size_label,
130                      'paged_out_time': sec_label}
131
132    histodata = {}
133    for k, v in raw_stats.items():
134        # Parse out a data point
135        ka = k.split('_')
136        k = '_'.join(ka[0:-1])
137        kstart, kend = [int(x) for x in ka[-1].split(',')]
138
139        # Create a label for the data point
140        label_func = time_label
141        if k.endswith("Size") or k.endswith("Seek"):
142            label_func = size_label
143        elif k in special_labels:
144            label_func = special_labels[k]
145
146        label = "%s - %s" % (label_func(kstart), label_func(kend))
147        
148        if not k in histodata:
149            histodata[k] = []
150        histodata[k].append({'start' : int(kstart),
151                             'end'   : int(kend),
152                             'label' : label,
153                             'lb_fun': label_func,
154                             'value' : int(v)})
155    
156    for name, data_points in histodata.items():
157        max_label_len = max([len(stat['label']) for stat in data_points])
158        widestval = len(str(max([stat['value'] for stat in data_points])))
159        total = sum([stat['value'] for stat in data_points])
160        avg = sum([((x['end'] - x['start']) * x['value']) for x in data_points]) / total
161
162        print " %s (%d total)" % (name, total)
163
164        total_seen = 0
165        for dp in sorted(data_points, key=lambda x: x['start']):
166            total_seen += dp['value']
167            pcnt = (total_seen * 100.0) / total
168            toprint  = "    %s : (%6.02f%%) %s" % \
169                       (dp['label'].ljust(max_label_len), pcnt,
170                       str(dp['value']).rjust(widestval))
171
172            remaining = termWidth() - len(toprint) - 2
173            lpcnt = float(dp['value']) / total
174            print "%s %s" % (toprint, '#' * int(lpcnt * remaining))
175        print "    %s : (%s)" % ("Avg".ljust(max_label_len),
176                                dp['lb_fun'](avg).rjust(7))
177
178@cmd
179def stats_key(mc, key, vb):
180    cmd = "key %s %s" % (key, str(vb))
181    try:
182        vbs = mc.stats(cmd)
183    except mc_bin_client.MemcachedError, e:
184        print e.message
185        sys.exit()
186    except Exception, e:
187        print "Stats '%s' are not available from the requested engine." % cmd
188        sys.exit()
189
190    if vbs:
191        print "stats for key", key
192        stats_formatter(vbs)
193
194@cmd
195def stats_vkey(mc, key, vb):
196    cmd = "vkey %s %s" % (key, str(vb))
197    try:
198        vbs = mc.stats(cmd)
199    except mc_bin_client.MemcachedError, e:
200        print e.message
201        sys.exit()
202    except Exception, e:
203        print "Stats '%s' are not available from the requested engine." % cmd
204        sys.exit()
205
206    if vbs:
207        print "verification for key", key
208        stats_formatter(vbs)
209
210@cmd
211def stats_tap_takeover(mc, vb, name):
212    cmd = "tap-vbtakeover %s %s" % (str(vb), name)
213    stats_formatter(stats_perform(mc, cmd))
214
215@cmd
216def stats_dcp_takeover(mc, vb, name):
217    cmd = "dcp-vbtakeover %s %s" % (str(vb), name)
218    stats_formatter(stats_perform(mc, cmd))
219
220@cmd
221def stats_all(mc):
222    stats_formatter(stats_perform(mc))
223
224@cmd
225def stats_timings(mc):
226    if output_json:
227        print 'Json output not supported for timing stats'
228        return
229    h = stats_perform(mc, 'timings')
230    if h:
231        histograms(mc, h)
232
233@cmd
234def stats_tap(mc):
235    stats_formatter(stats_perform(mc, 'tap'))
236
237@cmd
238def stats_tapagg(mc):
239    stats_formatter(stats_perform(mc, 'tapagg _'))
240
241@cmd
242def stats_dcp(mc):
243    stats_formatter(stats_perform(mc, 'dcp'))
244
245@cmd
246def stats_dcpagg(mc):
247    stats_formatter(stats_perform(mc, 'dcpagg :'))
248
249@cmd
250def stats_checkpoint(mc, vb=-1):
251    try:
252        vb = int(vb)
253        if vb == -1:
254            cmd = 'checkpoint'
255        else:
256            cmd = "checkpoint %s" % (str(vb))
257        stats_formatter(stats_perform(mc, cmd))
258    except ValueError:
259        print 'Specified vbucket \"%s\" is not valid' % str(vb)
260
261@cmd
262def stats_allocator(mc):
263    print stats_perform(mc, 'allocator')['detailed']
264
265@cmd
266def stats_slabs(mc):
267    stats_formatter(stats_perform(mc, 'slabs'))
268
269@cmd
270def stats_items(mc):
271    stats_formatter(stats_perform(mc, 'items'))
272
273@cmd
274def stats_uuid(mc):
275    stats_formatter(stats_perform(mc, 'uuid'))
276
277@cmd
278def stats_vbucket(mc):
279    stats_formatter(stats_perform(mc, 'vbucket'))
280
281@cmd
282def stats_vbucket_details(mc, vb=-1):
283    try:
284        vb = int(vb)
285        if vb == -1:
286            cmd = 'vbucket-details'
287        else:
288            cmd = "vbucket-details %s" % (str(vb))
289        stats_formatter(stats_perform(mc, cmd))
290    except ValueError:
291        print 'Specified vbucket \"%s\" is not valid' % str(vb)
292
293@cmd
294def stats_vbucket_seqno(mc, vb = -1):
295    try:
296        vb = int(vb)
297        if vb == -1:
298            cmd = 'vbucket-seqno'
299        else:
300            cmd = "vbucket-seqno %s" % (str(vb))
301        stats_formatter(stats_perform(mc, cmd))
302    except ValueError:
303        print 'Specified vbucket \"%s\" is not valid' % str(vb)
304@cmd
305def stats_failovers(mc, vb = -1):
306    try:
307        vb = int(vb)
308        if vb == -1:
309            cmd = 'failovers'
310        else:
311            cmd = "failovers %s" % (str(vb))
312        stats_formatter(stats_perform(mc, cmd))
313    except ValueError:
314        print 'Specified vbucket \"%s\" is not valid' % str(vb)
315
316@cmd
317def stats_prev_vbucket(mc):
318    stats_formatter(stats_perform(mc, 'prev-vbucket'))
319
320@cmd
321def stats_memory(mc):
322    stats_formatter(stats_perform(mc, 'memory'))
323
324@cmd
325def stats_config(mc):
326    stats_formatter(stats_perform(mc, 'config'))
327
328@cmd
329def stats_warmup(mc):
330    stats_formatter(stats_perform(mc, 'warmup'))
331
332@cmd
333def stats_info(mc):
334    stats_formatter(stats_perform(mc, 'info'))
335
336@cmd
337def stats_workload(mc):
338    stats_formatter(stats_perform(mc, 'workload'))
339
340@cmd
341def stats_raw(mc, arg):
342    stats_formatter(stats_perform(mc,arg))
343
344@cmd
345def stats_kvstore(mc):
346    stats_formatter(stats_perform(mc, 'kvstore'))
347
348@cmd
349def stats_kvtimings(mc):
350    if output_json:
351        print 'Json output not supported for kvtiming stats'
352        return
353    h = stats_perform(mc, 'kvtimings')
354    if h:
355        histograms(mc, h)
356
357def avg(s):
358    return sum(s) / len(s)
359
360def _maybeInt(x):
361    try:
362        return int(x)
363    except:
364        return x
365
366def _maybeFloat(x):
367    try:
368        return float(x)
369    except:
370        return x
371
372def _magicConvert(s):
373    return [_maybeInt(x) for x in MAGIC_CONVERT_RE.split(s)]
374
375def magic_cmp(a, b):
376    am = _magicConvert(a[0])
377    bm = _magicConvert(b[0])
378    return cmp(am, bm)
379
380@cmd
381def stats_diskinfo(mc, with_detail=None):
382    cmd_str = 'diskinfo'
383    with_detail = with_detail == 'detail'
384    if with_detail:
385        cmd_str = 'diskinfo detail'
386
387    stats_formatter(stats_perform(mc, cmd_str))
388
389@cmd
390def stats_hash(mc, with_detail=None):
391    h = stats_perform(mc,'hash')
392    if not h:
393        return
394    with_detail = with_detail == 'detail'
395
396    mins = []
397    maxes = []
398    counts = []
399    for k,v in h.items():
400        if 'max_dep' in k:
401            maxes.append(int(v))
402        if 'min_dep' in k:
403            mins.append(int(v))
404        if ':counted' in k:
405            counts.append(int(v))
406        if ':histo' in k:
407            vb, kbucket = k.split(':')
408            skey = 'summary:' + kbucket
409            h[skey] = int(v) + h.get(skey, 0)
410
411    h['avg_min'] = avg(mins)
412    h['avg_max'] = avg(maxes)
413    h['avg_count'] = avg(counts)
414    h['min_count'] = min(counts)
415    h['max_count'] = max(counts)
416    h['total_counts'] = sum(counts)
417    h['largest_min'] = max(mins)
418    h['largest_max'] = max(maxes)
419
420    toDisplay = h
421    if not with_detail:
422        toDisplay = {}
423        for k in h:
424            if 'vb_' not in k:
425                toDisplay[k] = h[k]
426
427    stats_formatter(toDisplay, cmp=magic_cmp)
428
429@cmd
430def stats_scheduler(mc):
431    if output_json:
432        print 'Json output not supported for scheduler stats'
433        return
434    h = stats_perform(mc, 'scheduler')
435    if h:
436        histograms(mc, h)
437
438@cmd
439def stats_runtimes(mc):
440    if output_json:
441        print 'Json output not supported for runtimes stats'
442        return
443    h = stats_perform(mc, 'runtimes')
444    if h:
445        histograms(mc, h)
446
447@cmd
448def stats_dispatcher(mc, with_logs='no'):
449    if output_json:
450        print 'Json output not supported for dispatcher stats'
451        return
452    with_logs = with_logs == 'logs'
453    sorig = stats_perform(mc,'dispatcher')
454    if not sorig:
455        return
456    s = {}
457    logs = {}
458    slowlogs = {}
459    for k,v in sorig.items():
460        ak = tuple(k.split(':'))
461        if ak[-1] == 'runtime':
462            v = time_label(int(v))
463
464        dispatcher = ak[0]
465
466        for h in [logs, slowlogs]:
467            if dispatcher not in h:
468                h[dispatcher] = {}
469
470        if ak[0] not in s:
471            s[dispatcher] = {}
472
473        if ak[1] in ['log', 'slow']:
474            offset = int(ak[2])
475            field = ak[3]
476            h = {'log': logs, 'slow': slowlogs}[ak[1]]
477            if offset not in h[dispatcher]:
478                h[dispatcher][offset] = {}
479            h[dispatcher][offset][field] = v
480        else:
481            field = ak[1]
482            s[dispatcher][field] = v
483
484    for dispatcher in sorted(s):
485        print " %s" % dispatcher
486        stats_formatter(s[dispatcher], "     ")
487        for l,h in [('Slow jobs', slowlogs), ('Recent jobs', logs)]:
488            if with_logs and h[dispatcher]:
489                print "     %s:" % l
490                for offset, fields in sorted(h[dispatcher].items()):
491                    stats_formatter(fields, "        ")
492                    print "        ---------"
493
494@cmd
495def reset(mc):
496    stats_perform(mc, 'reset')
497
498def main():
499    c = clitool.CliTool()
500
501    c.addCommand('all', stats_all, 'all')
502    c.addCommand('allocator', stats_allocator, 'allocator')
503    c.addCommand('checkpoint', stats_checkpoint, 'checkpoint [vbid]')
504    c.addCommand('config', stats_config, 'config')
505    c.addCommand('diskinfo', stats_diskinfo, 'diskinfo [detail]')
506    c.addCommand('scheduler', stats_scheduler, 'scheduler')
507    c.addCommand('runtimes', stats_runtimes, 'runtimes')
508    c.addCommand('dispatcher', stats_dispatcher, 'dispatcher [logs]')
509    c.addCommand('workload', stats_workload, 'workload')
510    c.addCommand('failovers', stats_failovers, 'failovers [vbid]')
511    c.addCommand('hash', stats_hash, 'hash [detail]')
512    c.addCommand('items', stats_items, 'items (memcached bucket only)')
513    c.addCommand('key', stats_key, 'key keyname vbid')
514    c.addCommand('kvstore', stats_kvstore, 'kvstore')
515    c.addCommand('kvtimings', stats_kvtimings, 'kvtimings')
516    c.addCommand('memory', stats_memory, 'memory')
517    c.addCommand('prev-vbucket', stats_prev_vbucket, 'prev-vbucket')
518    c.addCommand('raw', stats_raw, 'raw argument')
519    c.addCommand('reset', reset, 'reset')
520    c.addCommand('slabs', stats_slabs, 'slabs (memcached bucket only)')
521    c.addCommand('tap', stats_tap, 'tap')
522    c.addCommand('tapagg', stats_tapagg, 'tapagg')
523    c.addCommand('tap-takeover', stats_tap_takeover, 'tap-takeover vb name')
524    c.addCommand('dcp', stats_dcp, 'dcp')
525    c.addCommand('dcpagg', stats_dcpagg, 'dcpagg')
526    c.addCommand('dcp-takeover', stats_dcp_takeover, 'dcp-takeover vb name')
527    c.addCommand('timings', stats_timings, 'timings')
528    c.addCommand('vbucket', stats_vbucket, 'vbucket')
529    c.addCommand('vbucket-details', stats_vbucket_details, 'vbucket-details [vbid]')
530    c.addCommand('vbucket-seqno', stats_vbucket_seqno, 'vbucket-seqno [vbid]')
531    c.addCommand('vkey', stats_vkey, 'vkey keyname vbid')
532    c.addCommand('warmup', stats_warmup, 'warmup')
533    c.addCommand('uuid', stats_uuid, 'uuid')
534    c.addFlag('-j', 'json', 'output the results in json format')
535    c.addHiddenFlag('-a', 'allBuckets')
536    c.addOption('-b', 'bucketName', 'the bucket to get stats from (Default: default)')
537    c.addOption('-p', 'password', 'the password for the bucket if one exists')
538
539    c.execute()
540
541if __name__ == '__main__':
542    main()
543