Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shallow clone trees to save on memory usage #268

Closed
wants to merge 3 commits into from

Conversation

wagoodman
Copy link
Contributor

@wagoodman wagoodman commented Jul 10, 2024

Based off of the inuse memory in stereoscope we spend of lot of memory on copying the filetree for squashing. An observation here is that all tree mutation operations always create or replace nodes, but never mutate existing nodes. This means that we can share references across the multiple trees created for the squashfs functionality for each layer.

Here is a snapshot of inuse memory today for image read for a sample image:
Screenshot 2024-07-10 at 1 48 13 PM

Here is a snapshot of inuse memory after using map.Clone to shallow clone existing trees:
Screenshot 2024-07-10 at 1 48 22 PM

Partially fixes #233 in terms of inuse memory

Copy link

github-actions bot commented Jul 10, 2024

Benchmark Test Results

Benchmark results from the latest changes vs base branch
make .tool/task
make[1]: Entering directory '/home/runner/work/stereoscope/stereoscope'
make[1]: Leaving directory '/home/runner/work/stereoscope/stereoscope'
.tool/task show-benchstat
?   	github.com/anchore/stereoscope	[no test files]
?   	github.com/anchore/stereoscope/examples	[no test files]
PASS
ok  	github.com/anchore/stereoscope/internal	0.004s
?   	github.com/anchore/stereoscope/internal/bus	[no test files]
PASS
ok  	github.com/anchore/stereoscope/internal/containerd	0.007s
PASS
ok  	github.com/anchore/stereoscope/internal/docker	0.004s
?   	github.com/anchore/stereoscope/internal/log	[no test files]
PASS
ok  	github.com/anchore/stereoscope/internal/podman	0.004s
?   	github.com/anchore/stereoscope/pkg/event	[no test files]
?   	github.com/anchore/stereoscope/pkg/event/parsers	[no test files]
goos: linux
goarch: amd64
pkg: github.com/anchore/stereoscope/pkg/file
cpu: AMD EPYC 7763 64-Core Processor                
BenchmarkTarIndex-4   	   33931	     35337 ns/op	    5701 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33606	     35484 ns/op	    5700 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33586	     35578 ns/op	    5699 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33547	     35510 ns/op	    5700 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33752	     35458 ns/op	    5700 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33733	     35517 ns/op	    5699 B/op	      93 allocs/op
BenchmarkTarIndex-4   	   33644	     36200 ns/op	    5700 B/op	      93 allocs/op
PASS
ok  	github.com/anchore/stereoscope/pkg/file	10.930s
PASS
ok  	github.com/anchore/stereoscope/pkg/filetree	0.005s
?   	github.com/anchore/stereoscope/pkg/filetree/filenode	[no test files]
PASS
ok  	github.com/anchore/stereoscope/pkg/image	0.005s
PASS
ok  	github.com/anchore/stereoscope/pkg/image/containerd	0.008s
PASS
ok  	github.com/anchore/stereoscope/pkg/image/docker	0.005s
PASS
ok  	github.com/anchore/stereoscope/pkg/image/oci	0.005s
PASS
ok  	github.com/anchore/stereoscope/pkg/image/oci/credhelpers	0.005s
?   	github.com/anchore/stereoscope/pkg/image/podman	[no test files]
PASS
ok  	github.com/anchore/stereoscope/pkg/image/sif	0.004s
?   	github.com/anchore/stereoscope/pkg/imagetest	[no test files]
PASS
ok  	github.com/anchore/stereoscope/pkg/tree	0.003s
PASS
ok  	github.com/anchore/stereoscope/pkg/tree/node	0.003s
goos: linux
goarch: amd64
pkg: github.com/anchore/stereoscope/test/integration
cpu: AMD EPYC 7763 64-Core Processor                
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1089	   1117031 ns/op	  272927 B/op	    2288 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1065	   1115117 ns/op	  272343 B/op	    2287 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1071	   1112794 ns/op	  272319 B/op	    2287 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1063	   1108192 ns/op	  272218 B/op	    2286 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1054	   1142481 ns/op	  272185 B/op	    2286 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1075	   1168712 ns/op	  271843 B/op	    2286 allocs/op
BenchmarkSimpleImage_GetImage/docker-archive-4 	    1059	   1209491 ns/op	  272065 B/op	    2286 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      66	  18751020 ns/op	  401089 B/op	    2690 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      62	  18140414 ns/op	  399278 B/op	    2687 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      66	  17726432 ns/op	  400614 B/op	    2689 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      66	  19432686 ns/op	  401008 B/op	    2689 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      70	  17756842 ns/op	  399851 B/op	    2687 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      63	  17576693 ns/op	  400216 B/op	    2686 allocs/op
BenchmarkSimpleImage_GetImage/podman-4         	      69	  17751029 ns/op	  400324 B/op	    2687 allocs/op
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 345B done
#1 DONE 0.0s

