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