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