Skip to content

Commit

Permalink
Add initial iteration
Browse files Browse the repository at this point in the history
Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com>
  • Loading branch information
kakkoyun committed Oct 3, 2023
1 parent 89fe53f commit 0d7302e
Show file tree
Hide file tree
Showing 41 changed files with 149,117 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
BasedOnStyle: Google
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
ColumnLimit: 120
IndentWidth: 4
SortIncludes: false
Empty file added .clippy.toml
Empty file.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/bpf/vmlinux.h linguist-generated
129 changes: 129 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
name: Build
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
rust: [stable]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@1.71.0
with:
toolchain: ${{matrix.rust}}
components: rust-src, rustfmt
- name: Install build system dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get -y install --no-install-recommends \
curl \
ca-certificates \
clang \
make \
pkg-config \
libelf-dev \
zlib1g-dev
- name: Build
run: |
export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu'
cargo build
- name: Static build
run: |
export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu -C target-feature=+crt-static'
cargo build --target x86_64-unknown-linux-gnu
lint:
name: Lint
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
rust: [stable]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@1.71.0
with:
toolchain: ${{matrix.rust}}
components: rust-src, rustfmt
- name: Run cargo fmt
run: |
# These files are generated at build time, so some rustfmt versions
# fail with Error writing files: failed to resolve mod `bpf` if it
# does not exist
touch src/bpf/py-perf.rs
touch src/bpf/features.rs
cargo fmt
git diff --exit-code
clippy:
name: Clippy
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@1.71.0
with:
components: rust-src, clippy, rustfmt
- name: Install build system dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get -y install --no-install-recommends \
curl \
ca-certificates \
clang \
make \
pkg-config \
libelf-dev \
zlib1g-dev
- name: Run clippy
run: |
export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu'
cargo clippy -- -Dclippy::all
test:
name: Test
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
rust: [stable]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@1.71.0
with:
toolchain: ${{matrix.rust}}
components: rust-src, rustfmt
- name: Install build system dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get -y install --no-install-recommends \
curl \
ca-certificates \
clang \
make \
pkg-config \
libelf-dev \
zlib1g-dev
- name: Run unittests
run: |
export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu'
export RUST_BACKTRACE=1
cargo test -- --skip py-perf::tests
- name: Install podman
run: sudo apt-get -y install --no-install-recommends podman
- name: Pull Ruby containers
run: tools/pull_ruby_images
- name: Run integration tests
run: |
export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu'
export RUST_BACKTRACE=1
# Running only 3.1.2 for a bit, will enable the rest once we make sure
# that things are looking good
cargo test -- py-perf::tests::py-perf_test_3_1_2 --nocapture
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,21 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Added by cargo

/target

src/bpf/py-perf.rs

py-perf_out*
py-perf_flame*

TODO.md

/tmp
py-perf_*.pb
py-perf_*.pb.gz
py-perf_*.txt
py-perf_*.svg
py-perf_*.json
61 changes: 61 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[package]
name = "py-perf"
description = "A Proof-of-concept low-overhead sampling CPU profiler written in Rust for Python implemented using eBPF."
version = "0.1.0"
edition = "2021"
repository = "https://github.com/kakkoyun/py-perf"
authors = ["Kemal Akkoyun <kakkoyun@gmail.com>"]
keywords = ["bpf", "ebpf", "python", "CPython", "profiler"]
license = "Apache-2.0"
categories = ["development-tools", "profiling", "performance"]
readme = "README.md"

[profile.release]
lto = true

