Skip to content

process: reuse and preallocate memory#355

Merged
florianl merged 8 commits intomainfrom
process-alloc
Feb 25, 2025
Merged

process: reuse and preallocate memory#355
florianl merged 8 commits intomainfrom
process-alloc

Conversation

@florianl
Copy link
Copy Markdown
Member

No description provided.

Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
@florianl florianl requested review from a team as code owners February 17, 2025 11:13
@rockdaboot
Copy link
Copy Markdown
Contributor

Can you add memory allocations for before and after?

Comment thread process/process.go
Comment thread process/process.go Outdated
Comment thread process/process.go
@florianl
Copy link
Copy Markdown
Member Author

florianl commented Feb 18, 2025

Updated benchmark on 4b538b5:

$ benchstat old.txt new.txt 
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/ebpf-profiler/process
cpu: 12th Gen Intel(R) Core(TM) i7-12700H
               │   old.txt   │               new.txt               │
               │   sec/op    │   sec/op     vs base                │
GetMappings-20   37.14µ ± 1%   38.01µ ± 1%  +2.34% (p=0.000 n=10)

               │    old.txt    │               new.txt               │
               │     B/op      │     B/op      vs base               │
GetMappings-20   10.574Ki ± 0%   9.605Ki ± 0%  -9.16% (p=0.000 n=10)

               │  old.txt   │              new.txt               │
               │ allocs/op  │ allocs/op   vs base                │
GetMappings-20   28.00 ± 0%   24.00 ± 0%  -14.29% (p=0.000 n=10)

BenchmarkGetMappings
func BenchmarkGetMappings(b *testing.B) {
	pr := New(libpf.PID(os.Getpid()))
	b.ResetTimer()
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		mappings, err := pr.GetMappings()
		if err != nil {
			b.Fatal(err)
		}
		if len(mappings) == 0 {
			b.Fatal("expected non-empty mappings")
		}
	}
}

@florianl
Copy link
Copy Markdown
Member Author

florianl commented Feb 18, 2025

Updated benchmark on 4b538b5:

$ benchstat old.txt new.txt 
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/ebpf-profiler/process
cpu: 12th Gen Intel(R) Core(TM) i7-12700H
                 │   old.txt   │              new.txt               │
                 │   sec/op    │   sec/op     vs base               │
ParseMappings-20   202.4µ ± 2%   204.4µ ± 5%  +0.97% (p=0.009 n=10)

                 │   old.txt    │               new.txt               │
                 │     B/op     │     B/op      vs base               │
ParseMappings-20   164.7Ki ± 0%   162.3Ki ± 0%  -1.48% (p=0.000 n=10)

                 │  old.txt   │              new.txt              │
                 │ allocs/op  │ allocs/op   vs base               │
ParseMappings-20   444.0 ± 0%   438.0 ± 0%  -1.35% (p=0.000 n=10)

BenchmarkParseMappings
func BenchmarkParseMappings(b *testing.B) {
    // Get sample data from current process
    mapsFile, err := os.Open(fmt.Sprintf("/proc/%d/maps", os.Getpid()))
    if err != nil {
        b.Fatal(err)
    }
    defer mapsFile.Close()
    
    // Read all content for reuse in benchmark
    content, err := io.ReadAll(mapsFile)
    if err != nil {
        b.Fatal(err)
    }

    b.ResetTimer()
    b.ReportAllocs()
    
    for i := 0; i < b.N; i++ {
        reader := bytes.NewReader(content)
        mappings, err := parseMappings(reader)
        if err != nil {
            b.Fatal(err)
        }
        if len(mappings) == 0 {
            b.Fatal("expected non-empty mappings")
        }
    }
}

