Skip to content

Commit bd15203

Browse files
authored
Merge pull request #1 from loks0n/feat/pyroscope-sidecar
Continuous Pyroscope export + distroless GHCR sidecar image
2 parents 66999c2 + e4ce097 commit bd15203

20 files changed

Lines changed: 1158 additions & 135 deletions

.github/workflows/ci.yml

Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -105,41 +105,12 @@ jobs:
105105
- uses: Swatinem/rust-cache@v2
106106
with:
107107
key: ${{ matrix.target }}-smoke
108-
- name: install Sury PHP 8.3
109-
run: |
110-
sudo apt-get update
111-
sudo apt-get install -y --no-install-recommends \
112-
curl ca-certificates gnupg lsb-release
113-
curl -sSLo /tmp/sury.gpg https://packages.sury.org/php/apt.gpg
114-
sudo install -m 644 /tmp/sury.gpg /usr/share/keyrings/sury.gpg
115-
. /etc/os-release
116-
echo "deb [signed-by=/usr/share/keyrings/sury.gpg] https://packages.sury.org/php/ ${VERSION_CODENAME} main" \
117-
| sudo tee /etc/apt/sources.list.d/sury.list
118-
sudo apt-get update
119-
sudo apt-get install -y --no-install-recommends php8.3-cli
108+
- name: install PHP
109+
run: ./scripts/ci/install-php.sh
120110
- name: build
121111
run: cargo build --release --target ${{ matrix.target }}
122-
- name: allow ptrace
123-
run: |
124-
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope || true
125112
- name: smoke
126-
run: |
127-
cat > /tmp/spin.php <<'PHP'
128-
<?php
129-
class W { public function spin(): never { while (true) $this->a(); }
130-
public function a(): void { $this->b(); }
131-
public function b(): void { usleep(500); } }
132-
(new W())->spin();
133-
PHP
134-
php8.3 /tmp/spin.php &
135-
PID=$!
136-
sleep 1
137-
./target/${{ matrix.target }}/release/pfp -p $PID -d 2 -H 99 -o /tmp/out.txt
138-
kill $PID || true
139-
wait || true
140-
test "$(grep -c '^0 ' /tmp/out.txt)" -ge 100
141-
grep -q 'W::a' /tmp/out.txt
142-
grep -q 'W::b' /tmp/out.txt
113+
run: ./scripts/ci/smoke.sh ./target/${{ matrix.target }}/release/pfp
143114

144115
smoke-zts:
145116
name: smoke-zts (${{ matrix.target }})
@@ -167,35 +138,29 @@ jobs:
167138
- name: build
168139
run: cargo build --release --target ${{ matrix.target }}
169140
- name: smoke (ZTS)
170-
# Run the ZTS PHP inside the official php:X.Y-zts image (the only
171-
# convenient source of prebuilt ZTS binaries — Sury doesn't ship
172-
# them). pfp runs inside the same container so we don't need to
173-
# plumb pid namespaces between host and container.
174-
run: |
175-
cat > /tmp/spin.php <<'PHP'
176-
<?php
177-
class W { public function spin(): never { while (true) $this->a(); }
178-
public function a(): void { $this->b(); }
179-
public function b(): void { usleep(500); } }
180-
(new W())->spin();
181-
PHP
182-
BIN=./target/${{ matrix.target }}/release/pfp
183-
docker run -d --name php-zts \
184-
--cap-add=SYS_PTRACE \
185-
-v /tmp/spin.php:/spin.php:ro \
186-
-v "$(realpath $BIN):/pfp:ro" \
187-
${{ matrix.php_image }} \
188-
php /spin.php
189-
sleep 1
190-
docker exec php-zts php -i 2>/dev/null | grep -i 'thread safety' \
191-
| grep -qi enabled
192-
# php is the container's PID 1 — the docker-php-entrypoint execs
193-
# the command — and the php:*-zts image ships no pgrep (no procps).
194-
PHP_PID=1
195-
docker exec php-zts /pfp -p "$PHP_PID" -d 2 -H 99 -o /tmp/out.txt
196-
OUT=$(docker exec php-zts cat /tmp/out.txt)
197-
docker rm -f php-zts
198-
echo "$OUT" | head -20
199-
test "$(echo "$OUT" | grep -c '^0 ')" -ge 100
200-
echo "$OUT" | grep -q 'W::a'
201-
echo "$OUT" | grep -q 'W::b'
141+
run: ./scripts/ci/smoke-zts.sh ./target/${{ matrix.target }}/release/pfp ${{ matrix.php_image }}
142+
143+
smoke-pyroscope:
144+
name: smoke-pyroscope
145+
runs-on: ubuntu-latest
146+
needs: test
147+
services:
148+
pyroscope:
149+
image: grafana/pyroscope:latest
150+
ports:
151+
- 4040:4040
152+
steps:
153+
- uses: actions/checkout@v4
154+
- uses: dtolnay/rust-toolchain@stable
155+
- uses: arduino/setup-protoc@v3
156+
with:
157+
repo-token: ${{ secrets.GITHUB_TOKEN }}
158+
- uses: Swatinem/rust-cache@v2
159+
with:
160+
key: x86_64-unknown-linux-gnu-smoke-pyroscope
161+
- name: install PHP
162+
run: ./scripts/ci/install-php.sh
163+
- name: build
164+
run: cargo build --release
165+
- name: smoke (pyroscope)
166+
run: ./scripts/ci/smoke-pyroscope.sh ./target/release/pfp

