Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ed04528

Browse files
committedMar 13, 2025··
Lazy allocate memory for process
Only allocate memory for a process if a probe requires the allocation. Move the allocation functionality to be a method of the process.Info. This is intrinsically linked to a single process, therefore, encapsulate the functionality as a method.
1 parent b9a7efe commit ed04528

File tree

13 files changed

+96
-38
lines changed

13 files changed

+96
-38
lines changed
 

‎internal/pkg/instrumentation/bpf/database/sql/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
4747
ID: id,
4848
Logger: logger,
4949
Consts: []probe.Const{
50-
probe.AllocationConst{},
50+
probe.AllocationConst{Logger: logger},
5151
probe.KeyValConst{
5252
Key: "should_include_db_statement",
5353
Val: shouldIncludeDBStatement(),

‎internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/consumer/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
3737
ID: id,
3838
Logger: logger,
3939
Consts: []probe.Const{
40-
probe.AllocationConst{},
40+
probe.AllocationConst{Logger: logger},
4141
probe.StructFieldConst{
4242
Key: "message_headers_pos",
4343
ID: structfield.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Headers"),

‎internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/producer/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
3838
ID: id,
3939
Logger: logger,
4040
Consts: []probe.Const{
41-
probe.AllocationConst{},
41+
probe.AllocationConst{Logger: logger},
4242
probe.StructFieldConst{
4343
Key: "writer_topic_pos",
4444
ID: structfield.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Writer", "Topic"),

‎internal/pkg/instrumentation/bpf/go.opentelemetry.io/auto/sdk/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func New(logger *slog.Logger) probe.Probe {
3030
ID: id,
3131
Logger: logger,
3232
Consts: []probe.Const{
33-
probe.AllocationConst{},
33+
probe.AllocationConst{Logger: logger},
3434
probe.StructFieldConst{
3535
Key: "span_context_trace_id_pos",
3636
ID: structfield.NewID(

‎internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func New(logger *slog.Logger) probe.Probe {
9393
ID: id,
9494
Logger: logger,
9595
Consts: []probe.Const{
96-
probe.AllocationConst{},
96+
probe.AllocationConst{Logger: logger},
9797
probe.KeyValConst{
9898
Key: "attr_type_invalid",
9999
Val: uint64(attribute.INVALID),

‎internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
6363
ID: id,
6464
Logger: logger,
6565
Consts: []probe.Const{
66-
probe.AllocationConst{},
66+
probe.AllocationConst{Logger: logger},
6767
writeStatusConst{},
6868
probe.StructFieldConst{
6969
Key: "clientconn_target_ptr_pos",

‎internal/pkg/instrumentation/bpf/google.golang.org/grpc/server/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func New(logger *slog.Logger, ver string) probe.Probe {
5252
ID: id,
5353
Logger: logger,
5454
Consts: []probe.Const{
55-
probe.AllocationConst{},
55+
probe.AllocationConst{Logger: logger},
5656
serverAddrConst{},
5757
probe.StructFieldConst{
5858
Key: "stream_method_ptr_pos",

‎internal/pkg/instrumentation/bpf/net/http/client/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
6868
ID: id,
6969
Logger: logger,
7070
Consts: []probe.Const{
71-
probe.AllocationConst{},
71+
probe.AllocationConst{Logger: logger},
7272
probe.StructFieldConst{
7373
Key: "method_ptr_pos",
7474
ID: structfield.NewID("std", "net/http", "Request", "Method"),

‎internal/pkg/instrumentation/manager.go

+2-16
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ func NewManager(logger *slog.Logger, otelController *opentelemetry.Controller, p
8282
return nil, err
8383
}
8484

85-
alloc, err := process.Allocate(logger, pid)
86-
if err != nil {
87-
return nil, err
88-
}
89-
m.proc.Allocation = alloc
90-
9185
m.logger.Info("loaded process info", "process", m.proc)
9286

9387
m.filterUnusedProbes()
@@ -362,7 +356,8 @@ func (m *Manager) loadProbes() error {
362356
}
363357
m.exe = exe
364358

365-
if err := m.mount(); err != nil {
359+
m.logger.Debug("Mounting bpffs")
360+
if err := bpffsMount(m.proc); err != nil {
366361
return err
367362
}
368363

@@ -382,15 +377,6 @@ func (m *Manager) loadProbes() error {
382377
return nil
383378
}
384379

385-
func (m *Manager) mount() error {
386-
if m.proc.Allocation != nil {
387-
m.logger.Debug("Mounting bpffs", "allocation", m.proc.Allocation)
388-
} else {
389-
m.logger.Debug("Mounting bpffs")
390-
}
391-
return bpffsMount(m.proc)
392-
}
393-
394380
func (m *Manager) cleanup() error {
395381
ctx := context.Background()
396382
err := m.cp.Shutdown(context.Background())

‎internal/pkg/instrumentation/probe/probe.go

+28-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package probe
66

77
import (
88
"bytes"
9+
"context"
910
"encoding/binary"
1011
"errors"
1112
"fmt"
@@ -572,16 +573,40 @@ func (c StructFieldConstMinVersion) InjectOption(info *process.Info) (inject.Opt
572573

573574
// AllocationConst is a [Const] for all the allocation details that need to be
574575
// injected into an eBPF program.
575-
type AllocationConst struct{}
576+
type AllocationConst struct {
577+
Logger *slog.Logger
578+
}
579+
580+
func (c AllocationConst) logger() *slog.Logger {
581+
l := c.Logger
582+
if l == nil {
583+
return slog.New(discardHandlerIntance)
584+
}
585+
return l
586+
}
587+
588+
var discardHandlerIntance = discardHandler{}
589+
590+
// Copy of slog.DiscardHandler. Remove when support for Go < 1.24 is dropped.
591+
type discardHandler struct{}
592+
593+
func (dh discardHandler) Enabled(context.Context, slog.Level) bool { return false }
594+
func (dh discardHandler) Handle(context.Context, slog.Record) error { return nil }
595+
func (dh discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return dh }
596+
func (dh discardHandler) WithGroup(name string) slog.Handler { return dh }
576597

577598
// InjectOption returns the appropriately configured
578599
// [inject.WithAllocation] if the [process.Allocation] within td
579600
// are not nil. An error is returned if [process.Allocation] is nil.
580601
func (c AllocationConst) InjectOption(info *process.Info) (inject.Option, error) {
581-
if info.Allocation == nil {
602+
alloc, err := info.Alloc(c.logger())
603+
if err != nil {
604+
return nil, err
605+
}
606+
if alloc == nil {
582607
return nil, errors.New("no allocation details")
583608
}
584-
return inject.WithAllocation(*info.Allocation), nil
609+
return inject.WithAllocation(*alloc), nil
585610
}
586611

587612
// KeyValConst is a [Const] for a generic key-value pair.

‎internal/pkg/instrumentation/testutils/testutils.go

-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ func ProbesLoad(t *testing.T, p TestProbe, libs map[string]*semver.Version) {
3232

3333
info := &process.Info{
3434
ID: 1,
35-
Allocation: &process.Allocation{
36-
StartAddr: 140434497441792,
37-
EndAddr: 140434497507328,
38-
},
3935
Modules: map[string]*semver.Version{
4036
"std": testGoVersion,
4137
},

‎internal/pkg/process/allocate.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type Allocation struct {
2121
NumCPU uint64
2222
}
2323

24-
// Allocate allocates memory for the instrumented process.
25-
func Allocate(logger *slog.Logger, id ID) (*Allocation, error) {
24+
// allocate allocates memory for the instrumented process.
25+
func allocate(logger *slog.Logger, id ID) (*Allocation, error) {
2626
// runtime.NumCPU doesn't query any kind of hardware or OS state,
2727
// but merely uses affinity APIs to count what CPUs the given go process is available to run on.
2828
// Go's implementation of runtime.NumCPU (https://github.com/golang/go/blob/48d899dcdbed4534ed942f7ec2917cf86b18af22/src/runtime/os_linux.go#L97)

‎internal/pkg/process/analyze.go

+56-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"debug/elf"
88
"errors"
99
"fmt"
10+
"log/slog"
11+
"sync"
12+
"sync/atomic"
1013

1114
"github.com/Masterminds/semver/v3"
1215

@@ -15,11 +18,26 @@ import (
1518

1619
// Info are the details about a target process.
1720
type Info struct {
18-
ID ID
19-
Functions []*binary.Func
20-
GoVersion *semver.Version
21-
Modules map[string]*semver.Version
22-
Allocation *Allocation
21+
ID ID
22+
Functions []*binary.Func
23+
GoVersion *semver.Version
24+
Modules map[string]*semver.Version
25+
26+
allocOnce onceResult[*Allocation]
27+
}
28+
29+
// Alloc allocates memory for the process described by Info i.
30+
//
31+
// The underlying memory allocation is only successfully performed once for the
32+
// instance i. Meaning, it is safe to call this multiple times. The first
33+
// successful result will be returned to all subsequent calls. If an error is
34+
// returned, subsequent calls will re-attempt to perform the allocation.
35+
//
36+
// It is safe to call this method concurrently.
37+
func (i *Info) Alloc(logger *slog.Logger) (*Allocation, error) {
38+
return i.allocOnce.Do(func() (*Allocation, error) {
39+
return allocate(logger, i.ID)
40+
})
2341
}
2442

2543
// GetFunctionOffset returns the offset for of the function with name.
@@ -104,3 +122,36 @@ func (a *Analyzer) findFunctions(elfF *elf.File, relevantFuncs map[string]interf
104122

105123
return result, nil
106124
}
125+
126+
// onceResult is an object that will perform exactly one action if that action
127+
// does not error. For errors, no state is stored and subsequent attempts will
128+
// be tried.
129+
type onceResult[T any] struct {
130+
done atomic.Bool
131+
mutex sync.Mutex
132+
val T
133+
}
134+
135+
// Do runs f only once, and only stores the result if f returns a nil error.
136+
// Subsequent calls to Do will return the stored value or they will re-attempt
137+
// to run f and store the result if an error had been returned.
138+
func (o *onceResult[T]) Do(f func() (T, error)) (T, error) {
139+
if o.done.Load() {
140+
o.mutex.Lock()
141+
defer o.mutex.Unlock()
142+
return o.val, nil
143+
}
144+
145+
o.mutex.Lock()
146+
defer o.mutex.Unlock()
147+
if o.done.Load() {
148+
return o.val, nil
149+
}
150+
151+
var err error
152+
o.val, err = f()
153+
if err == nil {
154+
o.done.Store(true)
155+
}
156+
return o.val, err
157+
}

0 commit comments

Comments
 (0)
Please sign in to comment.