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

linux: optimize parseKeyValue #186

Merged
merged 6 commits into from
Aug 16, 2023
Merged

linux: optimize parseKeyValue #186

merged 6 commits into from
Aug 16, 2023

Conversation

ShimmerGlass
Copy link
Contributor

As seen in this alloc_objects profile from auditbeat linux.parseKeyValue() accounts for a significant amount of the program allocations:
profile003

This PR adds tests on the current implem to validate the expected behavior and proposes a new implementation for this function that removes all allocs. As a nice side effect, it also cuts down cpu time by ~90%:

goos: linux
goarch: amd64
pkg: github.com/elastic/go-sysinfo/providers/linux
cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
                 │   old.bench   │              new.bench              │
                 │    sec/op     │   sec/op     vs base                │
ParseKeyValue-12   9169.0n ± 13%   984.8n ± 4%  -89.26% (p=0.000 n=10)

                 │  old.bench   │               new.bench               │
                 │     B/op     │     B/op      vs base                 │
ParseKeyValue-12   6.672Ki ± 0%   0.000Ki ± 0%  -100.00% (p=0.000 n=10)

                 │ old.bench  │             new.bench              │
                 │ allocs/op  │ allocs/op  vs base                 │
ParseKeyValue-12   58.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

@cla-checker-service
Copy link

cla-checker-service bot commented Jul 26, 2023

💚 CLA has been signed

@elasticmachine
Copy link
Collaborator

elasticmachine commented Jul 26, 2023

💔 Tests Failed

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview preview

Expand to view the summary

Build stats

  • Start Time: 2023-07-27T15:52:20.987+0000

  • Duration: 5 min 12 sec

Test stats 🧪

Test Results
Failed 3
Passed 170
Skipped 2
Total 175

Test errors 3

Expand to view the tests failures

Test / Matrix - GO_VERSION = '1.20.4', PLATFORM = 'orka && darwin && arm64', CGO_ENABLED = '0' / Test / TestVmStatParse – github.com/elastic/go-sysinfo/providers/linux
    Expand to view the error details

     Failed 
    

    Expand to view the stacktrace

     === RUN   TestVmStatParse
    --- FAIL: TestVmStatParse (0.00s)
    panic: runtime error: slice bounds out of range [15:0] [recovered]
    	panic: runtime error: slice bounds out of range [15:0]
    
    goroutine 46 [running]:
    testing.tRunner.func1.2({0x10500f2c0, 0x140000a0c90})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1526 +0x1c8
    testing.tRunner.func1()
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1529 +0x384
    panic({0x10500f2c0, 0x140000a0c90})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/runtime/panic.go:884 +0x204
    github.com/elastic/go-sysinfo/providers/linux.parseKeyValue({0x14000196000?, 0x14000112b00?, 0xab0?}, 0x20, 0x14000092ec8)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/util.go:43 +0x1d8
    github.com/elastic/go-sysinfo/providers/linux.parseVMStat({0x14000196000, 0xab0, 0xc00})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat.go:48 +0x120
    github.com/elastic/go-sysinfo/providers/linux.TestVmStatParse(0x140001884e0)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat_test.go:163 +0x38
    testing.tRunner(0x140001884e0, 0x1050224e8)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1576 +0x10c
    created by testing.(*T).Run
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1629 +0x368
     
    