#2 [internal] load .dockerignore
#2 transferring context: 2B done
#2 DONE 0.0s

#3 [internal] load build context
#3 transferring context: 209B done
#3 DONE 0.0s

#4 [1/3] ADD file-1.txt /somefile-1.txt
#4 CACHED

#5 [2/3] ADD file-2.txt /somefile-2.txt
#5 CACHED

#6 [3/3] ADD target /
#6 CACHED

#7 exporting to image
#7 exporting layers done
#7 writing image sha256:ab24e64a6e07eeed801342233f4b4b3f6bbf9c9fba8de79062fb0fcd4a90c084 done
#7 naming to docker.io/library/stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7 done
#7 naming to docker.io/library/stereoscope-fixture-image-simple:latest done
#7 DONE 0.0s
ctr: failed to dial "/run/containerd/containerd.sock": connection error: desc = "transport: error while dialing: dial unix /run/containerd/containerd.sock: connect: permission denied"
--- FAIL: BenchmarkSimpleImage_GetImage
    image_fixtures.go:193: using existing image tar: 'test-fixtures/cache/stereoscope-fixture-image-simple-04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7.tar' (size: 22528, modified: 2024-11-23 14:23:16.055256519 +0000 UTC, mode: -rw-r--r--)
    image_fixtures.go:241: Build docker image: name="stereoscope-fixture-image-simple" tag="04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
    image_fixtures.go:291: saveImage running: docker image save stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7
    image_fixtures.go:286: 
        	Error Trace:	/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:286
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:162
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:152
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:33
        	            				/home/runner/work/stereoscope/stereoscope/test/integration/fixture_image_simple_test.go:163
        	Error:      	Received unexpected error:
        	            	exit status 1
        	Test:       	BenchmarkSimpleImage_GetImage
        	Messages:   	could not import docker image to containerd (shell out)
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53263	     22247 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53539	     22331 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53355	     22308 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53769	     22345 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53619	     22252 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53511	     22285 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/docker-archive-4         	   53347	     22221 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   53391	     22245 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   53799	     22131 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   54122	     22184 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   54019	     22070 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   53866	     22161 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   53862	     22119 ns/op	    2712 B/op	      21 allocs/op
BenchmarkSimpleImage_FetchSquashedContents/podman-4                 	   50556	     22135 ns/op	    2712 B/op	      21 allocs/op
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 345B done
#1 DONE 0.0s

#2 [internal] load .dockerignore
#2 transferring context: 2B done
#2 DONE 0.0s

#3 [internal] load build context
#3 transferring context: 209B done
#3 DONE 0.0s

#4 [1/3] ADD file-1.txt /somefile-1.txt
#4 CACHED

#5 [2/3] ADD file-2.txt /somefile-2.txt
#5 CACHED

#6 [3/3] ADD target /
#6 CACHED

#7 exporting to image
#7 exporting layers done
#7 writing image sha256:ab24e64a6e07eeed801342233f4b4b3f6bbf9c9fba8de79062fb0fcd4a90c084 done
#7 naming to docker.io/library/stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7 done
#7 naming to docker.io/library/stereoscope-fixture-image-simple:latest done
#7 DONE 0.0s
ctr: failed to dial "/run/containerd/containerd.sock": connection error: desc = "transport: error while dialing: dial unix /run/containerd/containerd.sock: connect: permission denied"
--- FAIL: BenchmarkSimpleImage_FetchSquashedContents
    image_fixtures.go:193: using existing image tar: 'test-fixtures/cache/stereoscope-fixture-image-simple-04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7.tar' (size: 22528, modified: 2024-11-23 14:23:16.055256519 +0000 UTC, mode: -rw-r--r--)
    image_fixtures.go:241: Build docker image: name="stereoscope-fixture-image-simple" tag="04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
    image_fixtures.go:291: saveImage running: docker image save stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7
    image_fixtures.go:286: 
        	Error Trace:	/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:286
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:162
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:152
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:33
        	            				/home/runner/work/stereoscope/stereoscope/pkg/imagetest/image_fixtures.go:64
        	            				/home/runner/work/stereoscope/stereoscope/test/integration/fixture_image_simple_test.go:189
        	Error:      	Received unexpected error:
        	            	exit status 1
        	Test:       	BenchmarkSimpleImage_FetchSquashedContents
        	Messages:   	could not import docker image to containerd (shell out)
