2 # -*- coding: utf-8 -*-
4 This script can help you isolate memory leaks in HHVM. It will request a heap
5 dump from HHVM at regular intervals and diff successive dumps to show you
6 which code paths are allocating memory.
10 * a copy of libjemalloc compiled with `--enable-prof`.
11 (the script will check your libjemalloc for profiling capabilities
12 and alert you if they are missing.)
13 * Google Perf Tools (for diffing heaps).
14 * Ability to make HTTP or HTTPS requests to the HHVM admin server.
16 usage: hhvm-leak-isolator [--heap-dir HEAP_DIR] [--check-only]
17 [--admin-server URL] [--interval MINS]
20 -h, --help show this help message and exit
21 --heap-dir HEAP_DIR Dump heaps to this directory (default: $TMP).
22 --check-only Check for heap profiling capabilities and exit.
23 --admin-server URL HHVM admin server URL (default: http://localhost:9002)
24 --interval MINS Time to sleep between heap dumps (default: 5).
29 from __future__
import print_function
34 import distutils
.spawn
43 from urllib
import urlencode
44 from urllib2
import urlopen
45 from urlparse
import urljoin
47 from urllib
.parse
import urlencode
, urljoin
48 from urllib
.request
import urlopen
51 temp_dir
= tempfile
.gettempdir()
52 ap
= argparse
.ArgumentParser(
53 description
=textwrap
.dedent(__doc__
.split('usage')[0]),
54 formatter_class
=argparse
.RawDescriptionHelpFormatter
)
55 ap
.add_argument('--heap-dir', default
=temp_dir
, type=os
.path
.abspath
,
56 help='Dump heaps to this directory (default: %s).' % temp_dir
)
57 ap
.add_argument('--check-only', action
='store_true', default
=False,
58 help='Check for heap profiling capabilities and exit.')
59 ap
.add_argument('--admin-server', default
='http://localhost:9002',
60 help='HHVM admin server URL (default: http://localhost:9002)',
62 ap
.add_argument('--interval', default
=5, type=int, metavar
='MINS',
63 help='Time to sleep between heap dumps (default: 5).')
64 args
= ap
.parse_args()
67 def get_jemalloc_can_profile():
68 """Check if libjemalloc was built with `--enable-prof` and thus
69 has heap profiling capabilities."""
70 jemalloc_so
= ctypes
.util
.find_library('jemalloc')
71 if jemalloc_so
is None:
72 raise EnvironmentError('Could not find libjemalloc!')
73 jemalloc
= ctypes
.CDLL(jemalloc_so
, use_errno
=True)
75 has_prof
= ctypes
.c_bool(False)
76 has_prof_len
= ctypes
.c_size_t(ctypes
.sizeof(has_prof
))
77 rv
= jemalloc
.mallctl(b
'config.prof', ctypes
.byref(has_prof
),
78 ctypes
.byref(has_prof_len
), None, None)
80 err
= ctypes
.get_errno()
81 raise OSError(err
, os
.strerror(err
))
86 def hhvm_admin_do(endpoint
, **query_params
):
87 url
= urljoin(args
.admin_server
, endpoint
)
89 url
+= '?' + urlencode(query_params
)
91 return req
.read().decode('utf-8')
94 if get_jemalloc_can_profile():
95 print('OK: jemalloc built with `--enable-prof`.')
97 print('NOT OK: your copy of libjemalloc was not built with `--enable-prof`'
98 ' and thus has no heap profiling capabilities.', file=sys
.stderr
)
102 stats
= hhvm_admin_do('jemalloc-stats-print')
103 except EnvironmentError:
104 print('NOT OK: Could not connect to an HHVM admin server at %s.\n'
105 ' Use `--admin-server` to specify an alternate URL.'
106 % args
.admin_server
, file=sys
.stderr
)
109 if 'opt.prof: true' in stats
:
110 print('OK: jemalloc heap profiling is active (prof: true).')
112 print('NOT OK: jemalloc heap profiling is off.'
113 ' Run `sudo ln -sf prof:true /etc/malloc.conf` and restart HHVM.',
117 hhvm_path
= distutils
.spawn
.find_executable('hhvm')
118 pprof_path
= (distutils
.spawn
.find_executable('google-pprof') or
119 distutils
.spawn
.find_executable('pprof'))
121 print('OK: pprof is %s.' % pprof_path
)
123 print('WARN: Could not find `pprof` or `google-pprof` in $PATH. '
124 'pprof is required for diffing heap dumps.\n'
125 'Install the `google-perftools` package or see '
126 'https://code.google.com/p/gperftools/.', file=sys
.stderr
)
132 hhvm_admin_do('jemalloc-prof-activate')
133 if 'ACTIVE' not in hhvm_admin_do('jemalloc-prof-status'):
134 print('NOT OK: failed to activate jemalloc profiling.', file=sys
.stderr
)
137 print('I will write heap files to %s every %d minute(s).' %
138 (os
.path
.join(args
.heap_dir
, 'hhvm-heap.*'), args
.interval
))
142 heap_file
= os
.path
.join(args
.heap_dir
, 'hhvm-heap.%d' % time
.time())
143 heap_files
.append(heap_file
)
144 hhvm_admin_do('jemalloc-prof-dump', file=heap_file
)
145 print('Wrote %s.' % heap_file
)
146 if pprof_path
and len(heap_files
) > 1:
147 base
, heap
= heap_files
[-2:]
148 cmd
= (pprof_path
, hhvm_path
, '--show_bytes', '--text',
149 '--base=%s' % base
, heap
)
150 out
= subprocess
.check_output(cmd
, stderr
=open(os
.devnull
, 'w'))
151 out
= out
.decode('utf-8', errors
='ignore')
153 print('-' * 80, '\n', out
, '\n', '-' * 80)
155 print('-- no diff --')
156 print('Sleeping for %d minute(s).' % args
.interval
)
157 time
.sleep(args
.interval
* 60)