1
1
// Copyright The OpenTelemetry Authors
2
2
// SPDX-License-Identifier: Apache-2.0
3
3
4
- //go:build ebpf_test
5
-
6
4
package instrumentation
7
5
8
6
import (
7
+ "errors"
9
8
"log/slog"
9
+ "os"
10
+ "os/exec"
11
+ "path/filepath"
10
12
"testing"
11
13
12
14
"github.com/Masterminds/semver/v3"
15
+ "github.com/cilium/ebpf"
16
+ "github.com/cilium/ebpf/rlimit"
13
17
"github.com/stretchr/testify/assert"
14
18
"github.com/stretchr/testify/require"
19
+
15
20
"go.opentelemetry.io/auto/internal/pkg/inject"
16
21
dbSql "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/database/sql"
17
22
kafkaConsumer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/consumer"
@@ -22,71 +27,161 @@ import (
22
27
grpcServer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/google.golang.org/grpc/server"
23
28
httpClient "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/net/http/client"
24
29
httpServer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/net/http/server"
30
+ "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpffs"
25
31
"go.opentelemetry.io/auto/internal/pkg/instrumentation/probe"
26
- "go.opentelemetry.io/auto/internal/pkg/instrumentation/testutils"
27
32
"go.opentelemetry.io/auto/internal/pkg/instrumentation/utils"
28
33
"go.opentelemetry.io/auto/internal/pkg/process"
29
- "go.opentelemetry.io/auto/internal/pkg/process/binary"
30
34
)
31
35
32
36
func TestLoadProbes (t * testing.T ) {
37
+ if err := rlimit .RemoveMemlock (); err != nil {
38
+ t .Skip ("cannot manage memory, skipping test." )
39
+ }
40
+
41
+ id := setupTestModule (t )
42
+ pid := process .ID (id )
43
+
44
+ info , err := process .NewInfo (pid , make (map [string ]interface {}))
45
+ if info == nil {
46
+ t .Fatalf ("failed to create process.Info: %v" , err )
47
+ }
48
+ // Reset Info module information.
49
+ info .Modules = make (map [string ]* semver.Version )
50
+
51
+ logger := slog .Default ()
52
+ info .Allocation , err = process .Allocate (logger , pid )
53
+ if err != nil {
54
+ t .Fatalf ("failed to allocate for process %d: %v" , id , err )
55
+ }
56
+
33
57
ver := utils .GetLinuxKernelVersion ()
34
58
require .NotNil (t , ver )
35
59
t .Logf ("Running on kernel %s" , ver .String ())
36
- m := fakeManager (t )
37
60
38
- for _ , p := range m .probes {
61
+ probes := []probe.Probe {
62
+ grpcClient .New (logger , "" ),
63
+ grpcServer .New (logger , "" ),
64
+ httpServer .New (logger , "" ),
65
+ httpClient .New (logger , "" ),
66
+ dbSql .New (logger , "" ),
67
+ kafkaProducer .New (logger , "" ),
68
+ kafkaConsumer .New (logger , "" ),
69
+ autosdk .New (logger ),
70
+ otelTraceGlobal .New (logger ),
71
+ }
72
+
73
+ for _ , p := range probes {
39
74
manifest := p .Manifest ()
40
75
fields := manifest .StructFields
41
- offsets := map [string ]* semver.Version {}
42
76
for _ , f := range fields {
43
77
_ , ver := inject .GetLatestOffset (f )
44
78
if ver != nil {
45
- offsets [f .PkgPath ] = ver
46
- offsets [f .ModPath ] = ver
79
+ info . Modules [f .PkgPath ] = ver
80
+ info . Modules [f .ModPath ] = ver
47
81
}
48
82
}
49
83
t .Run (p .Manifest ().ID .String (), func (t * testing.T ) {
50
- testProbe , ok := p .(testutils.TestProbe )
51
- assert .True (t , ok )
52
- testutils .ProbesLoad (t , testProbe , offsets )
84
+ require .Implements (t , (* TestProbe )(nil ), p )
85
+ ProbesLoad (t , info , p .(TestProbe ))
53
86
})
54
87
}
55
88
}
56
89
57
- func fakeManager (t * testing.T , fnNames ... string ) * Manager {
58
- logger := slog .Default ()
59
- probes := []probe.Probe {
60
- grpcClient .New (logger , "" ),
61
- grpcServer .New (logger , "" ),
62
- httpServer .New (logger , "" ),
63
- httpClient .New (logger , "" ),
64
- dbSql .New (logger , "" ),
65
- kafkaProducer .New (logger , "" ),
66
- kafkaConsumer .New (logger , "" ),
67
- autosdk .New (logger ),
68
- otelTraceGlobal .New (logger ),
90
+ const mainGoContent = `package main
91
+
92
+ import (
93
+ "time"
94
+ )
95
+
96
+ func main() {
97
+ for {
98
+ time.Sleep(time.Hour)
99
+ }
100
+ }`
101
+
102
+ func setupTestModule (t * testing.T ) int {
103
+ t .Helper ()
104
+
105
+ tempDir := t .TempDir ()
106
+
107
+ // Initialize a Go module
108
+ cmd := exec .Command ("go" , "mod" , "init" , "example.com/testmodule" )
109
+ cmd .Dir = tempDir
110
+ if err := cmd .Run (); err != nil {
111
+ t .Fatalf ("failed to initialize Go module: %v" , err )
112
+ }
113
+
114
+ mainGoPath := filepath .Join (tempDir , "main.go" )
115
+ if err := os .WriteFile (mainGoPath , []byte (mainGoContent ), 0o600 ); err != nil {
116
+ t .Fatalf ("failed to write main.go: %v" , err )
69
117
}
70
- ver := semver .New (1 , 20 , 0 , "" , "" )
71
- var fn []* binary.Func
72
- for _ , name := range fnNames {
73
- fn = append (fn , & binary.Func {Name : name })
118
+
119
+ // Compile the Go program
120
+ binaryPath := filepath .Join (tempDir , "testbinary" )
121
+ cmd = exec .Command ("go" , "build" , "-o" , binaryPath , mainGoPath )
122
+ cmd .Dir = tempDir
123
+ if err := cmd .Run (); err != nil {
124
+ t .Fatalf ("failed to compile binary: %v" , err )
125
+ }
126
+
127
+ // Run the compiled binary
128
+ cmd = exec .Command (binaryPath )
129
+ cmd .Dir = tempDir
130
+ cmd .Stdout = os .Stdout
131
+ cmd .Stderr = os .Stderr
132
+ if err := cmd .Start (); err != nil {
133
+ t .Fatalf ("failed to start binary: %v" , err )
74
134
}
75
- m := & Manager {
76
- logger : slog .Default (),
77
- cp : NewNoopConfigProvider (nil ),
78
- probes : make (map [probe.ID ]probe.Probe ),
79
- proc : & process.Info {
80
- ID : 1 ,
81
- Functions : fn ,
82
- GoVersion : ver ,
83
- Modules : map [string ]* semver.Version {},
135
+
136
+ // Ensure the process is killed when the test ends
137
+ t .Cleanup (func () {
138
+ _ = cmd .Process .Kill ()
139
+ _ , _ = cmd .Process .Wait ()
140
+ })
141
+
142
+ // Return the process ID
143
+ return cmd .Process .Pid
144
+ }
145
+
146
+ type TestProbe interface {
147
+ Spec () (* ebpf.CollectionSpec , error )
148
+ InjectConsts (* process.Info , * ebpf.CollectionSpec ) error
149
+ }
150
+
151
+ func ProbesLoad (t * testing.T , info * process.Info , p TestProbe ) {
152
+ t .Helper ()
153
+
154
+ require .NoError (t , bpffs .Mount (info ))
155
+ t .Cleanup (func () { _ = bpffs .Cleanup (info ) })
156
+
157
+ spec , err := p .Spec ()
158
+ require .NoError (t , err )
159
+
160
+ // Inject the same constants as the BPF program. It is important to inject
161
+ // the same constants as those that will be used in the actual run, since
162
+ // From Linux 5.5 the verifier will use constants to eliminate dead code.
163
+ require .NoError (t , p .InjectConsts (info , spec ))
164
+
165
+ opts := ebpf.CollectionOptions {
166
+ Maps : ebpf.MapOptions {
167
+ PinPath : bpffs .PathForTargetApplication (info ),
84
168
},
85
169
}
86
- for _ , p := range probes {
87
- m .probes [p .Manifest ().ID ] = p
170
+
171
+ collectVerifierLogs := utils .ShouldShowVerifierLogs ()
172
+ if collectVerifierLogs {
173
+ opts .Programs .LogLevel = ebpf .LogLevelStats | ebpf .LogLevelInstruction
88
174
}
89
- m .filterUnusedProbes ()
90
175
91
- return m
176
+ c , err := ebpf .NewCollectionWithOptions (spec , opts )
177
+ if ! assert .NoError (t , err ) {
178
+ var ve * ebpf.VerifierError
179
+ if errors .As (err , & ve ) && collectVerifierLogs {
180
+ t .Logf ("Verifier log: %-100v\n " , ve )
181
+ }
182
+ }
183
+
184
+ if c != nil {
185
+ t .Cleanup (c .Close )
186
+ }
92
187
}
0 commit comments