FAIL
exit status 1
FAIL	github.com/anchore/stereoscope/test/integration	38.939s
?   	github.com/anchore/stereoscope/test/integration/test-fixtures/registry	[no test files]
FAIL
goos: linux
goarch: amd64
pkg: github.com/anchore/stereoscope/pkg/file
cpu: AMD EPYC 7763 64-Core Processor                
ctr: 
           │ .tmp/benchmark-3645415.txt │
           │           sec/op           │
TarIndex-4                  35.51µ ± 2%

           │ .tmp/benchmark-3645415.txt │
           │            B/op            │
TarIndex-4                 5.566Ki ± 0%

           │ .tmp/benchmark-3645415.txt │
           │         allocs/op          │
TarIndex-4                   93.00 ± 0%

pkg: github.com/anchore/stereoscope/test/integration
                                      │ .tmp/benchmark-3645415.txt │
                                      │           sec/op           │
SimpleImage_GetImage/docker-archive-4                  1.117m ± 8%
SimpleImage_GetImage/podman-4                          17.76m ± 9%
geomean                                                4.454m

                                      │ .tmp/benchmark-3645415.txt │
                                      │            B/op            │
SimpleImage_GetImage/docker-archive-4                 265.8Ki ± 0%
SimpleImage_GetImage/podman-4                         390.9Ki ± 0%
geomean                                               322.4Ki

                                      │ .tmp/benchmark-3645415.txt │
                                      │         allocs/op          │
SimpleImage_GetImage/docker-archive-4                  2.286k ± 0%
SimpleImage_GetImage/podman-4                          2.687k ± 0%
geomean                                                2.478k

ctr: failed to dial "/run/containerd/containerd.sock": connection error: desc = "transport: error while dialing: dial unix /run/containerd/containerd.sock: connect: permission denied"
                                                   │ .tmp/benchmark-3645415.txt │
                                                   │           sec/op           │
SimpleImage_FetchSquashedContents/docker-archive-4                  22.29µ ± 0%
SimpleImage_FetchSquashedContents/podman-4                          22.14µ ± 0%
geomean                                                             22.21µ

                                                   │ .tmp/benchmark-3645415.txt │
                                                   │            B/op            │
SimpleImage_FetchSquashedContents/docker-archive-4                 2.648Ki ± 0%
SimpleImage_FetchSquashedContents/podman-4                         2.648Ki ± 0%
geomean                                                            2.648Ki

                                                   │ .tmp/benchmark-3645415.txt │
                                                   │         allocs/op          │
SimpleImage_FetchSquashedContents/docker-archive-4                   21.00 ± 0%
SimpleImage_FetchSquashedContents/podman-4                           21.00 ± 0%
geomean                                                              21.00
goos: linux
goarch: amd64
pkg: github.com/anchore/stereoscope/pkg/file
cpu: AMD EPYC 7763 64-Core Processor                
ctr: 
           │ .tmp/benchmark-3645415.txt │
           │           sec/op           │
TarIndex-4                  35.51µ ± 2%

           │ .tmp/benchmark-3645415.txt │
           │            B/op            │
TarIndex-4                 5.566Ki ± 0%

           │ .tmp/benchmark-3645415.txt │
           │         allocs/op          │
TarIndex-4                   93.00 ± 0%

pkg: github.com/anchore/stereoscope/test/integration
                                      │ .tmp/benchmark-3645415.txt │
                                      │           sec/op           │
SimpleImage_GetImage/docker-archive-4                  1.117m ± 8%
SimpleImage_GetImage/podman-4                          17.76m ± 9%
geomean                                                4.454m

                                      │ .tmp/benchmark-3645415.txt │
                                      │            B/op            │
SimpleImage_GetImage/docker-archive-4                 265.8Ki ± 0%
SimpleImage_GetImage/podman-4                         390.9Ki ± 0%
geomean                                               322.4Ki

                                      │ .tmp/benchmark-3645415.txt │
                                      │         allocs/op          │
SimpleImage_GetImage/docker-archive-4                  2.286k ± 0%
SimpleImage_GetImage/podman-4                          2.687k ± 0%
geomean                                                2.478k

