mirror of
https://github.com/golang/go
synced 2024-11-20 03:54:40 -07:00
db70c019d7
Reduce allocation to avoid running out of memory on the openbsd/arm builder, until issue/12032 is resolved. Update issue #12032 Change-Id: Ibd513829ffdbd0db6cd86a0a5409934336131156 Reviewed-on: https://go-review.googlesource.com/15242 Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
447 lines
8.5 KiB
Go
447 lines
8.5 KiB
Go
// Copyright 2014 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.
|
|
|
|
package trace_test
|
|
|
|
import (
|
|
"bytes"
|
|
"internal/trace"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
. "runtime/trace"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestTraceStartStop(t *testing.T) {
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
Stop()
|
|
size := buf.Len()
|
|
if size == 0 {
|
|
t.Fatalf("trace is empty")
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
if size != buf.Len() {
|
|
t.Fatalf("trace writes after stop: %v -> %v", size, buf.Len())
|
|
}
|
|
}
|
|
|
|
func TestTraceDoubleStart(t *testing.T) {
|
|
Stop()
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
if err := Start(buf); err == nil {
|
|
t.Fatalf("succeed to start tracing second time")
|
|
}
|
|
Stop()
|
|
Stop()
|
|
}
|
|
|
|
func TestTrace(t *testing.T) {
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
Stop()
|
|
_, err := trace.Parse(buf)
|
|
if err == trace.ErrTimeOrder {
|
|
t.Skipf("skipping trace: %v", err)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to parse trace: %v", err)
|
|
}
|
|
}
|
|
|
|
func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc, error) {
|
|
events, err := trace.Parse(r)
|
|
if err == trace.ErrTimeOrder {
|
|
t.Skipf("skipping trace: %v", err)
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
gs := trace.GoroutineStats(events)
|
|
for goid := range gs {
|
|
// We don't do any particular checks on the result at the moment.
|
|
// But still check that RelatedGoroutines does not crash, hang, etc.
|
|
_ = trace.RelatedGoroutines(events, goid)
|
|
}
|
|
return events, gs, nil
|
|
}
|
|
|
|
func TestTraceStress(t *testing.T) {
|
|
var wg sync.WaitGroup
|
|
done := make(chan bool)
|
|
|
|
// Create a goroutine blocked before tracing.
|
|
wg.Add(1)
|
|
go func() {
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
|
|
// Create a goroutine blocked in syscall before tracing.
|
|
rp, wp, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("failed to create pipe: %v", err)
|
|
}
|
|
defer func() {
|
|
rp.Close()
|
|
wp.Close()
|
|
}()
|
|
wg.Add(1)
|
|
go func() {
|
|
var tmp [1]byte
|
|
rp.Read(tmp[:])
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
time.Sleep(time.Millisecond) // give the goroutine above time to block
|
|
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
|
|
procs := runtime.GOMAXPROCS(10)
|
|
time.Sleep(50 * time.Millisecond) // test proc stop/start events
|
|
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
runtime.Gosched()
|
|
}
|
|
}
|
|
}()
|
|
|
|
runtime.GC()
|
|
// Trigger GC from malloc.
|
|
n := int(1e3)
|
|
if runtime.GOOS == "openbsd" && runtime.GOARCH == "arm" {
|
|
// Reduce allocation to avoid running out of
|
|
// memory on the builder - see issue/12032.
|
|
n = 512
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
_ = make([]byte, 1<<20)
|
|
}
|
|
|
|
// Create a bunch of busy goroutines to load all Ps.
|
|
for p := 0; p < 10; p++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
// Do something useful.
|
|
tmp := make([]byte, 1<<16)
|
|
for i := range tmp {
|
|
tmp[i]++
|
|
}
|
|
_ = tmp
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
// Block in syscall.
|
|
wg.Add(1)
|
|
go func() {
|
|
var tmp [1]byte
|
|
rp.Read(tmp[:])
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
|
|
// Test timers.
|
|
timerDone := make(chan bool)
|
|
go func() {
|
|
time.Sleep(time.Millisecond)
|
|
timerDone <- true
|
|
}()
|
|
<-timerDone
|
|
|
|
// A bit of network.
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen failed: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
var buf [1]byte
|
|
c.Write(buf[:])
|
|
c.Close()
|
|
}()
|
|
c, err := net.Dial("tcp", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("dial failed: %v", err)
|
|
}
|
|
var tmp [1]byte
|
|
c.Read(tmp[:])
|
|
c.Close()
|
|
|
|
go func() {
|
|
runtime.Gosched()
|
|
select {}
|
|
}()
|
|
|
|
// Unblock helper goroutines and wait them to finish.
|
|
wp.Write(tmp[:])
|
|
wp.Write(tmp[:])
|
|
close(done)
|
|
wg.Wait()
|
|
|
|
runtime.GOMAXPROCS(procs)
|
|
|
|
Stop()
|
|
_, _, err = parseTrace(t, buf)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse trace: %v", err)
|
|
}
|
|
}
|
|
|
|
// Do a bunch of various stuff (timers, GC, network, etc) in a separate goroutine.
|
|
// And concurrently with all that start/stop trace 3 times.
|
|
func TestTraceStressStartStop(t *testing.T) {
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
|
|
outerDone := make(chan bool)
|
|
|
|
go func() {
|
|
defer func() {
|
|
outerDone <- true
|
|
}()
|
|
|
|
var wg sync.WaitGroup
|
|
done := make(chan bool)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
|
|
rp, wp, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("failed to create pipe: %v", err)
|
|
}
|
|
defer func() {
|
|
rp.Close()
|
|
wp.Close()
|
|
}()
|
|
wg.Add(1)
|
|
go func() {
|
|
var tmp [1]byte
|
|
rp.Read(tmp[:])
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
time.Sleep(time.Millisecond)
|
|
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
runtime.Gosched()
|
|
}
|
|
}
|
|
}()
|
|
|
|
runtime.GC()
|
|
// Trigger GC from malloc.
|
|
n := int(1e3)
|
|
if runtime.GOOS == "openbsd" && runtime.GOARCH == "arm" {
|
|
// Reduce allocation to avoid running out of
|
|
// memory on the builder - see issue/12032.
|
|
n = 512
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
_ = make([]byte, 1<<20)
|
|
}
|
|
|
|
// Create a bunch of busy goroutines to load all Ps.
|
|
for p := 0; p < 10; p++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
// Do something useful.
|
|
tmp := make([]byte, 1<<16)
|
|
for i := range tmp {
|
|
tmp[i]++
|
|
}
|
|
_ = tmp
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
// Block in syscall.
|
|
wg.Add(1)
|
|
go func() {
|
|
var tmp [1]byte
|
|
rp.Read(tmp[:])
|
|
<-done
|
|
wg.Done()
|
|
}()
|
|
|
|
runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
|
|
|
// Test timers.
|
|
timerDone := make(chan bool)
|
|
go func() {
|
|
time.Sleep(time.Millisecond)
|
|
timerDone <- true
|
|
}()
|
|
<-timerDone
|
|
|
|
// A bit of network.
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen failed: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
var buf [1]byte
|
|
c.Write(buf[:])
|
|
c.Close()
|
|
}()
|
|
c, err := net.Dial("tcp", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("dial failed: %v", err)
|
|
}
|
|
var tmp [1]byte
|
|
c.Read(tmp[:])
|
|
c.Close()
|
|
|
|
go func() {
|
|
runtime.Gosched()
|
|
select {}
|
|
}()
|
|
|
|
// Unblock helper goroutines and wait them to finish.
|
|
wp.Write(tmp[:])
|
|
wp.Write(tmp[:])
|
|
close(done)
|
|
wg.Wait()
|
|
}()
|
|
|
|
for i := 0; i < 3; i++ {
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
Stop()
|
|
if _, _, err := parseTrace(t, buf); err != nil {
|
|
t.Fatalf("failed to parse trace: %v", err)
|
|
}
|
|
}
|
|
<-outerDone
|
|
}
|
|
|
|
func TestTraceFutileWakeup(t *testing.T) {
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
|
|
c0 := make(chan int, 1)
|
|
c1 := make(chan int, 1)
|
|
c2 := make(chan int, 1)
|
|
const procs = 2
|
|
var done sync.WaitGroup
|
|
done.Add(4 * procs)
|
|
for p := 0; p < procs; p++ {
|
|
const iters = 1e3
|
|
go func() {
|
|
for i := 0; i < iters; i++ {
|
|
runtime.Gosched()
|
|
c0 <- 0
|
|
}
|
|
done.Done()
|
|
}()
|
|
go func() {
|
|
for i := 0; i < iters; i++ {
|
|
runtime.Gosched()
|
|
<-c0
|
|
}
|
|
done.Done()
|
|
}()
|
|
go func() {
|
|
for i := 0; i < iters; i++ {
|
|
runtime.Gosched()
|
|
select {
|
|
case c1 <- 0:
|
|
case c2 <- 0:
|
|
}
|
|
}
|
|
done.Done()
|
|
}()
|
|
go func() {
|
|
for i := 0; i < iters; i++ {
|
|
runtime.Gosched()
|
|
select {
|
|
case <-c1:
|
|
case <-c2:
|
|
}
|
|
}
|
|
done.Done()
|
|
}()
|
|
}
|
|
done.Wait()
|
|
|
|
Stop()
|
|
events, _, err := parseTrace(t, buf)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse trace: %v", err)
|
|
}
|
|
// Check that (1) trace does not contain EvFutileWakeup events and
|
|
// (2) there are no consecutive EvGoBlock/EvGCStart/EvGoBlock events
|
|
// (we call runtime.Gosched between all operations, so these would be futile wakeups).
|
|
gs := make(map[uint64]int)
|
|
for _, ev := range events {
|
|
switch ev.Type {
|
|
case trace.EvFutileWakeup:
|
|
t.Fatalf("found EvFutileWakeup event")
|
|
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect:
|
|
if gs[ev.G] == 2 {
|
|
t.Fatalf("goroutine %v blocked on %v at %v right after start",
|
|
ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts)
|
|
}
|
|
if gs[ev.G] == 1 {
|
|
t.Fatalf("goroutine %v blocked on %v at %v while blocked",
|
|
ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts)
|
|
}
|
|
gs[ev.G] = 1
|
|
case trace.EvGoStart:
|
|
if gs[ev.G] == 1 {
|
|
gs[ev.G] = 2
|
|
}
|
|
default:
|
|
delete(gs, ev.G)
|
|
}
|
|
}
|
|
}
|