.github/workflows/release.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77

88
permissions:
99
contents: write
10+
packages: write
1011

1112
env:
1213
CARGO_TERM_COLOR: always
@@ -83,3 +84,74 @@ jobs:
8384
files: artifacts/*
8485
generate_release_notes: true
8586
fail_on_unmatched_files: true
87+
88+
# Build the distroless image natively on each arch (no QEMU compile), push by
89+
# digest, then stitch a multi-arch manifest. See docs.docker.com multi-platform.
90+
docker:
91+
name: docker (${{ matrix.platform }})
92+
runs-on: ${{ matrix.runner }}
93+
strategy:
94+
fail-fast: false
95+
matrix:
96+
include:
97+
- platform: linux/amd64
98+
runner: ubuntu-latest
99+
arch: amd64
100+
- platform: linux/arm64
101+
runner: ubuntu-24.04-arm
102+
arch: arm64
103+
steps:
104+
- uses: actions/checkout@v4
105+
- uses: docker/setup-buildx-action@v3
106+
- uses: docker/login-action@v3
107+
with:
108+
registry: ghcr.io
109+
username: ${{ github.actor }}
110+
password: ${{ secrets.GITHUB_TOKEN }}
111+
- name: build and push by digest
112+
id: build
113+
uses: docker/build-push-action@v6
114+
with:
115+
context: .
116+
platforms: ${{ matrix.platform }}
117+
cache-from: type=gha,scope=docker-${{ matrix.arch }}
118+
cache-to: type=gha,mode=max,scope=docker-${{ matrix.arch }}
119+
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
120+
- name: export digest
121+
run: |
122+
mkdir -p /tmp/digests
123+
digest="${{ steps.build.outputs.digest }}"
124+
touch "/tmp/digests/${digest#sha256:}"
125+
- uses: actions/upload-artifact@v4
126+
with:
127+
name: digest-${{ matrix.arch }}
128+
path: /tmp/digests/*
129+
if-no-files-found: error
130+
retention-days: 1
131+
132+
docker-manifest:
133+
name: docker manifest
134+
needs: docker
135+
runs-on: ubuntu-latest
136+
steps:
137+
- uses: actions/download-artifact@v4
138+
with:
139+
path: /tmp/digests
140+
pattern: digest-*
141+
merge-multiple: true
142+
- uses: docker/setup-buildx-action@v3
143+
- uses: docker/login-action@v3
144+
with:
145+
registry: ghcr.io
146+
username: ${{ github.actor }}
147+
password: ${{ secrets.GITHUB_TOKEN }}
148+
- name: create manifest list and push
149+
working-directory: /tmp/digests
150+
run: |
151+
img=ghcr.io/${{ github.repository }}
152+
docker buildx imagetools create \
153+
-t "${img}:${{ github.ref_name }}" \
154+
-t "${img}:latest" \
155+
$(printf "${img}@sha256:%s " *)
156+
- name: inspect
157+
run: docker buildx imagetools inspect ghcr.io/${{ github.repository }}:${{ github.ref_name }}

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Continuous **Pyroscope** export (sidecar mode). `--pyroscope-url` makes pfp
12+
run continuously and push a gzipped pprof profile to a Grafana Pyroscope
13+
server every `--push-interval-secs` (default 10) instead of writing a file.
14+
Configurable via `--pyroscope-app`, repeatable `--pyroscope-label`, and auth
15+
via `--pyroscope-auth-token` / `--pyroscope-tenant-id`, plus arbitrary
16+
ingest headers via repeatable `--pyroscope-header "Name: value"`. Push failures are
17+
logged and never interrupt sampling. Pulls in a small synchronous HTTP
18+
client behind the default-on `pyroscope` feature.
19+
- Distroless multi-arch (amd64/arm64) container image, published to
20+
`ghcr.io/loks0n/php-fast-profile` on each release, for running pfp as a
21+
profiling sidecar.
22+
23+
### Changed
24+
- When the target binary's path (from its `/proc/PID/maps`) is not present in
25+
pfp's own mount namespace, fall back to `/proc/PID/root/<path>`. This lets a
26+
sidecar in a separate container resolve the profiled binary's symbols.
27+
1028
## [0.1.1] - 2026-06-02
1129

1230
### Added

0 commit comments

Comments
 (0)