ctr: failed to dial "/run/containerd/containerd.sock": connection error: desc = "transport: error while dialing: dial unix /run/containerd/containerd.sock: connect: permission denied"
                                                   │ .tmp/benchmark-3645415.txt │
                                                   │           sec/op           │
SimpleImage_FetchSquashedContents/docker-archive-4                  22.29µ ± 0%
SimpleImage_FetchSquashedContents/podman-4                          22.14µ ± 0%
geomean                                                             22.21µ

                                                   │ .tmp/benchmark-3645415.txt │
                                                   │            B/op            │
SimpleImage_FetchSquashedContents/docker-archive-4                 2.648Ki ± 0%
SimpleImage_FetchSquashedContents/podman-4                         2.648Ki ± 0%
geomean                                                            2.648Ki

                                                   │ .tmp/benchmark-3645415.txt │
                                                   │         allocs/op          │
SimpleImage_FetchSquashedContents/docker-archive-4                   21.00 ± 0%
SimpleImage_FetchSquashedContents/podman-4                           21.00 ± 0%
geomean                                                              21.00

@wagoodman wagoodman marked this pull request as ready for review July 15, 2024 13:45
@wagoodman wagoodman requested a review from a team October 14, 2024 18:28
kzantow
kzantow previously approved these changes Oct 14, 2024
@wagoodman
Copy link
Contributor Author

After talking with @willmurphyscode I'm going to hold off on merging in order to demonstrate a little more proof of this working as intended in syft. If all goes well I think this will land on the order of days 🎉

@willmurphyscode
Copy link
Contributor

The test we still want to do is get a big image that has different file types for the same location, and edit the file, and prove that the filetree object doesn't get mutated. This can probably be done with the stereoscope example application. Basically, make an image that, for a given path, has layers where that path is a directory with children, then where it's a file, where it's a link, where it's a file with different contents, etc. Basically fuzzing the tree to prove that the structs that are now being shallow-copied won't be mutated.

@willmurphyscode willmurphyscode self-assigned this Nov 22, 2024
@willmurphyscode willmurphyscode dismissed kzantow’s stale review November 23, 2024 12:43

we decided to do additional testing before merging

@willmurphyscode
Copy link
Contributor

I ran a test, and I think we have some differences here.

Overview

  1. build an image that does silly things with a path (e.g. there's a path that's a symlink in one layer, an empty dir in another, a dir with contents in another, a file in another). Dockerfile below.
  2. Check out main and write go run ./examples/basic.go and save the result
  3. Check out this PR, rebase on main, and then run go run ./examples/basic.go and save the result
  4. Diff the files from steps 2 and 3. If there's no diff, the change has no effect.

Setup

❯ tree .
.
├── Dockerfile
└── chimera
    ├── a.txt
    └── b.txt
FROM debian:trixie-slim

# makes /chimera/a.txt /chimera/b.txt
COPY . / 

# make a layer where the dir is empty
RUN rm /chimera/a.txt /chimera/b.txt

RUN rmdir /chimera

RUN touch /chimera

RUN rm /chimera

RUN ln -s /etc/hostname /chimera

RUN unlink /chimera

RUN mkdir /chimera
docker build . -t localhost/aggressive

Saving results like

go run ./examples/basic.go localhost/aggressive > willtmp/before.txt
git checkout shallow-tree-clone
git rebase origin/main
go run ./examples/basic.go localhost/aggressive > willtmp/after-with-rebase.txt

Results

Running diff -u --color willtmp/before.txt willtmp/after-with-rebase.txt produces the following output:

--- willtmp/before.txt  2024-11-23 07:39:21
+++ willtmp/after-with-rebase.txt       2024-11-23 07:44:20
@@ -10336,8 +10336,6 @@
     /bin/znew
     /boot
     /chimera
-    /chimera/a.txt
-    /chimera/b.txt
     /dev
     /etc
     /etc/.pwd.lock

I think some more investigation is needed before we merge this. A reasonable next step is probably to write a failing test that exhibits this bug and push it to this branch. The above testing proves there is a difference, but not much about what the cause is.

@willmurphyscode
Copy link
Contributor

I've pushed a test case that passes on main but fails on this branch that I think illustrates the bug.

@wagoodman
Copy link
Contributor Author

Thanks for taking the due diligence here and proving that this doesn't quite work in the context of syft -- I'm going to close this for now.

@wagoodman wagoodman closed this Nov 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Very High Memory Usage Using Syft
3 participants