GDB automation with Python script to measure memory usage in dashd (#1609)
* standart STL containers and Masternode-related classes processing * masternode-related classes * fix st containers processing, use reflection to process simple classes * Content descrioption in README.md * Increase python scripts performance Use gdb objects instead strings and type info for creating Pyton wrappers * fixed simple classes list * addition to README * fix nits * missed `the` Co-Authored-By: gladcow <sergey@dash.org> * fix nits Co-Authored-By: gladcow <sergey@dash.org> * fixed grammatical issues Co-Authored-By: gladcow <sergey@dash.org> * fixed phrase construction Co-Authored-By: gladcow <sergey@dash.org> * missed point Co-Authored-By: gladcow <sergey@dash.org> * fixed grammatical issues Co-Authored-By: gladcow <sergey@dash.org> * remove double space Co-Authored-By: gladcow <sergey@dash.org> * fixed grammatical issues Co-Authored-By: gladcow <sergey@dash.org>
This commit is contained in:
parent
d998dc13ed
commit
1c9ed7806a
45
contrib/auto_gdb/README.md
Normal file
45
contrib/auto_gdb/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Contents
|
||||
This directory contains tools to automatically get data about the memory consumption by some objects in dashd process with the help of GDB debugger.
|
||||
|
||||
## dash_dbg.sh
|
||||
This shell script attaches GDB to the running dashd process (should be built with debug info), executes debug.gdb script and detaches.
|
||||
By default it uses testnet settings, see script comments to attach it to mainnet dashd.
|
||||
|
||||
## debug.gdb
|
||||
Contains debugger instructions to execute during attach: loads python code and executes it for the objects we want to investigate.
|
||||
|
||||
## log_size.py
|
||||
Contains definition of the gdb command log_size. After this script loads it can be called from gdb command line or other gdb scripts.
|
||||
Command params:
|
||||
`log_size arg0 arg1`
|
||||
`arg0` - name of object whose memory will be written in log file
|
||||
`arg1` - name of the log file
|
||||
Example:
|
||||
```
|
||||
log_size mnodeman "memlog.txt"
|
||||
```
|
||||
|
||||
## used_size.py
|
||||
Contains definition of the gdb command used_size. After loading of this script it can be called from gdb command line or other gdb scripts.
|
||||
Command params:
|
||||
`used_size arg0 arg1`
|
||||
`arg0` - variable to store memory used by the object
|
||||
`arg1` - name of object whose memory will be calculated and stored in the first argument
|
||||
Example:
|
||||
```
|
||||
>(gdb) set $size = 0
|
||||
>(gdb) used_size $size mnodeman
|
||||
>(gdb) p $size
|
||||
```
|
||||
|
||||
## stl_containers.py
|
||||
Contains helper classes to calculate memory used by the STL containers (list, vector, map, set, pair).
|
||||
|
||||
## simple_class_obj.py
|
||||
Contains a helper class to calculate the memory used by an object as a sum of the memory used by its fields.
|
||||
All processed objects of such type are listed in the this file, you can add new types you are interested in to this list.
|
||||
If a type is not listed here, its size is the return of sizeof (except STL containers which are processed in stl_containers.py).
|
||||
|
||||
## common_helpers.py
|
||||
Several helper functions that are used in other python code.
|
||||
|
57
contrib/auto_gdb/common_helpers.py
Normal file
57
contrib/auto_gdb/common_helpers.py
Normal file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError as e:
|
||||
raise ImportError("This script must be run in GDB: ", str(e))
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
import stl_containers
|
||||
import simple_class_obj
|
||||
|
||||
SIZE_OF_INT = 4
|
||||
SIZE_OF_BOOL = 1
|
||||
SIZE_OF_INT64 = 8
|
||||
SIZE_OF_UINT256 = 32
|
||||
|
||||
|
||||
def get_special_type_obj(gobj):
|
||||
obj_type = gobj.type.strip_typedefs()
|
||||
if stl_containers.VectorObj.is_this_type(obj_type):
|
||||
return stl_containers.VectorObj(gobj)
|
||||
if stl_containers.ListObj.is_this_type(obj_type):
|
||||
return stl_containers.ListObj(gobj)
|
||||
if stl_containers.PairObj.is_this_type(obj_type):
|
||||
return stl_containers.PairObj(gobj)
|
||||
if stl_containers.MapObj.is_this_type(obj_type):
|
||||
return stl_containers.MapObj(gobj)
|
||||
if stl_containers.SetObj.is_this_type(obj_type):
|
||||
return stl_containers.SetObj(gobj)
|
||||
if simple_class_obj.SimpleClassObj.is_this_type(obj_type):
|
||||
return simple_class_obj.SimpleClassObj(gobj)
|
||||
return False
|
||||
|
||||
|
||||
def is_special_type(obj_type):
|
||||
if stl_containers.VectorObj.is_this_type(obj_type):
|
||||
return True
|
||||
if stl_containers.ListObj.is_this_type(obj_type):
|
||||
return True
|
||||
if stl_containers.PairObj.is_this_type(obj_type):
|
||||
return True
|
||||
if stl_containers.MapObj.is_this_type(obj_type):
|
||||
return True
|
||||
if stl_containers.SetObj.is_this_type(obj_type):
|
||||
return True
|
||||
if simple_class_obj.SimpleClassObj.is_this_type(obj_type):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_instance_size(gobj):
|
||||
obj = get_special_type_obj(gobj)
|
||||
if not obj:
|
||||
return gobj.type.sizeof
|
||||
return obj.get_used_size()
|
4
contrib/auto_gdb/dash_dbg.sh
Executable file
4
contrib/auto_gdb/dash_dbg.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# use testnet settings, if you need mainnet, use ~/.dashcore/dashd.pid file instead
|
||||
dash_pid=$(<~/.dashcore/testnet3/dashd.pid)
|
||||
sudo gdb -batch -ex "source debug.gdb" dashd ${dash_pid}
|
12
contrib/auto_gdb/debug.gdb
Normal file
12
contrib/auto_gdb/debug.gdb
Normal file
@ -0,0 +1,12 @@
|
||||
set pagination off
|
||||
source used_size.py
|
||||
source log_size.py
|
||||
source test_used_size.gdb
|
||||
#logsize privateSendClient "memlog.txt"
|
||||
#logsize privateSendServer "memlog.txt"
|
||||
#logsize mnodeman "memlog.txt"
|
||||
logsize mnpayments "memlog.txt"
|
||||
#logsize instantsend "memlog.txt"
|
||||
#logsize sporkManager "memlog.txt"
|
||||
#logsize masternodeSync "memlog.txt"
|
||||
#logsize governance "memlog.txt"
|
34
contrib/auto_gdb/log_size.py
Normal file
34
contrib/auto_gdb/log_size.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError as e:
|
||||
raise ImportError("This script must be run in GDB: ", str(e))
|
||||
import traceback
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
import common_helpers
|
||||
|
||||
|
||||
class LogSizeCommand (gdb.Command):
|
||||
"""calc size of the memory used by the object and write it to file"""
|
||||
|
||||
def __init__ (self):
|
||||
super (LogSizeCommand, self).__init__ ("logsize", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
try:
|
||||
args = gdb.string_to_argv(arg)
|
||||
obj = gdb.parse_and_eval(args[0])
|
||||
logfile = open(args[1], 'a')
|
||||
size = common_helpers.get_instance_size(obj)
|
||||
logfile.write("%s %s: %d\n" % (str(datetime.datetime.now()), args[0], size))
|
||||
logfile.close()
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise e
|
||||
|
||||
LogSizeCommand()
|
58
contrib/auto_gdb/simple_class_obj.py
Normal file
58
contrib/auto_gdb/simple_class_obj.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError as e:
|
||||
raise ImportError("This script must be run in GDB: ", str(e))
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
import common_helpers
|
||||
|
||||
|
||||
simple_types = ["CMasternode", "CMasternodeVerification",
|
||||
"CMasternodeBroadcast", "CMasternodePing",
|
||||
"CMasternodeMan", "CDarksendQueue", "CDarkSendEntry",
|
||||
"CTransaction", "CMutableTransaction", "CPrivateSendBaseSession",
|
||||
"CPrivateSendBaseManager", "CPrivateSendClientSession",
|
||||
"CPrivateSendClientManager", "CPrivateSendServer", "CMasternodePayments",
|
||||
"CMasternodePaymentVote", "CMasternodeBlockPayees",
|
||||
"CMasternodePayee", "CInstantSend", "CTxLockRequest",
|
||||
"CTxLockVote", "CTxLockCandidate", "COutPoint",
|
||||
"COutPointLock", "CSporkManager", "CMasternodeSync",
|
||||
"CGovernanceManager", "CRateCheckBuffer", "CGovernanceObject",
|
||||
"CGovernanceVote", "CGovernanceObjectVoteFile"]
|
||||
|
||||
simple_templates = ["CacheMultiMap", "CacheMap"]
|
||||
|
||||
|
||||
class SimpleClassObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
str_type = str(obj_type)
|
||||
if str_type in simple_types:
|
||||
return True
|
||||
for templ in simple_templates:
|
||||
if str_type.find(templ + "<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_used_size(self):
|
||||
size = 0
|
||||
fields = self.obj.type.fields()
|
||||
for f in fields:
|
||||
# check if it is static field
|
||||
if not hasattr(f, "bitpos"):
|
||||
continue
|
||||
# process base class size
|
||||
if f.is_base_class:
|
||||
size += common_helpers.get_instance_size(self.obj.cast(f.type.strip_typedefs()))
|
||||
continue
|
||||
# process simple field
|
||||
size += common_helpers.get_instance_size(self.obj[f.name])
|
||||
return size
|
264
contrib/auto_gdb/stl_containers.py
Normal file
264
contrib/auto_gdb/stl_containers.py
Normal file
@ -0,0 +1,264 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError as e:
|
||||
raise ImportError("This script must be run in GDB: ", str(e))
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
import common_helpers
|
||||
|
||||
|
||||
def find_type(orig, name):
|
||||
typ = orig.strip_typedefs()
|
||||
while True:
|
||||
# Strip cv qualifiers
|
||||
search = '%s::%s' % (typ.unqualified(), name)
|
||||
try:
|
||||
return gdb.lookup_type(search)
|
||||
except RuntimeError:
|
||||
pass
|
||||
# type is not found, try superclass search
|
||||
field = typ.fields()[0]
|
||||
if not field.is_base_class:
|
||||
raise ValueError("Cannot find type %s::%s" % (str(orig), name))
|
||||
typ = field.type
|
||||
|
||||
|
||||
def get_value_from_aligned_membuf(buf, valtype):
|
||||
"""Returns the value held in a __gnu_cxx::__aligned_membuf."""
|
||||
return buf['_M_storage'].address.cast(valtype.pointer()).dereference()
|
||||
|
||||
|
||||
def get_value_from_node(node):
|
||||
valtype = node.type.template_argument(0)
|
||||
return get_value_from_aligned_membuf(node['_M_storage'], valtype)
|
||||
|
||||
|
||||
class VectorObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
type_name = str(obj_type)
|
||||
if type_name.find("std::vector<") == 0:
|
||||
return True
|
||||
if type_name.find("std::__cxx11::vector<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def element_type(self):
|
||||
return self.obj.type.template_argument(0)
|
||||
|
||||
def size(self):
|
||||
return int(self.obj['_M_impl']['_M_finish'] -
|
||||
self.obj['_M_impl']['_M_start'])
|
||||
|
||||
def get_used_size(self):
|
||||
if common_helpers.is_special_type(self.element_type()):
|
||||
size = self.obj.type.sizeof
|
||||
item = self.obj['_M_impl']['_M_start']
|
||||
finish = self.obj['_M_impl']['_M_finish']
|
||||
while item != finish:
|
||||
elem = item.dereference()
|
||||
obj = common_helpers.get_special_type_obj(elem)
|
||||
size += obj.get_used_size()
|
||||
item = item + 1
|
||||
return size
|
||||
return self.obj.type.sizeof + self.size() * self.element_type().sizeof
|
||||
|
||||
|
||||
class ListObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
type_name = str(obj_type)
|
||||
if type_name.find("std::list<") == 0:
|
||||
return True
|
||||
if type_name.find("std::__cxx11::list<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def element_type(self):
|
||||
return self.obj.type.template_argument(0)
|
||||
|
||||
def get_used_size(self):
|
||||
is_special = common_helpers.is_special_type(self.element_type())
|
||||
head = self.obj['_M_impl']['_M_node']
|
||||
# nodetype = find_type(self.obj.type, '_Node')
|
||||
nodetype = head.type
|
||||
nodetype = nodetype.strip_typedefs().pointer()
|
||||
current = head['_M_next']
|
||||
size = self.obj.type.sizeof
|
||||
while current != head.address:
|
||||
if is_special:
|
||||
elem = current.cast(nodetype).dereference()
|
||||
size += common_helpers.get_instance_size(elem)
|
||||
else:
|
||||
size += self.element_type().sizeof
|
||||
current = current['_M_next']
|
||||
|
||||
return size
|
||||
|
||||
|
||||
class PairObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
type_name = str(obj_type)
|
||||
if type_name.find("std::pair<") == 0:
|
||||
return True
|
||||
if type_name.find("std::__cxx11::pair<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def key_type(self):
|
||||
return self.obj.type.template_argument(0)
|
||||
|
||||
def value_type(self):
|
||||
return self.obj.type.template_argument(1)
|
||||
|
||||
def get_used_size(self):
|
||||
if not common_helpers.is_special_type(self.key_type()) and not common_helpers.is_special_type(self.value_type()):
|
||||
return self.key_type().sizeof + self.value_type().sizeof
|
||||
|
||||
size = 0
|
||||
|
||||
if common_helpers.is_special_type(self.key_type()):
|
||||
obj = common_helpers.get_special_type_obj(self.obj['first'])
|
||||
size += obj.get_used_size()
|
||||
else:
|
||||
size += self.key_type().sizeof
|
||||
|
||||
if common_helpers.is_special_type(self.value_type()):
|
||||
obj = common_helpers.get_special_type_obj(self.obj['second'])
|
||||
size += obj.get_used_size()
|
||||
else:
|
||||
size += self.value_type().sizeof
|
||||
|
||||
return size
|
||||
|
||||
|
||||
class MapObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
self.obj_type = gobj.type
|
||||
rep_type = find_type(self.obj_type, "_Rep_type")
|
||||
self.node_type = find_type(rep_type, "_Link_type")
|
||||
self.node_type = self.node_type.strip_typedefs()
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
type_name = str(obj_type)
|
||||
if type_name.find("std::map<") == 0:
|
||||
return True
|
||||
if type_name.find("std::__cxx11::map<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def key_type(self):
|
||||
return self.obj_type.template_argument(0).strip_typedefs()
|
||||
|
||||
def value_type(self):
|
||||
return self.obj_type.template_argument(1).strip_typedefs()
|
||||
|
||||
def size(self):
|
||||
res = int(self.obj['_M_t']['_M_impl']['_M_node_count'])
|
||||
return res
|
||||
|
||||
def get_used_size(self):
|
||||
if not common_helpers.is_special_type(self.key_type()) and not common_helpers.is_special_type(self.value_type()):
|
||||
return self.obj_type.sizeof + self.size() * (self.key_type().sizeof + self.value_type().sizeof)
|
||||
if self.size() == 0:
|
||||
return self.obj_type.sizeof
|
||||
size = self.obj_type.sizeof
|
||||
row_node = self.obj['_M_t']['_M_impl']['_M_header']['_M_left']
|
||||
for i in range(self.size()):
|
||||
node_val = row_node.cast(self.node_type).dereference()
|
||||
pair = get_value_from_node(node_val)
|
||||
|
||||
obj = common_helpers.get_special_type_obj(pair)
|
||||
size += obj.get_used_size()
|
||||
|
||||
node = row_node
|
||||
if node.dereference()['_M_right']:
|
||||
node = node.dereference()['_M_right']
|
||||
while node.dereference()['_M_left']:
|
||||
node = node.dereference()['_M_left']
|
||||
else:
|
||||
parent = node.dereference()['_M_parent']
|
||||
while node == parent.dereference()['_M_right']:
|
||||
node = parent
|
||||
parent = parent.dereference()['_M_parent']
|
||||
if node.dereference()['_M_right'] != parent:
|
||||
node = parent
|
||||
row_node = node
|
||||
return size
|
||||
|
||||
|
||||
class SetObj:
|
||||
|
||||
def __init__ (self, gobj):
|
||||
self.obj = gobj
|
||||
self.obj_type = gobj.type
|
||||
rep_type = find_type(self.obj_type, "_Rep_type")
|
||||
self.node_type = find_type(rep_type, "_Link_type")
|
||||
self.node_type = self.node_type.strip_typedefs()
|
||||
|
||||
@classmethod
|
||||
def is_this_type(cls, obj_type):
|
||||
type_name = str(obj_type)
|
||||
if type_name.find("std::set<") == 0:
|
||||
return True
|
||||
if type_name.find("std::__cxx11::set<") == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def element_type(self):
|
||||
return self.obj_type.template_argument(0)
|
||||
|
||||
def size(self):
|
||||
res = int(self.obj['_M_t']['_M_impl']['_M_node_count'])
|
||||
return res
|
||||
|
||||
def get_used_size(self):
|
||||
if not common_helpers.is_special_type(self.element_type()):
|
||||
return self.obj_type.sizeof + self.size() * self.element_type().sizeof
|
||||
if self.size() == 0:
|
||||
return self.obj_type.sizeof
|
||||
size = self.obj_type.sizeof
|
||||
row_node = self.obj['_M_t']['_M_impl']['_M_header']['_M_left']
|
||||
for i in range(self.size()):
|
||||
node_val = row_node.cast(self.node_type).dereference()
|
||||
val = get_value_from_node(node_val)
|
||||
|
||||
obj = common_helpers.get_special_type_obj(val)
|
||||
size += obj.get_used_size()
|
||||
|
||||
node = row_node
|
||||
if node.dereference()['_M_right']:
|
||||
node = node.dereference()['_M_right']
|
||||
while node.dereference()['_M_left']:
|
||||
node = node.dereference()['_M_left']
|
||||
else:
|
||||
parent = node.dereference()['_M_parent']
|
||||
while node == parent.dereference()['_M_right']:
|
||||
node = parent
|
||||
parent = parent.dereference()['_M_parent']
|
||||
if node.dereference()['_M_right'] != parent:
|
||||
node = parent
|
||||
row_node = node
|
||||
return size
|
||||
|
||||
|
5
contrib/auto_gdb/test_used_size.gdb
Normal file
5
contrib/auto_gdb/test_used_size.gdb
Normal file
@ -0,0 +1,5 @@
|
||||
define test_used_size
|
||||
set $size_ext = 0
|
||||
usedsize $size_ext $arg0
|
||||
p $size_ext
|
||||
end
|
44
contrib/auto_gdb/used_size.py
Normal file
44
contrib/auto_gdb/used_size.py
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError as e:
|
||||
raise ImportError("This script must be run in GDB: ", str(e))
|
||||
import traceback
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
import common_helpers
|
||||
|
||||
|
||||
class UsedSizeCommand (gdb.Command):
|
||||
"""calc size of the memory used by the object"""
|
||||
|
||||
def __init__ (self):
|
||||
super (UsedSizeCommand, self).__init__ ("usedsize", gdb.COMMAND_USER)
|
||||
|
||||
@classmethod
|
||||
def assign_value(cls, obj_name, value):
|
||||
gdb.execute("set " + obj_name + " = " + str(value))
|
||||
|
||||
@classmethod
|
||||
def get_type(cls, obj_name):
|
||||
return gdb.parse_and_eval(obj_name).type
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
try:
|
||||
args = gdb.string_to_argv(arg)
|
||||
obj = gdb.parse_and_eval(args[1])
|
||||
obj_type = obj.type
|
||||
print (args[1] + " is " + str(obj_type))
|
||||
size = common_helpers.get_instance_size(obj)
|
||||
UsedSizeCommand.assign_value(args[0], size)
|
||||
print (size)
|
||||
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise e
|
||||
|
||||
UsedSizeCommand()
|
||||
|
Loading…
Reference in New Issue
Block a user