summaryrefslogtreecommitdiffstats
path: root/src/tools/rgw/parse-cr-dump.py
blob: 539929b113d87aab22065e97fd8632d86d27c8ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/python
from __future__ import print_function
from collections import Counter
import argparse
import json
import re
import sys

def gen_mgrs(args, cr_dump):
    """ traverse and return one manager at a time """
    mgrs = cr_dump['coroutine_managers']
    if args.manager is not None:
        yield mgrs[args.manager]
    else:
        for mgr in mgrs:
            yield mgr

def gen_stacks(args, cr_dump):
    """ traverse and return one stack at a time """
    for mgr in gen_mgrs(args, cr_dump):
        for ctx in mgr['run_contexts']:
            for stack in ctx['entries']:
                yield stack

def gen_ops(args, cr_dump):
    """ traverse and return one op at a time """
    for stack in gen_stacks(args, cr_dump):
        for op in stack['ops']:
            yield stack, op

def op_status(op):
    """ return op status or (none) """
    # "status": {"status": "...", "timestamp": "..."}
    return op.get('status', {}).get('status', '(none)')

def do_crs(args, cr_dump):
    """ print a sorted list of coroutines """
    counter = Counter()

    if args.group == 'status':
        print('Count:\tStatus:')
        for _, op in gen_ops(args, cr_dump):
            if args.filter and not re.search(args.filter, op['type']):
                continue
            counter[op_status(op)] += 1
    else:
        print('Count:\tCoroutine:')
        for _, op in gen_ops(args, cr_dump):
            name = op['type']
            if args.filter and not re.search(args.filter, name):
                continue
            counter[name] += 1

    crs = counter.most_common();

    if args.order == 'asc':
        crs.reverse()
    if args.limit:
        crs = crs[:args.limit]

    for op in crs:
        print('%d\t%s' % (op[1], op[0]))
    print('Total:', sum(counter.values()))
    return 0

def match_ops(name, ops):
    """ return true if any op matches the given filter """
    for op in ops:
        if re.search(name, op):
            return True
    return False

def do_stacks(args, cr_dump):
    """ print a list of coroutine stacks """
    print('Stack:\t\tCoroutines:')
    count = 0
    for stack in gen_stacks(args, cr_dump):
        stack_id = stack['stack']
        ops = [op['type'] for op in stack['ops']]
        if args.filter and not match_ops(args.filter, ops):
            continue
        if args.limit and count == args.limit:
            print('...')
            break
        print('%s\t%s' % (stack_id, ', '.join(ops)))
        count += 1
    print('Total:', count)
    return 0

def traverse_spawned_stacks(args, stack, depth, stacks, callback):
    """ recurse through spawned stacks, passing each op to the callback """
    for op in stack['ops']:
        # only filter ops in base stack
        if depth == 0 and args.filter and not re.search(args.filter, op['type']):
            continue
        if not callback(stack, op, depth):
            return False
        for spawned in op.get('spawned', []):
            s = stacks.get(spawned)
            if not s:
                continue
            if not traverse_spawned_stacks(args, s, depth + 1, stacks, callback):
                return False
    return True

def do_stack(args, cr_dump):
    """ inspect a given stack and its descendents """
    # build a lookup table of stacks by id
    stacks = {s['stack']: s for s in gen_stacks(args, cr_dump)}

    stack = stacks.get(args.stack)
    if not stack:
        print('Stack %s not found' % args.stack, file=sys.stderr)
        return 1

    do_stack.count = 0 # for use in closure
    def print_stack_op(stack, op, depth):
        indent = ' ' * depth * 4
        if args.limit and do_stack.count == args.limit:
            print('%s...' % indent)
            return False # stop traversal
        do_stack.count += 1
        print('%s[%s] %s: %s' % (indent, stack['stack'], op['type'], op_status(op)))
        return True

    traverse_spawned_stacks(args, stack, 0, stacks, print_stack_op)
    return 0

def do_spawned(args, cr_dump):
    """ search all ops for the given spawned stack """
    for stack, op in gen_ops(args, cr_dump):
        if args.stack in op.get('spawned', []):
            print('Stack %s spawned by [%s] %s' % (args.stack, stack['stack'], op['type']))
            return 0
    print('Stack %s not spawned' % args.stack, file=sys.stderr)
    return 1

def main():
    parser = argparse.ArgumentParser(description='Parse and inspect the output of the "cr dump" admin socket command.')
    parser.add_argument('--filename', type=argparse.FileType(), default=sys.stdin, help='Input filename (or stdin if empty)')
    parser.add_argument('--filter', type=str, help='Filter by coroutine type (regex syntax is supported)')
    parser.add_argument('--limit', type=int)
    parser.add_argument('--manager', type=int, help='Index into coroutine_managers[]')

    subparsers = parser.add_subparsers()

    crs_parser = subparsers.add_parser('crs', help='Produce a sorted list of coroutines')
    crs_parser.add_argument('--group', type=str, choices=['type', 'status'])
    crs_parser.add_argument('--order', type=str, choices=['desc', 'asc'])
    crs_parser.set_defaults(func=do_crs)

    stacks_parser = subparsers.add_parser('stacks', help='Produce a list of coroutine stacks and their ops')
    stacks_parser.set_defaults(func=do_stacks)

    stack_parser = subparsers.add_parser('stack', help='Inspect a given coroutine stack')
    stack_parser.add_argument('stack', type=str)
    stack_parser.set_defaults(func=do_stack)

    spawned_parser = subparsers.add_parser('spawned', help='Find the op that spawned the given stack')
    spawned_parser.add_argument('stack', type=str)
    spawned_parser.set_defaults(func=do_spawned)

    args = parser.parse_args()
    return args.func(args, json.load(args.filename))

if __name__ == "__main__":
    result = main()
    sys.exit(result)