Skip to content

Commit 8adf845

Browse files
authored
Merge pull request #90 from jonashaag/gzip-subproc-2
Gzip subproc. I'll release it in a pre release version shortly.
2 parents fae6815 + b4273d5 commit 8adf845

File tree

5 files changed

+87
-18
lines changed

5 files changed

+87
-18
lines changed

setup.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from setuptools import setup, find_packages, Extension
22
import os, sys
33

4-
if '__pypy__' in sys.builtin_module_names:
4+
PY3 = sys.version_info[0] >= 3
5+
IS_PYPY = '__pypy__' in sys.builtin_module_names
6+
7+
if IS_PYPY:
58
ext_modules = [] # built-in
69
else:
710
if sys.platform != 'win32':
@@ -21,6 +24,11 @@
2124
extra_compile_args=extra_compile_args,
2225
libraries=[])]
2326

27+
if PY3:
28+
extra_install_requires = []
29+
else:
30+
extra_install_requires = ["backports.shutil_which"]
31+
2432
setup(
2533
name='vmprof',
2634
author='vmprof team',
@@ -33,7 +41,7 @@
3341
install_requires=[
3442
'requests',
3543
'six',
36-
],
44+
] + extra_install_requires,
3745
tests_require=['pytest'],
3846
entry_points = {
3947
'console_scripts': [

tox.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tox]
2-
envlist = py27, py34, pypy
2+
envlist = py27, py34, py35, pypy
33

44

55
[testenv]
66
deps=pytest
7-
commands=py.test tests/
7+
commands=py.test vmprof/test/

vmprof/__init__.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import os
22
import sys
3+
import subprocess
4+
try:
5+
from shutil import which
6+
except ImportError:
7+
from backports.shutil_which import which
38

49
from . import cli
510

@@ -24,20 +29,50 @@
2429
def enable(fileno, period=DEFAULT_PERIOD, memory=False, lines=False):
2530
if not isinstance(period, float):
2631
raise ValueError("You need to pass a float as an argument")
27-
_vmprof.enable(fileno, period, memory, lines)
28-
29-
def disable():
30-
_vmprof.disable()
32+
gz_fileno = _gzip_start(fileno)
33+
_vmprof.enable(gz_fileno, period, memory, lines)
3134
else:
3235
def enable(fileno, period=DEFAULT_PERIOD, memory=False, lines=False, warn=True):
3336
if not isinstance(period, float):
3437
raise ValueError("You need to pass a float as an argument")
3538
if warn and sys.pypy_version_info[:3] < (4, 1, 0):
36-
print("PyPy <4.1 have various kinds of bugs, pass warn=False if you know what you're doing\n")
3739
raise Exception("PyPy <4.1 have various kinds of bugs, pass warn=False if you know what you're doing")
40+
if warn and memory:
41+
print("Memory profiling is currently unsupported for PyPy. Running without memory statistics.")
3842
if warn and lines:
3943
print('Line profiling is currently unsupported for PyPy. Running without lines statistics.\n')
40-
_vmprof.enable(fileno, period)
44+
gz_fileno = _gzip_start(fileno)
45+
_vmprof.enable(gz_fileno, period)
4146

42-
def disable():
47+
def disable():
48+
try:
4349
_vmprof.disable()
50+
_gzip_finish()
51+
except IOError as e:
52+
raise Exception("Error while writing profile: " + str(e))
53+
54+
55+
_gzip_proc = None
56+
57+
def _gzip_start(fileno):
58+
"""Spawn a gzip subprocess that writes compressed profile data to `fileno`.
59+
60+
Return the subprocess' input fileno.
61+
"""
62+
# Prefer system gzip and fall back to Python's gzip module
63+
if which("gzip"):
64+
gzip_cmd = ["gzip", "-", "-4"]
65+
else:
66+
gzip_cmd = ["python", "-m", "gzip"]
67+
global _gzip_proc
68+
_gzip_proc = subprocess.Popen(gzip_cmd, stdin=subprocess.PIPE,
69+
stdout=fileno, bufsize=-1,
70+
close_fds=(sys.platform != "win32"))
71+
return _gzip_proc.stdin.fileno()
72+
73+
def _gzip_finish():
74+
global _gzip_proc
75+
if _gzip_proc is not None:
76+
_gzip_proc.stdin.close()
77+
_gzip_proc.wait()
78+
_gzip_proc = None

vmprof/reader.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from __future__ import print_function
22
import re
3+
import os
34
import struct
45
import subprocess
56
import sys
67
from six.moves import xrange
8+
import io
9+
import gzip
710

8-
PY3 = sys.version_info[0] >= 3
11+
from vmprof.binary import read_word, read_string, read_words
912

13+
PY3 = sys.version_info[0] >= 3
1014
WORD_SIZE = struct.calcsize('L')
1115

12-
from vmprof.binary import read_word, read_string, read_words
1316

1417

1518
def read_trace(fileobj, depth, version, profile_lines=False):
@@ -30,6 +33,7 @@ def read_trace(fileobj, depth, version, profile_lines=False):
3033
trace[i] = -trace[i]
3134
return trace
3235

36+
3337
MARKER_STACKTRACE = b'\x01'
3438
MARKER_VIRTUAL_IP = b'\x02'
3539
MARKER_TRAILER = b'\x03'
@@ -53,12 +57,14 @@ def read_trace(fileobj, depth, version, profile_lines=False):
5357
VMPROF_GC_TAG = 5
5458
VMPROF_ASSEMBLER_TAG = 6
5559

60+
5661
class AssemblerCode(int):
5762
pass
5863

5964
class JittedCode(int):
6065
pass
6166

67+
6268
def wrap_kind(kind, pc):
6369
if kind == VMPROF_ASSEMBLER_TAG:
6470
return AssemblerCode(pc)
@@ -67,6 +73,15 @@ def wrap_kind(kind, pc):
6773
assert kind == VMPROF_CODE_TAG
6874
return pc
6975

76+
77+
def gunzip(fileobj):
78+
is_gzipped = fileobj.read(2) == b'\037\213'
79+
fileobj.seek(-2, os.SEEK_CUR)
80+
if is_gzipped:
81+
fileobj = io.BufferedReader(gzip.GzipFile(fileobj=fileobj))
82+
return fileobj
83+
84+
7085
class BufferTooSmallError(Exception):
7186
def get_buf(self):
7287
return b"".join(self.args[0])
@@ -174,6 +189,7 @@ def read_one_marker(fileobj, status, buffer_so_far=None):
174189
return False
175190

176191
def read_prof_bit_by_bit(fileobj):
192+
fileobj = gunzip(fileobj)
177193
# note that we don't want to use all of this on normal files, since it'll
178194
# cost us quite a bit in memory and performance and parsing 200M files in
179195
# CPython is slow (pypy does better, use pypy)
@@ -193,7 +209,9 @@ def read_prof_bit_by_bit(fileobj):
193209
buf = e.get_buf()
194210
return status.period, status.profiles, status.virtual_ips, status.interp_name
195211

196-
def read_prof(fileobj, virtual_ips_only=False): #
212+
def read_prof(fileobj, virtual_ips_only=False):
213+
fileobj = gunzip(fileobj)
214+
197215
assert read_word(fileobj) == 0 # header count
198216
assert read_word(fileobj) == 3 # header size
199217
assert read_word(fileobj) == 0

vmprof/test/test_run.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11

22
""" Test the actual run
33
"""
4-
54
import py
65
import sys
76
import tempfile
7+
import gzip
88

99
import six
1010

@@ -22,7 +22,7 @@
2222
COUNT = 100000
2323
else:
2424
COUNT = 10000
25-
25+
2626
def function_foo():
2727
for k in range(1000):
2828
l = [a for a in xrange(COUNT)]
@@ -45,7 +45,7 @@ def test_basic():
4545
function_foo()
4646
vmprof.disable()
4747
tmpfile.close()
48-
assert b"function_foo" in open(tmpfile.name, 'rb').read()
48+
assert b"function_foo" in gzip.GzipFile(tmpfile.name).read()
4949

5050
def test_read_bit_by_bit():
5151
tmpfile = tempfile.NamedTemporaryFile(delete=False)
@@ -155,6 +155,15 @@ def function_bar():
155155

156156
s = prof.get_stats()
157157

158+
def test_gzip_problem():
159+
tmpfile = tempfile.NamedTemporaryFile(delete=False)
160+
vmprof.enable(tmpfile.fileno())
161+
vmprof._gzip_proc.kill()
162+
function_foo()
163+
with py.test.raises(Exception) as exc_info:
164+
vmprof.disable()
165+
assert "Error while writing profile" in str(exc_info)
166+
tmpfile.close()
158167

159168
def test_line_profiling():
160169
tmpfile = tempfile.NamedTemporaryFile(delete=False)
@@ -175,6 +184,5 @@ def walk(tree):
175184
walk(stats.get_tree())
176185

177186

178-
179187
if __name__ == '__main__':
180188
test_line_profiling()

0 commit comments

Comments
 (0)