[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
chrono = "0.4"
clap = { version = "4.3", features = ["derive"] }
crossbeam = "0.8.2"
ctrlc = "3.4"
env_logger = "0.10"
errno = "0.3"
goblin = "0.7"
humantime = "2"
inferno = "0.11"
libbpf-rs = { version = "0.21", features = ["static"] }
libc = "0.2"
log = "0.4"
nix = "0.26"
num_cpus = "1.16"
perf-event-open-sys = "4.0"
plain = "0.2.3"
# pprof = { path = "../../Sandbox/Profiling/pprof-rs", features = [
pprof = { git = "ssh://git@github.com/kakkoyun/py-spy.git", features = [
"flamegraph",
"inferno",
"protobuf",
"protobuf-codec",
] }
proc-maps = "0.3"
# py-spy = { path = "../../Sandbox/Profilers/py-spy" }
# TODO(kakkoyun): Send a patch to upstream.
py-spy = { git = "ssh://git@github.com/kakkoyun/py-spy.git" }
remoteprocess = { version = "0.4.12", features = ["unwind"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
thiserror = "1.0"
time = { version = "0.3.24", features = [
"formatting",
"local-offset",
"macros",
] }

[build-dependencies]
bindgen = "0.66"
libbpf-cargo = "0.21"

[workspace]
members = [".", "xtask"]
37 changes: 37 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# TODO(kakkoyun): DRY this file.

target/static-libs:
@echo "Building static-libs"
./scripts/download_and_build_static_libs.sh

target/debug/deps/libelf.a: target/static-libs
mkdir -p target/debug/deps
cp target/static-libs/libelf.a target/debug/deps/libelf.a

target/release/deps/libelf.a: target/static-libs
mkdir -p target/release/deps
cp target/static-libs/libelf.a target/release/deps/libelf.a

target/debug/deps/libz.a: target/static-libs
mkdir -p target/debug/deps
cp target/static-libs/libz.a target/debug/deps/libz.a

target/release/deps/libz.a: target/static-libs
mkdir -p target/release/deps
cp target/static-libs/libz.a target/release/deps/libz.a

deps: target/debug/deps/libelf.a target/debug/deps/libz.a target/release/deps/libelf.a target/release/deps/libz.a
mkdir -p out/ruby_versions
mkdir -p out/python_versions

.PHONY: build
build: target/debug/deps/libelf.a target/debug/deps/libz.a
cargo build

.PHONY: release-build
release-build: target/release/deps/libelf.a target/release/deps/libz.a
cargo build --release

.PHONY: clean
clean:
rm -rf target
113 changes: 112 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,113 @@
[![wakatime](https://wakatime.com/badge/user/c03c2c3a-0328-4e74-ba79-1ce0eb43a4f8/project/6de0edd3-d3d9-48b1-8f9e-e019fc7b42f1.svg)](https://wakatime.com/badge/user/c03c2c3a-0328-4e74-ba79-1ce0eb43a4f8/project/6de0edd3-d3d9-48b1-8f9e-e019fc7b42f1)

# py-perf
[Proof-of-concept] Low-overhead sampling profiler for Python implemented using eBPF

A Proof-of-concept low-overhead sampling CPU profiler written in Rust for Python implemented using eBPF.
It is heavily "influenced" by [rbperf](https://github.com/javierhonduco/rbperf) and [py-spy](https://github.com/benfred/py-spy).

> IT IS NOT READY FOR PRODUCTION USE AND IT IS NOT INTENDED TO BE A REPLACEMENT FOR EXISTING TOOLS.
> If you are looking for a production-ready tool, please check out [parca-agent](https://github.com/parca-dev/parca-agent) instead.
## Features

The main goals for `py-perf` are:

- On-CPU profiling support
- Low overhead
- Profiled processes don't have to be restarted or modified in any way

## Installation

The latest release is available [here](https://github.com/kakkoyun/py-perf/releases/latest).

## Usage

### CPU sampling

```shell
sudo py-perf record --pid `pidof python` cpu
```

Some debug information will be printed, and a flame graph called `py-perf_flame_$date` will be written to disk 🎉

## Supported Python versions

The currently supported Python (CPython) versions:

- **2.7**: 2.7.x
- **3.x**: 3.3.x, 3.5.x, 3.6.x, 3.7.x, 3.8.x, 3.9.x, 3.10.x, 3.11.x

## Supported kernels

Linux kernel 4.18 is the minimum required version but 5.x and greater is recommended.

## Building

To build `py-perf` you would need a modern Linux machine with:

- The Rust toolchain
- `clang` to compile the BPF code
- `elfutils` and `zlib` installed
- `make` and `pkg-config` to build libbpf

Once the dependencies are installed:

```shell
# As we are statically linking elfutils and zlib, we have to tell Rustc
# where are they located. On my Ubuntu system they are under
$ export RUSTFLAGS='-L /usr/lib/x86_64-linux-gnu'
$ cargo build [--release]
```

The built binary can be found under `target/(debug|release)/py-perf`.

## Developing and troubleshooting

Debug logs can be enabled with `RUST_LOG=debug`. The info subcommand, `py-perf info` shows the supported BPF features as well as other supported details.

## Stability

`py-perf` is in active development and the CLI and APIs might change any time.

## Bugs

If you encounter any bugs, feel free to open an issue on py-perf's [repo](https://github.com/kakkoyun/py-perf).

## Acknowledgments

`py-perf` wouldn't be possible without all the open-source projects that we benefit from, such as [Rust](https://github.com/rust-lang), [rbperf](https://github.com/javierhonduco/rbperf), [py-spy](https://github.com/benfred/py-spy) and all the superb crates we use in this project, Python, the BPF ecosystem, and many others!

## License

User-space code: Apache 2

Kernel-space code (eBPF profiler): GNU General Public License, version 2

#### TODO

- TODO(kakkoyun): Add sections from parca-agent!
- TODO(kakkoyun): Add reference to bcc, bcc/granulate and linux/tool examples from facebook.

## Features:

- Supports profiling Python processes running in Docker containers. Tested using official Python
Docker images (`python:X.Y`).
- Supports glibc- and musl-based environments.
- Supports Python compiled in both PIE and non-PIE configurations.
- Supports Python running standalone and as a library (linked with `libpythonX.Y`).

## Limitations:

- Architecture: x86_64.
- Linux kernel version: oldest version tested is 4.14. Versions 4.11-4.14 may work. Required for
`bpf_probe_read_str`.
- BCC version: using BCC nightly is recommended. v0.17 is known to work.
- Clang/LLVM: at least version 9.

## Overview

PyPerf uses Linux's perf events subsystem to gather stack samples of running Python interpreters at
a constant interval. Instead of capturing native execution stacks, PyPerf reads the information
stored by the Python interpreter regarding the current state of execution. Unlike many existing
tools however, the memory of the process is read from a kernel context. The advantages of this
approach are mainly reduced system overhead and no intervention with the program being profiled.
Loading

0 comments on commit 0d7302e

Please sign in to comment.