runtime/pprof: mark TestCPUProfileMultithreadMagnitude as flaky

The Linux kernel starting in 5.9 and fixed in 5.16 has a bug that can
break CPU timer signal delivery on new new threads if the timer
interrupt fires during handling of the clone system call.

Broken CPU timer signal deliver will skew CPU profile results and cause
this test to fail.

There is currently no known workaround, so mark the test as flaky on
builders with known broken kernels.

For #49065

Change-Id: I37ceb9ea244869b0aab5cd9a36b27ca2f7e5d315
Reviewed-on: https://go-review.googlesource.com/c/go/+/363214
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Pratt 2021-11-10 17:35:13 -05:00
parent ecd2e140ec
commit 95196512b6
3 changed files with 100 additions and 0 deletions

View File

@ -116,6 +116,30 @@ func TestCPUProfileMultithreadMagnitude(t *testing.T) {
t.Skip("issue 35057 is only confirmed on Linux")
}
// Linux [5.9,5.16) has a kernel bug that can break CPU timers on newly
// created threads, breaking our CPU accounting.
major, minor, patch, err := linuxKernelVersion()
if err != nil {
t.Errorf("Error determining kernel version: %v", err)
}
t.Logf("Running on Linux %d.%d.%d", major, minor, patch)
defer func() {
if t.Failed() {
t.Logf("Failure of this test may indicate that your system suffers from a known Linux kernel bug fixed on newer kernels. See https://golang.org/issue/49065.")
}
}()
// Disable on affected builders to avoid flakiness, but otherwise keep
// it enabled to potentially warn users that they are on a broken
// kernel.
if testenv.Builder() != "" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64") {
have59 := major > 5 || (major == 5 && minor >= 9)
have516 := major > 5 || (major == 5 && minor >= 16)
if have59 && !have516 {
testenv.SkipFlaky(t, 49065)
}
}
// Run a workload in a single goroutine, then run copies of the same
// workload in several goroutines. For both the serial and parallel cases,
// the CPU time the process measures with its own profiler should match the

View File

@ -0,0 +1,61 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux
package pprof
import (
"fmt"
"regexp"
"strconv"
"syscall"
)
var versionRe = regexp.MustCompile(`^(\d+)(?:\.(\d+)(?:\.(\d+))).*$`)
func linuxKernelVersion() (major, minor, patch int, err error) {
var uname syscall.Utsname
if err := syscall.Uname(&uname); err != nil {
return 0, 0, 0, err
}
buf := make([]byte, 0, len(uname.Release))
for _, b := range uname.Release {
if b == 0 {
break
}
buf = append(buf, byte(b))
}
rl := string(buf)
m := versionRe.FindStringSubmatch(rl)
if m == nil {
return 0, 0, 0, fmt.Errorf("error matching version number in %q", rl)
}
v, err := strconv.ParseInt(m[1], 10, 64)
if err != nil {
return 0, 0, 0, fmt.Errorf("error parsing major version %q in %s: %w", m[1], rl, err)
}
major = int(v)
if len(m) >= 3 {
v, err := strconv.ParseInt(m[2], 10, 64)
if err != nil {
return 0, 0, 0, fmt.Errorf("error parsing minor version %q in %s: %w", m[2], rl, err)
}
minor = int(v)
}
if len(m) >= 4 {
v, err := strconv.ParseInt(m[3], 10, 64)
if err != nil {
return 0, 0, 0, fmt.Errorf("error parsing patch version %q in %s: %w", m[3], rl, err)
}
patch = int(v)
}
return
}

View File

@ -0,0 +1,15 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !linux
package pprof
import (
"errors"
)
func linuxKernelVersion() (major, minor, patch int, err error) {
return 0, 0, 0, errors.New("not running on linux")
}