diff options
Diffstat (limited to 'src/tools/rgw')
-rwxr-xr-x | src/tools/rgw/parse-cr-dump.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/tools/rgw/parse-cr-dump.py b/src/tools/rgw/parse-cr-dump.py new file mode 100755 index 00000000..539929b1 --- /dev/null +++ b/src/tools/rgw/parse-cr-dump.py @@ -0,0 +1,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) |