Skip to content

Commit 186bbce

Browse files
committed
Patch: memory fix
1 parent 9cf1e66 commit 186bbce

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: BetterStack Docker Build and Push
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- betterstack-*
8+
tags:
9+
- 'v*'
10+
pull_request:
11+
branches:
12+
- main
13+
14+
env:
15+
IMAGE_NAME: betterstack/beyla
16+
17+
jobs:
18+
build-and-push:
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
packages: write
23+
24+
steps:
25+
- name: Checkout code
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 0
29+
30+
- name: Set up Go
31+
uses: actions/setup-go@v5
32+
with:
33+
go-version-file: go.mod
34+
35+
- name: Apply memory leak fix
36+
run: |
37+
echo "Applying BetterStack memory leak fix..."
38+
patch -p1 < fix-memory-leak-minimal.patch
39+
40+
- name: Set up QEMU
41+
uses: docker/setup-qemu-action@v3
42+
43+
- name: Set up Docker Buildx
44+
uses: docker/setup-buildx-action@v3
45+
46+
- name: Log in to DockerHub
47+
if: github.event_name != 'pull_request'
48+
uses: docker/login-action@v3
49+
with:
50+
username: ${{ secrets.DOCKERHUB_USERNAME }}
51+
password: ${{ secrets.DOCKERHUB_TOKEN }}
52+
53+
- name: Extract metadata
54+
id: meta
55+
uses: docker/metadata-action@v5
56+
with:
57+
images: ${{ env.IMAGE_NAME }}
58+
tags: |
59+
# For main branch, use 'latest' tag
60+
type=raw,value=latest,enable={{is_default_branch}}
61+
# For version tags, use the version
62+
type=ref,event=tag
63+
# For branches, use branch name
64+
type=ref,event=branch
65+
# SHA prefix for all builds
66+
type=sha,prefix={{branch}}-,format=short
67+
68+
- name: Build and test
69+
run: |
70+
make build
71+
make test
72+
73+
- name: Build and push Docker image
74+
uses: docker/build-push-action@v5
75+
with:
76+
context: .
77+
platforms: linux/amd64,linux/arm64
78+
push: ${{ github.event_name != 'pull_request' }}
79+
tags: ${{ steps.meta.outputs.tags }}
80+
labels: ${{ steps.meta.outputs.labels }}
81+
cache-from: type=gha
82+
cache-to: type=gha,mode=max
83+
build-args: |
84+
VERSION=${{ github.ref_name }}
85+
COMMIT=${{ github.sha }}
86+
87+
- name: Update Docker Hub description
88+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
89+
uses: peter-evans/dockerhub-description@v3
90+
with:
91+
username: ${{ secrets.DOCKERHUB_USERNAME }}
92+
password: ${{ secrets.DOCKERHUB_TOKEN }}
93+
repository: ${{ env.IMAGE_NAME }}
94+
short-description: "BetterStack's fork of Beyla with memory leak fixes"
95+
readme-filepath: ./README_BETTERSTACK.md

README_BETTERSTACK.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# BetterStack Beyla Fork
2+
3+
This is BetterStack's fork of [Grafana Beyla](https://github.com/grafana/beyla) with critical fixes for production use.
4+
5+
## Why This Fork?
6+
7+
This fork includes essential patches that fix memory leaks in Beyla when monitoring high-connection processes. The patches have been submitted upstream but are critical for production use.
8+
9+
## Patches Applied
10+
11+
### Memory Leak Fix
12+
- **Issue**: Unbounded array growth when tracking processes with many ephemeral connections
13+
- **Fix**: Deduplicates ports and caps the maximum tracked ports per process
14+
- **Impact**: Prevents OOM kills when monitoring load generators or high-traffic clients
15+
- **PR**: [Pending upstream submission]
16+
17+
## Docker Images
18+
19+
Pre-built Docker images with our patches are available at:
20+
```
21+
docker pull betterstack/beyla:latest
22+
```
23+
24+
## Usage
25+
26+
This fork is a drop-in replacement for the official Beyla. Simply replace your image reference:
27+
28+
```yaml
29+
# Before
30+
image: grafana/beyla:latest
31+
32+
# After
33+
image: betterstack/beyla:latest
34+
```
35+
36+
## Building From Source
37+
38+
```bash
39+
# Clone this repository
40+
git clone [email protected]:BetterStackHQ/beyla.git
41+
cd beyla
42+
43+
# The patch is automatically applied during CI/CD
44+
# To apply manually:
45+
patch -p1 < fix-memory-leak-minimal.patch
46+
47+
# Build
48+
make build
49+
```
50+
51+
## Staying Up-to-Date
52+
53+
This fork is regularly synced with upstream Grafana Beyla. Our CI/CD automatically:
54+
1. Applies our patches
55+
2. Runs all tests
56+
3. Builds multi-architecture Docker images
57+
4. Pushes to DockerHub
58+
59+
## Contributing
60+
61+
Issues and PRs related to our patches should be opened in this fork. For general Beyla issues, please contribute upstream at [grafana/beyla](https://github.com/grafana/beyla).
62+
63+
## License
64+
65+
This fork maintains the same Apache 2.0 license as the original Beyla project.

fix-memory-leak-minimal.patch

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--- a/vendor/go.opentelemetry.io/obi/pkg/components/discover/watcher_proc.go
2+
+++ b/vendor/go.opentelemetry.io/obi/pkg/components/discover/watcher_proc.go
3+
@@ -469,6 +469,11 @@ func processAgeFunc(pid int32) time.Duration {
4+
return time.Since(createTime)
5+
}
6+
7+
+const (
8+
+ // Maximum ports to track per process to prevent memory exhaustion
9+
+ maxPortsPerProcess = 1000
10+
+)
11+
+
12+
// fetchProcessConnections returns a map with the PIDs of all the running processes as a key,
13+
// and the open ports for the given process as a value
14+
func fetchProcessPorts(scanPorts bool) (map[PID]ProcessAttrs, error) {
15+
@@ -488,11 +493,29 @@ func fetchProcessPorts(scanPorts bool) (map[PID]ProcessAttrs, error) {
16+
log.Debug("can't get connections for process. Skipping", "pid", pid, "error", err)
17+
continue
18+
}
19+
- var openPorts []uint32
20+
- // TODO: Cap the size of this array, leaking client ephemeral ports will cause this to grow very long
21+
+
22+
+ // Use a map to deduplicate ports first
23+
+ portSet := make(map[uint32]bool)
24+
for _, conn := range conns {
25+
- openPorts = append(openPorts, conn.Laddr.Port)
26+
+ portSet[conn.Laddr.Port] = true
27+
+ }
28+
+
29+
+ // Convert to array with size limit to prevent memory exhaustion
30+
+ var openPorts []uint32
31+
+ cappedPorts := false
32+
+ for port := range portSet {
33+
+ if len(openPorts) >= maxPortsPerProcess {
34+
+ cappedPorts = true
35+
+ break
36+
+ }
37+
+ openPorts = append(openPorts, port)
38+
}
39+
+
40+
+ // Log at debug level when we cap ports (not warn to avoid log spam)
41+
+ if cappedPorts {
42+
+ log.Debug("capped ports for process to prevent memory exhaustion",
43+
+ "pid", pid, "total_unique_ports", len(portSet), "tracked_ports", len(openPorts))
44+
+ }
45+
+
46+
processes[PID(pid)] = ProcessAttrs{pid: PID(pid), openPorts: openPorts, processAge: time.Duration(0)}
47+
}
48+
return processes, nil

0 commit comments

Comments
 (0)