mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-19 16:15:07 +00:00
Fails with newer GDB with: TypeError: %x format: an integer is required, not gdb.Value Message-Id: <1488981412-22279-1-git-send-email-tgrabiec@scylladb.com>
961 lines
36 KiB
Python
961 lines
36 KiB
Python
import gdb, gdb.printing, uuid, argparse
|
|
from operator import attrgetter
|
|
from collections import defaultdict
|
|
|
|
def template_arguments(gdb_type):
|
|
n = 0
|
|
while True:
|
|
try:
|
|
yield gdb_type.template_argument(n)
|
|
n += 1
|
|
except RuntimeError:
|
|
return
|
|
|
|
def get_template_arg_with_prefix(gdb_type, prefix):
|
|
for arg in template_arguments(gdb_type):
|
|
if str(arg).startswith(prefix):
|
|
return arg
|
|
|
|
def get_base_class_offset(gdb_type, base_class_name):
|
|
name_pattern = re.escape(base_class_name) + "(<.*>)?$"
|
|
for field in gdb_type.fields():
|
|
if field.is_base_class and re.match(name_pattern, field.name):
|
|
return field.bitpos / 8
|
|
|
|
class intrusive_list:
|
|
size_t = gdb.lookup_type('size_t')
|
|
|
|
def __init__(self, list_ref):
|
|
list_type = list_ref.type.strip_typedefs()
|
|
self.node_type = list_type.template_argument(0)
|
|
rps = list_ref['data_']['root_plus_size_']
|
|
try:
|
|
self.root = rps['root_']
|
|
except:
|
|
# Some boost versions have this instead
|
|
self.root = rps['m_header']
|
|
member_hook = get_template_arg_with_prefix(list_type, "boost::intrusive::member_hook")
|
|
if member_hook:
|
|
self.link_offset = member_hook.template_argument(2).cast(self.size_t)
|
|
else:
|
|
self.link_offset = get_base_class_offset(self.node_type, "boost::intrusive::list_base_hook")
|
|
if self.link_offset == None:
|
|
raise Exception("Class does not extend list_base_hook: " + str(self.node_type))
|
|
|
|
def __iter__(self):
|
|
hook = self.root['next_']
|
|
while hook != self.root.address:
|
|
node_ptr = hook.cast(self.size_t) - self.link_offset
|
|
yield node_ptr.cast(self.node_type.pointer()).dereference()
|
|
hook = hook['next_']
|
|
|
|
def __nonzero__(self):
|
|
return self.root['next_'] != self.root.address
|
|
|
|
def __bool__(self):
|
|
return self.__nonzero__()
|
|
|
|
class std_array:
|
|
def __init__(self, ref):
|
|
self.ref = ref
|
|
|
|
def __len__(self):
|
|
elems = self.ref['_M_elems']
|
|
return elems.type.sizeof / elems[0].type.sizeof
|
|
|
|
def __iter__(self):
|
|
elems = self.ref['_M_elems']
|
|
count = self.__len__()
|
|
i = 0
|
|
while i < count:
|
|
yield elems[i]
|
|
i += 1
|
|
|
|
def __nonzero__(self):
|
|
return self.__len__() > 0
|
|
|
|
def __bool__(self):
|
|
return self.__nonzero__()
|
|
|
|
class std_vector:
|
|
def __init__(self, ref):
|
|
self.ref = ref
|
|
|
|
def __len__(self):
|
|
return self.ref['_M_impl']['_M_finish'] - self.ref['_M_impl']['_M_start']
|
|
|
|
def __iter__(self):
|
|
i = self.ref['_M_impl']['_M_start']
|
|
end = self.ref['_M_impl']['_M_finish']
|
|
while i != end:
|
|
yield i.dereference()
|
|
i += 1
|
|
|
|
def __nonzero__(self):
|
|
return self.__len__() > 0
|
|
|
|
def __bool__(self):
|
|
return self.__nonzero__()
|
|
|
|
def uint64_t(val):
|
|
val = int(val)
|
|
if val < 0:
|
|
val += 1 << 64
|
|
return val
|
|
|
|
class sstring_printer(gdb.printing.PrettyPrinter):
|
|
'print an sstring'
|
|
def __init__(self, val):
|
|
self.val = val
|
|
def to_string(self):
|
|
if self.val['u']['internal']['size'] >= 0:
|
|
array = self.val['u']['internal']['str']
|
|
len = int(self.val['u']['internal']['size'])
|
|
return ''.join([chr(array[x]) for x in range(len)])
|
|
else:
|
|
return self.val['u']['external']['str']
|
|
def display_hint(self):
|
|
return 'string'
|
|
|
|
class uuid_printer(gdb.printing.PrettyPrinter):
|
|
'print a uuid'
|
|
def __init__(self, val):
|
|
self.val = val
|
|
def to_string(self):
|
|
msb = uint64_t(self.val['most_sig_bits'])
|
|
lsb = uint64_t(self.val['least_sig_bits'])
|
|
return str(uuid.UUID(int=(msb << 64) | lsb))
|
|
def display_hint(self):
|
|
return 'string'
|
|
|
|
|
|
def build_pretty_printer():
|
|
pp = gdb.printing.RegexpCollectionPrettyPrinter('scylla')
|
|
pp.add_printer('sstring', r'^basic_sstring<char,.*>$', sstring_printer)
|
|
pp.add_printer('uuid', r'^utils::UUID$', uuid_printer)
|
|
return pp
|
|
|
|
gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer(), replace=True)
|
|
|
|
def cpus():
|
|
return int(gdb.parse_and_eval('::smp::count'))
|
|
|
|
def find_db(shard):
|
|
return gdb.parse_and_eval('::debug::db')['_instances']['_M_impl']['_M_start'][shard]['service']['_p']
|
|
|
|
def find_dbs():
|
|
return [find_db(shard) for shard in range(cpus())]
|
|
|
|
def list_unordered_map(map):
|
|
kt = map.type.template_argument(0)
|
|
vt = map.type.template_argument(1)
|
|
value_type = gdb.lookup_type('::std::pair<{} const, {}>'.format(kt.name, vt.name))
|
|
hashnode_ptr_type = gdb.lookup_type('::std::__detail::_Hash_node<' + value_type.name + ', true>').pointer()
|
|
h = map['_M_h']
|
|
p = h['_M_before_begin']['_M_nxt']
|
|
while p:
|
|
pc = p.cast(hashnode_ptr_type)['_M_storage']['_M_storage']['__data'].cast(value_type.pointer())
|
|
yield (pc['first'], pc['second'].address)
|
|
p = p['_M_nxt']
|
|
raise StopIteration()
|
|
|
|
class scylla(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND, True)
|
|
|
|
class scylla_databases(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla databases', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
for shard in range(cpus()):
|
|
db = find_db(shard)
|
|
gdb.write('{:5} (database*){}\n'.format(shard, db))
|
|
|
|
class scylla_keyspaces(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla keyspaces', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
for shard in range(cpus()):
|
|
db = find_db(shard)
|
|
keyspaces = db['_keyspaces']
|
|
for (key, value) in list_unordered_map(keyspaces):
|
|
gdb.write('{:5} {:20} (keyspace*){}\n'.format(shard, key, value))
|
|
|
|
class scylla_column_families(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla column_families', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
for shard in range(cpus()):
|
|
db = find_db(shard)
|
|
cfs = db['_column_families']
|
|
for (key, value) in list_unordered_map(cfs):
|
|
value = value['_p']['_value'] # it's a lw_shared_ptr
|
|
schema = value['_schema']['_p'].reinterpret_cast(gdb.lookup_type('schema').pointer())
|
|
name = str(schema['_raw']['_ks_name']) + '/' + str(schema['_raw']['_cf_name'])
|
|
schema_version = str(schema['_raw']['_version'])
|
|
gdb.write('{:5} {} v={} {:45} (column_family*){}\n'.format(shard, key, schema_version, name, value.address))
|
|
|
|
class scylla_memory(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla memory', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
cpu_mem = gdb.parse_and_eval('\'memory::cpu_mem\'')
|
|
page_size = int(gdb.parse_and_eval('\'memory::page_size\''))
|
|
free_mem = int(cpu_mem['nr_free_pages']) * page_size
|
|
total_mem = int(cpu_mem['nr_pages']) * page_size
|
|
gdb.write('Used memory: {used_mem:>13}\nFree memory: {free_mem:>13}\nTotal memory: {total_mem:>12}\n\n'
|
|
.format(used_mem=total_mem-free_mem, free_mem=free_mem, total_mem=total_mem))
|
|
|
|
gdb.write('Small pools:\n')
|
|
small_pools = cpu_mem['small_pools']
|
|
nr = small_pools['nr_small_pools']
|
|
gdb.write('{objsize:>5} {span_size:>6} {use_count:>10} {memory:>12} {wasted_percent:>5}\n'
|
|
.format(objsize='objsz', span_size='spansz', use_count='usedobj', memory='memory', wasted_percent='wst%'))
|
|
for i in range(int(nr)):
|
|
sp = small_pools['_u']['a'][i]
|
|
object_size = int(sp['_object_size'])
|
|
span_size = int(sp['_span_size']) * page_size
|
|
free_count = int(sp['_free_count'])
|
|
spans_in_use = int(sp['_spans_in_use'])
|
|
memory = spans_in_use * span_size
|
|
use_count = spans_in_use * int(span_size / object_size) - free_count
|
|
wasted = free_count * object_size
|
|
wasted_percent = wasted * 100.0 / memory if memory else 0
|
|
gdb.write('{objsize:5} {span_size:6} {use_count:10} {memory:12} {wasted_percent:5.1f}\n'
|
|
.format(objsize=object_size, span_size=span_size, use_count=use_count, memory=memory, wasted_percent=wasted_percent))
|
|
|
|
gdb.write('Page spans:\n')
|
|
gdb.write('{index:5} {size:>13} {total}\n'.format(index="index", size="size [B]", total="free [B]"))
|
|
for index in range(int(cpu_mem['nr_span_lists'])):
|
|
span_list = cpu_mem['fsu']['free_spans'][index]
|
|
front = int(span_list['_front'])
|
|
pages = cpu_mem['pages']
|
|
total = 0
|
|
while front:
|
|
span = pages[front]
|
|
total += int(span['span_size'])
|
|
front = int(span['link']['_next'])
|
|
gdb.write('{index:5} {size:13} {total}\n'.format(index=index, size=(1<<index)*page_size, total=total*page_size))
|
|
|
|
|
|
|
|
class TreeNode(object):
|
|
def __init__(self, key):
|
|
self.key = key
|
|
self.children_by_key = {}
|
|
|
|
def get_or_add(self, key):
|
|
node = self.children_by_key.get(key, None)
|
|
if not node:
|
|
node = self.__class__(key)
|
|
self.add(node)
|
|
return node
|
|
|
|
def add(self, node):
|
|
self.children_by_key[node.key] = node
|
|
|
|
def squash_child(self):
|
|
assert self.has_only_one_child()
|
|
self.children_by_key = next(iter(self.children)).children_by_key
|
|
|
|
@property
|
|
def children(self):
|
|
return self.children_by_key.values()
|
|
|
|
def has_only_one_child(self):
|
|
return len(self.children_by_key) == 1
|
|
|
|
def has_children(self):
|
|
return bool(self.children_by_key)
|
|
|
|
def remove_all(self):
|
|
self.children_by_key.clear()
|
|
|
|
class ProfNode(TreeNode):
|
|
def __init__(self, key):
|
|
super(ProfNode, self).__init__(key)
|
|
self.size = 0
|
|
self.count = 0
|
|
self.tail = []
|
|
|
|
@property
|
|
def attributes(self):
|
|
return {
|
|
'size': self.size,
|
|
'count': self.count
|
|
}
|
|
|
|
def collapse_similar(node):
|
|
while node.has_only_one_child():
|
|
child = next(iter(node.children))
|
|
if node.attributes == child.attributes:
|
|
node.squash_child()
|
|
node.tail.append(child.key)
|
|
else:
|
|
break
|
|
|
|
for child in node.children:
|
|
collapse_similar(child)
|
|
|
|
def strip_level(node, level):
|
|
if level <= 0:
|
|
node.remove_all()
|
|
else:
|
|
for child in node.children:
|
|
strip_level(child, level - 1)
|
|
|
|
def print_tree(root_node,
|
|
formatter=attrgetter('key'),
|
|
order_by=attrgetter('key'),
|
|
printer=sys.stdout.write,
|
|
node_filter=None):
|
|
|
|
def print_node(node, is_last_history):
|
|
stems = (" | ", " ")
|
|
branches = (" |-- ", " \-- ")
|
|
|
|
label_lines = formatter(node).rstrip('\n').split('\n')
|
|
prefix_without_branch = ''.join(map(stems.__getitem__, is_last_history[:-1]))
|
|
|
|
if is_last_history:
|
|
printer(prefix_without_branch)
|
|
printer(branches[is_last_history[-1]])
|
|
printer("%s\n" % label_lines[0])
|
|
|
|
for line in label_lines[1:]:
|
|
printer(''.join(map(stems.__getitem__, is_last_history)))
|
|
printer("%s\n" % line)
|
|
|
|
children = sorted(filter(node_filter, node.children), key=order_by)
|
|
if children:
|
|
for child in children[:-1]:
|
|
print_node(child, is_last_history + [False])
|
|
print_node(children[-1], is_last_history + [True])
|
|
|
|
is_last = not is_last_history or is_last_history[-1]
|
|
if not is_last:
|
|
printer("%s%s\n" % (prefix_without_branch, stems[False]))
|
|
|
|
if not node_filter or node_filter(root_node):
|
|
print_node(root_node, [])
|
|
|
|
|
|
class scylla_heapprof(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla heapprof', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
|
|
def invoke(self, arg, from_tty):
|
|
parser = argparse.ArgumentParser(description="scylla heapprof")
|
|
parser.add_argument("-G", "--inverted", action="store_true",
|
|
help="Compute caller-first profile instead of callee-first")
|
|
parser.add_argument("-a", "--addresses", action="store_true",
|
|
help="Show raw addresses before resolved symbol names")
|
|
parser.add_argument("--no-symbols", action="store_true",
|
|
help="Show only raw addresses")
|
|
parser.add_argument("--flame", action="store_true",
|
|
help="Write flamegraph data to heapprof.stacks instead of showing the profile")
|
|
parser.add_argument("--min", action="store", type=int, default=0,
|
|
help="Drop branches allocating less than given amount")
|
|
try:
|
|
args = parser.parse_args(arg.split())
|
|
except SystemExit:
|
|
return
|
|
|
|
root = ProfNode(None)
|
|
cpu_mem = gdb.parse_and_eval('\'memory::cpu_mem\'')
|
|
site = cpu_mem['alloc_site_list_head']
|
|
|
|
while site:
|
|
size = int(site['size'])
|
|
count = int(site['count'])
|
|
if size:
|
|
n = root
|
|
n.size += size
|
|
n.count += count
|
|
addresses = list(map(int, std_vector(site['backtrace'])))
|
|
addresses.pop(0) # drop memory::get_backtrace()
|
|
if args.inverted:
|
|
seq = reversed(addresses)
|
|
else:
|
|
seq = addresses
|
|
for addr in seq:
|
|
n = n.get_or_add(addr)
|
|
n.size += size
|
|
n.count += count
|
|
site = site['next']
|
|
|
|
def resolver(addr):
|
|
if args.no_symbols:
|
|
return '0x%x' % addr
|
|
if args.addresses:
|
|
return '0x%x %s' % (addr, resolve(addr) or '')
|
|
return resolve(addr) or ('0x%x' % addr)
|
|
|
|
if args.flame:
|
|
file_name = 'heapprof.stacks'
|
|
with open(file_name, 'w') as out:
|
|
trace = list()
|
|
def print_node(n):
|
|
if n.key:
|
|
trace.append(n.key)
|
|
trace.extend(n.tail)
|
|
for c in n.children:
|
|
print_node(c)
|
|
if not n.has_children():
|
|
out.write("%s %d\n" % (';'.join(map(lambda x: '%s (#%d)' % (x, n.count), map(resolver, trace))), n.size))
|
|
if n.key:
|
|
del trace[-1 - len(n.tail):]
|
|
print_node(root)
|
|
gdb.write('Wrote %s\n' % (file_name))
|
|
else:
|
|
def node_formatter(n):
|
|
if n.key is None:
|
|
name = "All"
|
|
else:
|
|
name = resolver(n.key)
|
|
return "%s (%d, #%d)\n%s" % (name, n.size, n.count, '\n'.join(map(resolver, n.tail)))
|
|
|
|
def node_filter(n):
|
|
return n.size >= args.min
|
|
|
|
collapse_similar(root)
|
|
print_tree(root,
|
|
formatter=node_formatter,
|
|
order_by=lambda n: -n.size,
|
|
node_filter=node_filter,
|
|
printer=gdb.write)
|
|
|
|
def get_seastar_memory_start_and_size():
|
|
cpu_mem = gdb.parse_and_eval('\'memory::cpu_mem\'')
|
|
page_size = int(gdb.parse_and_eval('\'memory::page_size\''))
|
|
total_mem = int(cpu_mem['nr_pages']) * page_size
|
|
start = int(cpu_mem['memory'])
|
|
return start, total_mem
|
|
|
|
def seastar_memory_layout():
|
|
results = []
|
|
for t in reactor_threads():
|
|
start, total_mem = get_seastar_memory_start_and_size()
|
|
results.append((t, start, total_mem))
|
|
return results
|
|
|
|
def get_thread_owning_memory(ptr):
|
|
for t in reactor_threads():
|
|
start, size = get_seastar_memory_start_and_size()
|
|
if start <= ptr < start + size:
|
|
return t
|
|
|
|
class scylla_ptr(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla ptr', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
ptr = int(arg, 0)
|
|
|
|
owning_thread = None
|
|
for t, start, size in seastar_memory_layout():
|
|
if ptr >= start and ptr < start + size:
|
|
owning_thread = t
|
|
break
|
|
|
|
if not owning_thread:
|
|
gdb.write("Not managed by seastar\n")
|
|
return
|
|
|
|
msg = "thread %d" % t.num
|
|
|
|
owning_thread.switch()
|
|
|
|
cpu_mem = gdb.parse_and_eval('\'memory::cpu_mem\'')
|
|
page_size = int(gdb.parse_and_eval('\'memory::page_size\''))
|
|
offset = ptr - int(cpu_mem['memory'])
|
|
|
|
page = cpu_mem['pages'][offset / page_size];
|
|
if page['free']:
|
|
msg += ', page is free'
|
|
gdb.write(msg + '\n')
|
|
return
|
|
|
|
pool = page['pool']
|
|
offset_in_span = int(page['offset_in_span']) * page_size + ptr % page_size
|
|
first_page_in_span = cpu_mem['pages'][offset / page_size - page['offset_in_span']];
|
|
if pool:
|
|
object_size = int(pool['_object_size'])
|
|
msg += ', small (size <= %d)' % object_size
|
|
offset_in_object = offset_in_span % object_size
|
|
free_object_ptr = gdb.lookup_type('void').pointer().pointer()
|
|
char_ptr = gdb.lookup_type('char').pointer()
|
|
# pool's free list
|
|
next_free = pool['_free']
|
|
free = False
|
|
while next_free:
|
|
if ptr >= next_free and ptr < next_free.reinterpret_cast(char_ptr) + object_size:
|
|
free = True
|
|
break
|
|
next_free = next_free.reinterpret_cast(free_object_ptr).dereference()
|
|
if not free:
|
|
# span's free list
|
|
next_free = first_page_in_span['freelist']
|
|
while next_free:
|
|
if ptr >= next_free and ptr < next_free.reinterpret_cast(char_ptr) + object_size:
|
|
free = True
|
|
break
|
|
next_free = next_free.reinterpret_cast(free_object_ptr).dereference()
|
|
if free:
|
|
msg += ', free'
|
|
else:
|
|
msg += ', live (0x%x +%d)' % (ptr - offset_in_object, offset_in_object)
|
|
else:
|
|
msg += ', large'
|
|
|
|
# FIXME: handle debug-mode build
|
|
segment_size = int(gdb.parse_and_eval('\'logalloc::segment\'::size'))
|
|
index = gdb.parse_and_eval('(%d - \'logalloc::shard_segment_pool\'._segments_base) / \'logalloc::segment\'::size' % (ptr))
|
|
desc = gdb.parse_and_eval('\'logalloc::shard_segment_pool\'._segments._M_impl._M_start[%d]' % (index))
|
|
if desc['_lsa_managed']:
|
|
msg += ', LSA-managed'
|
|
|
|
gdb.write(msg + '\n')
|
|
|
|
class scylla_segment_descs(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla segment-descs', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
# FIXME: handle debug-mode build
|
|
base = int(gdb.parse_and_eval('\'logalloc\'::shard_segment_pool._segments_base'))
|
|
segment_size = int(gdb.parse_and_eval('\'logalloc\'::segment::size'))
|
|
addr = base
|
|
for desc in std_vector(gdb.parse_and_eval('\'logalloc\'::shard_segment_pool._segments')):
|
|
if desc['_lsa_managed']:
|
|
gdb.write('0x%x: lsa free=%d region=0x%x zone=0x%x\n' % (addr, desc['_free_space'], desc['_region'], desc['_zone']))
|
|
else:
|
|
gdb.write('0x%x: std\n' % (addr))
|
|
addr += segment_size
|
|
|
|
class scylla_lsa(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla lsa', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
lsa = gdb.parse_and_eval('\'logalloc::shard_segment_pool\'')
|
|
segment_size = int(gdb.parse_and_eval('\'logalloc::segment::size\''))
|
|
|
|
lsa_mem = int(lsa['_segments_in_use']) * segment_size
|
|
non_lsa_mem = int(lsa['_non_lsa_memory_in_use'])
|
|
total_mem = lsa_mem + non_lsa_mem
|
|
gdb.write('Log Structured Allocator\n\nLSA memory in use: {lsa_mem:>16}\n'
|
|
'Non-LSA memory in use: {non_lsa_mem:>12}\nTotal memory in use: {total_mem:>14}\n\n'
|
|
.format(lsa_mem=lsa_mem, non_lsa_mem = non_lsa_mem, total_mem = total_mem))
|
|
|
|
er_goal = int(lsa['_current_emergency_reserve_goal'])
|
|
er_max = int(lsa['_emergency_reserve_max'])
|
|
er_current = int(lsa['_emergency_reserve']['_stack']['data_']['root_plus_size_']['size_'])
|
|
gdb.write('Emergency reserve goal: {er_goal:>11}\nEmergency reserve max: {er_max:>12}\n'
|
|
'Emergency reserve current: {er_current:>8}\n\n'
|
|
.format(er_goal=er_goal, er_max=er_max, er_current=er_current))
|
|
|
|
lsa_tracker = gdb.parse_and_eval('\'logalloc::tracker_instance\'._impl')['_M_t']['_M_head_impl']
|
|
regions = lsa_tracker['_regions']
|
|
region = regions['_M_impl']['_M_start']
|
|
gdb.write('LSA regions:\n')
|
|
while region != regions['_M_impl']['_M_finish']:
|
|
gdb.write(' Region #{r_id}\n - reclaimable: {r_en:>14}\n'
|
|
' - evictable: {r_ev:16}\n - non-LSA memory: {r_non_lsa:>11}\n'
|
|
' - closed LSA memory: {r_lsa:>8}\n - unused memory: {r_unused:>12}\n'
|
|
.format(r_id=int(region['_id']), r_en=bool(region['_reclaiming_enabled']),
|
|
r_ev=bool(region['_evictable']),
|
|
r_non_lsa=int(region['_non_lsa_occupancy']['_total_space']),
|
|
r_lsa=int(region['_closed_occupancy']['_total_space']),
|
|
r_unused=int(region['_closed_occupancy']['_free_space'])))
|
|
region = region + 1
|
|
|
|
def lsa_zone_tree(node):
|
|
if node:
|
|
zone = node.cast(gdb.lookup_type('::logalloc::segment_zone').pointer())
|
|
|
|
for x in lsa_zone_tree(node['left_']):
|
|
yield x
|
|
|
|
yield zone
|
|
|
|
for x in lsa_zone_tree(node['right_']):
|
|
yield x
|
|
|
|
class scylla_lsa_zones(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla lsa_zones', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
gdb.write('LSA zones:\n')
|
|
all_zones = gdb.parse_and_eval('\'logalloc::shard_segment_pool\'._all_zones')
|
|
for zone in lsa_zone_tree(all_zones['holder']['root']['parent_']):
|
|
gdb.write(' Zone:\n - base: {z_base:08X}\n - size: {z_size:>12}\n'
|
|
' - used: {z_used:>12}\n'
|
|
.format(z_base=int(zone['_base']), z_size=int(zone['_segments']['_bits_count']),
|
|
z_used=int(zone['_used_segment_count'])));
|
|
|
|
names = {} # addr (int) -> name (str)
|
|
def resolve(addr):
|
|
if addr in names:
|
|
return names[addr]
|
|
|
|
infosym = gdb.execute('info symbol 0x%x' % (addr), False, True)
|
|
if infosym.startswith('No symbol'):
|
|
name = None
|
|
else:
|
|
name = infosym[:infosym.find('in section')]
|
|
names[addr] = name
|
|
return name
|
|
|
|
class scylla_lsa_segment(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla lsa-segment', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
|
|
def invoke(self, arg, from_tty):
|
|
# See logalloc::region_impl::for_each_live()
|
|
ptr = int(arg, 0)
|
|
seg = gdb.parse_and_eval('(char*)(%d & ~(\'logalloc\'::segment::size - 1))' % (ptr))
|
|
segment_size = int(gdb.parse_and_eval('\'logalloc\'::segment::size'))
|
|
obj_desc_size = int(gdb.parse_and_eval('sizeof(\'logalloc\'::region_impl::object_descriptor)'))
|
|
obj_desc_ptr = gdb.lookup_type('logalloc::region_impl::object_descriptor').pointer()
|
|
offset = 0
|
|
while offset < segment_size:
|
|
padding = seg[offset] >> 2
|
|
offset += padding
|
|
if seg[offset] & 2:
|
|
break;
|
|
flags = seg[offset]
|
|
desc = (seg + offset).reinterpret_cast(obj_desc_ptr)
|
|
offset += obj_desc_size;
|
|
addr = seg + offset
|
|
if flags & 1:
|
|
migrator_name = resolve(int(desc['_migrator'])) or ('0x%x' % (addr))
|
|
gdb.write('0x%x: live size=%d migrator=%s\n' % (addr, desc['_size'], migrator_name))
|
|
else:
|
|
gdb.write('0x%x: free size=%d\n' % (addr, desc['_size']))
|
|
offset += desc['_size'];
|
|
|
|
class scylla_timers(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla timers', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
gdb.write('Timers:\n')
|
|
timer_set = gdb.parse_and_eval('local_engine->_timers')
|
|
for timer_list in std_array(timer_set['_buckets']):
|
|
for t in intrusive_list(timer_list):
|
|
gdb.write('(%s*) %s = %s\n' % (t.type, t.address, t))
|
|
timer_set = gdb.parse_and_eval('local_engine->_lowres_timers')
|
|
for timer_list in std_array(timer_set['_buckets']):
|
|
for t in intrusive_list(timer_list):
|
|
gdb.write('(%s*) %s = %s\n' % (t.type, t.address, t))
|
|
|
|
def has_reactor():
|
|
if gdb.parse_and_eval('local_engine'):
|
|
return True
|
|
return False
|
|
|
|
def reactor_threads():
|
|
orig = gdb.selected_thread()
|
|
for t in gdb.selected_inferior().threads():
|
|
t.switch()
|
|
if has_reactor():
|
|
yield t
|
|
orig.switch()
|
|
|
|
def reactors():
|
|
orig = gdb.selected_thread()
|
|
for t in gdb.selected_inferior().threads():
|
|
t.switch()
|
|
reactor = gdb.parse_and_eval('local_engine')
|
|
if reactor:
|
|
yield reactor.dereference()
|
|
orig.switch()
|
|
|
|
class scylla_apply(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla apply', gdb.COMMAND_USER, gdb.COMPLETE_COMMAND)
|
|
def invoke(self, arg, from_tty):
|
|
for r in reactors():
|
|
gdb.write("\nShard %d: \n\n" % (r['_id']))
|
|
gdb.execute(arg)
|
|
|
|
class scylla_shard(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla shard', gdb.COMMAND_USER, gdb.COMPLETE_NONE)
|
|
def invoke(self, arg, from_tty):
|
|
id = int(arg)
|
|
orig = gdb.selected_thread()
|
|
for t in gdb.selected_inferior().threads():
|
|
t.switch()
|
|
reactor = gdb.parse_and_eval('local_engine')
|
|
if reactor and reactor['_id'] == id:
|
|
gdb.write('Switched to thread %d\n' % t.num)
|
|
return
|
|
orig.switch()
|
|
gdb.write('Error: Shard %d not found\n' % (id))
|
|
|
|
class scylla_mem_ranges(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla mem-ranges', gdb.COMMAND_USER, gdb.COMPLETE_NONE)
|
|
def invoke(self, arg, from_tty):
|
|
for t, start, total_mem in seastar_memory_layout():
|
|
gdb.write('0x%x +%d\n' % (start, total_mem))
|
|
|
|
class scylla_mem_range(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla mem-range', gdb.COMMAND_USER, gdb.COMPLETE_NONE)
|
|
def invoke(self, arg, from_tty):
|
|
if not has_reactor():
|
|
gdb.write('Not a reactor thread')
|
|
return
|
|
gdb.write('0x%x +%d\n' % get_seastar_memory_start_and_size())
|
|
|
|
class thread_switched_in(object):
|
|
def __init__(self, gdb_thread):
|
|
self.new = gdb_thread
|
|
def __enter__(self):
|
|
self.old = gdb.selected_thread()
|
|
self.new.switch()
|
|
def __exit__(self, *_):
|
|
self.old.switch()
|
|
|
|
class seastar_thread_context(object):
|
|
ulong_type = gdb.lookup_type('unsigned long')
|
|
|
|
# FIXME: The jmpbuf interpreting code targets x86_64 and glibc 2.19
|
|
# Offsets taken from sysdeps/x86_64/jmpbuf-offsets.h.
|
|
jmpbuf_offsets = {
|
|
'rbx': 0,
|
|
'rbp': 1,
|
|
'r12': 2,
|
|
'r13': 3,
|
|
'r14': 4,
|
|
'r15': 5,
|
|
'rsp': 6,
|
|
'rip': 7,
|
|
}
|
|
mangled_registers = ['rip', 'rsp', 'rbp']
|
|
|
|
def save_regs(self):
|
|
result = {}
|
|
for reg in self.jmpbuf_offsets.keys():
|
|
result[reg] = gdb.parse_and_eval('$%s' % reg).cast(self.ulong_type)
|
|
return result
|
|
|
|
def restore_regs(self, values):
|
|
gdb.newest_frame().select()
|
|
for reg, value in values.items():
|
|
gdb.execute('set $%s = %s' % (reg, value))
|
|
|
|
def get_fs_base(self):
|
|
holder_addr = get_seastar_memory_start_and_size()[0]
|
|
holder = gdb.Value(holder_addr).reinterpret_cast(self.ulong_type.pointer())
|
|
saved = holder.dereference()
|
|
gdb.execute('set *(void**)%s = 0' % holder_addr)
|
|
if gdb.parse_and_eval('arch_prctl(0x1003, %d)' % holder_addr) != 0:
|
|
raise Exception('arch_prctl() failed')
|
|
fs_base = holder.dereference()
|
|
gdb.execute('set *(void**)%s = %s' % (holder_addr, saved))
|
|
return fs_base
|
|
|
|
def regs_from_jmpbuf(self, jmpbuf):
|
|
canary = gdb.Value(self.get_fs_base()).reinterpret_cast(self.ulong_type.pointer())[6]
|
|
result = {}
|
|
for reg, offset in self.jmpbuf_offsets.items():
|
|
value = jmpbuf['__jmpbuf'][offset].cast(self.ulong_type)
|
|
if reg in self.mangled_registers:
|
|
# glibc mangles by doing:
|
|
# xor %reg, %fs:0x30
|
|
# rol %reg, $0x11
|
|
bits = 64
|
|
shift = 0x11
|
|
value = (value << (bits-shift)) & (2**bits-1) | (value >> shift)
|
|
value = value ^ canary
|
|
result[reg] = value
|
|
return result
|
|
|
|
def is_switched_in(self):
|
|
jmpbuf_link_ptr = gdb.parse_and_eval('seastar::g_current_context')
|
|
if jmpbuf_link_ptr['thread'] == self.thread_ctx.address:
|
|
return True
|
|
return False
|
|
|
|
def __init__(self, thread_ctx):
|
|
self.thread_ctx = thread_ctx
|
|
self.old_frame = gdb.selected_frame()
|
|
self.old_regs = self.save_regs()
|
|
self.old_gdb_thread = gdb.selected_thread()
|
|
self.gdb_thread = get_thread_owning_memory(thread_ctx.address)
|
|
self.new_regs = None
|
|
|
|
def __enter__(self):
|
|
gdb.write('Switched to thread %d, (seastar::thread_context*) 0x%x\n' % (self.gdb_thread.num, self.thread_ctx.address))
|
|
self.gdb_thread.switch()
|
|
if not self.is_switched_in():
|
|
self.new_regs = self.regs_from_jmpbuf(self.thread_ctx['_context']['jmpbuf'])
|
|
self.restore_regs(self.new_regs)
|
|
|
|
def __exit__(self, *_):
|
|
if self.new_regs:
|
|
self.gdb_thread.switch()
|
|
self.restore_regs(self.old_regs)
|
|
self.old_gdb_thread.switch()
|
|
self.old_frame.select()
|
|
gdb.write('Switched to thread %d\n' % self.old_gdb_thread.num)
|
|
|
|
active_thread_context = None
|
|
|
|
def exit_thread_context():
|
|
global active_thread_context
|
|
if active_thread_context:
|
|
active_thread_context.__exit__()
|
|
active_thread_context = None
|
|
|
|
def seastar_threads_on_current_shard():
|
|
return intrusive_list(gdb.parse_and_eval('\'seastar::thread_context::_all_threads\''))
|
|
|
|
class scylla_thread(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla thread', gdb.COMMAND_USER,
|
|
gdb.COMPLETE_COMMAND, True)
|
|
def invoke_apply_all(self, args):
|
|
for r in reactors():
|
|
for t in seastar_threads_on_current_shard():
|
|
gdb.write('\n[shard %2d] (seastar::thread_context*) 0x%x:\n\n' % (r['_id'], int(t.address)))
|
|
with seastar_thread_context(t):
|
|
gdb.execute(' '.join(args))
|
|
|
|
def print_usage(self):
|
|
gdb.write("""Missing argument. Usage:
|
|
|
|
scylla thread <seastar::thread_context pointer> - switches to given seastar thread
|
|
scylla thread apply all <cmd> - executes cmd in the context of each seastar thread
|
|
|
|
""")
|
|
|
|
def invoke(self, arg, for_tty):
|
|
args = arg.split()
|
|
|
|
if len(args) < 1:
|
|
self.print_usage()
|
|
return
|
|
|
|
if args[0] == 'apply':
|
|
args.pop(0)
|
|
if len(args) < 2 or args[0] != 'all':
|
|
self.print_usage()
|
|
return
|
|
args.pop(0)
|
|
self.invoke_apply_all(args)
|
|
return
|
|
|
|
addr = gdb.parse_and_eval(args[0])
|
|
ctx = addr.reinterpret_cast(gdb.lookup_type('seastar::thread_context').pointer()).dereference()
|
|
exit_thread_context()
|
|
global active_thread_context
|
|
active_thread_context = seastar_thread_context(ctx)
|
|
active_thread_context.__enter__()
|
|
|
|
class scylla_unthread(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla unthread', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
def invoke(self, arg, for_tty):
|
|
exit_thread_context()
|
|
|
|
class scylla_threads(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla threads', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
def invoke(self, arg, for_tty):
|
|
for r in reactors():
|
|
shard = r['_id']
|
|
for t in seastar_threads_on_current_shard():
|
|
gdb.write('[shard %2d] (seastar::thread_context*) 0x%x\n' % (shard, int(t.address)))
|
|
|
|
class circular_buffer(object):
|
|
def __init__(self, ref):
|
|
self.ref = ref
|
|
def __iter__(self):
|
|
impl = self.ref['_impl']
|
|
st = impl['storage']
|
|
cap = impl['capacity']
|
|
i = impl['begin']
|
|
end = impl['end']
|
|
while i < end:
|
|
yield st[i % cap]
|
|
i += 1
|
|
|
|
# Prints histogram of task types in reactor's pending task queue.
|
|
#
|
|
# Example:
|
|
# (gdb) scylla task-stats
|
|
# 16243: 0x18904f0 vtable for lambda_task<later()::{lambda()#1}> + 16
|
|
# 16091: 0x197fc60 _ZTV12continuationIZN6futureIJEE12then_wrappedIZNS1_16handle_exception...
|
|
# 16090: 0x19bab50 _ZTV12continuationIZN6futureIJEE12then_wrappedINS1_12finally_bodyIZN7s...
|
|
# 14280: 0x1b36940 _ZTV12continuationIZN6futureIJEE12then_wrappedIZN17smp_message_queue15...
|
|
#
|
|
# ^ ^ ^
|
|
# | | '-- symbol name for vtable pointer
|
|
# | '------------ vtable pointer for the object pointed to by task*
|
|
# '------------------- task count
|
|
#
|
|
class scylla_task_stats(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla task-stats', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
def invoke(self, arg, for_tty):
|
|
vptr_count = defaultdict(int)
|
|
vptr_type = gdb.lookup_type('uintptr_t').pointer()
|
|
for t in circular_buffer(gdb.parse_and_eval('local_engine._pending_tasks')):
|
|
vptr = int(t['_M_t']['_M_head_impl'].reinterpret_cast(vptr_type).dereference())
|
|
vptr_count[vptr] += 1
|
|
for vptr, count in sorted(vptr_count.items(), key=lambda e: -e[1]):
|
|
gdb.write('%10d: 0x%x %s\n' % (count, vptr, resolve(vptr)))
|
|
|
|
|
|
# Prints contents of reactor pending tasks queue.
|
|
#
|
|
# Example:
|
|
# (gdb) scylla tasks
|
|
# (task*) 0x60017d8c7f88 _ZTV12continuationIZN6futureIJEE12then_wrappedIZN17smp_message_queu...
|
|
# (task*) 0x60019a391730 _ZTV12continuationIZN6futureIJEE12then_wrappedIZNS1_16handle_except...
|
|
# (task*) 0x60018fac2208 vtable for lambda_task<later()::{lambda()#1}> + 16
|
|
# (task*) 0x60016e8b7428 _ZTV12continuationIZN6futureIJEE12then_wrappedINS1_12finally_bodyIZ...
|
|
# (task*) 0x60017e5bece8 _ZTV12continuationIZN6futureIJEE12then_wrappedINS1_12finally_bodyIZ...
|
|
# (task*) 0x60017e7f8aa0 _ZTV12continuationIZN6futureIJEE12then_wrappedIZNS1_16handle_except...
|
|
# (task*) 0x60018fac21e0 vtable for lambda_task<later()::{lambda()#1}> + 16
|
|
# (task*) 0x60016e8b7540 _ZTV12continuationIZN6futureIJEE12then_wrappedINS1_12finally_bodyIZ...
|
|
# (task*) 0x600174c34d58 _ZTV12continuationIZN6futureIJEE12then_wrappedINS1_12finally_bodyIZ...
|
|
#
|
|
# ^ ^
|
|
# | |
|
|
# | '------------ symbol name for task's vtable pointer
|
|
# '---------------------------- task pointer
|
|
#
|
|
class scylla_tasks(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, 'scylla tasks', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
def invoke(self, arg, for_tty):
|
|
vptr_type = gdb.lookup_type('uintptr_t').pointer()
|
|
for t in circular_buffer(gdb.parse_and_eval('local_engine._pending_tasks')):
|
|
ptr = t['_M_t']['_M_head_impl']
|
|
vptr = int(ptr.reinterpret_cast(vptr_type).dereference())
|
|
gdb.write('(task*) 0x%x %s\n' % (ptr, resolve(vptr)))
|
|
|
|
scylla()
|
|
scylla_databases()
|
|
scylla_keyspaces()
|
|
scylla_column_families()
|
|
scylla_memory()
|
|
scylla_ptr()
|
|
scylla_mem_ranges()
|
|
scylla_mem_range()
|
|
scylla_heapprof()
|
|
scylla_lsa()
|
|
scylla_lsa_zones()
|
|
scylla_lsa_segment()
|
|
scylla_segment_descs()
|
|
scylla_timers()
|
|
scylla_apply()
|
|
scylla_shard()
|
|
scylla_thread()
|
|
scylla_unthread()
|
|
scylla_threads()
|
|
scylla_task_stats()
|
|
scylla_tasks()
|