Test / Matrix - GO_VERSION = '1.20.4', PLATFORM = 'ubuntu-18 && immutable', CGO_ENABLED = '0' / Test / TestVmStatParse – github.com/elastic/go-sysinfo/providers/linux
    Expand to view the error details

     Failed 
    

    Expand to view the stacktrace

     === RUN   TestVmStatParse
    --- FAIL: TestVmStatParse (0.00s)
    panic: runtime error: slice bounds out of range [15:0] [recovered]
    	panic: runtime error: slice bounds out of range [15:0]
    
    goroutine 76 [running]:
    testing.tRunner.func1.2({0x8cdf00, 0xc00012c408})
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.linux.amd64/src/testing/testing.go:1526 +0x24e
    testing.tRunner.func1()
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.linux.amd64/src/testing/testing.go:1529 +0x39f
    panic({0x8cdf00, 0xc00012c408})
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.linux.amd64/src/runtime/panic.go:884 +0x213
    github.com/elastic/go-sysinfo/providers/linux.parseKeyValue({0xc0005d1000?, 0xc000348c00?, 0xab0?}, 0x20, 0xc0004436d0)
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/util.go:43 +0x227
    github.com/elastic/go-sysinfo/providers/linux.parseVMStat({0xc0005d1000, 0xab0, 0xc00})
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat.go:48 +0x10d
    github.com/elastic/go-sysinfo/providers/linux.TestVmStatParse(0xc0003c1d40)
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat_test.go:163 +0x37
    testing.tRunner(0xc0003c1d40, 0x93aa28)
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.linux.amd64/src/testing/testing.go:1576 +0x10b
    created by testing.(*T).Run
    	/var/lib/jenkins/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.linux.amd64/src/testing/testing.go:1629 +0x3ea
     
    

Test / Matrix - GO_VERSION = '1.20.4', PLATFORM = 'orka && darwin && arm64', CGO_ENABLED = '1' / Test / TestVmStatParse – github.com/elastic/go-sysinfo/providers/linux
    Expand to view the error details

     Failed 
    

    Expand to view the stacktrace

     === RUN   TestVmStatParse
    --- FAIL: TestVmStatParse (0.00s)
    panic: runtime error: slice bounds out of range [15:0] [recovered]
    	panic: runtime error: slice bounds out of range [15:0]
    
    goroutine 46 [running]:
    testing.tRunner.func1.2({0x1028232c0, 0x140000a0c90})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1526 +0x1c8
    testing.tRunner.func1()
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1529 +0x384
    panic({0x1028232c0, 0x140000a0c90})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/runtime/panic.go:884 +0x204
    github.com/elastic/go-sysinfo/providers/linux.parseKeyValue({0x14000196000?, 0x14000112b00?, 0xab0?}, 0x20, 0x14000092ec8)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/util.go:43 +0x1d8
    github.com/elastic/go-sysinfo/providers/linux.parseVMStat({0x14000196000, 0xab0, 0xc00})
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat.go:48 +0x120
    github.com/elastic/go-sysinfo/providers/linux.TestVmStatParse(0x14000186680)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/src/github.com/elastic/go-sysinfo/providers/linux/vmstat_test.go:163 +0x38
    testing.tRunner(0x14000186680, 0x1028364e8)
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1576 +0x10c
    created by testing.(*T).Run
    	/Users/admin/workspace/Library_go-sysinfo-mbp_PR-186/.gvm/versions/go1.20.4.darwin.arm64/src/testing/testing.go:1629 +0x368
     
    

Steps errors 3

Expand to view the steps failures

go test for 1.20.4 in ubuntu-18 && immutable
  • Took 1 min 27 sec . View more details here
  • Description: .ci/scripts/test.sh
go test for 1.20.4 in orka && darwin && arm64
  • Took 1 min 6 sec . View more details here
  • Description: .ci/scripts/test.sh
go test for 1.20.4 in orka && darwin && arm64
  • Took 1 min 8 sec . View more details here
  • Description: .ci/scripts/test.sh

@andrewkroh
Copy link
Member

The CLA check is failing because the author email in the commit does not match the email used to sign the CLA.

@andrewkroh
Copy link
Member

A changelog entry will also be required. See https://github.com/elastic/go-sysinfo/blob/main/CONTRIBUTING.md.

@andrewkroh andrewkroh added the enhancement New feature or request label Jul 26, 2023
@ShimmerGlass
Copy link
Contributor Author

ShimmerGlass commented Jul 27, 2023

@andrewkroh signed the CLA with the correct email, also provided a changelog file. lmk if the description of the change is ok :)

@andrewkroh
Copy link
Member

/test

Copy link
Member

@andrewkroh andrewkroh left a comment

Choose a reason for hiding this comment

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

Needs some changes because it can panic.

@ShimmerGlass
Copy link
Contributor Author