Co-authored-by: Tim Rühsen <tim.ruhsen@elastic.co>
Comment thread process/process.go Outdated
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
Comment thread process/process.go Outdated
Comment thread process/process.go
Comment thread process/process.go Outdated
florianl and others added 2 commits February 21, 2025 08:18
Co-authored-by: Christos Kalkanis <christos.kalkanis@elastic.co>
Co-authored-by: Christos Kalkanis <christos.kalkanis@elastic.co>
Comment thread process/process.go
Comment on lines +90 to +93
// Reset memory and return it for reuse.
for j := 0; j < len(*scanBuf); j++ {
(*scanBuf)[j] = 0x0
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought... why clear the memory at all here? parseMapping() does not rely on the contents of the buffer retrieved from the pool. The scanner read data and only acts on the read data in the buffer. It doesn't matter if we fill the bufferswith 0 bytes or random bytes or leave it as is.

Suggested change
// Reset memory and return it for reuse.
for j := 0; j < len(*scanBuf); j++ {
(*scanBuf)[j] = 0x0
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scanner, that uses this buffer, uses the default split function ScanLines. By not clearing the buffer, we might introduce subtle bugs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might introduce subtle bugs

Possibly yes. Can you add a test case that triggers such a bug. That makes it obvious to if someone ever touches that code or wonders if it is required.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I can't see how bufio.Scanner.Scan() uses ScanLines().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we don't set a specific Split function, the buffer uses the default https://pkg.go.dev/bufio#NewScanner.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reverted the applied suggestion, as I should not have applied in the first place. And I also reasked for review.
I have reverted the applied suggestion as resetting a buffer before reusing is something I will ask in a review, until it is shown that the operation can be done correctly with every state of the buffer. And this proposed change does not show that it will work as supposed with every state of the shared buffer.

Copy link
Copy Markdown
Contributor

@rockdaboot rockdaboot Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still didn't see any argument why or what exactly can fail. Without an explicit statement it is not possible to create a test case. Can't we please go with the facts and read the code instead of having some vague "fear"?

Copy link
Copy Markdown
Contributor

@rockdaboot rockdaboot Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said, the std library looks sound to me and doesn't require resetting the buffer to 0. Additionally, we can prove that empirically with a comparison fuzzer:

diff --git a/process/process_test.go b/process/process_test.go
index 3cd2b7d..f64700c 100644
--- a/process/process_test.go
+++ b/process/process_test.go
@@ -4,6 +4,7 @@
 package process
 
 import (
+       "bytes"
        "debug/elf"
        "os"
        "strings"
@@ -105,3 +106,16 @@ func TestNewPIDOfSelf(t *testing.T) {
        require.NoError(t, err)
        assert.NotEmpty(t, mappings)
 }
+
+func FuzzParseMappings(f *testing.F) {
+       f.Fuzz(func(t *testing.T, input []byte) {
+               r1 := bytes.NewReader(input)
+               m1, err1 := parseMappings(r1)
+
+               r2 := bytes.NewReader(input)
+               m2, err2 := parseMappingsNoReset(r2)
+
+               require.Equal(t, err1, err2)
+               require.Equal(t, m1, m2)
+       })
+}

After ~30min, there is no indication that resetting the buffer makes any difference.

...
fuzz: elapsed: 31m45s, execs: 219880737 (97874/sec), new interesting: 77 (total: 149)
fuzz: elapsed: 31m48s, execs: 220278902 (132716/sec), new interesting: 77 (total: 149)
fuzz: elapsed: 31m51s, execs: 220549371 (90139/sec), new interesting: 77 (total: 149)

Unexpected result from running the fuzzer: The agent exits if it fails to parse fields to integer values, see #366

Copy link
Copy Markdown
Member Author

@florianl florianl Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will likely close this PR without merge, as there is no consensus.
For me, reused memory should always be cleared unless it can be shown that not clearing the memory does not interfere with expected behavior. And this PR does not show, that the function works as expected with every state of reused and not reseted memory.
Clearing memory before reusing it prevents a whole class of memory related bugs and I don't want to be the person, introducing such a change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we now have all the facts on the table. Why not wait for other reviews and then follow whatever the consensus is?

Co-authored-by: Tim Rühsen <tim.ruhsen@elastic.co>
@florianl florianl requested a review from rockdaboot February 24, 2025 06:44
Copy link
Copy Markdown
Contributor

@rockdaboot rockdaboot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, we should create a follow-up issue or PR for the open discussion on whether we want resetting of the buffer.

@florianl florianl merged commit 3e1f4a2 into main Feb 25, 2025
@florianl florianl deleted the process-alloc branch February 25, 2025 15:31
bhavnajindal added a commit to instana/opentelemetry-ebpf-profiler that referenced this pull request Mar 12, 2025
Sync from upstream (2025-03-12)

Florian Lehner <florianl@users.noreply.github.com> symblib: expose API for single point lookups (open-telemetry#380)
Co-authored-by: GitHub <noreply@github.com>
Tolya Korniltsev <korniltsev.anatoly@gmail.com> chore: remove unused controller.Config fields (open-telemetry#387)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> libpf: drop unused code (open-telemetry#386)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> tracehandler: drop metadataWarnInhib (open-telemetry#385)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> Go: update to go.opentelemetry.io/otel@v1.35.0 (open-telemetry#383)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> processmanager: Don't synchronize a process that's waiting cleanup (open-telemetry#379)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> CI: use latest LTS kernel in tests (open-telemetry#382)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> Makefile: add cargo clean to target clean (open-telemetry#381)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Switch semantics for process.executable.name (open-telemetry#306)
Co-authored-by: GitHub <noreply@github.com>
Tim Rühsen <tim.ruhsen@elastic.co> Stabilize CI / integration tests (open-telemetry#378)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> Docker fixup (open-telemetry#375)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> Docker: fix rust set up (open-telemetry#371)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> tracer: attach to all kprobes with prefix for off CPU profiling (open-telemetry#370)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> Go: update to Go 1.23 (open-telemetry#372)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> support: generate *ProcInfo types with cgo (open-telemetry#367)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> process: reuse and preallocate memory (open-telemetry#355)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> rust: preparations to integrate Rust (open-telemetry#360)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Switch to OTel metrics (open-telemetry#348)
Co-authored-by: GitHub <noreply@github.com>
Tolya Korniltsev <korniltsev.anatoly@gmail.com> cargo: remove unused workspace dependency declarations (open-telemetry#364)
Co-authored-by: GitHub <noreply@github.com>
Tolya Korniltsev <korniltsev.anatoly@gmail.com> reporter: add custom gRPC dial options (open-telemetry#363)
Co-authored-by: GitHub <noreply@github.com>
umanwizard <brennan@umanwizard.com> Various fixes to node/V8 (open-telemetry#333)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> doc: fix path of tooling (open-telemetry#361)
Co-authored-by: GitHub <noreply@github.com>
OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Add FOSSA scanning workflow (open-telemetry#357)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> rust: use macro for debug output (open-telemetry#356)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> symblib/gosym: add single point lookup (open-telemetry#346)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> README: provide devfiler v0.14.0 (open-telemetry#354)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> CI: skip environment setup (open-telemetry#353)
Co-authored-by: GitHub <noreply@github.com>
Richard Chukwu <79311274+RichardChukwu@users.noreply.github.com> Improve contributor guide (open-telemetry#349)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Fix build (open-telemetry#350)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> processinfo: refactor process metadata (open-telemetry#344)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> reporter/pdata: do no generate profiles if there are no events (open-telemetry#347)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> README: provide devfiler v0.13.0 (open-telemetry#343)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> processmanager: Fix process exit regression (open-telemetry#337) (open-telemetry#338)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> libpf: drop Hash64 (open-telemetry#340)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> cargo: set license field (open-telemetry#336)
Co-authored-by: GitHub <noreply@github.com>
Damien Mathieu <42@dmathieu.com> Use dummy support for any non-arm64 and non-amd64 archs (open-telemetry#335)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> rust: drop anyhow dependency (open-telemetry#334)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> support: use cgo to generate Go constants from eBPF (open-telemetry#332)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> processmanager: Don't log inside critical areas (open-telemetry#328)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> CI: add test for Rust components (open-telemetry#326)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> processmanager: simplify API and return early (open-telemetry#325)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Add Rust native symbolization library and C API wrapper (open-telemetry#267)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Metrics for trace event perf event monitor (open-telemetry#322)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Delayed processing for ProcessManager.pidToProcessInfo (open-telemetry#321)
Co-authored-by: GitHub <noreply@github.com>
Christos Kalkanis <christos.kalkanis@elastic.co> Rework SymbolizationComplete (open-telemetry#307)
Co-authored-by: GitHub <noreply@github.com>
Tim Rühsen <tim.ruhsen@elastic.co> Amend -off-cpu-threshold value (open-telemetry#316)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> reporter/collector: fix reporting issue (open-telemetry#319)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> reporter: move pkg samples from internal to public (open-telemetry#314)
Co-authored-by: GitHub <noreply@github.com>
Florian Lehner <florianl@users.noreply.github.com> README: provide devfiler v0.11.0 (open-telemetry#313)
Co-authored-by: GitHub <noreply@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants