Description
Currently (Go 1.20-dev), syscalls as processed by go tool trace
from a runtime/trace profile visualize as points in time, being about 1 pixel wide no matter how long they took (see reproducer below). It would be nicer if they were slices, to visualize how much time is spent in (conceptually blocking) syscalls for a given goroutine.
Somewhere in the last release cycle (1.19), I tried making the obvious patch to cmd/trace:
case trace.EvGoSysCall:
- ctx.emitInstant(ev, "syscall", "")
+ if ev.Link != nil {
+ ctx.emitSlice(ev, "syscall")
+ } else {
+ ctx.emitInstant(ev, "syscall", "")
+ }
And I remembered it didn't work: most (all?) EvGoSysCall
events don't have a link to the finish event. But before filing this proposal I tried patching up cmd/trace from 1.20 and to my surprise it did work. I assume something changed in Go 1.20 and now more (most?) EvGoSysCall events have a link, which is great. Debugging output of my patched go tool trace
:
...
2022/12/14 05:42:10 syscall event with link (6/12):
101597987 GoSysCall p=1 g=1 off=2876 stkID=22 seq=0
-> 101613258 GoSysExit p=1000003 g=1 off=264 stkID=0 seq=0 { g=1 seq=17 ts=0 }
-> 101613376 GoStart p=0 g=1 off=270 stkID=0 seq=0 { g=1 seq=0 }
-> 101619219 GoSysBlock p=0 g=1 off=283 stkID=0 seq=0
2022/12/14 05:42:10 syscall event with link (6/13):
101618055 GoSysCall p=0 g=1 off=279 stkID=22 seq=0
-> 101633491 GoSysExit p=1000003 g=1 off=297 stkID=0 seq=0 { g=1 seq=19 ts=0 }
-> 101633634 GoStart p=0 g=1 off=303 stkID=0 seq=0 { g=1 seq=0 }
-> 101637671 GoSysBlock p=0 g=1 off=315 stkID=0 seq=0
...
Resulting trace view comparison:
Reproducer:
The program that generated this trace was:
package main
import (
"fmt"
"os"
"runtime/trace"
"time"
)
func main() {
if err := rmain(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
}
func rmain() error {
if err := trace.Start(os.Stdout); err != nil {
return err
}
defer trace.Stop()
f, err := os.Open("/dev/urandom")
if err != nil {
return err
}
var buf [1024 * 1024 * 1024]byte
for i, last := 0, time.Now(); i < 5; i++ {
fmt.Fprintf(os.Stderr, "start...")
n, err := f.Read(buf[:])
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("read: returned empty slice (%d, %v)", n, err)
}
now := time.Now()
fmt.Fprintf(os.Stderr, "took %v\n", now.Sub(last))
last = now
}
return nil
}
Running it:
$ go run longsyscall.go > longsyscall.trace
start...took 2.203378744s
start...took 2.207477581s
start...took 2.211924562s
start...took 2.202174787s
start...took 2.219617454s