1
2
3
4
5 package test
6
7 import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "internal/profile"
12 "internal/testenv"
13 "io"
14 "os"
15 "path/filepath"
16 "regexp"
17 "strings"
18 "testing"
19 )
20
21 const profFile = "inline_hot.pprof"
22 const preProfFile = "inline_hot.pprof.node_map"
23
24 func buildPGOInliningTest(t *testing.T, dir string, gcflag string) []byte {
25 const pkg = "example.com/pgo/inline"
26
27
28 goMod := fmt.Sprintf(`module %s
29 go 1.19
30 `, pkg)
31 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
32 t.Fatalf("error writing go.mod: %v", err)
33 }
34
35 exe := filepath.Join(dir, "test.exe")
36 args := []string{"test", "-c", "-o", exe, "-gcflags=" + gcflag}
37 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
38 cmd.Dir = dir
39 cmd = testenv.CleanCmdEnv(cmd)
40 t.Log(cmd)
41 out, err := cmd.CombinedOutput()
42 if err != nil {
43 t.Fatalf("build failed: %v, output:\n%s", err, out)
44 }
45 return out
46 }
47
48
49 func testPGOIntendedInlining(t *testing.T, dir string, profFile string) {
50 testenv.MustHaveGoRun(t)
51 t.Parallel()
52
53 const pkg = "example.com/pgo/inline"
54
55 want := []string{
56 "(*BS).NS",
57 }
58
59
60 wantNot := []string{
61
62
63 "A",
64
65
66 "benchmarkB",
67 }
68
69 must := map[string]bool{
70 "(*BS).NS": true,
71 }
72
73 notInlinedReason := make(map[string]string)
74 for _, fname := range want {
75 fullName := pkg + "." + fname
76 if _, ok := notInlinedReason[fullName]; ok {
77 t.Errorf("duplicate func: %s", fullName)
78 }
79 notInlinedReason[fullName] = "unknown reason"
80 }
81
82
83
84 expectedNotInlinedList := make(map[string]struct{})
85 for _, fname := range wantNot {
86 fullName := pkg + "." + fname
87 expectedNotInlinedList[fullName] = struct{}{}
88 }
89
90
91
92 gcflag := fmt.Sprintf("-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", profFile)
93 out := buildPGOInliningTest(t, dir, gcflag)
94
95 scanner := bufio.NewScanner(bytes.NewReader(out))
96 curPkg := ""
97 canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
98 haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
99 cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
100 for scanner.Scan() {
101 line := scanner.Text()
102 t.Logf("child: %s", line)
103 if strings.HasPrefix(line, "# ") {
104 curPkg = line[2:]
105 splits := strings.Split(curPkg, " ")
106 curPkg = splits[0]
107 continue
108 }
109 if m := haveInlined.FindStringSubmatch(line); m != nil {
110 fname := m[1]
111 delete(notInlinedReason, curPkg+"."+fname)
112 continue
113 }
114 if m := canInline.FindStringSubmatch(line); m != nil {
115 fname := m[1]
116 fullname := curPkg + "." + fname
117
118 if _, ok := must[fullname]; !ok {
119 delete(notInlinedReason, fullname)
120 continue
121 }
122 }
123 if m := cannotInline.FindStringSubmatch(line); m != nil {
124 fname, reason := m[1], m[2]
125 fullName := curPkg + "." + fname
126 if _, ok := notInlinedReason[fullName]; ok {
127
128 notInlinedReason[fullName] = reason
129 }
130 delete(expectedNotInlinedList, fullName)
131 continue
132 }
133 }
134 if err := scanner.Err(); err != nil {
135 t.Fatalf("error reading output: %v", err)
136 }
137 for fullName, reason := range notInlinedReason {
138 t.Errorf("%s was not inlined: %s", fullName, reason)
139 }
140
141
142
143 for fullName, _ := range expectedNotInlinedList {
144 t.Errorf("%s was expected not inlined", fullName)
145 }
146 }
147
148
149
150 func TestPGOIntendedInlining(t *testing.T) {
151 wd, err := os.Getwd()
152 if err != nil {
153 t.Fatalf("error getting wd: %v", err)
154 }
155 srcDir := filepath.Join(wd, "testdata/pgo/inline")
156
157
158 dir := t.TempDir()
159
160 for _, file := range []string{"inline_hot.go", "inline_hot_test.go", profFile} {
161 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
162 t.Fatalf("error copying %s: %v", file, err)
163 }
164 }
165
166 testPGOIntendedInlining(t, dir, profFile)
167 }
168
169
170
171 func TestPGOPreprocessInlining(t *testing.T) {
172 wd, err := os.Getwd()
173 if err != nil {
174 t.Fatalf("error getting wd: %v", err)
175 }
176 srcDir := filepath.Join(wd, "testdata/pgo/inline")
177
178
179 dir := t.TempDir()
180
181 for _, file := range []string{"inline_hot.go", "inline_hot_test.go", preProfFile} {
182 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
183 t.Fatalf("error copying %s: %v", file, err)
184 }
185 }
186
187 testPGOIntendedInlining(t, dir, preProfFile)
188 }
189
190
191
192 func TestPGOIntendedInliningShiftedLines(t *testing.T) {
193 wd, err := os.Getwd()
194 if err != nil {
195 t.Fatalf("error getting wd: %v", err)
196 }
197 srcDir := filepath.Join(wd, "testdata/pgo/inline")
198
199
200 dir := t.TempDir()
201
202
203 for _, file := range []string{"inline_hot_test.go", profFile} {
204 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
205 t.Fatalf("error copying %s : %v", file, err)
206 }
207 }
208
209
210
211 src, err := os.Open(filepath.Join(srcDir, "inline_hot.go"))
212 if err != nil {
213 t.Fatalf("error opening src inline_hot.go: %v", err)
214 }
215 defer src.Close()
216
217 dst, err := os.Create(filepath.Join(dir, "inline_hot.go"))
218 if err != nil {
219 t.Fatalf("error creating dst inline_hot.go: %v", err)
220 }
221 defer dst.Close()
222
223 if _, err := io.WriteString(dst, `// Autogenerated
224 // Lines
225 `); err != nil {
226 t.Fatalf("error writing comments to dst: %v", err)
227 }
228
229 if _, err := io.Copy(dst, src); err != nil {
230 t.Fatalf("error copying inline_hot.go: %v", err)
231 }
232
233 dst.Close()
234
235 testPGOIntendedInlining(t, dir, profFile)
236 }
237
238
239
240
241 func TestPGOSingleIndex(t *testing.T) {
242 for _, tc := range []struct {
243 originalIndex int
244 }{{
245
246
247
248
249
250
251 originalIndex: 0,
252 }, {
253 originalIndex: 1,
254 }} {
255 t.Run(fmt.Sprintf("originalIndex=%d", tc.originalIndex), func(t *testing.T) {
256 wd, err := os.Getwd()
257 if err != nil {
258 t.Fatalf("error getting wd: %v", err)
259 }
260 srcDir := filepath.Join(wd, "testdata/pgo/inline")
261
262
263 dir := t.TempDir()
264
265 originalPprofFile, err := os.Open(filepath.Join(srcDir, profFile))
266 if err != nil {
267 t.Fatalf("error opening %v: %v", profFile, err)
268 }
269 defer originalPprofFile.Close()
270
271 p, err := profile.Parse(originalPprofFile)
272 if err != nil {
273 t.Fatalf("error parsing %v: %v", profFile, err)
274 }
275
276
277 p.SampleType = []*profile.ValueType{p.SampleType[tc.originalIndex]}
278
279
280 for _, s := range p.Sample {
281 s.Value = []int64{s.Value[tc.originalIndex]}
282 }
283
284 modifiedPprofFile, err := os.Create(filepath.Join(dir, profFile))
285 if err != nil {
286 t.Fatalf("error creating %v: %v", profFile, err)
287 }
288 defer modifiedPprofFile.Close()
289
290 if err := p.Write(modifiedPprofFile); err != nil {
291 t.Fatalf("error writing %v: %v", profFile, err)
292 }
293
294 for _, file := range []string{"inline_hot.go", "inline_hot_test.go"} {
295 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
296 t.Fatalf("error copying %s: %v", file, err)
297 }
298 }
299
300 testPGOIntendedInlining(t, dir, profFile)
301 })
302 }
303 }
304
305 func copyFile(dst, src string) error {
306 s, err := os.Open(src)
307 if err != nil {
308 return err
309 }
310 defer s.Close()
311
312 d, err := os.Create(dst)
313 if err != nil {
314 return err
315 }
316 defer d.Close()
317
318 _, err = io.Copy(d, s)
319 return err
320 }
321
322
323 func TestPGOHash(t *testing.T) {
324 testenv.MustHaveGoRun(t)
325 t.Parallel()
326
327 const pkg = "example.com/pgo/inline"
328
329 wd, err := os.Getwd()
330 if err != nil {
331 t.Fatalf("error getting wd: %v", err)
332 }
333 srcDir := filepath.Join(wd, "testdata/pgo/inline")
334
335
336 dir := t.TempDir()
337
338 for _, file := range []string{"inline_hot.go", "inline_hot_test.go", profFile} {
339 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
340 t.Fatalf("error copying %s: %v", file, err)
341 }
342 }
343
344 pprof := filepath.Join(dir, profFile)
345
346
347 gcflag0 := fmt.Sprintf("-pgoprofile=%s -trimpath %s=>%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90,pgodebug=1", pprof, dir, pkg)
348
349
350 const srcPos = "example.com/pgo/inline/inline_hot.go:81:19"
351 const hashMatch = "pgohash triggered " + srcPos + " (inline)"
352 pgoDebugRE := regexp.MustCompile(`hot-budget check allows inlining for call .* at ` + strings.ReplaceAll(srcPos, ".", "\\."))
353 hash := "v1"
354 gcflag := gcflag0 + ",pgohash=" + hash
355 out := buildPGOInliningTest(t, dir, gcflag)
356 if !bytes.Contains(out, []byte(hashMatch)) || !pgoDebugRE.Match(out) {
357 t.Errorf("output does not contain expected source line, out:\n%s", out)
358 }
359
360
361 hash = "v0"
362 gcflag = gcflag0 + ",pgohash=" + hash
363 out = buildPGOInliningTest(t, dir, gcflag)
364 if bytes.Contains(out, []byte(hashMatch)) || pgoDebugRE.Match(out) {
365 t.Errorf("output contains unexpected source line, out:\n%s", out)
366 }
367 }
368
View as plain text