xref: /3.0.3-GA/ep-engine/management/cbstats (revision b4a7b33e)
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_upr_takeover(mc, vb, name):
205    cmd = "upr-vbtakeover %s %s" % (str(vb), name)
206    stats_formatter(stats_perform(mc, cmd))
207
208
209@cmd
210def stats_all(mc):
211    stats_formatter(stats_perform(mc))
212
213@cmd
214def stats_timings(mc):
215    h = stats_perform(mc, 'timings')
216    if h:
217        histograms(mc, h)
218
219@cmd
220def stats_tap(mc):
221    stats_formatter(stats_perform(mc, 'tap'))
222
223@cmd
224def stats_tapagg(mc):
225    stats_formatter(stats_perform(mc, 'tapagg _'))
226
227@cmd
228def stats_upr(mc):
229    stats_formatter(stats_perform(mc, 'upr'))
230
231@cmd
232def stats_upragg(mc):
233    stats_formatter(stats_perform(mc, 'upragg _'))
234
235@cmd
236def stats_checkpoint(mc, vb=-1):
237    try:
238        vb = int(vb)
239        if vb == -1:
240            cmd = 'checkpoint'
241        else:
242            cmd = "checkpoint %s" % (str(vb))
243        stats_formatter(stats_perform(mc, cmd))
244    except ValueError:
245        print 'Specified vbucket \"%s\" is not valid' % str(vb)
246
247@cmd
248def stats_allocator(mc):
249    print stats_perform(mc, 'allocator')['detailed']
250
251@cmd
252def stats_slabs(mc):
253    stats_formatter(stats_perform(mc, 'slabs'))
254
255@cmd
256def stats_items(mc):
257    stats_formatter(stats_perform(mc, 'items'))
258
259@cmd
260def stats_uuid(mc):
261    stats_formatter(stats_perform(mc, 'uuid'))
262
263@cmd
264def stats_vbucket(mc):
265    stats_formatter(stats_perform(mc, 'vbucket'))
266
267@cmd
268def stats_vbucket_details(mc, vb=-1):
269    try:
270        vb = int(vb)
271        if vb == -1:
272            cmd = 'vbucket-details'
273        else:
274            cmd = "vbucket-details %s" % (str(vb))
275        stats_formatter(stats_perform(mc, cmd))
276    except ValueError:
277        print 'Specified vbucket \"%s\" is not valid' % str(vb)
278
279@cmd
280def stats_vbucket_seqno(mc, vb = -1):
281    try:
282        vb = int(vb)
283        if vb == -1:
284            cmd = 'vbucket-seqno'
285        else:
286            cmd = "vbucket-seqno %s" % (str(vb))
287        stats_formatter(stats_perform(mc, cmd))
288    except ValueError:
289        print 'Specified vbucket \"%s\" is not valid' % str(vb)
290@cmd
291def stats_failovers(mc, vb = -1):
292    try:
293        vb = int(vb)
294        if vb == -1:
295            cmd = 'failovers'
296        else:
297            cmd = "failovers %s" % (str(vb))
298        stats_formatter(stats_perform(mc, cmd))
299    except ValueError:
300        print 'Specified vbucket \"%s\" is not valid' % str(vb)
301
302@cmd
303def stats_prev_vbucket(mc):
304    stats_formatter(stats_perform(mc, 'prev-vbucket'))
305
306@cmd
307def stats_memory(mc):
308    stats_formatter(stats_perform(mc, 'memory'))
309
310@cmd
311def stats_config(mc):
312    stats_formatter(stats_perform(mc, 'config'))
313
314@cmd
315def stats_warmup(mc):
316    stats_formatter(stats_perform(mc, 'warmup'))
317
318@cmd
319def stats_info(mc):
320    stats_formatter(stats_perform(mc, 'info'))
321
322@cmd
323def stats_raw(mc, arg):
324    stats_formatter(stats_perform(mc,arg))
325
326@cmd
327def stats_kvstore(mc):
328    stats_formatter(stats_perform(mc, 'kvstore'))
329
330@cmd
331def stats_kvtimings(mc):
332    h = stats_perform(mc, 'kvtimings')
333    if h:
334        histograms(mc, h)
335
336def avg(s):
337    return sum(s) / len(s)
338
339def _maybeInt(x):
340    try:
341        return int(x)
342    except:
343        return x
344
345def _magicConvert(s):
346    return [_maybeInt(x) for x in MAGIC_CONVERT_RE.split(s)]
347
348def magic_cmp(a, b):
349    am = _magicConvert(a[0])
350    bm = _magicConvert(b[0])
351    return cmp(am, bm)
352
353@cmd
354def stats_diskinfo(mc, with_detail=None):
355    cmd_str = 'diskinfo'
356    with_detail = with_detail == 'detail'
357    if with_detail:
358        cmd_str = 'diskinfo detail'
359
360    stats_formatter(stats_perform(mc, cmd_str))
361
362@cmd
363def stats_hash(mc, with_detail=None):
364    h = stats_perform(mc,'hash')
365    if not h:
366        return
367    with_detail = with_detail == 'detail'
368
369    mins = []
370    maxes = []
371    counts = []
372    for k,v in h.items():
373        if 'max_dep' in k:
374            maxes.append(int(v))
375        if 'min_dep' in k:
376            mins.append(int(v))
377        if ':counted' in k:
378            counts.append(int(v))
379        if ':histo' in k:
380            vb, kbucket = k.split(':')
381            skey = 'summary:' + kbucket
382            h[skey] = int(v) + h.get(skey, 0)
383
384    h['avg_min'] = avg(mins)
385    h['avg_max'] = avg(maxes)
386    h['avg_count'] = avg(counts)
387    h['min_count'] = min(counts)
388    h['max_count'] = max(counts)
389    h['total_counts'] = sum(counts)
390    h['largest_min'] = max(mins)
391    h['largest_max'] = max(maxes)
392
393    toDisplay = h
394    if not with_detail:
395        toDisplay = {}
396        for k in h:
397            if 'vb_' not in k:
398                toDisplay[k] = h[k]
399
400    stats_formatter(toDisplay, cmp=magic_cmp)
401
402@cmd
403def stats_dispatcher(mc, with_logs='no'):
404    with_logs = with_logs == 'logs'
405    sorig = stats_perform(mc,'dispatcher')
406    if not sorig:
407        return
408    s = {}
409    logs = {}
410    slowlogs = {}
411    for k,v in sorig.items():
412        ak = tuple(k.split(':'))
413        if ak[-1] == 'runtime':
414            v = time_label(int(v))
415
416        dispatcher = ak[0]
417
418        for h in [logs, slowlogs]:
419            if dispatcher not in h:
420                h[dispatcher] = {}
421
422        if ak[0] not in s:
423            s[dispatcher] = {}
424
425        if ak[1] in ['log', 'slow']:
426            offset = int(ak[2])
427            field = ak[3]
428            h = {'log': logs, 'slow': slowlogs}[ak[1]]
429            if offset not in h[dispatcher]:
430                h[dispatcher][offset] = {}
431            h[dispatcher][offset][field] = v
432        else:
433            field = ak[1]
434            s[dispatcher][field] = v
435
436    for dispatcher in sorted(s):
437        print " %s" % dispatcher
438        stats_formatter(s[dispatcher], "     ")
439        for l,h in [('Slow jobs', slowlogs), ('Recent jobs', logs)]:
440            if with_logs and h[dispatcher]:
441                print "     %s:" % l
442                for offset, fields in sorted(h[dispatcher].items()):
443                    stats_formatter(fields, "        ")
444                    print "        ---------"
445
446@cmd
447def reset(mc):
448    stats_perform(mc, 'reset')
449
450def main():
451    c = clitool.CliTool()
452
453    c.addCommand('all', stats_all, 'all')
454    c.addCommand('allocator', stats_allocator, 'allocator')
455    c.addCommand('checkpoint', stats_checkpoint, 'checkpoint [vbid]')
456    c.addCommand('config', stats_config, 'config')
457    c.addCommand('diskinfo', stats_diskinfo, 'diskinfo [detail]')
458    c.addCommand('dispatcher', stats_dispatcher, 'dispatcher [logs]')
459    c.addCommand('failovers', stats_failovers, 'failovers [vbid]')
460    c.addCommand('hash', stats_hash, 'hash [detail]')
461    c.addCommand('items', stats_items, 'items (memcached bucket only)')
462    c.addCommand('key', stats_key, 'key keyname vbid')
463    c.addCommand('kvstore', stats_kvstore, 'kvstore')
464    c.addCommand('kvtimings', stats_kvtimings, 'kvtimings')
465    c.addCommand('memory', stats_memory, 'memory')
466    c.addCommand('prev-vbucket', stats_prev_vbucket, 'prev-vbucket')
467    c.addCommand('raw', stats_raw, 'raw argument')
468    c.addCommand('reset', reset, 'reset')
469    c.addCommand('slabs', stats_slabs, 'slabs (memcached bucket only)')
470    c.addCommand('tap', stats_tap, 'tap')
471    c.addCommand('tapagg', stats_tapagg, 'tapagg')
472    c.addCommand('tap-takeover', stats_tap_takeover, 'tap-takeover vb name')
473    c.addCommand('upr', stats_upr, 'upr')
474    c.addCommand('upragg', stats_upragg, 'upragg')
475    c.addCommand('upr-takeover', stats_upr_takeover, 'upr-takeover vb name')
476    c.addCommand('timings', stats_timings, 'timings')
477    c.addCommand('vbucket', stats_vbucket, 'vbucket')
478    c.addCommand('vbucket-details', stats_vbucket_details, 'vbucket-details [vbid]')
479    c.addCommand('vbucket-seqno', stats_vbucket_seqno, 'vbucket-seqno [vbid]')
480    c.addCommand('vkey', stats_vkey, 'vkey keyname vbid')
481    c.addCommand('warmup', stats_warmup, 'warmup')
482    c.addCommand('uuid', stats_uuid, 'uuid')
483    c.addFlag('-a', 'allBuckets', 'iterate over all buckets (requires admin u/p)')
484    c.addOption('-b', 'bucketName', 'the bucket to get stats from (Default: default)')
485    c.addOption('-p', 'password', 'the password for the bucket if one exists')
486
487    c.execute()
488
489if __name__ == '__main__':
490    main()
491