github.com/remobjects/goldbaselibrary@v0.0.0-20230924164425-d458680a936b/Source/Gold/runtime/trace/trace_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package trace_test 6 7 import ( 8 "bytes" 9 "flag" 10 "internal/race" 11 "internal/trace" 12 "io" 13 "io/ioutil" 14 "net" 15 "os" 16 "runtime" 17 . "runtime/trace" 18 "strconv" 19 "sync" 20 "testing" 21 "time" 22 ) 23 24 var ( 25 saveTraces = flag.Bool("savetraces", false, "save traces collected by tests") 26 ) 27 28 // TestEventBatch tests Flush calls that happen during Start 29 // don't produce corrupted traces. 30 func TestEventBatch(t *testing.T) { 31 if race.Enabled { 32 t.Skip("skipping in race mode") 33 } 34 if IsEnabled() { 35 t.Skip("skipping because -test.trace is set") 36 } 37 if testing.Short() { 38 t.Skip("skipping in short mode") 39 } 40 // During Start, bunch of records are written to reflect the current 41 // snapshot of the program, including state of each goroutines. 42 // And some string constants are written to the trace to aid trace 43 // parsing. This test checks Flush of the buffer occurred during 44 // this process doesn't cause corrupted traces. 45 // When a Flush is called during Start is complicated 46 // so we test with a range of number of goroutines hoping that one 47 // of them triggers Flush. 48 // This range was chosen to fill up a ~64KB buffer with traceEvGoCreate 49 // and traceEvGoWaiting events (12~13bytes per goroutine). 50 for g := 4950; g < 5050; g++ { 51 n := g 52 t.Run("G="+strconv.Itoa(n), func(t *testing.T) { 53 var wg sync.WaitGroup 54 wg.Add(n) 55 56 in := make(chan bool, 1000) 57 for i := 0; i < n; i++ { 58 go func() { 59 <-in 60 wg.Done() 61 }() 62 } 63 buf := new(bytes.Buffer) 64 if err := Start(buf); err != nil { 65 t.Fatalf("failed to start tracing: %v", err) 66 } 67 68 for i := 0; i < n; i++ { 69 in <- true 70 } 71 wg.Wait() 72 Stop() 73 74 _, err := trace.Parse(buf, "") 75 if err == trace.ErrTimeOrder { 76 t.Skipf("skipping trace: %v", err) 77 } 78 79 if err != nil { 80 t.Fatalf("failed to parse trace: %v", err) 81 } 82 }) 83 } 84 } 85 86 func TestTraceStartStop(t *testing.T) { 87 if IsEnabled() { 88 t.Skip("skipping because -test.trace is set") 89 } 90 buf := new(bytes.Buffer) 91 if err := Start(buf); err != nil { 92 t.Fatalf("failed to start tracing: %v", err) 93 } 94 Stop() 95 size := buf.Len() 96 if size == 0 { 97 t.Fatalf("trace is empty") 98 } 99 time.Sleep(100 * time.Millisecond) 100 if size != buf.Len() { 101 t.Fatalf("trace writes after stop: %v -> %v", size, buf.Len()) 102 } 103 saveTrace(t, buf, "TestTraceStartStop") 104 } 105 106 func TestTraceDoubleStart(t *testing.T) { 107 if IsEnabled() { 108 t.Skip("skipping because -test.trace is set") 109 } 110 Stop() 111 buf := new(bytes.Buffer) 112 if err := Start(buf); err != nil { 113 t.Fatalf("failed to start tracing: %v", err) 114 } 115 if err := Start(buf); err == nil { 116 t.Fatalf("succeed to start tracing second time") 117 } 118 Stop() 119 Stop() 120 } 121 122 func TestTrace(t *testing.T) { 123 if IsEnabled() { 124 t.Skip("skipping because -test.trace is set") 125 } 126 buf := new(bytes.Buffer) 127 if err := Start(buf); err != nil { 128 t.Fatalf("failed to start tracing: %v", err) 129 } 130 Stop() 131 saveTrace(t, buf, "TestTrace") 132 _, err := trace.Parse(buf, "") 133 if err == trace.ErrTimeOrder { 134 t.Skipf("skipping trace: %v", err) 135 } 136 if err != nil { 137 t.Fatalf("failed to parse trace: %v", err) 138 } 139 } 140 141 func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc) { 142 res, err := trace.Parse(r, "") 143 if err == trace.ErrTimeOrder { 144 t.Skipf("skipping trace: %v", err) 145 } 146 if err != nil { 147 t.Fatalf("failed to parse trace: %v", err) 148 } 149 gs := trace.GoroutineStats(res.Events) 150 for goid := range gs { 151 // We don't do any particular checks on the result at the moment. 152 // But still check that RelatedGoroutines does not crash, hang, etc. 153 _ = trace.RelatedGoroutines(res.Events, goid) 154 } 155 return res.Events, gs 156 } 157 158 func testBrokenTimestamps(t *testing.T, data []byte) { 159 // On some processors cputicks (used to generate trace timestamps) 160 // produce non-monotonic timestamps. It is important that the parser 161 // distinguishes logically inconsistent traces (e.g. missing, excessive 162 // or misordered events) from broken timestamps. The former is a bug 163 // in tracer, the latter is a machine issue. 164 // So now that we have a consistent trace, test that (1) parser does 165 // not return a logical error in case of broken timestamps 166 // and (2) broken timestamps are eventually detected and reported. 167 trace.BreakTimestampsForTesting = true 168 defer func() { 169 trace.BreakTimestampsForTesting = false 170 }() 171 for i := 0; i < 1e4; i++ { 172 _, err := trace.Parse(bytes.NewReader(data), "") 173 if err == trace.ErrTimeOrder { 174 return 175 } 176 if err != nil { 177 t.Fatalf("failed to parse trace: %v", err) 178 } 179 } 180 } 181 182 func TestTraceStress(t *testing.T) { 183 if runtime.GOOS == "js" { 184 t.Skip("no os.Pipe on js") 185 } 186 if IsEnabled() { 187 t.Skip("skipping because -test.trace is set") 188 } 189 if testing.Short() { 190 t.Skip("skipping in -short mode") 191 } 192 193 var wg sync.WaitGroup 194 done := make(chan bool) 195 196 // Create a goroutine blocked before tracing. 197 wg.Add(1) 198 go func() { 199 <-done 200 wg.Done() 201 }() 202 203 // Create a goroutine blocked in syscall before tracing. 204 rp, wp, err := os.Pipe() 205 if err != nil { 206 t.Fatalf("failed to create pipe: %v", err) 207 } 208 defer func() { 209 rp.Close() 210 wp.Close() 211 }() 212 wg.Add(1) 213 go func() { 214 var tmp [1]byte 215 rp.Read(tmp[:]) 216 <-done 217 wg.Done() 218 }() 219 time.Sleep(time.Millisecond) // give the goroutine above time to block 220 221 buf := new(bytes.Buffer) 222 if err := Start(buf); err != nil { 223 t.Fatalf("failed to start tracing: %v", err) 224 } 225 226 procs := runtime.GOMAXPROCS(10) 227 time.Sleep(50 * time.Millisecond) // test proc stop/start events 228 229 go func() { 230 runtime.LockOSThread() 231 for { 232 select { 233 case <-done: 234 return 235 default: 236 runtime.Gosched() 237 } 238 } 239 }() 240 241 runtime.GC() 242 // Trigger GC from malloc. 243 n := int(1e3) 244 if isMemoryConstrained() { 245 // Reduce allocation to avoid running out of 246 // memory on the builder - see issue/12032. 247 n = 512 248 } 249 for i := 0; i < n; i++ { 250 _ = make([]byte, 1<<20) 251 } 252 253 // Create a bunch of busy goroutines to load all Ps. 254 for p := 0; p < 10; p++ { 255 wg.Add(1) 256 go func() { 257 // Do something useful. 258 tmp := make([]byte, 1<<16) 259 for i := range tmp { 260 tmp[i]++ 261 } 262 _ = tmp 263 <-done 264 wg.Done() 265 }() 266 } 267 268 // Block in syscall. 269 wg.Add(1) 270 go func() { 271 var tmp [1]byte 272 rp.Read(tmp[:]) 273 <-done 274 wg.Done() 275 }() 276 277 // Test timers. 278 timerDone := make(chan bool) 279 go func() { 280 time.Sleep(time.Millisecond) 281 timerDone <- true 282 }() 283 <-timerDone 284 285 // A bit of network. 286 ln, err := net.Listen("tcp", "127.0.0.1:0") 287 if err != nil { 288 t.Fatalf("listen failed: %v", err) 289 } 290 defer ln.Close() 291 go func() { 292 c, err := ln.Accept() 293 if err != nil { 294 return 295 } 296 time.Sleep(time.Millisecond) 297 var buf [1]byte 298 c.Write(buf[:]) 299 c.Close() 300 }() 301 c, err := net.Dial("tcp", ln.Addr().String()) 302 if err != nil { 303 t.Fatalf("dial failed: %v", err) 304 } 305 var tmp [1]byte 306 c.Read(tmp[:]) 307 c.Close() 308 309 go func() { 310 runtime.Gosched() 311 select {} 312 }() 313 314 // Unblock helper goroutines and wait them to finish. 315 wp.Write(tmp[:]) 316 wp.Write(tmp[:]) 317 close(done) 318 wg.Wait() 319 320 runtime.GOMAXPROCS(procs) 321 322 Stop() 323 saveTrace(t, buf, "TestTraceStress") 324 trace := buf.Bytes() 325 parseTrace(t, buf) 326 testBrokenTimestamps(t, trace) 327 } 328 329 // isMemoryConstrained reports whether the current machine is likely 330 // to be memory constrained. 331 // This was originally for the openbsd/arm builder (Issue 12032). 332 // TODO: move this to testenv? Make this look at memory? Look at GO_BUILDER_NAME? 333 func isMemoryConstrained() bool { 334 if runtime.GOOS == "plan9" { 335 return true 336 } 337 switch runtime.GOARCH { 338 case "arm", "mips", "mipsle": 339 return true 340 } 341 return false 342 } 343 344 // Do a bunch of various stuff (timers, GC, network, etc) in a separate goroutine. 345 // And concurrently with all that start/stop trace 3 times. 346 func TestTraceStressStartStop(t *testing.T) { 347 if runtime.GOOS == "js" { 348 t.Skip("no os.Pipe on js") 349 } 350 if IsEnabled() { 351 t.Skip("skipping because -test.trace is set") 352 } 353 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) 354 outerDone := make(chan bool) 355 356 go func() { 357 defer func() { 358 outerDone <- true 359 }() 360 361 var wg sync.WaitGroup 362 done := make(chan bool) 363 364 wg.Add(1) 365 go func() { 366 <-done 367 wg.Done() 368 }() 369 370 rp, wp, err := os.Pipe() 371 if err != nil { 372 t.Errorf("failed to create pipe: %v", err) 373 return 374 } 375 defer func() { 376 rp.Close() 377 wp.Close() 378 }() 379 wg.Add(1) 380 go func() { 381 var tmp [1]byte 382 rp.Read(tmp[:]) 383 <-done 384 wg.Done() 385 }() 386 time.Sleep(time.Millisecond) 387 388 go func() { 389 runtime.LockOSThread() 390 for { 391 select { 392 case <-done: 393 return 394 default: 395 runtime.Gosched() 396 } 397 } 398 }() 399 400 runtime.GC() 401 // Trigger GC from malloc. 402 n := int(1e3) 403 if isMemoryConstrained() { 404 // Reduce allocation to avoid running out of 405 // memory on the builder. 406 n = 512 407 } 408 for i := 0; i < n; i++ { 409 _ = make([]byte, 1<<20) 410 } 411 412 // Create a bunch of busy goroutines to load all Ps. 413 for p := 0; p < 10; p++ { 414 wg.Add(1) 415 go func() { 416 // Do something useful. 417 tmp := make([]byte, 1<<16) 418 for i := range tmp { 419 tmp[i]++ 420 } 421 _ = tmp 422 <-done 423 wg.Done() 424 }() 425 } 426 427 // Block in syscall. 428 wg.Add(1) 429 go func() { 430 var tmp [1]byte 431 rp.Read(tmp[:]) 432 <-done 433 wg.Done() 434 }() 435 436 runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) 437 438 // Test timers. 439 timerDone := make(chan bool) 440 go func() { 441 time.Sleep(time.Millisecond) 442 timerDone <- true 443 }() 444 <-timerDone 445 446 // A bit of network. 447 ln, err := net.Listen("tcp", "127.0.0.1:0") 448 if err != nil { 449 t.Errorf("listen failed: %v", err) 450 return 451 } 452 defer ln.Close() 453 go func() { 454 c, err := ln.Accept() 455 if err != nil { 456 return 457 } 458 time.Sleep(time.Millisecond) 459 var buf [1]byte 460 c.Write(buf[:]) 461 c.Close() 462 }() 463 c, err := net.Dial("tcp", ln.Addr().String()) 464 if err != nil { 465 t.Errorf("dial failed: %v", err) 466 return 467 } 468 var tmp [1]byte 469 c.Read(tmp[:]) 470 c.Close() 471 472 go func() { 473 runtime.Gosched() 474 select {} 475 }() 476 477 // Unblock helper goroutines and wait them to finish. 478 wp.Write(tmp[:]) 479 wp.Write(tmp[:]) 480 close(done) 481 wg.Wait() 482 }() 483 484 for i := 0; i < 3; i++ { 485 buf := new(bytes.Buffer) 486 if err := Start(buf); err != nil { 487 t.Fatalf("failed to start tracing: %v", err) 488 } 489 time.Sleep(time.Millisecond) 490 Stop() 491 saveTrace(t, buf, "TestTraceStressStartStop") 492 trace := buf.Bytes() 493 parseTrace(t, buf) 494 testBrokenTimestamps(t, trace) 495 } 496 <-outerDone 497 } 498 499 func TestTraceFutileWakeup(t *testing.T) { 500 if IsEnabled() { 501 t.Skip("skipping because -test.trace is set") 502 } 503 buf := new(bytes.Buffer) 504 if err := Start(buf); err != nil { 505 t.Fatalf("failed to start tracing: %v", err) 506 } 507 508 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) 509 c0 := make(chan int, 1) 510 c1 := make(chan int, 1) 511 c2 := make(chan int, 1) 512 const procs = 2 513 var done sync.WaitGroup 514 done.Add(4 * procs) 515 for p := 0; p < procs; p++ { 516 const iters = 1e3 517 go func() { 518 for i := 0; i < iters; i++ { 519 runtime.Gosched() 520 c0 <- 0 521 } 522 done.Done() 523 }() 524 go func() { 525 for i := 0; i < iters; i++ { 526 runtime.Gosched() 527 <-c0 528 } 529 done.Done() 530 }() 531 go func() { 532 for i := 0; i < iters; i++ { 533 runtime.Gosched() 534 select { 535 case c1 <- 0: 536 case c2 <- 0: 537 } 538 } 539 done.Done() 540 }() 541 go func() { 542 for i := 0; i < iters; i++ { 543 runtime.Gosched() 544 select { 545 case <-c1: 546 case <-c2: 547 } 548 } 549 done.Done() 550 }() 551 } 552 done.Wait() 553 554 Stop() 555 saveTrace(t, buf, "TestTraceFutileWakeup") 556 events, _ := parseTrace(t, buf) 557 // Check that (1) trace does not contain EvFutileWakeup events and 558 // (2) there are no consecutive EvGoBlock/EvGCStart/EvGoBlock events 559 // (we call runtime.Gosched between all operations, so these would be futile wakeups). 560 gs := make(map[uint64]int) 561 for _, ev := range events { 562 switch ev.Type { 563 case trace.EvFutileWakeup: 564 t.Fatalf("found EvFutileWakeup event") 565 case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect: 566 if gs[ev.G] == 2 { 567 t.Fatalf("goroutine %v blocked on %v at %v right after start", 568 ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts) 569 } 570 if gs[ev.G] == 1 { 571 t.Fatalf("goroutine %v blocked on %v at %v while blocked", 572 ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts) 573 } 574 gs[ev.G] = 1 575 case trace.EvGoStart: 576 if gs[ev.G] == 1 { 577 gs[ev.G] = 2 578 } 579 default: 580 delete(gs, ev.G) 581 } 582 } 583 } 584 585 func saveTrace(t *testing.T, buf *bytes.Buffer, name string) { 586 if !*saveTraces { 587 return 588 } 589 if err := ioutil.WriteFile(name+".trace", buf.Bytes(), 0600); err != nil { 590 t.Errorf("failed to write trace file: %s", err) 591 } 592 }