Skip to content

Commit 4b57fe9

Browse files
committed
Golang stdlib impovements
- Support golang.org/toolchain paths - Use mappings if there is no version identified
1 parent 78feb09 commit 4b57fe9

File tree

7 files changed

+102
-50
lines changed

7 files changed

+102
-50
lines changed

.pyroscope.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
source_code:
22
mappings:
33
- path:
4-
- prefix: GOROOT
4+
- prefix: $GOROOT/src
55
language: go
66
source:
77
github:

pkg/frontend/vcs/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ type FileSpec struct {
159159
func (c *PyroscopeConfig) FindMapping(file FileSpec) *MappingConfig {
160160
// Find the longest matching prefix
161161
var bestMatch *MappingConfig
162-
var bestMatchLen int = -1
162+
var bestMatchLen = -1
163163
for _, m := range c.SourceCode.Mappings {
164164
if result := m.Match(file); result > bestMatchLen {
165165
bestMatch = &m

pkg/frontend/vcs/config/config_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,13 +294,13 @@ func TestFindMapping(t *testing.T) {
294294
require.NotNil(t, result)
295295
require.Len(t, result.Path, 1)
296296
assert.Equal(t, tt.expectedPrefix, result.Path[0].Prefix)
297-
if tt.expectedSource == "local" {
297+
switch tt.expectedSource {
298+
case "local":
298299
assert.NotNil(t, result.Source.Local)
299-
} else if tt.expectedSource == "github" {
300+
case "github":
300301
assert.NotNil(t, result.Source.GitHub)
301302
}
302303
}
303304
})
304305
}
305306
}
306-

pkg/frontend/vcs/source/find_go.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"path"
8+
"path/filepath"
89
"strings"
910
"time"
1011

@@ -30,8 +31,8 @@ func (ff FileFinder) findGoFile(ctx context.Context, mappings ...*config.Mapping
3031
sp.SetTag("file.path", ff.file.Path)
3132
sp.SetTag("file.function_name", ff.file.FunctionName)
3233

33-
if url, ok := golang.StandardLibraryURL(ff.file.Path); ok {
34-
return ff.fetchURL(ctx, url, false)
34+
if path, version, ok := golang.IsStandardLibraryPath(ff.file.Path); ok {
35+
return ff.fetchGoStdlib(ctx, path, version)
3536
}
3637

3738
if relativePath, ok := golang.VendorRelativePath(ff.file.Path); ok {
@@ -56,6 +57,36 @@ func (ff FileFinder) findGoFile(ctx context.Context, mappings ...*config.Mapping
5657
return ff.tryFindGoFile(ctx, 30)
5758
}
5859

60+
func (ff FileFinder) fetchGoStdlib(ctx context.Context, path string, version string) (*vcsv1.GetFileResponse, error) {
61+
sp, ctx := opentracing.StartSpanFromContext(ctx, "fetchGoStdlib")
62+
defer sp.Finish()
63+
64+
// if there is no version detected, use the one from .pyroscope.yaml
65+
if version == "" {
66+
mapping := ff.config.FindMapping(config.FileSpec{Path: "$GOROOT/src"})
67+
if mapping != nil {
68+
return ff.fetchMappingFile(ctx, mapping, path)
69+
}
70+
}
71+
72+
// use master branch as fallback
73+
ref := "master"
74+
if version != "" {
75+
ref = "go" + version
76+
}
77+
78+
content, err := ff.client.GetFile(ctx, client.FileRequest{
79+
Owner: "golang",
80+
Repo: "go",
81+
Path: filepath.Join("src", path),
82+
Ref: ref,
83+
})
84+
if err != nil {
85+
return nil, err
86+
}
87+
return newFileResponse(content.Content, content.URL)
88+
}
89+
5990
func (ff FileFinder) fetchGoMod(ctx context.Context) (*modfile.File, error) {
6091
sp, ctx := opentracing.StartSpanFromContext(ctx, "fetchGoMod")
6192
defer sp.Finish()

pkg/frontend/vcs/source/find_java.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/opentracing/opentracing-go"
9+
810
vcsv1 "github.com/grafana/pyroscope/api/gen/proto/go/vcs/v1"
911
"github.com/grafana/pyroscope/pkg/frontend/vcs/config"
10-
"github.com/opentracing/opentracing-go"
1112
)
1213

1314
const (

pkg/frontend/vcs/source/golang/golang.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package golang
22

33
import (
4-
"fmt"
54
"path/filepath"
65
"strings"
76

@@ -14,37 +13,43 @@ const (
1413
stdGoRoot = "$GOROOT/src/"
1514
)
1615

17-
var stdLibRegex = regexp.MustCompile(`.*?\/go\/.*?(?P<version>(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?).*?\/src\/(?P<path>.*)`)
16+
var (
17+
stdLibRegex = regexp.MustCompile(`.*?\/go\/.*?(?P<version>(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?).*?\/src\/(?P<path>.*)`)
18+
toolchainGoVersion = regexp.MustCompile(`-go(?P<version>[0-9]+\.[0-9]+(?:\.[0-9]+)?(?:rc[0-9]+|beta[0-9]+)?)(?:\.|-)`)
19+
)
1820

19-
// StandardLibraryURL returns the URL of the standard library package
20-
// from the given local path if it exists.
21-
func StandardLibraryURL(path string) (string, bool) {
21+
// IsStandardLibraryPath returns the cleaned path of the standard library package and a potential version if detected
22+
func IsStandardLibraryPath(path string) (string, string, bool) {
2223
if len(path) == 0 {
23-
return "", false
24+
return "", "", false
25+
}
26+
27+
// match toolchain go mod paths
28+
if modFile, ok := ParseModuleFromPath(path); ok && modFile.Path == "golang.org/toolchain" {
29+
// figure out version
30+
matches := toolchainGoVersion.FindStringSubmatch(modFile.Version.Version)
31+
if len(matches) > 1 {
32+
return strings.TrimPrefix(modFile.FilePath, "src/"), matches[1], true
33+
}
2434
}
2535

2636
if stdLibRegex.MatchString(path) {
2737
matches := stdLibRegex.FindStringSubmatch(path)
2838
version := matches[stdLibRegex.SubexpIndex("version")]
2939
path = matches[stdLibRegex.SubexpIndex("path")]
30-
return fmt.Sprintf(`https://raw.githubusercontent.com/golang/go/go%s/src/%s`, version, path), true
40+
return path, version, true
3141
}
3242

3343
path = strings.TrimPrefix(path, stdLocal)
3444
path = strings.TrimPrefix(path, stdGoRoot)
3545
fileName := filepath.Base(path)
3646
packageName := strings.TrimSuffix(path, "/"+fileName)
37-
// Todo: Send more metadata from SDK to fetch the correct version of Go std packages.
38-
// For this we should use arbitrary k/v metadata in our request so that we don't need to change the API.
39-
// I thought about using go.mod go version but it's a min and doesn't guarantee it hasn't been built with a higher version.
40-
// Alternatively we could interpret the build system and use the version of the go compiler.
41-
ref := "master"
4247
isStdVendor := strings.HasPrefix(packageName, vendorPath)
4348

4449
if _, isStd := StandardPackages[packageName]; !isStdVendor && !isStd {
45-
return "", false
50+
return "", "", false
4651
}
47-
return fmt.Sprintf(`https://raw.githubusercontent.com/golang/go/%s/src/%s`, ref, path), true
52+
return path, "", true
4853
}
4954

5055
// VendorRelativePath returns the relative path of the given path

pkg/frontend/vcs/source/golang/golang_test.go

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,74 @@ import (
66
"github.com/stretchr/testify/require"
77
)
88

9-
func TestStandardLibraryURL(t *testing.T) {
9+
func TestIsStandardLibraryPath(t *testing.T) {
1010
for _, tt := range []struct {
11-
input string
12-
expected string
13-
expectedOk bool
11+
input string
12+
expectedPath string
13+
expectedVersion string
14+
expectedOk bool
1415
}{
1516
{
1617
input: "github.com/grafana/grafana/pkg/frontend/vcs.go",
17-
expected: "",
1818
expectedOk: false,
1919
},
2020
{
21-
input: "/usr/local/go/src/bufio/bufio.go",
22-
expected: "https://raw.githubusercontent.com/golang/go/master/src/bufio/bufio.go",
23-
expectedOk: true,
21+
input: "/usr/local/go/src/bufio/bufio.go",
22+
expectedPath: "bufio/bufio.go",
23+
expectedOk: true,
2424
},
2525
{
26-
input: "$GOROOT/src/unicode/utf8/utf8.go",
27-
expected: "https://raw.githubusercontent.com/golang/go/master/src/unicode/utf8/utf8.go",
28-
expectedOk: true,
26+
input: "$GOROOT/src/unicode/utf8/utf8.go",
27+
expectedPath: "unicode/utf8/utf8.go",
28+
expectedOk: true,
2929
},
3030
{
31-
input: "fmt/scan.go",
32-
expected: "https://raw.githubusercontent.com/golang/go/master/src/fmt/scan.go",
33-
expectedOk: true,
31+
input: "fmt/scan.go",
32+
expectedPath: "fmt/scan.go",
33+
expectedOk: true,
3434
},
3535
{
36-
input: "$GOROOT/src/vendor/golang.org/x/crypto/cryptobyte/asn1.go",
37-
expected: "https://raw.githubusercontent.com/golang/go/master/src/vendor/golang.org/x/crypto/cryptobyte/asn1.go",
38-
expectedOk: true,
36+
input: "$GOROOT/src/vendor/golang.org/x/crypto/cryptobyte/asn1.go",
37+
expectedPath: "vendor/golang.org/x/crypto/cryptobyte/asn1.go",
38+
expectedOk: true,
3939
},
4040
{
41-
input: "/usr/local/go/src/vendor/golang.org/x/net/http2/hpack/tables.go",
42-
expected: "https://raw.githubusercontent.com/golang/go/master/src/vendor/golang.org/x/net/http2/hpack/tables.go",
43-
expectedOk: true,
41+
input: "/usr/local/go/src/vendor/golang.org/x/net/http2/hpack/tables.go",
42+
expectedPath: "vendor/golang.org/x/net/http2/hpack/tables.go",
43+
expectedOk: true,
4444
},
4545
{
46-
input: "/usr/local/Cellar/go/1.21.3/libexec/src/runtime/netpoll_kqueue.go",
47-
expected: "https://raw.githubusercontent.com/golang/go/go1.21.3/src/runtime/netpoll_kqueue.go",
48-
expectedOk: true,
46+
input: "/usr/local/Cellar/go/1.21.3/libexec/src/runtime/netpoll_kqueue.go",
47+
expectedPath: "runtime/netpoll_kqueue.go",
48+
expectedVersion: "1.21.3",
49+
expectedOk: true,
4950
},
5051
{
51-
input: "/opt/hostedtoolcache/go/1.21.6/x64/src/runtime/mgc.go",
52-
expected: "https://raw.githubusercontent.com/golang/go/go1.21.6/src/runtime/mgc.go",
53-
expectedOk: true,
52+
input: "/opt/hostedtoolcache/go/1.21.6/x64/src/runtime/mgc.go",
53+
expectedPath: "runtime/mgc.go",
54+
expectedVersion: "1.21.6",
55+
expectedOk: true,
56+
},
57+
{
58+
input: "/Users/pyroscope/.golang/packages/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.6.darwin-arm64/src/runtime/proc.go",
59+
expectedPath: "runtime/proc.go",
60+
expectedVersion: "1.24.6",
61+
expectedOk: true,
62+
},
63+
{
64+
input: "/Users/christian/.golang/packages/pkg/mod/golang.org/toolchain@v0.0.1-go1.25rc1.darwin-arm64/src/runtime/type.go",
65+
expectedPath: "runtime/type.go",
66+
expectedVersion: "1.25rc1",
67+
expectedOk: true,
5468
},
5569
} {
5670
t.Run(tt.input, func(t *testing.T) {
57-
actual, ok := StandardLibraryURL(tt.input)
71+
actualPath, actualVersion, ok := IsStandardLibraryPath(tt.input)
5872
if !tt.expectedOk {
5973
require.False(t, ok)
6074
}
61-
require.Equal(t, tt.expected, actual)
75+
require.Equal(t, tt.expectedPath, actualPath)
76+
require.Equal(t, tt.expectedVersion, actualVersion)
6277
})
6378
}
6479
}

0 commit comments

Comments
 (0)