Skip to content

Commit

Permalink
Use ainur.StreamReader instead of io.RuneReader
Browse files Browse the repository at this point in the history
goos: linux
goarch: amd64
pkg: github.com/parca-dev/parca-agent/pkg/runtime/nodejs
cpu: AMD Ryzen 9 5950X 16-Core Processor
                     │    old.txt    │               new.txt               │
                     │    sec/op     │   sec/op     vs base                │
_scanVersionBytes-32   11650.7µ ± 1%   692.3µ ± 4%  -94.06% (p=0.000 n=10)

                     │   old.txt    │               new.txt               │
                     │     B/op     │     B/op      vs base               │
_scanVersionBytes-32   31.84Ki ± 1%   31.39Ki ± 0%  -1.42% (p=0.001 n=10)

                     │  old.txt   │              new.txt              │
                     │ allocs/op  │ allocs/op   vs base               │
_scanVersionBytes-32   178.0 ± 0%   174.0 ± 0%  -2.25% (p=0.000 n=10)

Signed-off-by: Kemal Akkoyun <[email protected]>
  • Loading branch information
kakkoyun committed Nov 27, 2023
1 parent fe60cdd commit 4ec3b98
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 27 deletions.
49 changes: 34 additions & 15 deletions pkg/runtime/nodejs/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package nodejs

import (
"bufio"
"debug/elf"
"errors"
"fmt"
Expand All @@ -26,6 +25,7 @@ import (

"github.com/Masterminds/semver/v3"
"github.com/prometheus/procfs"
"github.com/xyproto/ainur"

"github.com/parca-dev/parca-agent/pkg/runtime"
)
Expand Down Expand Up @@ -229,25 +229,44 @@ const semVerRegex string = `v([0-9]+)(\.[0-9]+)(\.[0-9]+)` +
func scanVersionBytes(r io.ReadSeeker) (string, error) {
nodejsVersionRegex := regexp.MustCompile(semVerRegex)

match := nodejsVersionRegex.FindReaderSubmatchIndex(bufio.NewReader(r))
if match == nil {
return "", errors.New("failed to find version string")
bufferSize := 4096
sr, err := ainur.NewStreamReader(r, bufferSize)
if err != nil {
return "", fmt.Errorf("failed to create stream reader: %w", err)
}

if _, err := r.Seek(int64(match[0]), io.SeekStart); err != nil {
return "", fmt.Errorf("seek to start: %w", err)
}
for {
b, err := sr.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return "", fmt.Errorf("failed to read next: %w", err)
}

matched := make([]byte, match[1]-match[0])
matches := nodejsVersionRegex.FindSubmatchIndex(b)
if matches == nil {
continue
}

if _, err := r.Read(matched); err != nil {
return "", fmt.Errorf("read matched: %w", err)
}
for i := 0; i < len(matches); i++ {
if matches[i] == -1 {
continue
}

ver, err := semver.NewVersion(string(matched))
if err != nil {
return "", fmt.Errorf("new version, %s: %w", string(matched), err)
if _, err := r.Seek(int64(matches[i]), io.SeekStart); err != nil {
return "", fmt.Errorf("failed to seek to start: %w", err)
}

matched := b[matches[i]:matches[i+1]]
ver, err := semver.NewVersion(string(matched))
if err != nil {
return "", fmt.Errorf("failed to create new version, %s: %w", string(matched), err)
}

return ver.Original(), nil
}
}

return ver.Original(), nil
return "", errors.New("version not found")
}
67 changes: 55 additions & 12 deletions pkg/runtime/nodejs/nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,72 +15,94 @@ package nodejs

import (
"bytes"
"debug/elf"
"io"
"testing"

"github.com/stretchr/testify/require"
)

func Test_scanVersionBytes(t *testing.T) {
ef, err := elf.Open("testdata/node")
require.NoError(t, err)
t.Cleanup(func() { ef.Close() })

sec := ef.Section(".rodata")
require.NotNil(t, sec)

roSec := ef.Section(".rodata")
require.NotNil(t, roSec)

tests := []struct {
name string
input []byte
input io.ReadSeeker
expected string
expectedErr bool
}{
{
name: "empty",
input: []byte{},
input: bytes.NewReader([]byte{}),
expectedErr: true,
},
{
name: "unmatched",
input: []byte("asdasd"),
input: bytes.NewReader([]byte("asdasd")),
expectedErr: true,
},
{
name: "v1.2.3",
input: []byte("asdasd v1.2.3 asdasd"),
input: bytes.NewReader([]byte("asdasd v1.2.3 asdasd")),
expected: "v1.2.3",
},
{
name: "only major",
input: []byte("asdasd v1 asdasd"),
input: bytes.NewReader([]byte("asdasd v1 asdasd")),
expectedErr: true,
},
{
name: "only major and minor",
input: []byte("asdasd v1.2 asdasd"),
input: bytes.NewReader([]byte("asdasd v1.2 asdasd")),
expectedErr: true,
},
{
name: "Pre-release",
input: []byte("asdasd v1.2.3-pre (asdasd)"),
input: bytes.NewReader([]byte("asdasd v1.2.3-pre (asdasd)")),
expected: "v1.2.3-pre",
},
{
name: "Release candidate",
input: []byte("asdasd v1.2.3-rc.1 (asdasd)"),
input: bytes.NewReader([]byte("asdasd v1.2.3-rc.1 (asdasd)")),
expected: "v1.2.3-rc.1",
},
{
name: "Build metadata",
input: []byte("asdasd v1.2.3+build.1 (asdasd)"),
input: bytes.NewReader([]byte("asdasd v1.2.3+build.1 (asdasd)")),
expected: "v1.2.3+build.1",
},
{
name: "Pre-release and build metadata",
input: []byte("asdasd v1.2.3-pre+build.1 (asdasd)"),
input: bytes.NewReader([]byte("asdasd v1.2.3-pre+build.1 (asdasd)")),
expected: "v1.2.3-pre+build.1",
},
{
name: "With nodejs/ prefix",
input: []byte(`asdasd nodejs/v1.2.3-pre+build.1 (asdasd)`),
input: bytes.NewReader([]byte(`asdasd nodejs/v1.2.3-pre+build.1 (asdasd)`)),
expected: "v1.2.3-pre+build.1",
},
{
name: "With real data, .data",
input: sec.Open(),
expected: "v20.8.1",
},
{
name: "With real data, .rodata",
input: roSec.Open(),
expected: "v20.8.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
version, err := scanVersionBytes(bytes.NewReader(tt.input))
version, err := scanVersionBytes(tt.input)
if tt.expectedErr {
require.Error(t, err)
} else {
Expand Down Expand Up @@ -124,3 +146,24 @@ func Test_isNodeJSLib(t *testing.T) {
})
}
}

func Benchmark_scanVersionBytes(b *testing.B) {
ef, err := elf.Open("testdata/node")
require.NoError(b, err)

sec := ef.Section(".rodata")
require.NotNil(b, sec)

roSec := ef.Section(".rodata")
require.NotNil(b, roSec)

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := scanVersionBytes(sec.Open())
require.NoError(b, err)

_, err = scanVersionBytes(roSec.Open())
require.NoError(b, err)
}
}

0 comments on commit 4ec3b98

Please sign in to comment.