aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/runtime/debug
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2022-02-11 15:02:44 -0800
committerIan Lance Taylor <iant@golang.org>2022-02-11 15:02:44 -0800
commit9a510fb0970d3d9a4201bce8965cabe67850386b (patch)
tree43d7fd2bbfd7ad8c9625a718a5e8718889351994 /libgo/go/runtime/debug
parenta6d3012b274f38b20e2a57162106f625746af6c6 (diff)
parent8dc2499aa62f768c6395c9754b8cabc1ce25c494 (diff)
downloadgcc-9a510fb0970d3d9a4201bce8965cabe67850386b.zip
gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.gz
gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.bz2
Merge from trunk revision 8dc2499aa62f768c6395c9754b8cabc1ce25c494
Diffstat (limited to 'libgo/go/runtime/debug')
-rw-r--r--libgo/go/runtime/debug/garbage_test.go73
-rw-r--r--libgo/go/runtime/debug/mod.go188
-rw-r--r--libgo/go/runtime/debug/panic_test.go1
3 files changed, 200 insertions, 62 deletions
diff --git a/libgo/go/runtime/debug/garbage_test.go b/libgo/go/runtime/debug/garbage_test.go
index 62eeb2c..8986606 100644
--- a/libgo/go/runtime/debug/garbage_test.go
+++ b/libgo/go/runtime/debug/garbage_test.go
@@ -6,6 +6,7 @@ package debug_test
import (
"internal/testenv"
+ "os"
"runtime"
. "runtime/debug"
"testing"
@@ -87,27 +88,75 @@ func TestReadGCStats(t *testing.T) {
}
}
-var big = make([]byte, 1<<20)
+var big []byte
func TestFreeOSMemory(t *testing.T) {
- var ms1, ms2 runtime.MemStats
-
- if big == nil {
- t.Skip("test is not reliable when run multiple times")
+ if runtime.Compiler == "gccgo" {
+ t.Skip("conservative GC")
}
- big = nil
+
+ // Tests FreeOSMemory by making big susceptible to collection
+ // and checking that at least that much memory is returned to
+ // the OS after.
+
+ const bigBytes = 32 << 20
+ big = make([]byte, bigBytes)
+
+ // Make sure any in-progress GCs are complete.
runtime.GC()
- runtime.ReadMemStats(&ms1)
+
+ var before runtime.MemStats
+ runtime.ReadMemStats(&before)
+
+ // Clear the last reference to the big allocation, making it
+ // susceptible to collection.
+ big = nil
+
+ // FreeOSMemory runs a GC cycle before releasing memory,
+ // so it's fine to skip a GC here.
+ //
+ // It's possible the background scavenger runs concurrently
+ // with this function and does most of the work for it.
+ // If that happens, it's OK. What we want is a test that fails
+ // often if FreeOSMemory does not work correctly, and a test
+ // that passes every time if it does.
FreeOSMemory()
- runtime.ReadMemStats(&ms2)
- if ms1.HeapReleased >= ms2.HeapReleased {
- t.Errorf("released before=%d; released after=%d; did not go up", ms1.HeapReleased, ms2.HeapReleased)
+
+ var after runtime.MemStats
+ runtime.ReadMemStats(&after)
+
+ // Check to make sure that the big allocation (now freed)
+ // had its memory shift into HeapReleased as a result of that
+ // FreeOSMemory.
+ if after.HeapReleased <= before.HeapReleased {
+ t.Fatalf("no memory released: %d -> %d", before.HeapReleased, after.HeapReleased)
+ }
+
+ // Check to make sure bigBytes was released, plus some slack. Pages may get
+ // allocated in between the two measurements above for a variety for reasons,
+ // most commonly for GC work bufs. Since this can get fairly high, depending
+ // on scheduling and what GOMAXPROCS is, give a lot of slack up-front.
+ //
+ // Add a little more slack too if the page size is bigger than the runtime page size.
+ // "big" could end up unaligned on its ends, forcing the scavenger to skip at worst
+ // 2x pages.
+ slack := uint64(bigBytes / 2)
+ pageSize := uint64(os.Getpagesize())
+ if pageSize > 8<<10 {
+ slack += pageSize * 2
+ }
+ if slack > bigBytes {
+ // We basically already checked this.
+ return
+ }
+ if after.HeapReleased-before.HeapReleased < bigBytes-slack {
+ t.Fatalf("less than %d released: %d -> %d", bigBytes, before.HeapReleased, after.HeapReleased)
}
}
var (
- setGCPercentBallast interface{}
- setGCPercentSink interface{}
+ setGCPercentBallast any
+ setGCPercentSink any
)
func TestSetGCPercent(t *testing.T) {
diff --git a/libgo/go/runtime/debug/mod.go b/libgo/go/runtime/debug/mod.go
index feac168..61b15ad 100644
--- a/libgo/go/runtime/debug/mod.go
+++ b/libgo/go/runtime/debug/mod.go
@@ -5,6 +5,9 @@
package debug
import (
+ "bytes"
+ "fmt"
+ "runtime"
"strings"
_ "unsafe" // for go:linkname
)
@@ -16,15 +19,32 @@ func modinfo() string
// in the running binary. The information is available only
// in binaries built with module support.
func ReadBuildInfo() (info *BuildInfo, ok bool) {
- return readBuildInfo(modinfo())
+ data := modinfo()
+ if len(data) < 32 {
+ return nil, false
+ }
+ data = data[16 : len(data)-16]
+ bi := &BuildInfo{}
+ if err := bi.UnmarshalText([]byte(data)); err != nil {
+ return nil, false
+ }
+
+ // The go version is stored separately from other build info, mostly for
+ // historical reasons. It is not part of the modinfo() string, and
+ // ParseBuildInfo does not recognize it. We inject it here to hide this
+ // awkwardness from the user.
+ bi.GoVersion = runtime.Version()
+
+ return bi, true
}
-// BuildInfo represents the build information read from
-// the running binary.
+// BuildInfo represents the build information read from a Go binary.
type BuildInfo struct {
- Path string // The main package path
- Main Module // The module containing the main package
- Deps []*Module // Module dependencies
+ GoVersion string // Version of Go that produced this binary.
+ Path string // The main package path
+ Main Module // The module containing the main package
+ Deps []*Module // Module dependencies
+ Settings []BuildSetting // Other information about the build.
}
// Module represents a module.
@@ -35,81 +55,151 @@ type Module struct {
Replace *Module // replaced by this module
}
-func readBuildInfo(data string) (*BuildInfo, bool) {
- if len(data) < 32 {
- return nil, false
+// BuildSetting describes a setting that may be used to understand how the
+// binary was built. For example, VCS commit and dirty status is stored here.
+type BuildSetting struct {
+ // Key and Value describe the build setting.
+ // Key must not contain an equals sign, space, tab, or newline.
+ // Value must not contain newlines ('\n').
+ Key, Value string
+}
+
+func (bi *BuildInfo) MarshalText() ([]byte, error) {
+ buf := &bytes.Buffer{}
+ if bi.GoVersion != "" {
+ fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion)
}
- data = data[16 : len(data)-16]
+ if bi.Path != "" {
+ fmt.Fprintf(buf, "path\t%s\n", bi.Path)
+ }
+ var formatMod func(string, Module)
+ formatMod = func(word string, m Module) {
+ buf.WriteString(word)
+ buf.WriteByte('\t')
+ buf.WriteString(m.Path)
+ mv := m.Version
+ if mv == "" {
+ mv = "(devel)"
+ }
+ buf.WriteByte('\t')
+ buf.WriteString(mv)
+ if m.Replace == nil {
+ buf.WriteByte('\t')
+ buf.WriteString(m.Sum)
+ } else {
+ buf.WriteByte('\n')
+ formatMod("=>", *m.Replace)
+ }
+ buf.WriteByte('\n')
+ }
+ if bi.Main.Path != "" {
+ formatMod("mod", bi.Main)
+ }
+ for _, dep := range bi.Deps {
+ formatMod("dep", *dep)
+ }
+ for _, s := range bi.Settings {
+ if strings.ContainsAny(s.Key, "= \t\n") {
+ return nil, fmt.Errorf("invalid build setting key %q", s.Key)
+ }
+ if strings.Contains(s.Value, "\n") {
+ return nil, fmt.Errorf("invalid build setting value for key %q: contains newline", s.Value)
+ }
+ fmt.Fprintf(buf, "build\t%s=%s\n", s.Key, s.Value)
+ }
+
+ return buf.Bytes(), nil
+}
- const (
- pathLine = "path\t"
- modLine = "mod\t"
- depLine = "dep\t"
- repLine = "=>\t"
+func (bi *BuildInfo) UnmarshalText(data []byte) (err error) {
+ *bi = BuildInfo{}
+ lineNum := 1
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not parse Go build info: line %d: %w", lineNum, err)
+ }
+ }()
+
+ var (
+ pathLine = []byte("path\t")
+ modLine = []byte("mod\t")
+ depLine = []byte("dep\t")
+ repLine = []byte("=>\t")
+ buildLine = []byte("build\t")
+ newline = []byte("\n")
+ tab = []byte("\t")
)
- readEntryFirstLine := func(elem []string) (Module, bool) {
+ readModuleLine := func(elem [][]byte) (Module, error) {
if len(elem) != 2 && len(elem) != 3 {
- return Module{}, false
+ return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem))
}
sum := ""
if len(elem) == 3 {
- sum = elem[2]
+ sum = string(elem[2])
}
return Module{
- Path: elem[0],
- Version: elem[1],
+ Path: string(elem[0]),
+ Version: string(elem[1]),
Sum: sum,
- }, true
+ }, nil
}
var (
- info = &BuildInfo{}
last *Module
- line string
+ line []byte
ok bool
)
- // Reverse of cmd/go/internal/modload.PackageBuildInfo
+ // Reverse of BuildInfo.String(), except for go version.
for len(data) > 0 {
- i := strings.IndexByte(data, '\n')
- if i < 0 {
+ line, data, ok = bytes.Cut(data, newline)
+ if !ok {
break
}
- line, data = data[:i], data[i+1:]
switch {
- case strings.HasPrefix(line, pathLine):
+ case bytes.HasPrefix(line, pathLine):
elem := line[len(pathLine):]
- info.Path = elem
- case strings.HasPrefix(line, modLine):
- elem := strings.Split(line[len(modLine):], "\t")
- last = &info.Main
- *last, ok = readEntryFirstLine(elem)
- if !ok {
- return nil, false
+ bi.Path = string(elem)
+ case bytes.HasPrefix(line, modLine):
+ elem := bytes.Split(line[len(modLine):], tab)
+ last = &bi.Main
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return err
}
- case strings.HasPrefix(line, depLine):
- elem := strings.Split(line[len(depLine):], "\t")
+ case bytes.HasPrefix(line, depLine):
+ elem := bytes.Split(line[len(depLine):], tab)
last = new(Module)
- info.Deps = append(info.Deps, last)
- *last, ok = readEntryFirstLine(elem)
- if !ok {
- return nil, false
+ bi.Deps = append(bi.Deps, last)
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return err
}
- case strings.HasPrefix(line, repLine):
- elem := strings.Split(line[len(repLine):], "\t")
+ case bytes.HasPrefix(line, repLine):
+ elem := bytes.Split(line[len(repLine):], tab)
if len(elem) != 3 {
- return nil, false
+ return fmt.Errorf("expected 3 columns for replacement; got %d", len(elem))
}
if last == nil {
- return nil, false
+ return fmt.Errorf("replacement with no module on previous line")
}
last.Replace = &Module{
- Path: elem[0],
- Version: elem[1],
- Sum: elem[2],
+ Path: string(elem[0]),
+ Version: string(elem[1]),
+ Sum: string(elem[2]),
}
last = nil
+ case bytes.HasPrefix(line, buildLine):
+ key, val, ok := strings.Cut(string(line[len(buildLine):]), "=")
+ if !ok {
+ return fmt.Errorf("invalid build line")
+ }
+ if key == "" {
+ return fmt.Errorf("empty key")
+ }
+ bi.Settings = append(bi.Settings, BuildSetting{Key: key, Value: val})
}
+ lineNum++
}
- return info, true
+ return nil
}
diff --git a/libgo/go/runtime/debug/panic_test.go b/libgo/go/runtime/debug/panic_test.go
index 65f9555..ec5294c 100644
--- a/libgo/go/runtime/debug/panic_test.go
+++ b/libgo/go/runtime/debug/panic_test.go
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
-// +build aix darwin dragonfly freebsd linux netbsd openbsd
// TODO: test on Windows?