summaryrefslogtreecommitdiffstats
path: root/src/tools/rgw
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rgw')
-rwxr-xr-xsrc/tools/rgw/parse-cr-dump.py168
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)