Skip to content

Commit f5582b2

Browse files
committed
LocalRPC: add commands to help debug memory leaks
1 parent 5f4dd2c commit f5582b2

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

electrumx/server/session.py

+35-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection,
2727
ReplyAndDisconnect, Request, RPCError, RPCSession,
2828
handler_invocation, serve_rs, serve_ws, sleep,
29-
NewlineFramer, TaskTimeout, timeout_after)
29+
NewlineFramer, TaskTimeout, timeout_after, run_in_thread)
3030

3131
import electrumx
3232
import electrumx.lib.util as util
@@ -167,7 +167,8 @@ def __init__(
167167

168168
# Set up the RPC request handlers
169169
cmds = ('add_peer daemon_url disconnect getinfo groups log peers '
170-
'query reorg sessions stop'.split())
170+
'query reorg sessions stop debug_memusage_list_all_objects '
171+
'debug_memusage_get_random_backref_chain'.split())
171172
LocalRPC.request_handlers = {cmd: getattr(self, 'rpc_' + cmd)
172173
for cmd in cmds}
173174

@@ -593,6 +594,38 @@ async def rpc_reorg(self, count):
593594
raise RPCError(BAD_REQUEST, 'still catching up with daemon')
594595
return f'scheduled a reorg of {count:,d} blocks'
595596

597+
async def rpc_debug_memusage_list_all_objects(self, limit: int) -> str:
598+
"""Return a string listing the most common types in memory."""
599+
import objgraph # optional dependency
600+
import io
601+
with io.StringIO() as fd:
602+
objgraph.show_most_common_types(
603+
limit=limit,
604+
shortnames=False,
605+
file=fd)
606+
return fd.getvalue()
607+
608+
async def rpc_debug_memusage_get_random_backref_chain(self, objtype: str) -> str:
609+
"""Return a dotfile as text containing the backref chain
610+
for a randomly selected object of type objtype.
611+
612+
Warning: very slow! and it blocks the server.
613+
614+
To convert to image:
615+
$ dot -Tps filename.dot -o outfile.ps
616+
"""
617+
import objgraph # optional dependency
618+
import random
619+
import io
620+
with io.StringIO() as fd:
621+
await run_in_thread(lambda:
622+
objgraph.show_chain(
623+
objgraph.find_backref_chain(
624+
random.choice(objgraph.by_type(objtype)),
625+
objgraph.is_proper_module),
626+
output=fd))
627+
return fd.getvalue()
628+
596629
# --- External Interface
597630

598631
async def serve(self, notifications, event):

electrumx_rpc

+22
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ other_commands = {
7474
'help': 'number of blocks to back up'
7575
},
7676
),
77+
'debug_memusage_list_all_objects': (
78+
'Print a table of types of most common types in memory',
79+
['--limit'], {
80+
'type': int,
81+
'default': 50,
82+
'help': 'max number of types to return',
83+
},
84+
),
85+
'debug_memusage_get_random_backref_chain': (
86+
'Return a dotfile as text containing the backref chain for a randomly selected object of type objtype',
87+
[], {
88+
'type': str,
89+
'dest': 'objtype',
90+
'help': 'e.g. "_asyncio.Task"',
91+
},
92+
),
7793
}
7894

7995

@@ -125,6 +141,12 @@ def main():
125141
if method in ('query', ):
126142
for line in result:
127143
print(line)
144+
elif method in (
145+
'debug_memusage_list_all_objects',
146+
'debug_memusage_get_random_backref_chain',
147+
):
148+
for line in result.split('\n'):
149+
print(line)
128150
elif method in ('groups', 'peers', 'sessions'):
129151
lines_func = getattr(text, f'{method}_lines')
130152
for line in lines_func(result):

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
install_requires=['aiorpcX[ws]>=0.22.0,<0.23', 'attrs',
1010
'plyvel', 'pylru', 'aiohttp>=3.3,<4'],
1111
extras_require={
12+
'dev': ['objgraph'],
1213
'rapidjson': ['python-rapidjson>=0.4.1,<2.0'],
1314
'rocksdb': ['python-rocksdb>=0.6.9'],
1415
'ujson': ['ujson>=2.0.0,<4.0.0'],

0 commit comments

Comments
 (0)