├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── tracefocus │ └── main.go └── tracehist │ ├── histogram.go │ └── main.go ├── pprof ├── pprof.go └── trace.go ├── trace ├── goroutines.go ├── mkcanned.bash ├── order.go ├── parser.go ├── parser_test.go ├── testdata │ ├── http_1_5_good │ ├── http_1_7_good │ ├── stress_1_5_good │ ├── stress_1_5_unordered │ ├── stress_1_7_good │ ├── stress_start_stop_1_5_good │ └── stress_start_stop_1_7_good └── writer.go └── vendor ├── github.com ├── google │ └── pprof │ │ └── profile │ │ ├── LICENSE │ │ ├── encode.go │ │ ├── filter.go │ │ ├── index.go │ │ ├── legacy_java_profile.go │ │ ├── legacy_profile.go │ │ ├── merge.go │ │ ├── profile.go │ │ ├── proto.go │ │ └── prune.go └── wadey │ └── gocovmerge │ ├── LICENSE │ └── gocovmerge.go ├── golang.org └── x │ └── tools │ ├── cmd │ └── goimports │ │ ├── LICENSE │ │ ├── doc.go │ │ ├── goimports.go │ │ ├── goimports_gc.go │ │ └── goimports_not_gc.go │ ├── cover │ ├── LICENSE │ └── profile.go │ ├── go │ └── ast │ │ └── astutil │ │ ├── LICENSE │ │ ├── enclosing.go │ │ ├── imports.go │ │ └── util.go │ └── imports │ ├── LICENSE │ ├── fastwalk.go │ ├── fastwalk_dirent_fileno.go │ ├── fastwalk_dirent_ino.go │ ├── fastwalk_portable.go │ ├── fastwalk_unix.go │ ├── fix.go │ ├── imports.go │ ├── mkindex.go │ ├── mkstdlib.go │ ├── sortimports.go │ └── zstdlib.go └── manifest /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | /.GOPATH 26 | /bin 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Filippo Valsorda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMPORT_PATH := github.com/FiloSottile/tracetools 2 | 3 | V := 1 # When V is set, print commands and build progress. 4 | 5 | # Space separated patterns of packages to skip in list, test, format. 6 | IGNORED_PACKAGES := /vendor/ 7 | 8 | .PHONY: all 9 | all: tracefocus tracehist 10 | 11 | .PHONY: tracefocus 12 | tracefocus: .GOPATH/.ok 13 | $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/tracefocus 14 | 15 | .PHONY: tracehist 16 | tracehist: .GOPATH/.ok 17 | $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/tracehist 18 | 19 | ##### =====> Utility targets <===== ##### 20 | 21 | .PHONY: clean test list cover format 22 | 23 | clean: 24 | $Q rm -rf bin .GOPATH 25 | 26 | test: .GOPATH/.ok 27 | $Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run 28 | ifndef CI 29 | $Q go vet $(allpackages) 30 | $Q GODEBUG=cgocheck=2 go test -race $(allpackages) 31 | else 32 | $Q ( go vet $(allpackages); echo $$? ) | \ 33 | tee .GOPATH/test/vet.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/vet.txt) 34 | $Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \ 35 | tee .GOPATH/test/output.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/output.txt) 36 | endif 37 | 38 | list: .GOPATH/.ok 39 | @echo $(allpackages) 40 | 41 | cover: bin/gocovmerge .GOPATH/.ok 42 | @echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!" 43 | $Q rm -f .GOPATH/cover/*.out .GOPATH/cover/all.merged 44 | $(if $V,@echo "-- go test -coverpkg=./... -coverprofile=.GOPATH/cover/... ./...") 45 | @for MOD in $(allpackages); do \ 46 | go test -coverpkg=`echo $(allpackages)|tr " " ","` \ 47 | -coverprofile=.GOPATH/cover/unit-`echo $$MOD|tr "/" "_"`.out \ 48 | $$MOD 2>&1 | grep -v "no packages being tested depend on"; \ 49 | done 50 | $Q ./bin/gocovmerge .GOPATH/cover/*.out > .GOPATH/cover/all.merged 51 | ifndef CI 52 | $Q go tool cover -html .GOPATH/cover/all.merged 53 | else 54 | $Q go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html 55 | endif 56 | @echo "" 57 | @echo "=====> Total test coverage: <=====" 58 | @echo "" 59 | $Q go tool cover -func .GOPATH/cover/all.merged 60 | 61 | format: bin/goimports .GOPATH/.ok 62 | $Q find .GOPATH/src/$(IMPORT_PATH)/ -iname \*.go | grep -v \ 63 | -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) | xargs ./bin/goimports -w 64 | 65 | ##### =====> Internals <===== ##### 66 | 67 | .PHONY: setup 68 | setup: clean .GOPATH/.ok 69 | @if ! grep "/.GOPATH" .gitignore > /dev/null 2>&1; then \ 70 | echo "/.GOPATH" >> .gitignore; \ 71 | echo "/bin" >> .gitignore; \ 72 | fi 73 | go get -u github.com/FiloSottile/gvt 74 | - ./bin/gvt fetch golang.org/x/tools/cmd/goimports 75 | - ./bin/gvt fetch github.com/wadey/gocovmerge 76 | 77 | VERSION := $(shell git describe --tags --always --dirty="-dev") 78 | DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC') 79 | VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"' 80 | 81 | # cd into the GOPATH to workaround ./... not following symlinks 82 | _allpackages = $(shell ( cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && \ 83 | GOPATH=$(CURDIR)/.GOPATH go list ./... 2>&1 1>&3 | \ 84 | grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) 1>&2 ) 3>&1 | \ 85 | grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES))) 86 | 87 | # memoize allpackages, so that it's executed only once and only if used 88 | allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages) 89 | 90 | export GOPATH := $(CURDIR)/.GOPATH 91 | unexport GOBIN 92 | 93 | Q := $(if $V,,@) 94 | 95 | .GOPATH/.ok: 96 | $Q mkdir -p "$(dir .GOPATH/src/$(IMPORT_PATH))" 97 | $Q ln -s ../../../.. ".GOPATH/src/$(IMPORT_PATH)" 98 | $Q mkdir -p .GOPATH/test .GOPATH/cover 99 | $Q mkdir -p bin 100 | $Q ln -s ../bin .GOPATH/bin 101 | $Q touch $@ 102 | 103 | .PHONY: bin/gocovmerge bin/goimports 104 | bin/gocovmerge: .GOPATH/.ok 105 | @test -d ./vendor/github.com/wadey/gocovmerge || \ 106 | { echo "Vendored gocovmerge not found, try running 'make setup'..."; exit 1; } 107 | $Q go install $(IMPORT_PATH)/vendor/github.com/wadey/gocovmerge 108 | bin/goimports: .GOPATH/.ok 109 | @test -d ./vendor/golang.org/x/tools/cmd/goimports || \ 110 | { echo "Vendored goimports not found, try running 'make setup'..."; exit 1; } 111 | $Q go install $(IMPORT_PATH)/vendor/golang.org/x/tools/cmd/goimports 112 | 113 | # Based on https://github.com/cloudflare/hellogopher - v1.1 - MIT License 114 | # 115 | # Copyright (c) 2017 Cloudflare 116 | # 117 | # Permission is hereby granted, free of charge, to any person obtaining a copy 118 | # of this software and associated documentation files (the "Software"), to deal 119 | # in the Software without restriction, including without limitation the rights 120 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 121 | # copies of the Software, and to permit persons to whom the Software is 122 | # furnished to do so, subject to the following conditions: 123 | # 124 | # The above copyright notice and this permission notice shall be included in all 125 | # copies or substantial portions of the Software. 126 | # 127 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 128 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 129 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 130 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 131 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 132 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 133 | # SOFTWARE. 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tracetools 2 | Tools to process Go trace logs into various profiles. Complement for "go tool trace". 3 | -------------------------------------------------------------------------------- /cmd/tracefocus/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "regexp" 9 | 10 | "github.com/FiloSottile/tracetools/pprof" 11 | "github.com/FiloSottile/tracetools/trace" 12 | ) 13 | 14 | var usageMessage = `Usage: tracefocus -filter=ServeHTTP [binary] trace.out 15 | 16 | -filter=REGEX Only include events caused by functions that 17 | match REGEX, either by it being a caller, or by 18 | it having started the goroutine.` 19 | 20 | var ( 21 | filter = flag.String("filter", "", "") 22 | ) 23 | 24 | func filterStack(Stk []*trace.Frame, re *regexp.Regexp) bool { 25 | for _, f := range Stk { 26 | if re.FindStringIndex(f.Fn) != nil { 27 | return true 28 | } 29 | } 30 | return false 31 | } 32 | 33 | func main() { 34 | flag.Usage = func() { 35 | fmt.Fprintln(os.Stderr, usageMessage) 36 | os.Exit(2) 37 | } 38 | flag.Parse() 39 | 40 | // Go 1.7 traces embed symbol info and does not require the binary. 41 | // But we optionally accept binary as first arg for Go 1.5 traces. 42 | var programBinary, traceFile string 43 | switch flag.NArg() { 44 | case 1: 45 | traceFile = flag.Arg(0) 46 | case 2: 47 | programBinary = flag.Arg(0) 48 | traceFile = flag.Arg(1) 49 | default: 50 | flag.Usage() 51 | } 52 | 53 | if *filter == "" { 54 | flag.Usage() 55 | } 56 | re, err := regexp.Compile(*filter) 57 | if err != nil { 58 | log.Fatalln("Faile to compile filter regex:", err) 59 | } 60 | 61 | events, err := pprof.LoadTrace(traceFile, programBinary) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | var childG = make(map[uint64]struct{}) 67 | var lastGLen int 68 | for { 69 | for _, ev := range events { 70 | if ev.Type != trace.EvGoCreate { 71 | continue 72 | } 73 | if _, ok := childG[ev.G]; !ok && !filterStack(ev.Stk, re) { 74 | continue 75 | } 76 | childG[ev.Args[0]] = struct{}{} 77 | } 78 | if len(childG) == lastGLen { 79 | break 80 | } 81 | lastGLen = len(childG) 82 | } 83 | 84 | prof := make(map[uint64]pprof.Record) 85 | for _, ev := range events { 86 | if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 87 | continue 88 | } 89 | if _, ok := childG[ev.G]; !ok && !filterStack(ev.Stk, re) { 90 | continue 91 | } 92 | rec := prof[ev.StkID] 93 | rec.Stk = ev.Stk 94 | rec.N++ 95 | rec.Time += ev.Link.Ts - ev.Ts 96 | prof[ev.StkID] = rec 97 | } 98 | if err := pprof.BuildProfile(prof).Write(os.Stdout); err != nil { 99 | log.Fatal(err) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cmd/tracehist/histogram.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sort" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const ( 12 | barChar = "∎" 13 | ) 14 | 15 | type histogram struct { 16 | points []time.Duration 17 | slowest time.Duration 18 | fastest time.Duration 19 | } 20 | 21 | // newHistogram produces an empty histogram. 22 | func newHistogram() *histogram { 23 | return &histogram{ 24 | slowest: 0, 25 | fastest: time.Duration(math.MaxInt64), 26 | } 27 | } 28 | 29 | // Observe updates the histogram with a new measurement. 30 | func (h *histogram) Observe(d time.Duration) { 31 | h.points = append(h.points, d) 32 | if d > h.slowest { 33 | h.slowest = d 34 | } 35 | if d < h.fastest { 36 | h.fastest = d 37 | } 38 | } 39 | 40 | type ByDuration []time.Duration 41 | 42 | func (a ByDuration) Len() int { return len(a) } 43 | func (a ByDuration) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 44 | func (a ByDuration) Less(i, j int) bool { return a[i] < a[j] } 45 | 46 | // Print formats the histogram data and prints it to stdout. 47 | func (h *histogram) Print(log bool) { 48 | sort.Sort(ByDuration(h.points)) 49 | 50 | // buckets is the upper threshold for each bucket in the hist 51 | buckets := make([]time.Duration, 8) 52 | if log { 53 | bs := h.slowest - h.fastest 54 | for i := range buckets { 55 | buckets[len(buckets)-1-i] = h.fastest + bs 56 | bs = bs / 2 57 | } 58 | } else { 59 | bs := int64(h.slowest-h.fastest)/int64(len(buckets)-1) + 1 60 | for i := range buckets { 61 | buckets[i] = h.fastest + time.Duration(bs*int64(i)) 62 | } 63 | } 64 | 65 | // counts is the number of latencies that fell in each bucket. 66 | counts := make([]int, len(buckets)) 67 | var bi, max int 68 | for i := 0; i < len(h.points); { 69 | if h.points[i] <= buckets[bi] { 70 | i++ 71 | counts[bi]++ 72 | if max < counts[bi] { 73 | max = counts[bi] 74 | } 75 | } else if bi < len(buckets)-1 { 76 | bi++ 77 | } else { 78 | panic(fmt.Sprintf("%d is higher than %d", h.points[i], buckets[bi])) 79 | } 80 | } 81 | 82 | // Print histogram to stdout. 83 | lowerBound := time.Duration(0) 84 | for i, upperBound := range buckets { 85 | // Normalize bar lengths. 86 | var barLen int 87 | if max > 0 { 88 | barLen = counts[i] * 40 / max 89 | } 90 | fmt.Printf(" %3dms - %3dms [%v]\t|%v\n", lowerBound/time.Millisecond, 91 | upperBound/time.Millisecond, counts[i], strings.Repeat(barChar, barLen)) 92 | lowerBound = upperBound 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /cmd/tracehist/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "regexp" 9 | "time" 10 | 11 | "github.com/FiloSottile/tracetools/pprof" 12 | "github.com/FiloSottile/tracetools/trace" 13 | ) 14 | 15 | var usageMessage = `Usage: tracehist -filter=ServeHTTP [binary] trace.out 16 | 17 | -filter=REGEX Syscall events matching this regex will be plotted.` 18 | 19 | var ( 20 | filter = flag.String("filter", "", "") 21 | ) 22 | 23 | func main() { 24 | flag.Usage = func() { 25 | fmt.Fprintln(os.Stderr, usageMessage) 26 | os.Exit(2) 27 | } 28 | flag.Parse() 29 | 30 | // Go 1.7 traces embed symbol info and does not require the binary. 31 | // But we optionally accept binary as first arg for Go 1.5 traces. 32 | var programBinary, traceFile string 33 | switch flag.NArg() { 34 | case 1: 35 | traceFile = flag.Arg(0) 36 | case 2: 37 | programBinary = flag.Arg(0) 38 | traceFile = flag.Arg(1) 39 | default: 40 | flag.Usage() 41 | } 42 | 43 | if *filter == "" { 44 | flag.Usage() 45 | } 46 | re, err := regexp.Compile(*filter) 47 | if err != nil { 48 | log.Fatalln("Failed to compile filter regex:", err) 49 | } 50 | 51 | events, err := pprof.LoadTrace(traceFile, programBinary) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | durations := make(map[uint64][]int64) 57 | names := make(map[uint64]string) 58 | for _, ev := range events { 59 | if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 60 | continue 61 | } 62 | if re.FindStringIndex(ev.Stk[0].Fn) == nil { 63 | continue 64 | } 65 | d := durations[ev.StkID] 66 | d = append(d, ev.Link.Ts-ev.Ts) 67 | durations[ev.StkID] = d 68 | names[ev.StkID] = ev.Stk[0].Fn 69 | } 70 | 71 | for StkID, dur := range durations { 72 | hist := newHistogram() 73 | for _, d := range dur { 74 | hist.Observe(time.Duration(d)) 75 | } 76 | fmt.Println("Histogram of durations for", names[StkID]) 77 | hist.Print(false) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pprof/pprof.go: -------------------------------------------------------------------------------- 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 pprof offers tools to easily generate pprof-like 6 | // profiles for events with a count and a duration. 7 | // It is extracted from cmd/trace. 8 | package pprof 9 | 10 | import ( 11 | "github.com/FiloSottile/tracetools/trace" 12 | "github.com/google/pprof/profile" 13 | ) 14 | 15 | // Record represents one entry in pprof-like profiles. 16 | type Record struct { 17 | Stk []*trace.Frame 18 | N uint64 19 | Time int64 20 | } 21 | 22 | // BuildProfile creates a profile from a set of Record. The key is 23 | // irrelevant. 24 | func BuildProfile(prof map[uint64]Record) *profile.Profile { 25 | p := &profile.Profile{ 26 | PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, 27 | Period: 1, 28 | SampleType: []*profile.ValueType{ 29 | {Type: "contentions", Unit: "count"}, 30 | {Type: "delay", Unit: "nanoseconds"}, 31 | }, 32 | } 33 | locs := make(map[uint64]*profile.Location) 34 | funcs := make(map[string]*profile.Function) 35 | for _, rec := range prof { 36 | var sloc []*profile.Location 37 | for _, frame := range rec.Stk { 38 | loc := locs[frame.PC] 39 | if loc == nil { 40 | fn := funcs[frame.File+frame.Fn] 41 | if fn == nil { 42 | fn = &profile.Function{ 43 | ID: uint64(len(p.Function) + 1), 44 | Name: frame.Fn, 45 | SystemName: frame.Fn, 46 | Filename: frame.File, 47 | } 48 | p.Function = append(p.Function, fn) 49 | funcs[frame.File+frame.Fn] = fn 50 | } 51 | loc = &profile.Location{ 52 | ID: uint64(len(p.Location) + 1), 53 | Address: frame.PC, 54 | Line: []profile.Line{ 55 | profile.Line{ 56 | Function: fn, 57 | Line: int64(frame.Line), 58 | }, 59 | }, 60 | } 61 | p.Location = append(p.Location, loc) 62 | locs[frame.PC] = loc 63 | } 64 | sloc = append(sloc, loc) 65 | } 66 | p.Sample = append(p.Sample, &profile.Sample{ 67 | Value: []int64{int64(rec.N), rec.Time}, 68 | Location: sloc, 69 | }) 70 | } 71 | return p 72 | } 73 | -------------------------------------------------------------------------------- /pprof/trace.go: -------------------------------------------------------------------------------- 1 | package pprof 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/FiloSottile/tracetools/trace" 9 | ) 10 | 11 | func LoadTrace(traceFile, programBinary string) ([]*trace.Event, error) { 12 | tracef, err := os.Open(traceFile) 13 | if err != nil { 14 | return nil, fmt.Errorf("failed to open trace file: %v", err) 15 | } 16 | defer tracef.Close() 17 | 18 | // Parse and symbolize. 19 | events, err := trace.Parse(bufio.NewReader(tracef), programBinary) 20 | if err != nil { 21 | return nil, fmt.Errorf("failed to parse trace: %v", err) 22 | } 23 | 24 | return events, nil 25 | } 26 | -------------------------------------------------------------------------------- /trace/goroutines.go: -------------------------------------------------------------------------------- 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 6 | 7 | // GDesc contains statistics about execution of a single goroutine. 8 | type GDesc struct { 9 | ID uint64 10 | Name string 11 | PC uint64 12 | CreationTime int64 13 | StartTime int64 14 | EndTime int64 15 | 16 | ExecTime int64 17 | SchedWaitTime int64 18 | IOTime int64 19 | BlockTime int64 20 | SyscallTime int64 21 | GCTime int64 22 | SweepTime int64 23 | TotalTime int64 24 | 25 | *gdesc // private part 26 | } 27 | 28 | // gdesc is a private part of GDesc that is required only during analysis. 29 | type gdesc struct { 30 | lastStartTime int64 31 | blockNetTime int64 32 | blockSyncTime int64 33 | blockSyscallTime int64 34 | blockSweepTime int64 35 | blockGCTime int64 36 | blockSchedTime int64 37 | } 38 | 39 | // GoroutineStats generates statistics for all goroutines in the trace. 40 | func GoroutineStats(events []*Event) map[uint64]*GDesc { 41 | gs := make(map[uint64]*GDesc) 42 | var lastTs int64 43 | var gcStartTime int64 44 | for _, ev := range events { 45 | lastTs = ev.Ts 46 | switch ev.Type { 47 | case EvGoCreate: 48 | g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)} 49 | g.blockSchedTime = ev.Ts 50 | gs[g.ID] = g 51 | case EvGoStart, EvGoStartLabel: 52 | g := gs[ev.G] 53 | if g.PC == 0 { 54 | g.PC = ev.Stk[0].PC 55 | g.Name = ev.Stk[0].Fn 56 | } 57 | g.lastStartTime = ev.Ts 58 | if g.StartTime == 0 { 59 | g.StartTime = ev.Ts 60 | } 61 | if g.blockSchedTime != 0 { 62 | g.SchedWaitTime += ev.Ts - g.blockSchedTime 63 | g.blockSchedTime = 0 64 | } 65 | case EvGoEnd, EvGoStop: 66 | g := gs[ev.G] 67 | g.ExecTime += ev.Ts - g.lastStartTime 68 | g.TotalTime = ev.Ts - g.CreationTime 69 | g.EndTime = ev.Ts 70 | case EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect, 71 | EvGoBlockSync, EvGoBlockCond: 72 | g := gs[ev.G] 73 | g.ExecTime += ev.Ts - g.lastStartTime 74 | g.blockSyncTime = ev.Ts 75 | case EvGoSched, EvGoPreempt: 76 | g := gs[ev.G] 77 | g.ExecTime += ev.Ts - g.lastStartTime 78 | g.blockSchedTime = ev.Ts 79 | case EvGoSleep, EvGoBlock: 80 | g := gs[ev.G] 81 | g.ExecTime += ev.Ts - g.lastStartTime 82 | case EvGoBlockNet: 83 | g := gs[ev.G] 84 | g.ExecTime += ev.Ts - g.lastStartTime 85 | g.blockNetTime = ev.Ts 86 | case EvGoBlockGC: 87 | g := gs[ev.G] 88 | g.ExecTime += ev.Ts - g.lastStartTime 89 | g.blockGCTime = ev.Ts 90 | case EvGoUnblock: 91 | g := gs[ev.Args[0]] 92 | if g.blockNetTime != 0 { 93 | g.IOTime += ev.Ts - g.blockNetTime 94 | g.blockNetTime = 0 95 | } 96 | if g.blockSyncTime != 0 { 97 | g.BlockTime += ev.Ts - g.blockSyncTime 98 | g.blockSyncTime = 0 99 | } 100 | g.blockSchedTime = ev.Ts 101 | case EvGoSysBlock: 102 | g := gs[ev.G] 103 | g.ExecTime += ev.Ts - g.lastStartTime 104 | g.blockSyscallTime = ev.Ts 105 | case EvGoSysExit: 106 | g := gs[ev.G] 107 | if g.blockSyscallTime != 0 { 108 | g.SyscallTime += ev.Ts - g.blockSyscallTime 109 | g.blockSyscallTime = 0 110 | } 111 | g.blockSchedTime = ev.Ts 112 | case EvGCSweepStart: 113 | g := gs[ev.G] 114 | if g != nil { 115 | // Sweep can happen during GC on system goroutine. 116 | g.blockSweepTime = ev.Ts 117 | } 118 | case EvGCSweepDone: 119 | g := gs[ev.G] 120 | if g != nil && g.blockSweepTime != 0 { 121 | g.SweepTime += ev.Ts - g.blockSweepTime 122 | g.blockSweepTime = 0 123 | } 124 | case EvGCStart: 125 | gcStartTime = ev.Ts 126 | case EvGCDone: 127 | for _, g := range gs { 128 | if g.EndTime == 0 { 129 | g.GCTime += ev.Ts - gcStartTime 130 | } 131 | } 132 | } 133 | } 134 | 135 | for _, g := range gs { 136 | if g.TotalTime == 0 { 137 | g.TotalTime = lastTs - g.CreationTime 138 | } 139 | if g.EndTime == 0 { 140 | g.EndTime = lastTs 141 | } 142 | if g.blockNetTime != 0 { 143 | g.IOTime += lastTs - g.blockNetTime 144 | g.blockNetTime = 0 145 | } 146 | if g.blockSyncTime != 0 { 147 | g.BlockTime += lastTs - g.blockSyncTime 148 | g.blockSyncTime = 0 149 | } 150 | if g.blockSyscallTime != 0 { 151 | g.SyscallTime += lastTs - g.blockSyscallTime 152 | g.blockSyscallTime = 0 153 | } 154 | if g.blockSchedTime != 0 { 155 | g.SchedWaitTime += lastTs - g.blockSchedTime 156 | g.blockSchedTime = 0 157 | } 158 | g.gdesc = nil 159 | } 160 | 161 | return gs 162 | } 163 | 164 | // RelatedGoroutines finds a set of goroutines related to goroutine goid. 165 | func RelatedGoroutines(events []*Event, goid uint64) map[uint64]bool { 166 | // BFS of depth 2 over "unblock" edges 167 | // (what goroutines unblock goroutine goid?). 168 | gmap := make(map[uint64]bool) 169 | gmap[goid] = true 170 | for i := 0; i < 2; i++ { 171 | gmap1 := make(map[uint64]bool) 172 | for g := range gmap { 173 | gmap1[g] = true 174 | } 175 | for _, ev := range events { 176 | if ev.Type == EvGoUnblock && gmap[ev.Args[0]] { 177 | gmap1[ev.G] = true 178 | } 179 | } 180 | gmap = gmap1 181 | } 182 | gmap[0] = true // for GC events 183 | return gmap 184 | } 185 | -------------------------------------------------------------------------------- /trace/mkcanned.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2016 The Go Authors. All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | # mkcanned.bash creates canned traces for the trace test suite using 7 | # the current Go version. 8 | 9 | set -e 10 | 11 | if [ $# != 1 ]; then 12 | echo "usage: $0