Skip to content

Commit 6e201e2

Browse files
authored
python: Upgrade PyPerf to v1.0.3 (#70)
* Upgrade PyPerf to v1.0.3 (c568fdd751304ecf7907ebd27f8039ea3c492a4c) * Add tests for kernel stacks from PyPerf * Use larger events buffer & symbols map * Increase MAX_FREQUENCY to 1000 * Log PyPerf's stdout/stderr every interval * Bump gProfiler version to 1.0.4
1 parent 7a60f2c commit 6e201e2

File tree

13 files changed

+89
-45
lines changed

13 files changed

+89
-45
lines changed

Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential iperf
1616

1717
WORKDIR /bcc
1818

19-
RUN git clone --depth 1 -b v1.0.1 https://github.com/Granulate/bcc.git && cd bcc && git reset --hard 92b61ade89f554859950695b067288f60cb1f3e5
20-
RUN mkdir bcc/build && cd bcc/build && \
21-
cmake -DPYTHON_CMD=python3 -DINSTALL_CPP_EXAMPLES=y -DCMAKE_INSTALL_PREFIX=/bcc/root .. && \
22-
make -C examples/cpp/pyperf -j -l VERBOSE=1 install
19+
COPY ./scripts/pyperf_build.sh .
20+
RUN ./pyperf_build.sh
2321

2422

2523
FROM ubuntu:20.04

gprofiler/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.3"
1+
__version__ = "1.0.4"

gprofiler/python.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626

2727
class PythonProfilerBase(ProfilerBase):
28-
MAX_FREQUENCY = 100
28+
MAX_FREQUENCY: Optional[int] = None # set by base classes
2929

3030
def __init__(
3131
self,
@@ -35,6 +35,7 @@ def __init__(
3535
storage_dir: str,
3636
):
3737
super().__init__()
38+
assert isinstance(self.MAX_FREQUENCY, int)
3839
self._frequency = min(frequency, self.MAX_FREQUENCY)
3940
self._duration = duration
4041
self._stop_event = stop_event or Event()
@@ -119,7 +120,11 @@ def snapshot(self) -> Mapping[int, Mapping[str, int]]:
119120

120121

121122
class PythonEbpfProfiler(PythonProfilerBase):
123+
MAX_FREQUENCY = 1000
122124
PYPERF_RESOURCE = "python/pyperf/PyPerf"
125+
events_buffer_pages = 256 # 1mb and needs to be physically contiguous
126+
# 28mb (each symbol is 224 bytes), but needn't be physicall contiguous so don't care
127+
symbols_map_size = 131072
123128
dump_signal = signal.SIGUSR2
124129
dump_timeout = 5 # seconds
125130
poll_timeout = 10 # seconds
@@ -195,6 +200,10 @@ def start(self):
195200
str(self.output_path),
196201
"-F",
197202
str(self._frequency),
203+
"--events-buffer-pages",
204+
str(self.events_buffer_pages),
205+
"--symbols-map-size",
206+
str(self.symbols_map_size),
198207
# Duration is irrelevant here, we want to run continuously.
199208
]
200209
process = start_process(cmd, via_staticx=True)
@@ -204,6 +213,7 @@ def start(self):
204213
wait_event(self.poll_timeout, self._stop_event, lambda: os.path.exists(self.output_path))
205214
except TimeoutError:
206215
process.kill()
216+
logger.error(f"PyPerf failed to start. stdout {process.stdout.read()!r} stderr {process.stderr.read()!r}")
207217
raise
208218
else:
209219
self.process = process
@@ -225,7 +235,15 @@ def _dump(self) -> Path:
225235
self.process.send_signal(self.dump_signal)
226236

227237
try:
228-
return self._wait_for_output_file(self.dump_timeout)
238+
output = self._wait_for_output_file(self.dump_timeout)
239+
# PyPerf outputs sampling & error counters every interval (after writing the output file), print them.
240+
# also, makes sure its output pipe doesn't fill up.
241+
# using read1() which performs just a single read() call and doesn't read until EOF
242+
# (unlike Popen.communicate())
243+
assert self.process is not None
244+
# Python 3.6 doesn't have read1() without size argument :/
245+
logger.debug(f"PyPerf output: {self.process.stderr.read1(4096)}")
246+
return output
229247
except TimeoutError:
230248
# error flow :(
231249
try:

pyi.Dockerfile

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ RUN yum install -y \
2626

2727
WORKDIR /bcc
2828

29-
RUN git clone --depth 1 -b v1.0.1 https://github.com/Granulate/bcc.git && cd bcc && git reset --hard 92b61ade89f554859950695b067288f60cb1f3e5
30-
3129
RUN yum install -y centos-release-scl-rh
3230
# mostly taken from https://github.com/iovisor/bcc/blob/master/INSTALL.md#install-and-compile-llvm
3331
RUN yum install -y devtoolset-8 \
@@ -37,10 +35,8 @@ RUN yum install -y devtoolset-8 \
3735
llvm-toolset-7-clang-devel \
3836
devtoolset-8-elfutils-libelf-devel
3937

40-
RUN mkdir bcc/build && cd bcc/build && \
41-
source scl_source enable devtoolset-8 llvm-toolset-7 && \
42-
cmake -DPYTHON_CMD=python3 -DINSTALL_CPP_EXAMPLES=y -DCMAKE_INSTALL_PREFIX=/bcc/root .. && \
43-
make -C examples/cpp/pyperf -j -l VERBOSE=1 install
38+
COPY ./scripts/pyperf_build.sh .
39+
RUN source scl_source enable devtoolset-8 llvm-toolset-7 && source ./pyperf_build.sh
4440

4541
# gProfiler part
4642

scripts/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ curl -fL https://github.com/Granulate/async-profiler/releases/download/v2.0g1/as
1313
-z build/async-profiler-2.0-linux-x64.tar.gz -o build/async-profiler-2.0-linux-x64.tar.gz
1414
tar -xzf build/async-profiler-2.0-linux-x64.tar.gz -C gprofiler/resources/java --strip-components=2 async-profiler-2.0-linux-x64/build
1515

16-
# pyperf - just create the directory for it, it will be built/downloaded later
16+
# pyperf - just create the directory for it, it will be built later
1717
mkdir -p gprofiler/resources/python/pyperf
1818

1919
# perf

scripts/pyperf_build.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (c) Granulate. All rights reserved.
4+
# Licensed under the AGPL3 License. See LICENSE.md in the project root for license information.
5+
#
6+
set -e
7+
8+
git clone --depth 1 -b v1.0.3 https://github.com/Granulate/bcc.git && cd bcc && git reset --hard c568fdd751304ecf7907ebd27f8039ea3c492a4c
9+
mkdir build
10+
cd build
11+
cmake -DPYTHON_CMD=python3 -DINSTALL_CPP_EXAMPLES=y -DCMAKE_INSTALL_PREFIX=/bcc/root ..
12+
make -C examples/cpp/pyperf -j -l VERBOSE=1 install

tests/conftest.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import stat
77
from contextlib import contextmanager
8+
from functools import partial
89
from pathlib import Path
910
from subprocess import Popen, TimeoutExpired, run
1011
from time import sleep
@@ -17,7 +18,7 @@
1718
from pytest import fixture # type: ignore
1819

1920
from tests import CONTAINERS_DIRECTORY, PARENT
20-
from tests.utils import chmod_path_parts
21+
from tests.utils import assert_function_in_collapsed, chmod_path_parts
2122

2223

2324
@fixture
@@ -56,9 +57,9 @@ def java_command_line(class_path: Path) -> List:
5657
def command_line(tmp_path: Path, runtime: str) -> List:
5758
return {
5859
"java": java_command_line(tmp_path / "java"),
59-
# note: here we run "python /path/to/fibonacci.py" while in the container test we have
60-
# "CMD /path/to/fibonacci.py", to test processes with non-python /proc/pid/comm
61-
"python": ["python3", CONTAINERS_DIRECTORY / "python/fibonacci.py"],
60+
# note: here we run "python /path/to/lister.py" while in the container test we have
61+
# "CMD /path/to/lister.py", to test processes with non-python /proc/pid/comm
62+
"python": ["python3", CONTAINERS_DIRECTORY / "python/lister.py"],
6263
}[runtime]
6364

6465

@@ -160,15 +161,10 @@ def application_pid(in_container: bool, application_process: Popen, application_
160161
def assert_collapsed(runtime: str) -> Callable[[Mapping[str, int]], None]:
161162
function_name = {
162163
"java": "Fibonacci.main",
163-
"python": "fibonacci",
164+
"python": "burner",
164165
}[runtime]
165166

166-
def assert_collapsed(collapsed: Mapping[str, int]) -> None:
167-
print(f"collapsed: {collapsed}")
168-
assert collapsed is not None
169-
assert any((function_name in record) for record in collapsed.keys())
170-
171-
return assert_collapsed
167+
return partial(assert_function_in_collapsed, function_name)
172168

173169

174170
@fixture

tests/containers/python/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.6-alpine
22

33
WORKDIR /app
4-
ADD fibonacci.py /app
4+
ADD lister.py /app
55

6-
CMD ["/app/fibonacci.py"]
6+
CMD ["/app/lister.py"]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
FROM python:3.6-alpine
22

33
WORKDIR /app
4-
ADD fibonacci.py /app
4+
ADD lister.py /app
55
# this is used to test that we identify Python processes to profile based on "libpython" in their "/proc/pid/maps".
66
# so we'll run a Python script using non-"python" executable ("shmython" instead) but it'll have "libpython"
77
# loaded.
88
RUN ln /usr/local/bin/python3.6 /usr/local/bin/shmython && ! test -L /usr/local/bin/shmython && ldd /usr/local/bin/shmython | grep libpython > /dev/null
99

10-
CMD ["/usr/local/bin/shmython", "/app/fibonacci.py"]
10+
CMD ["/usr/local/bin/shmython", "/app/lister.py"]

tests/containers/python/fibonacci.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)