You're right ! I fixed the panic and included your fuzz test

@andrewkroh
Copy link
Member

CI is failing on a missing license header. If you run make update it should add the header.

@andrewkroh
Copy link
Member

Tests are still failing:

=== Failed
=== FAIL: providers/linux TestVmStatParse (0.00s)
    vmstat_test.go:165: unterminated input

@ShimmerGlass
Copy link
Contributor Author

fixed

@andrewkroh andrewkroh requested a review from a team August 7, 2023 18:55
@andrewkroh andrewkroh added the Team:Security-External Integrations Label for the Security External Integrations team label Aug 7, 2023
Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

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

I think the logic of the parser could be made a fair bit simpler by using bytes.Cut.

Comment on lines 123 to 143
parseKeyValue(testProcStatus, ':', func(key, value []byte) error {
return nil
})
Copy link
Contributor

Choose a reason for hiding this comment

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

This can potentially be optimised away.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added an _ assignment to ensure evaluation

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm afraid I do not believe that that is necessarily safe. To avoid the potential issue, there needs to be a side effect.

providers/linux/util.go Show resolved Hide resolved
if len(parts) != 2 {
func parseKeyValue(content []byte, separator byte, callback func(key, value []byte) error) error {
for len(content) > 0 {
sepIdx := bytes.IndexByte(content, separator)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we tolerate empty lines? The previous code did.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a test specifically for empty lines

@ShimmerGlass
Copy link
Contributor Author

You're right, I updated my change to use bytes.Cut() instead and the code is indeed much shorter and simpler.
Here are the updated benchmark results for this new implem:

goos: linux
goarch: amd64
pkg: github.com/elastic/go-sysinfo/providers/linux
cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
                 │  old.bench   │              new.bench              │
                 │    sec/op    │   sec/op     vs base                │
ParseKeyValue-12   9.169µ ± 13%   1.611µ ± 3%  -82.44% (p=0.000 n=10)

                 │  old.bench   │               new.bench               │
                 │     B/op     │     B/op      vs base                 │
ParseKeyValue-12   6.672Ki ± 0%   0.000Ki ± 0%  -100.00% (p=0.000 n=10)

                 │ old.bench  │             new.bench              │
                 │ allocs/op  │ allocs/op  vs base                 │
ParseKeyValue-12   58.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

This new version seems slightly slower than the one using bytes.Index() but given the reduction in code complexity I think that this totally acceptable.

@andrewkroh
Copy link
Member

I compared the benchmark results from e4ac65c to f3a5024. Looks good.

benchstat before.txt after.txt 
goos: darwin
goarch: arm64
pkg: github.com/elastic/go-sysinfo/providers/linux
                 │  before.txt   │              after.txt              │
                 │    sec/op     │    sec/op     vs base               │
ParseKeyValue-10   3540.0n ± ∞ ¹   972.6n ± ∞ ¹  -72.53% (p=0.008 n=5)
¹ need >= 6 samples for confidence interval at level 0.95

                 │  before.txt   │               after.txt               │
                 │     B/op      │     B/op       vs base                │
ParseKeyValue-10   6.672Ki ± ∞ ¹   0.000Ki ± ∞ ¹  -100.00% (p=0.008 n=5)
¹ need >= 6 samples for confidence interval at level 0.95

                 │ before.txt  │             after.txt              │
                 │  allocs/op  │ allocs/op   vs base                │
ParseKeyValue-10   58.00 ± ∞ ¹   0.00 ± ∞ ¹  -100.00% (p=0.008 n=5)
¹ need >= 6 samples for confidence interval at level 0.95

providers/linux/util.go Outdated Show resolved Hide resolved
@andrewkroh
Copy link
Member

@efd6 Can you please take another look.

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

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

Still LGTM. Stylistic nit that can be ignored.

providers/linux/util.go Outdated Show resolved Hide resolved
Co-authored-by: Dan Kortschak <[email protected]>
@andrewkroh andrewkroh merged commit 865a715 into elastic:main Aug 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Team:Security-External Integrations Label for the Security External Integrations team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants