Skip to content

Commit dc23bca

Browse files
authored
pkg/debuginfo: Add debuginfod client support (#611)
* [WIP]pkg/debuginfo: Add debuginfod client support Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com> * debuginfo: Add caching to debuginfod client other fixes: - add error handling - parse url correctly - add caching - other cleanup Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com> * debuginfo: Update PR - pass context per request - initialise bucket - fix tests - minor fixes Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com> * Update parca.go * pkg/debuginfo: Cleanup - remove dead code - handle errors correctly - fix lint errors - uniform naming - add flag for request timeout duration - close unclosed io.reader Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com> * Fix lint errors Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com> * trivial fixes Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com>
1 parent 640cb5e commit dc23bca

File tree

5 files changed

+209
-36
lines changed

5 files changed

+209
-36
lines changed

pkg/debuginfo/debuginfod.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2022 The Parca Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package debuginfo
15+
16+
import (
17+
"fmt"
18+
"io"
19+
"net/http"
20+
"net/url"
21+
"path"
22+
"time"
23+
24+
"github.com/go-kit/log"
25+
"github.com/go-kit/log/level"
26+
"github.com/thanos-io/thanos/pkg/objstore"
27+
"github.com/thanos-io/thanos/pkg/objstore/client"
28+
"golang.org/x/net/context"
29+
"gopkg.in/yaml.v2"
30+
)
31+
32+
type DebugInfodClient interface {
33+
GetDebugInfo(ctx context.Context, buildid string) (io.ReadCloser, error)
34+
}
35+
36+
type HTTPDebugInfodClient struct {
37+
logger log.Logger
38+
UpstreamServer *url.URL
39+
timeoutDuration time.Duration
40+
}
41+
42+
type DebugInfodClientObjectStorageCache struct {
43+
logger log.Logger
44+
client DebugInfodClient
45+
bucket objstore.Bucket
46+
}
47+
48+
func NewHTTPDebugInfodClient(logger log.Logger, serverURL string, timeoutDuration time.Duration) (*HTTPDebugInfodClient, error) {
49+
parsedURL, err := url.Parse(serverURL)
50+
if err != nil {
51+
return nil, err
52+
}
53+
return &HTTPDebugInfodClient{
54+
logger: logger,
55+
UpstreamServer: parsedURL,
56+
timeoutDuration: timeoutDuration,
57+
}, nil
58+
}
59+
60+
func NewDebugInfodClientWithObjectStorageCache(logger log.Logger, config *Config, h DebugInfodClient) (*DebugInfodClientObjectStorageCache, error) {
61+
cfg, err := yaml.Marshal(config.Bucket)
62+
if err != nil {
63+
return nil, fmt.Errorf("marshal content of debuginfod object storage configuration: %w", err)
64+
}
65+
66+
bucket, err := client.NewBucket(logger, cfg, nil, "parca")
67+
if err != nil {
68+
return nil, fmt.Errorf("instantiate debuginfod object storage: %w", err)
69+
}
70+
return &DebugInfodClientObjectStorageCache{
71+
logger: logger,
72+
client: h,
73+
bucket: bucket,
74+
}, nil
75+
}
76+
77+
func (c *DebugInfodClientObjectStorageCache) GetDebugInfo(ctx context.Context, buildID string) (io.ReadCloser, error) {
78+
path := "/debuginfod-cache/" + buildID + "/debuginfo"
79+
80+
if exists, _ := c.bucket.Exists(ctx, path); exists {
81+
debuginfoFile, err := c.bucket.Get(ctx, path)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to download object file from debuginfod cache: build_id: %v: %w", buildID, err)
84+
}
85+
return debuginfoFile, nil
86+
}
87+
88+
debugInfo, err := c.client.GetDebugInfo(ctx, buildID)
89+
if err != nil {
90+
return nil, ErrDebugInfoNotFound
91+
}
92+
93+
err = c.bucket.Upload(ctx, path, debugInfo)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to upload to debuginfod cache: %w", err)
96+
}
97+
98+
debugInfoReader, err := c.bucket.Get(ctx, path)
99+
if err != nil {
100+
return nil, fmt.Errorf("failed to download object file from debuginfod cache: build_id: %v: %w", buildID, err)
101+
}
102+
103+
return debugInfoReader, nil
104+
}
105+
106+
func (c *HTTPDebugInfodClient) GetDebugInfo(ctx context.Context, buildID string) (io.ReadCloser, error) {
107+
buildIDURL := *c.UpstreamServer
108+
buildIDURL.Path = path.Join(buildIDURL.Path, buildID, "debuginfo")
109+
110+
ctx, cancel := context.WithTimeout(ctx, c.timeoutDuration)
111+
defer cancel()
112+
113+
req, err := http.NewRequestWithContext(ctx, "GET", buildIDURL.String(), nil)
114+
if err != nil {
115+
level.Debug(c.logger).Log("msg", "failed to create new HTTP request", "err", err)
116+
return nil, err
117+
}
118+
119+
client := http.DefaultClient
120+
resp, err := client.Do(req)
121+
if err != nil {
122+
level.Debug(c.logger).Log("msg", "object not found in public server", "object", buildID, "err", err)
123+
return nil, ErrDebugInfoNotFound
124+
}
125+
126+
return resp.Body, nil
127+
}

pkg/debuginfo/store.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ type CacheConfig struct {
6363
type Store struct {
6464
debuginfopb.UnimplementedDebugInfoServiceServer
6565

66+
debuginfodClientCache DebugInfodClient
67+
6668
bucket objstore.Bucket
6769
logger log.Logger
6870

@@ -71,7 +73,7 @@ type Store struct {
7173
}
7274

7375
// NewStore returns a new debug info store.
74-
func NewStore(logger log.Logger, symbolizer *symbol.Symbolizer, config *Config) (*Store, error) {
76+
func NewStore(logger log.Logger, symbolizer *symbol.Symbolizer, config *Config, debuginfodClient DebugInfodClient) (*Store, error) {
7577
cfg, err := yaml.Marshal(config.Bucket)
7678
if err != nil {
7779
return nil, fmt.Errorf("marshal content of object storage configuration: %w", err)
@@ -93,10 +95,11 @@ func NewStore(logger log.Logger, symbolizer *symbol.Symbolizer, config *Config)
9395
}
9496

9597
return &Store{
96-
logger: log.With(logger, "component", "debuginfo"),
97-
bucket: bucket,
98-
cacheDir: cache.Directory,
99-
symbolizer: symbolizer,
98+
debuginfodClientCache: debuginfodClient,
99+
logger: log.With(logger, "component", "debuginfo"),
100+
bucket: bucket,
101+
cacheDir: cache.Directory,
102+
symbolizer: symbolizer,
100103
}, nil
101104
}
102105

@@ -228,9 +231,15 @@ func (s *Store) fetchObjectFile(ctx context.Context, buildID string) (string, er
228231
// Check if it's already cached locally; if not download.
229232
if _, err := os.Stat(mappingPath); os.IsNotExist(err) {
230233
r, err := s.bucket.Get(ctx, path.Join(buildID, "debuginfo"))
234+
231235
if s.bucket.IsObjNotFoundErr(err) {
232-
level.Debug(s.logger).Log("msg", "object not found", "object", buildID, "err", err)
233-
return "", ErrDebugInfoNotFound
236+
level.Debug(s.logger).Log("msg", "object not found in parca object storage", "object", buildID, "err", err)
237+
238+
r, err = s.debuginfodClientCache.GetDebugInfo(ctx, buildID)
239+
if err != nil {
240+
return "", fmt.Errorf("get object files from debuginfod storage: %w", err)
241+
}
242+
defer r.Close()
234243
}
235244
if err != nil {
236245
return "", fmt.Errorf("get object from object storage: %w", err)

pkg/debuginfo/store_test.go

+26-14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"net"
2323
"os"
2424
"testing"
25+
"time"
2526

2627
"github.com/go-kit/log"
2728
"github.com/stretchr/testify/require"
@@ -47,23 +48,34 @@ func TestStore(t *testing.T) {
4748
sym, err := symbol.NewSymbolizer(logger)
4849
require.NoError(t, err)
4950

51+
// cfg := config.Config{}
52+
cfg := &Config{
53+
Bucket: &client.BucketConfig{
54+
Type: client.FILESYSTEM,
55+
Config: filesystem.Config{
56+
Directory: dir,
57+
},
58+
},
59+
Cache: &CacheConfig{
60+
Type: FILESYSTEM,
61+
Config: &FilesystemCacheConfig{
62+
Directory: cacheDir,
63+
},
64+
},
65+
}
66+
67+
httpDebugInfodClient, err := NewHTTPDebugInfodClient(logger, "https://debuginfod.systemtap.org", 4*time.Millisecond)
68+
require.NoError(t, err)
69+
70+
debuginfodClientCache, err := NewDebugInfodClientWithObjectStorageCache(logger, cfg, httpDebugInfodClient)
71+
require.NoError(t, err)
72+
5073
s, err := NewStore(
5174
logger,
5275
sym,
53-
&Config{
54-
Bucket: &client.BucketConfig{
55-
Type: client.FILESYSTEM,
56-
Config: filesystem.Config{
57-
Directory: dir,
58-
},
59-
},
60-
Cache: &CacheConfig{
61-
Type: FILESYSTEM,
62-
Config: &FilesystemCacheConfig{
63-
Directory: cacheDir,
64-
},
65-
},
66-
})
76+
cfg,
77+
debuginfodClientCache,
78+
)
6779
require.NoError(t, err)
6880

6981
lis, err := net.Listen("tcp", ":0")

pkg/parca/parca.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ type Flags struct {
7272
SymbolizerNumberOfTries int `default:"3" help:"Number of tries to attempt to symbolize an unsybolized location"`
7373

7474
Metastore string `default:"badgerinmemory" help:"Which metastore implementation to use" enum:"sqliteinmemory,badgerinmemory"`
75+
76+
UpstreamDebuginfodServer string `default:"https://debuginfod.systemtap.org" help:"Upstream private/public server for debuginfod files. Defaults to https://debuginfod.systemtap.org."`
77+
DebugInfodHTTPRequestTimeout time.Duration `default:"5m" help:"Timeout duration for HTTP request to upstream debuginfod server. Defaults to 5m"`
7578
}
7679

7780
// Run the parca server.
@@ -176,7 +179,19 @@ func Run(ctx context.Context, logger log.Logger, reg *prometheus.Registry, flags
176179
return err
177180
}
178181

179-
dbgInfo, err := debuginfo.NewStore(logger, sym, cfg.DebugInfo)
182+
httpDebugInfoClient, err := debuginfo.NewHTTPDebugInfodClient(logger, flags.UpstreamDebuginfodServer, flags.DebugInfodHTTPRequestTimeout)
183+
if err != nil {
184+
level.Error(logger).Log("msg", "failed to initialize debuginfod http client", "err", err)
185+
return err
186+
}
187+
188+
debugInfodClientCache, err := debuginfo.NewDebugInfodClientWithObjectStorageCache(logger, cfg.DebugInfo, httpDebugInfoClient)
189+
if err != nil {
190+
level.Error(logger).Log("msg", "failed to initialize debuginfod client cache", "err", err)
191+
return err
192+
}
193+
194+
dbgInfo, err := debuginfo.NewStore(logger, sym, cfg.DebugInfo, debugInfodClientCache)
180195
if err != nil {
181196
level.Error(logger).Log("msg", "failed to initialize debug info store", "err", err)
182197
return err

pkg/symbolizer/symbolizer_test.go

+24-14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os"
2323
"sort"
2424
"testing"
25+
"time"
2526

2627
"github.com/go-kit/log"
2728
"github.com/google/pprof/profile"
@@ -411,23 +412,32 @@ func setup(t *testing.T) (*grpc.ClientConn, *debuginfo.Store, metastore.ProfileM
411412
sym, err := symbol.NewSymbolizer(logger)
412413
require.NoError(t, err)
413414

415+
cfg := &debuginfo.Config{
416+
Bucket: &client.BucketConfig{
417+
Type: client.FILESYSTEM,
418+
Config: filesystem.Config{
419+
Directory: "testdata/",
420+
},
421+
},
422+
Cache: &debuginfo.CacheConfig{
423+
Type: debuginfo.FILESYSTEM,
424+
Config: &debuginfo.FilesystemCacheConfig{
425+
Directory: cacheDir,
426+
},
427+
},
428+
}
429+
430+
httpDebugInfodClient, err := debuginfo.NewHTTPDebugInfodClient(logger, "https://debuginfod.systemtap.org", 5*time.Minute)
431+
require.NoError(t, err)
432+
433+
debuginfodClientCache, err := debuginfo.NewDebugInfodClientWithObjectStorageCache(logger, cfg, httpDebugInfodClient)
434+
require.NoError(t, err)
435+
414436
dbgStr, err := debuginfo.NewStore(
415437
logger,
416438
sym,
417-
&debuginfo.Config{
418-
Bucket: &client.BucketConfig{
419-
Type: client.FILESYSTEM,
420-
Config: filesystem.Config{
421-
Directory: "testdata/",
422-
},
423-
},
424-
Cache: &debuginfo.CacheConfig{
425-
Type: debuginfo.FILESYSTEM,
426-
Config: &debuginfo.FilesystemCacheConfig{
427-
Directory: cacheDir,
428-
},
429-
},
430-
})
439+
cfg,
440+
debuginfodClientCache)
431441
require.NoError(t, err)
432442

433443
mStr := metastore.NewBadgerMetastore(

0 commit comments

Comments
 (0)