kopano_cache.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import bisect
  2. import collections
  3. import gc
  4. import gdb
  5. import struct
  6. import Gnuplot
  7. import tcstats
  8. """
  9. tool to visualize the memory usage of a kopano server, including
  10. 'lost' memory.
  11. uses tcstats.py to know about all memory blocks (allocations) in use.
  12. scans all 12 kopano caches to determine which of these blocks are
  13. used for caching.
  14. produces a graph which summarizes:
  15. -used memory (outside of cache, not 'lost')
  16. -cached memory (used by one of the 12 caches)
  17. -lost memory (unreachably by any (internal) pointer)
  18. -free memory (reserved by tcmalloc to serve new allocations)
  19. this may take a long time and a _lot_ of memory.
  20. TODO:
  21. - not all caches are scanned 100%, so some 'cached' memory currently
  22. ends up in 'used' (still need to scan objectdetails_t,
  23. serverdetails_t and quotadetails_t)
  24. pretty printing techniques taken from:
  25. https://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python/libstdcxx/v6/printers.py
  26. """
  27. cachemgr = gdb.parse_and_eval('g_lpSessionManager.m_lpECCacheManager')
  28. vopo = gdb.lookup_type('void').pointer()
  29. def std_map_nodes(map):
  30. nodetype = map['_M_t']['_M_impl'].type.fields()[0].type.template_argument(0).pointer()
  31. node = map['_M_t']['_M_impl']['_M_header']['_M_left']
  32. size = map['_M_t']['_M_impl']['_M_node_count']
  33. count = 0
  34. while count < size: # and count < 10000:
  35. result = node
  36. count += 1
  37. if node.dereference()['_M_right']:
  38. node = node.dereference()['_M_right']
  39. while node.dereference()['_M_left']:
  40. node = node.dereference()['_M_left']
  41. else:
  42. parent = node.dereference()['_M_parent']
  43. while node == parent.dereference()['_M_right']:
  44. node = parent
  45. parent = parent.dereference()['_M_parent']
  46. if node.dereference()['_M_right'] != parent:
  47. node = parent
  48. nodus = result.cast(nodetype).dereference()
  49. valtype = nodus.type.template_argument(0)
  50. yield nodus['_M_storage'].address.cast(valtype.pointer()).dereference()
  51. def unordered_map_buckets_(map):
  52. table_ = map['_M_h']
  53. return table_['_M_buckets']
  54. def unordered_map_nodevals(map):
  55. global COUNT
  56. map = map['_M_h']
  57. node = map['_M_before_begin']['_M_nxt']
  58. node_type = gdb.lookup_type(map.type.strip_typedefs().name+'::'+'__node_type').pointer()
  59. COUNT = 0
  60. COUNT2 = 0
  61. while True:
  62. if node == 0:
  63. break
  64. plop = node.cast(node_type)
  65. elt = plop.dereference()
  66. valptr = elt['_M_storage'].address
  67. valptr = valptr.cast(elt.type.template_argument(0).pointer())
  68. yield plop, valptr.dereference()
  69. COUNT2 += 1
  70. node = elt['_M_nxt']
  71. if COUNT2 % 10000 == 0:
  72. print('counts:', COUNT2, COUNT)
  73. # break
  74. def _quota_blocks(map, blocks, starts, start_block):
  75. found_blocks = []
  76. buckets_ = unordered_map_buckets_(map)
  77. if int(str(buckets_.cast(vopo)), 16) == 0:
  78. return found_blocks
  79. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  80. found_blocks.append(fb)
  81. for node, value in unordered_map_nodevals(map):
  82. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  83. found_blocks.append(fb)
  84. return found_blocks
  85. def quota_blocks(*args):
  86. print()
  87. print('QUOTA CACHE')
  88. map = cachemgr['m_QuotaCache']['m_map']
  89. return _quota_blocks(map, *args)
  90. def uquota_blocks(*args):
  91. print()
  92. print('QUOTAUSERDEFAULT CACHE')
  93. map = cachemgr['m_QuotaUserDefaultCache']['m_map']
  94. return _quota_blocks(map, *args)
  95. def store_blocks(blocks, starts, start_block):
  96. found_blocks = []
  97. map = cachemgr['m_StoresCache']['m_map']
  98. buckets_ = unordered_map_buckets_(map)
  99. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  100. found_blocks.append(fb)
  101. for node, value in unordered_map_nodevals(map):
  102. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  103. found_blocks.append(fb)
  104. return found_blocks
  105. def index1_blocks(blocks, starts, start_block):
  106. print()
  107. print('INDEX1 CACHE')
  108. map = cachemgr['m_PropToObjectCache']['m_map']
  109. found_blocks = []
  110. buckets_ = unordered_map_buckets_(map)
  111. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  112. found_blocks.append(fb)
  113. for node, value in unordered_map_nodevals(map):
  114. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  115. found_blocks.append(fb)
  116. addr = value['first']['lpData'].cast(vopo)
  117. fb = tcstats.bisect_block(int(str(addr), 16), starts, start_block)
  118. found_blocks.append(fb)
  119. return found_blocks
  120. def index2_blocks(blocks, starts, start_block):
  121. print()
  122. print('INDEX2 CACHE')
  123. found_blocks = []
  124. map = cachemgr['m_ObjectToPropCache']['m_map']
  125. for node in std_map_nodes(map):
  126. fb = tcstats.bisect_block(int(str(node.address), 16), starts, start_block)
  127. found_blocks.append(fb)
  128. addr = int(str(node['second']['lpData'].cast(vopo)), 16)
  129. fb = tcstats.bisect_block(addr, starts, start_block)
  130. found_blocks.append(fb)
  131. return found_blocks
  132. def obj_blocks(blocks, starts, start_block):
  133. print()
  134. print('OBJECTS CACHE')
  135. map = cachemgr['m_ObjectsCache']['m_map']
  136. found_blocks = []
  137. buckets_ = unordered_map_buckets_(map)
  138. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  139. found_blocks.append(fb)
  140. for node, value in unordered_map_nodevals(map):
  141. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  142. found_blocks.append(fb)
  143. return found_blocks
  144. COUNT = 0
  145. def _cell_stdmap_blocks(map, starts, start_block):
  146. global COUNT
  147. found_blocks = []
  148. for node in std_map_nodes(map):
  149. addr = int(str(node.address), 16)
  150. fb = tcstats.bisect_block(addr, starts, start_block)
  151. found_blocks.append(fb)
  152. COUNT += 1
  153. union = int(node['second']['__union'])
  154. if union in (1,2,3,4,5):
  155. pass
  156. elif union in (6,7,8): # lpszA, hilo, bin
  157. addr = int(str(node['second']['Value']['bin']), 16)
  158. fb = tcstats.bisect_block(addr, starts, start_block)
  159. found_blocks.append(fb)
  160. else:
  161. print('FOUT', union)
  162. fout
  163. return found_blocks
  164. def cell_blocks(blocks, starts, start_block):
  165. print()
  166. print('CELL CACHE')
  167. map = cachemgr['m_CellCache']['m_map']
  168. found_blocks = []
  169. buckets_ = unordered_map_buckets_(map)
  170. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  171. found_blocks.append(fb)
  172. for node, value in unordered_map_nodevals(map):
  173. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  174. found_blocks.append(fb)
  175. found_blocks.extend(_cell_stdmap_blocks(value['second']['mapPropVals'], starts, start_block))
  176. return found_blocks
  177. def userid_blocks(blocks, starts, start_block):
  178. print()
  179. print('USEROBJECT CACHE')
  180. map = cachemgr['m_UserObjectCache']['m_map']
  181. found_blocks = []
  182. buckets_ = unordered_map_buckets_(map)
  183. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  184. found_blocks.append(fb)
  185. for node, value in unordered_map_nodevals(map):
  186. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  187. found_blocks.append(fb)
  188. for field in ('strExternId', 'strSignature'):
  189. addr = value['second'][field]['_M_dataplus']['_M_p'].cast(vopo)
  190. fb = tcstats.bisect_block(int(str(addr), 16), starts, start_block)
  191. found_blocks.append(fb)
  192. return found_blocks
  193. def usereid_blocks(blocks, starts, start_block):
  194. print()
  195. print('USEREID BLOCKS')
  196. found_blocks = []
  197. map = cachemgr['m_UEIdObjectCache']['m_map']
  198. for node in std_map_nodes(map):
  199. fb = tcstats.bisect_block(int(str(node.address), 16), starts, start_block)
  200. found_blocks.append(fb)
  201. addr = int(str(node['first']['strExternId']['_M_dataplus']['_M_p'].cast(vopo)), 16)
  202. fb = tcstats.bisect_block(addr, starts, start_block)
  203. found_blocks.append(fb)
  204. addr = int(str(node['second']['strSignature']['_M_dataplus']['_M_p'].cast(vopo)), 16)
  205. fb = tcstats.bisect_block(addr, starts, start_block)
  206. found_blocks.append(fb)
  207. return found_blocks
  208. def userdetail_blocks(blocks, starts, start_block):
  209. print()
  210. print('USERDETAIL CACHE')
  211. map = cachemgr['m_UserObjectDetailsCache']['m_map']
  212. found_blocks = []
  213. buckets_ = unordered_map_buckets_(map)
  214. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  215. found_blocks.append(fb)
  216. for node, value in unordered_map_nodevals(map):
  217. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  218. found_blocks.append(fb)
  219. return found_blocks
  220. def acl_blocks(blocks, starts, start_block):
  221. print()
  222. print('ACL CACHE')
  223. map = cachemgr['m_AclCache']['m_map']
  224. found_blocks = []
  225. buckets_ = unordered_map_buckets_(map)
  226. fb = tcstats.bisect_block(int(str(buckets_), 16), starts, start_block)
  227. found_blocks.append(fb)
  228. for node, value in unordered_map_nodevals(map):
  229. fb = tcstats.bisect_block(int(str(node), 16), starts, start_block)
  230. found_blocks.append(fb)
  231. addr = value['second']['aACL'].cast(vopo)
  232. fb = tcstats.bisect_block(int(str(addr), 16), starts, start_block)
  233. found_blocks.append(fb)
  234. return found_blocks
  235. def server_blocks(blocks, starts, start_block):
  236. print()
  237. print('SERVERDETAIL CACHE')
  238. found_blocks = []
  239. map = cachemgr['m_ServerDetailsCache']['m_map']
  240. for node in std_map_nodes(map):
  241. fb = tcstats.bisect_block(int(str(node.address), 16), starts, start_block)
  242. found_blocks.append(fb)
  243. addr = int(str(node['first']['_M_dataplus']['_M_p'].cast(vopo)), 16)
  244. fb = tcstats.bisect_block(addr, starts, start_block)
  245. found_blocks.append(fb)
  246. return found_blocks
  247. def prepare_data(name, blocks):
  248. print()
  249. print(name.upper()+' FREQ:')
  250. # tcstats.dump_blocks(blocks)
  251. summary, count, size = tcstats.freq_blocks(blocks)
  252. size = '%.2fGB' % (float(size) / (10**9))
  253. return Gnuplot.Data(sorted(summary.items()), with_="linespoints", title='%s blocks (%s)' % (name, size))
  254. def main(blocks=None, used=None, free=None, starts=None, start_block=None):
  255. blocks = blocks or list(tcstats.pagemap_blocks())
  256. used = used or list(tcstats.used_blocks(blocks))
  257. free = free or list(tcstats.free_blocks(blocks))
  258. blocks = None # don't keep in memory
  259. starts = starts or sorted(b.address for b in used)
  260. start_block = start_block or {b.address: b for b in used}
  261. print('calc caches')
  262. args = (used, starts, start_block)
  263. c1 = cell_blocks(*args)
  264. c2 = obj_blocks(*args)
  265. c3 = index1_blocks(*args)
  266. c4 = index2_blocks(*args)
  267. c5 = store_blocks(*args)
  268. c6 = quota_blocks(*args)
  269. c7 = uquota_blocks(*args)
  270. c8 = userid_blocks(*args)
  271. c9 = usereid_blocks(*args)
  272. c10 = userdetail_blocks(*args)
  273. c11 = acl_blocks(*args)
  274. c12 = server_blocks(*args)
  275. caches = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12)
  276. for i, c in enumerate(caches):
  277. print('cache size', i, len(c), sum(b.size for b in c))
  278. print('calc unreached')
  279. lost = tcstats.unreachable_blocks(used)
  280. used = tcstats.subtract_blocks(used, free)
  281. print('prep data')
  282. for i, c in enumerate(caches):
  283. used = tcstats.subtract_blocks(used, c)
  284. d1 = prepare_data('used', used)
  285. d2 = prepare_data('cached', sum(caches, []))
  286. d3 = prepare_data('free', free)
  287. d4 = prepare_data('lost', lost)
  288. g = Gnuplot.Gnuplot()
  289. g('set logscale x')
  290. g('set logscale y')
  291. g.plot(d1, d2, d3, d4)
  292. g.hardcopy(filename='kopano_cache.png', terminal='png')
  293. if __name__ == '__main__':
  294. main()