├── .dockerignore ├── .gitignore ├── .test-cover.sh ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── glide.lock ├── glide.yaml ├── main.go ├── main_test.go ├── pprof ├── parser.go ├── parser_test.go ├── pprof.go ├── pprof_test.go ├── select_sample.go ├── select_sample_test.go └── testdata │ ├── pprof-memprofile-1.8.raw.txt │ ├── pprof.1.pb.gz │ ├── pprof.raw.txt │ ├── pprof2.raw.txt │ ├── pprof3.raw.txt │ └── pprof4.raw.txt ├── renderer ├── flamegraph.go ├── flamegraph_test.go ├── renderer.go └── renderer_test.go ├── stack ├── sample.go └── sample_test.go └── torchlog └── torchlog.go /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go-torch 2 | 3 | *.test 4 | *.svg 5 | 6 | *.log 7 | *.tmp 8 | 9 | Godeps/_workspace 10 | vendor 11 | -------------------------------------------------------------------------------- /.test-cover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | export GO15VENDOREXPERIMENT=1 6 | 7 | echo "mode: set" > acc.out 8 | FAIL=0 9 | 10 | # List all packages to run tests for 11 | PACKAGES=$(glide novendor) 12 | go test -i $PACKAGES 13 | for dir in $PACKAGES; 14 | do 15 | go test -coverprofile=profile.out $dir || FAIL=$? 16 | if [ -f profile.out ] 17 | then 18 | cat profile.out | grep -v "mode: set" | grep -v "mocks.go" >> acc.out 19 | rm profile.out 20 | fi 21 | done 22 | 23 | # Failures have incomplete results, so don't send 24 | if [ "$FAIL" -eq 0 ]; then 25 | goveralls -service=travis-ci -v -coverprofile=acc.out 26 | fi 27 | 28 | rm -f acc.out 29 | 30 | exit $FAIL 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | 4 | # 1.4 will probably work, but we don't test it since we use vendor. 5 | go: 6 | - 1.5 7 | - 1.6 8 | - 1.7 9 | - tip 10 | 11 | install: 12 | - go get github.com/Masterminds/glide 13 | - go get -u github.com/mattn/goveralls 14 | - glide install 15 | 16 | script: 17 | - ./.test-cover.sh 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.9-alpine 2 | 3 | ENV PATH $PATH:/opt/flamegraph 4 | ENV FLAMEGRAPH_SHA a93d905911c07c96a73b35ddbcb5ddb2f39da4b6 5 | 6 | RUN apk --update add git && \ 7 | apk add curl && \ 8 | curl -OL https://github.com/Masterminds/glide/releases/download/v0.12.3/glide-v0.12.3-linux-amd64.tar.gz && \ 9 | tar -xzf glide-v0.12.3-linux-amd64.tar.gz && \ 10 | mv linux-amd64/glide /usr/bin && \ 11 | apk add perl && \ 12 | git clone git://github.com/brendangregg/FlameGraph.git /opt/flamegraph && \ 13 | ( cd /opt/flamegraph && \ 14 | git reset --hard $FLAMEGRAPH_SHA && \ 15 | rm -rf .git ) 16 | 17 | COPY . /go/src/github.com/uber/go-torch 18 | 19 | RUN cd /go/src/github.com/uber/go-torch && glide install && go install ./... 20 | 21 | ENTRYPOINT ["go-torch"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) [INSERT_YEAR] Uber Technologies, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-torch [![Build Status](https://travis-ci.org/uber/go-torch.svg?branch=master)](https://travis-ci.org/uber/go-torch) [![Coverage Status](http://coveralls.io/repos/uber/go-torch/badge.svg?branch=master&service=github)](http://coveralls.io/github/uber/go-torch?branch=master) [![GoDoc](https://godoc.org/github.com/uber/go-torch?status.svg)](https://godoc.org/github.com/uber/go-torch) 2 | 3 | ## go-torch is deprecated, use pprof instead 4 | 5 | As of Go 1.11, flamegraph visualizations are available in `go tool pprof` directly! 6 | 7 | ``` 8 | # This will listen on :8081 and open a browser. 9 | # Change :8081 to a port of your choice. 10 | $ go tool pprof -http=":8081" [binary] [profile] 11 | ``` 12 | 13 | If you cannot use Go 1.11, you can get the latest `pprof` tool and use it instead: 14 | 15 | ``` 16 | # Get the pprof tool directly 17 | $ go get -u github.com/google/pprof 18 | 19 | $ pprof -http=":8081" [binary] [profile] 20 | ``` 21 | 22 | ## Synopsis 23 | 24 | Tool for stochastically profiling Go programs. Collects stack traces and 25 | synthesizes them into a flame graph. Uses Go's built in [pprof][] library. 26 | 27 | [pprof]: https://golang.org/pkg/net/http/pprof/ 28 | 29 | ## Example Flame Graph 30 | 31 | [![Inception](http://uber.github.io/go-torch/meta.svg)](http://uber.github.io/go-torch/meta.svg) 32 | 33 | ## Basic Usage 34 | 35 | ``` 36 | $ go-torch -h 37 | Usage: 38 | go-torch [options] [binary] 39 | 40 | pprof Options: 41 | -u, --url= Base URL of your Go program (default: http://localhost:8080) 42 | -s, --suffix= URL path of pprof profile (default: /debug/pprof/profile) 43 | -b, --binaryinput= File path of previously saved binary profile. (binary profile is anything accepted by https://golang.org/cmd/pprof) 44 | --binaryname= File path of the binary that the binaryinput is for, used for pprof inputs 45 | -t, --seconds= Number of seconds to profile for (default: 30) 46 | --pprofArgs= Extra arguments for pprof 47 | 48 | Output Options: 49 | -f, --file= Output file name (must be .svg) (default: torch.svg) 50 | -p, --print Print the generated svg to stdout instead of writing to file 51 | -r, --raw Print the raw call graph output to stdout instead of creating a flame graph; use with Brendan Gregg's flame graph perl script (see https://github.com/brendangregg/FlameGraph) 52 | --title= Graph title to display in the output file (default: Flame Graph) 53 | --width= Generated graph width (default: 1200) 54 | --hash Colors are keyed by function name hash 55 | --colors= Set color palette. Valid choices are: hot (default), mem, io, wakeup, chain, java, 56 | js, perl, red, green, blue, aqua, yellow, purple, orange 57 | --hash Graph colors are keyed by function name hash 58 | --cp Graph use consistent palette (palette.map) 59 | --inverted Icicle graph 60 | Help Options: 61 | -h, --help Show this help message 62 | ``` 63 | 64 | ### Write flamegraph using /debug/pprof endpoint 65 | 66 | The default options will hit `http://localhost:8080/debug/pprof/profile` for 67 | a 30 second CPU profile, and write it out to torch.svg 68 | 69 | ``` 70 | $ go-torch 71 | INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 30 http://localhost:8080/debug/pprof/profile 72 | INFO[19:11:03] Writing svg to torch.svg 73 | ``` 74 | 75 | You can customize the base URL by using `-u` 76 | 77 | ``` 78 | $ go-torch -u http://my-service:8080/ 79 | INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 30 http://my-service:8080/debug/pprof/profile 80 | INFO[19:11:03] Writing svg to torch.svg 81 | ``` 82 | 83 | Or change the number of seconds to profile using `--seconds`: 84 | 85 | ``` 86 | $ go-torch --seconds 5 87 | INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 5 http://localhost:8080/debug/pprof/profile 88 | INFO[19:11:03] Writing svg to torch.svg 89 | ``` 90 | 91 | 92 | ### Using pprof arguments 93 | 94 | `go-torch` will pass through arguments to `go tool pprof`, which lets you take 95 | existing pprof commands and easily make them work with `go-torch`. 96 | 97 | For example, after creating a CPU profile from a benchmark: 98 | ``` 99 | $ go test -bench . -cpuprofile=cpu.prof 100 | 101 | # This creates a cpu.prof file, and the $PKG.test binary. 102 | ``` 103 | 104 | The same arguments that can be used with `go tool pprof` will also work 105 | with `go-torch`: 106 | ``` 107 | $ go tool pprof main.test cpu.prof 108 | 109 | # Same arguments work with go-torch 110 | $ go-torch main.test cpu.prof 111 | INFO[19:00:29] Run pprof command: go tool pprof -raw -seconds 30 main.test cpu.prof 112 | INFO[19:00:29] Writing svg to torch.svg 113 | ``` 114 | 115 | 116 | Flags that are not handled by `go-torch` are passed through as well: 117 | ``` 118 | $ go-torch --alloc_objects main.test mem.prof 119 | INFO[19:00:29] Run pprof command: go tool pprof -raw -seconds 30 --alloc_objects main.test mem.prof 120 | INFO[19:00:29] Writing svg to torch.svg 121 | ``` 122 | 123 | ## Integrating With Your Application 124 | 125 | To add profiling endpoints in your application, follow the official 126 | Go docs [here][]. 127 | If your application is already running a server on the DefaultServeMux, 128 | just add this import to your application. 129 | 130 | [here]: https://golang.org/pkg/net/http/pprof/ 131 | 132 | ```go 133 | import _ "net/http/pprof" 134 | ``` 135 | 136 | If your application is not using the DefaultServeMux, you can still easily 137 | expose pprof endpoints by manually registering the net/http/pprof handlers or by 138 | using a library like [this one](https://github.com/e-dard/netbug). 139 | 140 | ## Installation 141 | 142 | ``` 143 | $ go get github.com/uber/go-torch 144 | ``` 145 | 146 | You can also use go-torch using docker: 147 | ``` 148 | $ docker run uber/go-torch -u http://[address-of-host] -p > torch.svg 149 | ``` 150 | 151 | Using `-p` will print the SVG to standard out, which can then be redirected 152 | to a file. This avoids mounting volumes to a container. 153 | 154 | ### Get the flame graph script: 155 | 156 | When using the `go-torch` binary locally, you will need the Flamegraph scripts 157 | in your `PATH`: 158 | 159 | ``` 160 | $ cd $GOPATH/src/github.com/uber/go-torch 161 | $ git clone https://github.com/brendangregg/FlameGraph.git 162 | ``` 163 | 164 | ## Development and Testing 165 | 166 | ### Install the Go dependencies: 167 | 168 | ``` 169 | $ go get github.com/Masterminds/glide 170 | $ cd $GOPATH/src/github.com/uber/go-torch 171 | $ glide install 172 | ``` 173 | 174 | ### Run the Tests 175 | 176 | ``` 177 | $ go test ./... 178 | ok github.com/uber/go-torch 0.012s 179 | ok github.com/uber/go-torch/graph 0.017s 180 | ok github.com/uber/go-torch/visualization 0.052s 181 | ``` 182 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: e02633304cb0e642a7f236a49dd73b0c5b9bddb3e2b35272191bdc16c6e62baa 2 | updated: 2017-01-18T09:23:43.933063939-08:00 3 | imports: 4 | - name: github.com/fatih/color 5 | version: 42c364ba490082e4815b5222728711b3440603eb 6 | - name: github.com/jessevdk/go-flags 7 | version: 4e64e4a4e2552194cf594243e23aa9baf3b4297e 8 | - name: github.com/mattn/go-colorable 9 | version: d228849504861217f796da67fae4f6e347643f15 10 | - name: github.com/mattn/go-isatty 11 | version: 30a891c33c7cde7b02a981314b4228ec99380cca 12 | - name: golang.org/x/sys 13 | version: d75a52659825e75fff6158388dddc6a5b04f9ba5 14 | subpackages: 15 | - unix 16 | testImports: 17 | - name: github.com/davecgh/go-spew 18 | version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 19 | subpackages: 20 | - spew 21 | - name: github.com/pmezard/go-difflib 22 | version: d8ed2627bdf02c080bf22230dbb337003b7aba2d 23 | subpackages: 24 | - difflib 25 | - name: github.com/stretchr/testify 26 | version: 2402e8e7a02fc811447d11f881aa9746cdc57983 27 | subpackages: 28 | - assert 29 | - require 30 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/uber/go-torch 2 | import: 3 | - package: github.com/fatih/color 4 | - package: github.com/jessevdk/go-flags 5 | testImport: 6 | - package: github.com/stretchr/testify 7 | subpackages: 8 | - assert 9 | - require 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package main is the entry point of go-torch, a stochastic flame graph 22 | // profiler for Go programs. 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | "io/ioutil" 28 | "os" 29 | "strconv" 30 | "strings" 31 | 32 | "github.com/uber/go-torch/pprof" 33 | "github.com/uber/go-torch/renderer" 34 | "github.com/uber/go-torch/torchlog" 35 | 36 | gflags "github.com/jessevdk/go-flags" 37 | ) 38 | 39 | // options are the parameters for go-torch. 40 | type options struct { 41 | PProfOptions pprof.Options `group:"pprof Options"` 42 | OutputOpts outputOptions `group:"Output Options"` 43 | } 44 | 45 | type outputOptions struct { 46 | File string `short:"f" long:"file" default:"torch.svg" description:"Output file name (must be .svg)"` 47 | Print bool `short:"p" long:"print" description:"Print the generated svg to stdout instead of writing to file"` 48 | Raw bool `short:"r" long:"raw" description:"Print the raw call graph output to stdout instead of creating a flame graph; use with Brendan Gregg's flame graph perl script (see https://github.com/brendangregg/FlameGraph)"` 49 | Title string `long:"title" default:"Flame Graph" description:"Graph title to display in the output file"` 50 | Width int64 `long:"width" default:"1200" description:"Generated graph width"` 51 | Hash bool `long:"hash" description:"Colors are keyed by function name hash"` 52 | Colors string `long:"colors" default:"" description:"set color palette. choices are: hot (default), mem, io, wakeup, chain, java, js, perl, red, green, blue, aqua, yellow, purple, orange"` 53 | ConsistentPalette bool `long:"cp" description:"Use consistent palette (palette.map)"` 54 | Reverse bool `long:"reverse" description:"Generate stack-reversed flame graph"` 55 | Inverted bool `long:"inverted" description:"icicle graph"` 56 | } 57 | 58 | // main is the entry point of the application 59 | func main() { 60 | if err := runWithArgs(os.Args[1:]...); err != nil { 61 | torchlog.Fatalf("Failed: %v", err) 62 | } 63 | } 64 | 65 | func runWithArgs(args ...string) error { 66 | opts := &options{} 67 | 68 | parser := gflags.NewParser(opts, gflags.Default|gflags.IgnoreUnknown) 69 | parser.Usage = "[options] [binary] " 70 | 71 | remaining, err := parser.ParseArgs(args) 72 | if err != nil { 73 | if flagErr, ok := err.(*gflags.Error); ok && flagErr.Type == gflags.ErrHelp { 74 | os.Exit(0) 75 | } 76 | return fmt.Errorf("could not parse options: %v", err) 77 | } 78 | if err := validateOptions(opts); err != nil { 79 | return fmt.Errorf("invalid options: %v", err) 80 | } 81 | 82 | return runWithOptions(opts, remaining) 83 | } 84 | 85 | func runWithOptions(allOpts *options, remaining []string) error { 86 | pprofRawOutput, err := pprof.GetRaw(allOpts.PProfOptions, remaining) 87 | if err != nil { 88 | return fmt.Errorf("could not get raw output from pprof: %v", err) 89 | } 90 | 91 | profile, err := pprof.ParseRaw(pprofRawOutput) 92 | if err != nil { 93 | return fmt.Errorf("could not parse raw pprof output: %v", err) 94 | } 95 | 96 | sampleIndex := pprof.SelectSample(remaining, profile.SampleNames) 97 | flameInput, err := renderer.ToFlameInput(profile, sampleIndex) 98 | if err != nil { 99 | return fmt.Errorf("could not convert stacks to flamegraph input: %v", err) 100 | } 101 | 102 | opts := allOpts.OutputOpts 103 | if opts.Raw { 104 | torchlog.Print("Printing raw flamegraph input to stdout") 105 | fmt.Printf("%s\n", flameInput) 106 | return nil 107 | } 108 | 109 | var flameGraphArgs = buildFlameGraphArgs(opts) 110 | flameGraph, err := renderer.GenerateFlameGraph(flameInput, flameGraphArgs...) 111 | if err != nil { 112 | return fmt.Errorf("could not generate flame graph: %v", err) 113 | } 114 | 115 | if opts.Print { 116 | torchlog.Print("Printing svg to stdout") 117 | fmt.Printf("%s\n", flameGraph) 118 | return nil 119 | } 120 | 121 | torchlog.Printf("Writing svg to %v", opts.File) 122 | if err := ioutil.WriteFile(opts.File, flameGraph, 0666); err != nil { 123 | return fmt.Errorf("could not write output file: %v", err) 124 | } 125 | 126 | return nil 127 | } 128 | 129 | func validateOptions(opts *options) error { 130 | file := opts.OutputOpts.File 131 | if file != "" && !strings.HasSuffix(file, ".svg") { 132 | return fmt.Errorf("output file must end in .svg") 133 | } 134 | if opts.PProfOptions.TimeSeconds < 1 { 135 | return fmt.Errorf("seconds must be an integer greater than 0") 136 | } 137 | 138 | // extra FlameGraph options 139 | if opts.OutputOpts.Title == "" { 140 | return fmt.Errorf("flamegraph title should not be empty") 141 | } 142 | if opts.OutputOpts.Width <= 0 { 143 | return fmt.Errorf("flamegraph default width is 1200 pixels") 144 | } 145 | if opts.OutputOpts.Colors != "" { 146 | switch opts.OutputOpts.Colors { 147 | case "hot", "mem", "io", "wakeup", "chain", "java", "js", "perl", "red", "green", "blue", "aqua", "yellow", "purple", "orange": 148 | // valid 149 | default: 150 | return fmt.Errorf("unknown flamegraph colors %q", opts.OutputOpts.Colors) 151 | } 152 | } 153 | 154 | return nil 155 | } 156 | 157 | func buildFlameGraphArgs(opts outputOptions) []string { 158 | var args []string 159 | 160 | if opts.Title != "" { 161 | args = append(args, "--title", opts.Title) 162 | } 163 | 164 | if opts.Width > 0 { 165 | args = append(args, "--width", strconv.FormatInt(opts.Width, 10)) 166 | } 167 | 168 | if opts.Colors != "" { 169 | args = append(args, "--colors", opts.Colors) 170 | } 171 | 172 | if opts.Hash { 173 | args = append(args, "--hash") 174 | } 175 | 176 | if opts.ConsistentPalette { 177 | args = append(args, "--cp") 178 | } 179 | 180 | if opts.Reverse { 181 | args = append(args, "--reverse") 182 | } 183 | 184 | if opts.Inverted { 185 | args = append(args, "--inverted") 186 | } 187 | 188 | return args 189 | } 190 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import ( 24 | "bufio" 25 | "io/ioutil" 26 | "os" 27 | "path/filepath" 28 | "reflect" 29 | "strings" 30 | "testing" 31 | 32 | gflags "github.com/jessevdk/go-flags" 33 | ) 34 | 35 | const testPProfInputFile = "./pprof/testdata/pprof.1.pb.gz" 36 | 37 | func getDefaultOptions() *options { 38 | opts := &options{} 39 | if _, err := gflags.ParseArgs(opts, nil); err != nil { 40 | panic(err) 41 | } 42 | opts.PProfOptions.BinaryFile = testPProfInputFile 43 | return opts 44 | } 45 | 46 | func TestBadArgs(t *testing.T) { 47 | err := runWithArgs("-t", "asd") 48 | if err == nil { 49 | t.Fatalf("expected run with bad arguments to fail") 50 | } 51 | 52 | expectedSubstr := []string{ 53 | "could not parse options", 54 | "invalid argument", 55 | } 56 | for _, substr := range expectedSubstr { 57 | if !strings.Contains(err.Error(), substr) { 58 | t.Errorf("error is missing message: %v", substr) 59 | } 60 | } 61 | } 62 | 63 | func TestMain(t *testing.T) { 64 | os.Args = []string{"go-torch", "--raw", "--binaryinput", testPProfInputFile} 65 | main() 66 | // Test should not fatal. 67 | } 68 | 69 | func TestMainRemaining(t *testing.T) { 70 | os.Args = []string{"go-torch", "--raw", testPProfInputFile} 71 | main() 72 | // Test should not fatal. 73 | } 74 | 75 | func TestInvalidOptions(t *testing.T) { 76 | tests := []struct { 77 | args []string 78 | errorMessage string 79 | }{ 80 | { 81 | args: []string{"--file", "bad.jpg"}, 82 | errorMessage: "must end in .svg", 83 | }, 84 | { 85 | args: []string{"-t", "0"}, 86 | errorMessage: "seconds must be an integer greater than 0", 87 | }, 88 | { 89 | args: []string{"--title", ""}, 90 | errorMessage: "flamegraph title should not be empty", 91 | }, 92 | { 93 | args: []string{"--width", "0"}, 94 | errorMessage: "flamegraph default width is 1200 pixels", 95 | }, 96 | { 97 | args: []string{"--colors", "foo"}, 98 | errorMessage: "unknown flamegraph colors \"foo\"", 99 | }, 100 | } 101 | 102 | for _, tt := range tests { 103 | err := runWithArgs(tt.args...) 104 | if err == nil { 105 | t.Errorf("Expected error when running with: %v", tt.args) 106 | continue 107 | } 108 | 109 | if !strings.Contains(err.Error(), tt.errorMessage) { 110 | t.Errorf("Error missing message, got %v want message %v", err.Error(), tt.errorMessage) 111 | } 112 | } 113 | } 114 | 115 | func TestRunRaw(t *testing.T) { 116 | opts := getDefaultOptions() 117 | opts.OutputOpts.Raw = true 118 | 119 | if err := runWithOptions(opts, nil); err != nil { 120 | t.Fatalf("Run with Raw failed: %v", err) 121 | } 122 | } 123 | 124 | func TestFlameGraphArgs(t *testing.T) { 125 | opts := getDefaultOptions() 126 | opts.OutputOpts.Raw = true 127 | 128 | opts.OutputOpts.Hash = true 129 | opts.OutputOpts.Colors = "perl" 130 | opts.OutputOpts.ConsistentPalette = true 131 | opts.OutputOpts.Reverse = true 132 | opts.OutputOpts.Inverted = true 133 | 134 | expectedCommandWithArgs := []string{"--title", "Flame Graph", "--width", "1200", "--colors", "perl", 135 | "--hash", "--cp", "--reverse", "--inverted"} 136 | 137 | if !reflect.DeepEqual(expectedCommandWithArgs, buildFlameGraphArgs(opts.OutputOpts)) { 138 | t.Fatalf("Invalid extra FlameGraph arguments!") 139 | } 140 | 141 | if err := runWithOptions(opts, nil); err != nil { 142 | t.Fatalf("Run with extra FlameGraph arguments failed: %v", err) 143 | } 144 | } 145 | 146 | func getTempFilename(t *testing.T, suffix string) string { 147 | f, err := ioutil.TempFile("", "") 148 | if err != nil { 149 | t.Fatalf("Failed to create temp file: %v", err) 150 | } 151 | 152 | defer f.Close() 153 | return f.Name() + suffix 154 | } 155 | 156 | func TestRunFile(t *testing.T) { 157 | opts := getDefaultOptions() 158 | opts.OutputOpts.File = getTempFilename(t, ".svg") 159 | 160 | withScriptsInPath(t, func() { 161 | if err := runWithOptions(opts, nil); err != nil { 162 | t.Fatalf("Run with Print failed: %v", err) 163 | } 164 | 165 | f, err := os.Open(opts.OutputOpts.File) 166 | if err != nil { 167 | t.Errorf("Failed to open output file: %v", err) 168 | } 169 | defer f.Close() 170 | 171 | // Our fake flamegraph scripts just add script names to the output. 172 | reader := bufio.NewReader(f) 173 | line1, err := reader.ReadString('\n') 174 | if err != nil { 175 | t.Errorf("Failed to read line 1 in output file: %v", err) 176 | } 177 | if !strings.Contains(line1, "flamegraph.pl") { 178 | t.Errorf("Output file has not been processed by flame graph scripts") 179 | } 180 | }) 181 | } 182 | 183 | func TestRunBadFile(t *testing.T) { 184 | opts := getDefaultOptions() 185 | opts.OutputOpts.File = "/dev/zero/invalid/file" 186 | 187 | withScriptsInPath(t, func() { 188 | if err := runWithOptions(opts, nil); err == nil { 189 | t.Fatalf("Run with bad file expected to fail") 190 | } 191 | }) 192 | } 193 | 194 | func TestRunPrint(t *testing.T) { 195 | opts := getDefaultOptions() 196 | opts.OutputOpts.Print = true 197 | 198 | withScriptsInPath(t, func() { 199 | if err := runWithOptions(opts, nil); err != nil { 200 | t.Fatalf("Run with Print failed: %v", err) 201 | } 202 | // TODO(prashantv): Verify that output is printed to stdout. 203 | }) 204 | } 205 | 206 | // scriptsPath is used to cache the fake scripts if we've already created it. 207 | var scriptsPath string 208 | 209 | func withScriptsInPath(t *testing.T, f func()) { 210 | oldPath := os.Getenv("PATH") 211 | defer os.Setenv("PATH", oldPath) 212 | 213 | // Create a temporary directory with fake flamegraph scripts if we haven't already. 214 | if scriptsPath == "" { 215 | var err error 216 | scriptsPath, err = ioutil.TempDir("", "go-torch-scripts") 217 | if err != nil { 218 | t.Fatalf("Failed to create temporary scripts dir: %v", err) 219 | } 220 | 221 | // Create scripts in this path. 222 | const scriptContents = `#!/bin/sh 223 | echo $0 224 | cat 225 | ` 226 | scriptFile := filepath.Join(scriptsPath, "flamegraph.pl") 227 | if err := ioutil.WriteFile(scriptFile, []byte(scriptContents), 0777); err != nil { 228 | t.Errorf("Failed to create script %v: %v", scriptFile, err) 229 | } 230 | } 231 | 232 | os.Setenv("PATH", scriptsPath+":"+oldPath) 233 | f() 234 | } 235 | -------------------------------------------------------------------------------- /pprof/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import ( 24 | "bufio" 25 | "bytes" 26 | "fmt" 27 | "io" 28 | "regexp" 29 | "strconv" 30 | "strings" 31 | 32 | "github.com/uber/go-torch/stack" 33 | ) 34 | 35 | type readState int 36 | 37 | const ( 38 | ignore readState = iota 39 | samplesHeader 40 | samples 41 | locations 42 | mappings 43 | ) 44 | 45 | // funcID is the ID of a given Location in the pprof raw output. 46 | type funcID int 47 | 48 | type rawParser struct { 49 | // err is the first error encountered by the parser. 50 | err error 51 | 52 | state readState 53 | funcNames map[funcID]string 54 | sampleNames []string 55 | records []*stackRecord 56 | } 57 | 58 | // ParseRaw parses the raw pprof output and returns call stacks. 59 | func ParseRaw(input []byte) (*stack.Profile, error) { 60 | parser := newRawParser() 61 | if err := parser.parse(input); err != nil { 62 | return nil, err 63 | } 64 | 65 | return parser.toProfile() 66 | } 67 | 68 | func newRawParser() *rawParser { 69 | return &rawParser{ 70 | funcNames: make(map[funcID]string), 71 | } 72 | } 73 | 74 | func (p *rawParser) parse(input []byte) error { 75 | reader := bufio.NewReader(bytes.NewReader(input)) 76 | 77 | for { 78 | line, err := reader.ReadString('\n') 79 | if err != nil { 80 | if err == io.EOF { 81 | if p.state < locations { 82 | p.setError(fmt.Errorf("parser ended before processing locations, state: %v", p.state)) 83 | } 84 | break 85 | } 86 | return err 87 | } 88 | 89 | p.processLine(strings.TrimSpace(line)) 90 | } 91 | 92 | return p.err 93 | } 94 | 95 | func (p *rawParser) setError(err error) { 96 | if p.err != nil { 97 | return 98 | } 99 | p.err = err 100 | } 101 | 102 | func (p *rawParser) processLine(line string) { 103 | switch p.state { 104 | case ignore: 105 | if strings.HasPrefix(line, "Samples") { 106 | p.state = samplesHeader 107 | return 108 | } 109 | case samplesHeader: 110 | p.sampleNames = strings.Split(line, " ") 111 | p.state = samples 112 | case samples: 113 | if strings.HasPrefix(line, "Locations") { 114 | p.state = locations 115 | return 116 | } 117 | p.addSample(line) 118 | case locations: 119 | if strings.HasPrefix(line, "Mappings") { 120 | p.state = mappings 121 | return 122 | } 123 | p.addLocation(line) 124 | case mappings: 125 | // Nothing to process. 126 | } 127 | } 128 | 129 | // toProfile aggregates stack sample counts and returns a profile with unique stack samples. 130 | func (p *rawParser) toProfile() (*stack.Profile, error) { 131 | profile, err := stack.NewProfile(p.sampleNames) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | samples := make(map[string]*stack.Sample) 137 | for _, r := range p.records { 138 | funcNames := r.funcNames(p.funcNames) 139 | funcKey := strings.Join(funcNames, ";") 140 | 141 | if sample, ok := samples[funcKey]; ok { 142 | if err := sample.Add(r.samples); err != nil { 143 | return nil, err 144 | } 145 | continue 146 | } 147 | 148 | samples[funcKey] = stack.NewSample(funcNames, r.samples) 149 | } 150 | 151 | profile.Samples = make([]*stack.Sample, 0, len(samples)) 152 | for _, s := range samples { 153 | profile.Samples = append(profile.Samples, s) 154 | } 155 | 156 | return profile, nil 157 | } 158 | 159 | // addLocation parses a location that looks like: 160 | // 292: 0x49dee1 github.com/uber/tchannel/golang.(*Frame).ReadIn :0 s=0 161 | // and creates a mapping from funcID to function name. 162 | func (p *rawParser) addLocation(line string) { 163 | parts := splitBySpace(line) 164 | if len(parts) < 4 { 165 | switch { 166 | case len(parts) == 2: 167 | // Some lines just have an ID and an address, we can ignore those. 168 | case len(parts) == 3 && strings.HasPrefix(parts[2], "M="): 169 | // Some lines have an ID, a mapping ID and an address, we can 170 | // ignore those as well. 171 | case len(parts) == 3 && strings.HasPrefix(parts[2], "s="): 172 | // See https://github.com/uber/go-torch/issues/63#issuecomment-315658039. 173 | // The raw "format" sometimes prints multiple lines per location. We can't 174 | // see previous lines here, so for now we just skip them. 175 | default: 176 | p.setError(fmt.Errorf("malformed location line: %v", line)) 177 | } 178 | return 179 | } 180 | funcID := p.toFuncID(strings.TrimSuffix(parts[0], ":")) 181 | if strings.HasPrefix(parts[2], "M=") { 182 | p.funcNames[funcID] = parts[3] 183 | } else { 184 | p.funcNames[funcID] = parts[2] 185 | } 186 | } 187 | 188 | type stackRecord struct { 189 | samples []int64 190 | stack []funcID 191 | } 192 | 193 | // addSample parses a sample that looks like: 194 | // 1 10000000: 1 2 3 4 195 | // and creates a stackRecord for it. 196 | func (p *rawParser) addSample(line string) { 197 | if strings.Contains(line, "bytes:[") { 198 | // Memory profiles have a line of bytes:[size1] which contains the size 199 | // of the object. Skip these lines as we do not use it currently. 200 | return 201 | } 202 | 203 | // Split by ":" which separates the data from the function IDs. 204 | lineParts := strings.Split(line, ":") 205 | if len(lineParts) != 2 { 206 | p.setError(fmt.Errorf("malformed sample line: %v", line)) 207 | return 208 | } 209 | 210 | samples := p.parseInts(lineParts[0]) 211 | funcIDs := p.parseFuncIDs(lineParts[1]) 212 | 213 | if len(samples) != len(p.sampleNames) { 214 | p.setError(fmt.Errorf("line has a different sample count (%v) than sample names (%v): %v", 215 | len(samples), len(p.sampleNames), line)) 216 | return 217 | } 218 | 219 | p.records = append(p.records, &stackRecord{ 220 | samples: samples, 221 | stack: funcIDs, 222 | }) 223 | } 224 | 225 | func getFunctionName(funcNames map[funcID]string, funcID funcID) string { 226 | if funcName, ok := funcNames[funcID]; ok { 227 | return funcName 228 | } 229 | return fmt.Sprintf("missing-function-%v", funcID) 230 | } 231 | 232 | // funcNames returns the function names for this stack sample. 233 | // It returns in parent first order. 234 | func (r *stackRecord) funcNames(funcNames map[funcID]string) []string { 235 | var names []string 236 | for i := len(r.stack) - 1; i >= 0; i-- { 237 | funcID := r.stack[i] 238 | names = append(names, getFunctionName(funcNames, funcID)) 239 | } 240 | return names 241 | } 242 | 243 | func (p *rawParser) parseFuncIDs(s string) []funcID { 244 | funcInts := p.parseInts(s) 245 | funcIDs := make([]funcID, len(funcInts)) 246 | for i, fID := range funcInts { 247 | funcIDs[i] = funcID(fID) 248 | } 249 | return funcIDs 250 | } 251 | 252 | func (p *rawParser) parseInts(s string) []int64 { 253 | ss := splitBySpace(s) 254 | samples := make([]int64, len(ss)) 255 | for i, s := range ss { 256 | samples[i] = p.parseInt(s) 257 | } 258 | return samples 259 | } 260 | 261 | // parseInt converts a string to an int64. It stores any errors using setError. 262 | func (p *rawParser) parseInt(s string) int64 { 263 | v, err := strconv.ParseInt(s, 10, 64) 264 | if err != nil { 265 | p.setError(err) 266 | return 0 267 | } 268 | 269 | return v 270 | } 271 | 272 | // toFuncID converts a string like "8" to a funcID. 273 | func (p *rawParser) toFuncID(s string) funcID { 274 | return funcID(p.parseInt(s)) 275 | } 276 | 277 | var spaceSplitter = regexp.MustCompile(`\s+`) 278 | 279 | // splitBySpace splits values separated by 1 or more spaces. 280 | func splitBySpace(s string) []string { 281 | return spaceSplitter.Split(strings.TrimSpace(s), -1) 282 | } 283 | -------------------------------------------------------------------------------- /pprof/parser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import ( 24 | "io/ioutil" 25 | "reflect" 26 | "strings" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | "github.com/stretchr/testify/require" 31 | "github.com/uber/go-torch/stack" 32 | ) 33 | 34 | func parseTestRawData(t *testing.T, file string) ([]byte, *rawParser) { 35 | rawBytes, err := ioutil.ReadFile(file) 36 | if err != nil { 37 | t.Fatalf("Failed to read %v: %v", file, err) 38 | } 39 | 40 | parser := newRawParser() 41 | if err := parser.parse(rawBytes); err != nil { 42 | t.Fatalf("Parse failed: %v", err) 43 | } 44 | 45 | return rawBytes, parser 46 | } 47 | 48 | func parseTest1(t *testing.T) ([]byte, *rawParser) { 49 | return parseTestRawData(t, "testdata/pprof.raw.txt") 50 | } 51 | 52 | func TestParseMemProfile(t *testing.T) { 53 | parseTestRawData(t, "testdata/pprof3.raw.txt") 54 | parseTestRawData(t, "testdata/pprof-memprofile-1.8.raw.txt") 55 | } 56 | 57 | func TestParse(t *testing.T) { 58 | _, parser := parseTest1(t) 59 | 60 | assert.Equal(t, []string{"samples/count", "cpu/nanoseconds"}, parser.sampleNames) 61 | 62 | // line 7 - 249 are stack records in the test file. 63 | const expectedNumRecords = 242 64 | if len(parser.records) != expectedNumRecords { 65 | t.Errorf("Failed to parse all records, got %v records, expected %v", 66 | len(parser.records), expectedNumRecords) 67 | } 68 | expectedRecords := map[int]*stackRecord{ 69 | 0: &stackRecord{ 70 | samples: []int64{1, 10000000}, 71 | stack: []funcID{1, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 2, 3, 3, 3, 2, 3, 2, 3, 2, 2, 3, 2, 2, 3, 4, 5, 6}, 72 | }, 73 | 18: &stackRecord{ 74 | samples: []int64{1, 10000000}, 75 | stack: []funcID{14, 2, 2, 3, 2, 2, 3, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 2, 3, 3, 3, 3, 3, 2, 4, 5, 6}, 76 | }, 77 | 45: &stackRecord{ 78 | samples: []int64{12, 120000000}, 79 | stack: []funcID{23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34}, 80 | }, 81 | } 82 | for recordNum, expected := range expectedRecords { 83 | if got := parser.records[recordNum]; !reflect.DeepEqual(got, expected) { 84 | t.Errorf("Unexpected record for %v:\n got %#v\n want %#v", recordNum, got, expected) 85 | } 86 | } 87 | 88 | // line 250 - 290 are locations (or funcID mappings) 89 | const expectedFuncIDs = 41 90 | if len(parser.funcNames) != expectedFuncIDs { 91 | t.Errorf("Failed to parse func ID mappings, got %v records, expected %v", 92 | len(parser.funcNames), expectedFuncIDs) 93 | } 94 | knownMappings := map[funcID]string{ 95 | 1: "main.fib", 96 | 20: "main.fib", 97 | 34: "runtime.morestack", 98 | } 99 | for funcID, expected := range knownMappings { 100 | if got := parser.funcNames[funcID]; got != expected { 101 | t.Errorf("Unexpected mapping for %v: got %v, want %v", funcID, got, expected) 102 | } 103 | } 104 | } 105 | 106 | func TestParseWithM(t *testing.T) { 107 | _, parser := parseTestRawData(t, "testdata/pprof2.raw.txt") 108 | assert.Equal(t, "runtime.scanobject", parser.funcNames[1], "location with with m=1 failed") 109 | } 110 | 111 | func TestParseRawValid(t *testing.T) { 112 | rawBytes, _ := parseTest1(t) 113 | got, err := ParseRaw(rawBytes) 114 | if err != nil { 115 | t.Fatalf("ParseRaw failed: %v", err) 116 | } 117 | 118 | if expected := 18; len(got.Samples) != expected { 119 | t.Errorf("Expected %v unique stack samples, got %v", expected, got) 120 | } 121 | } 122 | 123 | func TestParseLocation(t *testing.T) { 124 | contents := `Samples: 125 | samples/count cpu/nanoseconds 126 | 2 10000000: 1 2 127 | Locations 128 | 1: 0x206f main.fib :0 s=0 129 | 2: 0x16e1 M=1 130 | 3: 0x16f4 M=1 131 | 4: 0x1534 M=1 132 | 5: 0x207a main.fib :0 s=0 133 | 730: 0x4021625 runtime.heapBits.next /usr/local/Cellar/go/1.9beta2/libexec/src/runtime/mbitmap.go:464 s=0 134 | runtime.scanobject /usr/local/Cellar/go/1.9beta2/libexec/src/runtime/mgcmark.go:1162 s=0 135 | 731: 0x40473ab runtime.growslice /usr/local/Cellar/go/1.9beta2/libexec/src/runtime/slice.go:140 s=0 136 | ` 137 | _, err := ParseRaw([]byte(contents)) 138 | if err != nil { 139 | t.Fatalf("Could not parse valid profile, err: %v", err) 140 | } 141 | } 142 | 143 | func TestParseMissingLocation(t *testing.T) { 144 | contents := `Samples: 145 | samples/count cpu/nanoseconds 146 | 2 10000000: 1 2 147 | Locations: 148 | 1: 0xaaaaa funcName :0 s=0 149 | ` 150 | out, err := ParseRaw([]byte(contents)) 151 | if err != nil { 152 | t.Fatalf("Missing location should not cause an error, got %v", err) 153 | } 154 | 155 | expected := &stack.Profile{ 156 | SampleNames: []string{"samples/count", "cpu/nanoseconds"}, 157 | Samples: []*stack.Sample{{ 158 | Funcs: []string{"missing-function-2", "funcName"}, 159 | Counts: []int64{2, 10000000}, 160 | }}, 161 | } 162 | if !reflect.DeepEqual(out, expected) { 163 | t.Errorf("Missing function call stack should contain missing-function-2\n got %+v\n want %+v", expected, out) 164 | } 165 | } 166 | 167 | func TestParseMissingSourceLine(t *testing.T) { 168 | contents := `Samples: 169 | samples/count cpu/nanoseconds 170 | 2 10000000: 1 2 171 | Locations: 172 | 1: 0xaaaaa funcName :0 s=0 173 | 2: 0xaaaab 174 | ` 175 | out, err := ParseRaw([]byte(contents)) 176 | if err != nil { 177 | t.Fatalf("Missing location should not cause an error, got %v", err) 178 | } 179 | 180 | expected := &stack.Profile{ 181 | SampleNames: []string{"samples/count", "cpu/nanoseconds"}, 182 | Samples: []*stack.Sample{{ 183 | Funcs: []string{"missing-function-2", "funcName"}, 184 | Counts: []int64{2, 10000000}, 185 | }}, 186 | } 187 | if !reflect.DeepEqual(out, expected) { 188 | t.Errorf("Missing function call stack should contain missing-function-2\n got %+v\n want %+v", expected, out) 189 | } 190 | } 191 | 192 | func TestParseEmptySampleName(t *testing.T) { 193 | contents := `Samples: 194 | samples/count cpu/nanoseconds 195 | 2 3 10000000: 1 2 196 | Locations: 197 | 1: 0xaaaaa funcName :0 s=0 198 | 2: 0xaaaab 199 | ` 200 | _, err := ParseRaw([]byte(contents)) 201 | require.Error(t, err, "Should fail to parse profile with no sample name") 202 | assert.Contains(t, err.Error(), "empty sample names") 203 | } 204 | 205 | func TestParseSampleCountMismatch(t *testing.T) { 206 | contents := `Samples: 207 | samples/count cpu/nanoseconds alloc_objects/count 208 | 2 10000000: 1 209 | Locations: 210 | 1: 0xaaaaa funcName :0 s=0 211 | ` 212 | _, err := ParseRaw([]byte(contents)) 213 | require.Error(t, err, "Expected parseRaw to fail with sample count mismatch") 214 | assert.Contains(t, err.Error(), "different sample count (2) than sample names (3)") 215 | } 216 | 217 | func testParseRawBad(t *testing.T, errorReason, errorSubstr, contents string) { 218 | _, err := ParseRaw([]byte(contents)) 219 | if err == nil { 220 | t.Errorf("Bad %v should cause error while parsing:%s", errorReason, contents) 221 | return 222 | } 223 | 224 | if !strings.Contains(err.Error(), errorSubstr) { 225 | t.Errorf("Bad %v error should contain %q, got %v", errorReason, errorSubstr, err) 226 | } 227 | } 228 | 229 | // Test data for validating that bad input is handled. 230 | const ( 231 | sampleCount = "2" 232 | sampleTime = "10000000" 233 | funcIDLocation = "3" 234 | funcIDSample = "4" 235 | simpleTemplate = ` 236 | Samples: 237 | samples/count cpu/nanoseconds 238 | 2 10000000: 4 5 6 239 | Locations: 240 | 3: 0xaaaaa funcName :0 s=0 241 | ` 242 | ) 243 | 244 | func TestParseRawBadFuncID(t *testing.T) { 245 | { 246 | contents := strings.Replace(simpleTemplate, funcIDSample, "?sample?", -1) 247 | testParseRawBad(t, "funcID in sample", "strconv.ParseInt", contents) 248 | } 249 | 250 | { 251 | contents := strings.Replace(simpleTemplate, funcIDLocation, "?location?", -1) 252 | testParseRawBad(t, "funcID in location", "strconv.ParseInt", contents) 253 | } 254 | } 255 | 256 | func TestParseRawBadSample(t *testing.T) { 257 | { 258 | contents := strings.Replace(simpleTemplate, sampleCount, "??", -1) 259 | testParseRawBad(t, "sample count", "strconv.ParseInt", contents) 260 | } 261 | 262 | { 263 | contents := strings.Replace(simpleTemplate, sampleTime, "??", -1) 264 | testParseRawBad(t, "sample duration", "strconv.ParseInt", contents) 265 | } 266 | } 267 | 268 | func TestParseRawBadMultipleErrors(t *testing.T) { 269 | contents := strings.Replace(simpleTemplate, sampleCount, "?s?", -1) 270 | contents = strings.Replace(contents, sampleTime, "?t?", -1) 271 | testParseRawBad(t, "sample duration", `strconv.ParseInt: parsing "?s?"`, contents) 272 | } 273 | 274 | func TestParseRawBadMalformedSample(t *testing.T) { 275 | contents := ` 276 | Samples: 277 | samples/count cpu/nanoseconds 278 | 1 279 | Locations: 280 | 3: 0xaaaaa funcName :0 s=0 281 | ` 282 | testParseRawBad(t, "malformed sample line", "malformed sample", contents) 283 | } 284 | 285 | func TestParseRawBadMalformedLocation(t *testing.T) { 286 | contents := ` 287 | Samples: 288 | samples/count cpu/nanoseconds 289 | 1 10000: 2 290 | Locations: 291 | 3 292 | ` 293 | testParseRawBad(t, "malformed location line", "malformed location", contents) 294 | } 295 | 296 | func TestParseRawBadNoLocations(t *testing.T) { 297 | contents := ` 298 | Samples: 299 | samples/count cpu/nanoseconds 300 | 1 10000: 2 301 | ` 302 | testParseRawBad(t, "no locations", "parser ended before processing locations", contents) 303 | } 304 | 305 | func TestSplitBySpace(t *testing.T) { 306 | tests := []struct { 307 | s string 308 | expected []string 309 | }{ 310 | {"", []string{""}}, 311 | {"test", []string{"test"}}, 312 | {"1 2", []string{"1", "2"}}, 313 | {"1 2 3 4 ", []string{"1", "2", "3", "4"}}, 314 | } 315 | 316 | for _, tt := range tests { 317 | if got := splitBySpace(tt.s); !reflect.DeepEqual(got, tt.expected) { 318 | t.Errorf("splitBySpace(%v) failed:\n got %#v\n want %#v", tt.s, got, tt.expected) 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /pprof/pprof.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import ( 24 | "bytes" 25 | "fmt" 26 | "net/url" 27 | "os/exec" 28 | "strings" 29 | 30 | "github.com/uber/go-torch/torchlog" 31 | ) 32 | 33 | // Options are parameters for pprof. 34 | type Options struct { 35 | BaseURL string `short:"u" long:"url" default:"http://localhost:8080" description:"Base URL of your Go program"` 36 | URLSuffix string `long:"suffix" default:"/debug/pprof/profile" description:"URL path of pprof profile"` 37 | BinaryFile string `short:"b" long:"binaryinput" description:"File path of previously saved binary profile. (binary profile is anything accepted by https://golang.org/cmd/pprof)"` 38 | BinaryName string `long:"binaryname" description:"File path of the binary that the binaryinput is for, used for pprof inputs"` 39 | TimeSeconds int `short:"t" long:"seconds" default:"30" description:"Number of seconds to profile for"` 40 | ExtraArgs []string `long:"pprofArgs" description:"Extra arguments for pprof"` 41 | TimeAlias *int `hidden:"true" long:"time" description:"Alias for backwards compatibility"` 42 | } 43 | 44 | // GetRaw returns the raw output from pprof for the given options. 45 | func GetRaw(opts Options, remaining []string) ([]byte, error) { 46 | args, err := getArgs(opts, remaining) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return runPProf(args...) 52 | } 53 | 54 | // getArgs gets the arguments to run pprof with for a given set of Options. 55 | func getArgs(opts Options, remaining []string) ([]string, error) { 56 | if opts.TimeAlias != nil { 57 | opts.TimeSeconds = *opts.TimeAlias 58 | } 59 | if len(remaining) > 0 { 60 | var pprofArgs []string 61 | if opts.TimeSeconds > 0 { 62 | pprofArgs = append(pprofArgs, "-seconds", fmt.Sprint(opts.TimeSeconds)) 63 | } 64 | pprofArgs = append(pprofArgs, remaining...) 65 | return pprofArgs, nil 66 | } 67 | 68 | pprofArgs := opts.ExtraArgs 69 | if opts.BinaryFile != "" { 70 | if opts.BinaryName != "" { 71 | pprofArgs = append(pprofArgs, opts.BinaryName) 72 | } 73 | pprofArgs = append(pprofArgs, opts.BinaryFile) 74 | } else { 75 | u, err := url.Parse(opts.BaseURL) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed to parse URL: %v", err) 78 | } 79 | 80 | u.Path = opts.URLSuffix 81 | pprofArgs = append(pprofArgs, "-seconds", fmt.Sprint(opts.TimeSeconds), u.String()) 82 | } 83 | 84 | return pprofArgs, nil 85 | } 86 | 87 | func runPProf(args ...string) ([]byte, error) { 88 | allArgs := []string{"tool", "pprof", "-raw"} 89 | allArgs = append(allArgs, args...) 90 | 91 | var buf bytes.Buffer 92 | torchlog.Printf("Run pprof command: go %v", strings.Join(allArgs, " ")) 93 | cmd := exec.Command("go", allArgs...) 94 | cmd.Stderr = &buf 95 | out, err := cmd.Output() 96 | if err != nil { 97 | return nil, fmt.Errorf("pprof error: %v\nSTDERR:\n%s", err, buf.Bytes()) 98 | } 99 | 100 | // @HACK because 'go tool pprof' doesn't exit on errors with nonzero status codes. 101 | // Ironically, this means that Go's own os/exec package does not detect its errors. 102 | // See issue here https://github.com/golang/go/issues/11510 103 | if len(out) == 0 { 104 | return nil, fmt.Errorf("pprof error:\n%s", buf.Bytes()) 105 | } 106 | 107 | return out, nil 108 | } 109 | -------------------------------------------------------------------------------- /pprof/pprof_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import ( 24 | "bytes" 25 | "net/http" 26 | "net/http/httptest" 27 | "reflect" 28 | "testing" 29 | ) 30 | 31 | func TestGetArgs(t *testing.T) { 32 | four := 4 33 | tests := []struct { 34 | opts Options 35 | remaining []string 36 | expected []string 37 | wantErr bool 38 | }{ 39 | { 40 | opts: Options{ 41 | BaseURL: "http://localhost:1234", 42 | URLSuffix: "/path/to/profile", 43 | TimeSeconds: 5, 44 | }, 45 | expected: []string{"-seconds", "5", "http://localhost:1234/path/to/profile"}, 46 | }, 47 | { 48 | opts: Options{ 49 | BaseURL: "http://localhost:1234/", 50 | URLSuffix: "/path/to/profile", 51 | TimeSeconds: 5, 52 | }, 53 | expected: []string{"-seconds", "5", "http://localhost:1234/path/to/profile"}, 54 | }, 55 | { 56 | opts: Options{ 57 | BaseURL: "http://localhost:1234/", 58 | URLSuffix: "/path/to/profile", 59 | TimeAlias: &four, 60 | }, 61 | expected: []string{"-seconds", "4", "http://localhost:1234/path/to/profile"}, 62 | }, 63 | { 64 | opts: Options{ 65 | BaseURL: "http://localhost:1234/test", 66 | URLSuffix: "/path/to/profile", 67 | TimeSeconds: 5, 68 | }, 69 | expected: []string{"-seconds", "5", "http://localhost:1234/path/to/profile"}, 70 | }, 71 | { 72 | opts: Options{ 73 | BinaryFile: "/path/to/binaryfile", 74 | BaseURL: "http://localhost:1234", 75 | URLSuffix: "/profile", 76 | TimeSeconds: 5, 77 | }, 78 | expected: []string{"/path/to/binaryfile"}, 79 | }, 80 | { 81 | opts: Options{ 82 | BinaryFile: "/path/to/binaryfile", 83 | BinaryName: "/path/to/binaryname", 84 | BaseURL: "http://localhost:1234", 85 | URLSuffix: "/profile", 86 | TimeSeconds: 5, 87 | }, 88 | expected: []string{"/path/to/binaryname", "/path/to/binaryfile"}, 89 | }, 90 | { 91 | opts: Options{ 92 | BinaryFile: "/path/to/binaryfile", 93 | ExtraArgs: []string{"-arg1", "-arg2"}, 94 | }, 95 | expected: []string{"-arg1", "-arg2", "/path/to/binaryfile"}, 96 | }, 97 | { 98 | opts: Options{ 99 | BaseURL: "%-0", // this makes url.Parse fail. 100 | URLSuffix: "/profile", 101 | TimeSeconds: 5, 102 | }, 103 | wantErr: true, 104 | }, 105 | { 106 | remaining: []string{"binary", "input"}, 107 | expected: []string{"binary", "input"}, 108 | }, 109 | { 110 | opts: Options{ 111 | TimeSeconds: 5, 112 | }, 113 | remaining: []string{"binary", "input"}, 114 | expected: []string{"-seconds", "5", "binary", "input"}, 115 | }, 116 | { 117 | opts: Options{ 118 | TimeSeconds: 5, 119 | // All other fields are ignored when remaining is specified. 120 | BinaryFile: "/path/to/binaryfile", 121 | BinaryName: "/path/to/binaryname", 122 | URLSuffix: "/ignored", 123 | }, 124 | remaining: []string{"binary", "input"}, 125 | expected: []string{"-seconds", "5", "binary", "input"}, 126 | }, 127 | } 128 | 129 | for _, tt := range tests { 130 | got, err := getArgs(tt.opts, tt.remaining) 131 | if (err != nil) != tt.wantErr { 132 | t.Errorf("wantErr %v got error: %v", tt.wantErr, err) 133 | continue 134 | } 135 | if err != nil { 136 | continue 137 | } 138 | 139 | if !reflect.DeepEqual(tt.expected, got) { 140 | t.Errorf("got incorrect args for %v:\n got %v\n want %v", tt.opts, got, tt.expected) 141 | } 142 | } 143 | } 144 | 145 | func TestRunPProfUnknownFlag(t *testing.T) { 146 | if _, err := runPProf("-unknownFlag"); err == nil { 147 | t.Fatalf("expected error for unknown flag") 148 | } 149 | } 150 | 151 | func TestRunPProfMissingFile(t *testing.T) { 152 | if _, err := runPProf("unknown-file"); err == nil { 153 | t.Fatalf("expected error for unknown file") 154 | } 155 | } 156 | 157 | func TestRunPProfInvalidURL(t *testing.T) { 158 | server := httptest.NewServer(http.HandlerFunc(http.NotFound)) 159 | defer server.Close() 160 | 161 | if _, err := runPProf(server.URL); err == nil { 162 | t.Fatalf("expected error for unknown file") 163 | } 164 | } 165 | 166 | func TestGetPProfRawBadURL(t *testing.T) { 167 | opts := Options{ 168 | BaseURL: "%-0", 169 | } 170 | if _, err := GetRaw(opts, nil); err == nil { 171 | t.Error("expected bad BaseURL to fail") 172 | } 173 | } 174 | 175 | func TestGetPProfRawSuccess(t *testing.T) { 176 | opts := Options{ 177 | BinaryFile: "testdata/pprof.1.pb.gz", 178 | } 179 | raw, err := GetRaw(opts, nil) 180 | if err != nil { 181 | t.Fatalf("getPProfRaw failed: %v", err) 182 | } 183 | 184 | expectedSubstrings := []string{ 185 | "Duration: 3s", 186 | "Samples", 187 | "Locations", 188 | "main.fib", 189 | } 190 | for _, substr := range expectedSubstrings { 191 | if !bytes.Contains(raw, []byte(substr)) { 192 | t.Errorf("pprof raw output missing expected string: %s\ngot:\n%s", substr, raw) 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /pprof/select_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import "strconv" 24 | 25 | // SelectSample returns the index of the sample to use given the 26 | // sample names. 27 | func SelectSample(args, names []string) int { 28 | selected := 0 29 | 30 | findName := func(needle string) { 31 | for i, name := range names { 32 | if name == needle { 33 | selected = i 34 | } 35 | } 36 | } 37 | 38 | for i, arg := range args { 39 | switch arg { 40 | case "-inuse_space": 41 | findName("inuse_space/bytes") 42 | case "-inuse_objects": 43 | findName("inuse_objects/count") 44 | case "-alloc_space": 45 | findName("alloc_space/bytes") 46 | case "-alloc_objects": 47 | findName("alloc_objects/count") 48 | case "-sample_index": 49 | // Check if there's another argument after this 50 | if i+1 >= len(args) { 51 | continue 52 | } 53 | 54 | if parsed, ok := parseSampleIndex(args[i+1], names); ok { 55 | selected = parsed 56 | } 57 | } 58 | } 59 | 60 | return selected 61 | } 62 | 63 | func parseSampleIndex(s string, names []string) (int, bool) { 64 | parsed, err := strconv.Atoi(s) 65 | if err != nil { 66 | return 0, false 67 | } 68 | 69 | if parsed >= len(names) || parsed < 0 { 70 | return 0, false 71 | } 72 | 73 | return parsed, true 74 | } 75 | -------------------------------------------------------------------------------- /pprof/select_sample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package pprof 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestSelectSample(t *testing.T) { 30 | names := []string{ 31 | "samples/count", 32 | "cpu/nanoseconds", 33 | "alloc_objects/count", 34 | "alloc_space/bytes", 35 | "inuse_objects/count", 36 | "inuse_space/bytes", 37 | } 38 | 39 | tests := []struct { 40 | args []string 41 | want int 42 | }{ 43 | { 44 | args: nil, 45 | want: 0, 46 | }, 47 | { 48 | args: []string{"-sample_index", "5"}, 49 | want: 5, 50 | }, 51 | { 52 | // missing argument for sample_index 53 | args: []string{"-sample_index"}, 54 | want: 0, 55 | }, 56 | { 57 | // negative sample index is out of range. 58 | args: []string{"-sample_index", "-1"}, 59 | want: 0, 60 | }, 61 | { 62 | // sample index is not a number. 63 | args: []string{"-sample_index", "nan"}, 64 | want: 0, 65 | }, 66 | { 67 | // index out of range. 68 | args: []string{"-sample_index", "10"}, 69 | want: 0, 70 | }, 71 | { 72 | args: []string{"-unknown", "options"}, 73 | want: 0, 74 | }, 75 | { 76 | args: []string{"-alloc_objects"}, 77 | want: 2, 78 | }, 79 | { 80 | args: []string{"-alloc_space"}, 81 | want: 3, 82 | }, 83 | { 84 | args: []string{"-inuse_objects"}, 85 | want: 4, 86 | }, 87 | { 88 | args: []string{"-inuse_space"}, 89 | want: 5, 90 | }, 91 | } 92 | 93 | for _, tt := range tests { 94 | got := SelectSample(tt.args, names) 95 | assert.Equal(t, tt.want, got, "Args: %v", tt.args) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /pprof/testdata/pprof-memprofile-1.8.raw.txt: -------------------------------------------------------------------------------- 1 | PeriodType: space bytes 2 | Period: 524288 3 | Time: 2017-01-11 15:19:52.794622795 -0800 PST 4 | Samples: 5 | alloc_objects/count alloc_space/bytes inuse_objects/count inuse_space/bytes 6 | 372 524992 0 0: 1 2 3 4 5 6 7 8 9 10 11 7 | 372 524992 0 0: 1 2 12 13 14 6 7 8 9 10 11 8 | 10923 524312 0 0: 15 16 4 5 6 7 8 9 10 11 9 | 2982024 143137176 0 0: 17 18 19 20 21 22 23 24 25 26 27 28 29 30 6 7 8 9 10 11 10 | 1491 2099969 0 0: 1 2 3 31 6 7 8 9 10 11 11 | 2490387 19923096 0 0: 32 33 34 25 26 27 28 29 30 6 7 8 9 10 11 12 | 3587 3673601 0 0: 35 16 4 5 6 7 8 9 10 11 13 | 2834605 181414720 0 0: 36 9 10 11 14 | 2916441 93326112 0 0: 37 38 39 40 41 20 21 22 23 24 25 26 27 28 29 30 6 7 8 9 10 11 15 | 3178787 305163552 0 0: 42 43 20 21 22 23 24 25 26 27 28 29 30 6 7 8 9 10 11 16 | 3113435 498149603 0 0: 44 45 46 47 43 20 21 22 23 24 25 26 27 28 29 30 6 7 8 9 10 11 17 | 3006223 529095388 0 0: 48 27 28 29 30 6 7 8 9 10 11 18 | 10923 524312 0 0: 15 16 49 50 6 7 51 9 10 11 19 | 512 524800 0 0: 35 16 49 50 6 7 51 9 10 11 20 | 10923 524312 0 0: 15 16 4 5 6 7 51 9 10 11 21 | 372 524992 0 0: 1 2 3 31 6 7 51 9 10 11 22 | 3075 3148801 0 0: 35 16 4 5 6 7 51 9 10 11 23 | 2875129 506022844 0 0: 48 27 28 29 30 6 7 51 9 10 11 24 | 2957492 189279520 0 0: 52 9 10 11 25 | 2805368 448859011 0 0: 53 9 10 11 26 | 3014679 24117432 0 0: 32 33 34 25 26 27 28 29 30 6 7 51 9 10 11 27 | 2883672 92277504 0 0: 54 55 9 10 11 28 | 3029635 436267522 0 0: 44 45 56 57 58 59 60 24 25 26 27 28 29 30 6 7 51 9 10 11 29 | 10923 524312 0 0: 15 16 4 5 6 7 61 9 10 11 30 | 745 1049984 0 0: 1 2 3 4 5 6 7 61 9 10 11 31 | 372 524992 0 0: 1 2 3 31 6 7 61 9 10 11 32 | 8192 524320 0 0: 62 16 31 6 7 61 9 10 11 33 | 4390945 35127564 0 0: 32 33 34 25 26 27 28 29 30 6 7 61 9 10 11 34 | 1537 1574400 0 0: 35 16 4 5 6 7 61 9 10 11 35 | 3956977 253246560 0 0: 63 9 10 11 36 | 3915895 125308656 0 0: 54 64 9 10 11 37 | 3843437 676445046 0 0: 48 27 28 29 30 6 7 61 9 10 11 38 | 4043879 323510376 0 0: 65 9 10 11 39 | 6078835 389045440 0 0: 66 9 10 11 40 | 372 524992 0 0: 1 2 12 13 67 68 9 10 11 41 | 65540 4194560 0 0: 69 9 10 11 42 | 372 524992 0 0: 1 2 3 31 6 7 70 9 10 11 43 | 109675 112307235 0 0: 71 72 73 74 68 9 10 11 44 | 5898600 377510400 0 0: 75 9 10 11 45 | 372 524992 0 0: 1 2 3 49 50 6 7 76 9 10 11 46 | 8192 524320 0 0: 62 16 31 6 7 76 9 10 11 47 | 1025 1049600 0 0: 35 16 49 50 6 7 76 9 10 11 48 | 5865830 375413120 0 0: 77 9 10 11 49 | 372 524992 0 0: 1 2 3 49 50 6 7 78 9 10 11 50 | 372 524992 0 0: 1 2 3 31 6 7 78 9 10 11 51 | 6316417 404250720 0 0: 79 9 10 11 52 | 512 524800 0 0: 35 16 4 5 6 7 80 9 10 11 53 | 1540119 24641912 0 0: 81 82 9 10 11 54 | 6005102 384326560 0 0: 83 9 10 11 55 | 372 524992 0 0: 1 2 3 49 50 6 7 84 9 10 11 56 | 10923 524312 0 0: 15 16 49 50 6 7 84 9 10 11 57 | 372 524992 0 0: 1 2 3 31 6 7 84 9 10 11 58 | 6062450 387996800 0 0: 85 9 10 11 59 | 1025 1049600 0 0: 35 16 49 50 6 7 86 9 10 11 60 | 11100837 710453600 0 0: 87 9 10 11 61 | 8192 524320 0 0: 62 16 31 6 7 88 9 10 11 62 | 10923 524312 0 0: 15 16 4 5 6 7 88 9 10 11 63 | 512 524800 0 0: 35 16 4 5 6 7 88 9 10 11 64 | 11117222 711502240 0 0: 89 9 10 11 65 | 1025 1049600 0 0: 35 16 4 5 6 7 90 9 10 11 66 | 5914985 378559040 0 0: 91 9 10 11 67 | 372 524992 0 0: 1 2 3 49 50 6 7 92 9 10 11 68 | 8192 524320 0 0: 62 16 31 6 7 92 9 10 11 69 | 1537 1574400 0 0: 35 16 4 5 6 7 92 9 10 11 70 | 372 524992 0 0: 1 2 3 31 6 7 92 9 10 11 71 | 1260 524496 1260 524496: 93 94 95 96 97 98 99 100 72 | 11322035 724610240 0 0: 101 9 10 11 73 | 512 524800 0 0: 35 16 49 50 6 7 92 9 10 11 74 | 10923 524312 0 0: 102 103 104 105 106 11 75 | 128 526338 0 0: 107 108 11 76 | 4681 524344 0 0: 109 110 111 112 113 114 115 116 117 118 119 120 121 122 11 77 | 3277 524368 0 0: 109 110 111 112 113 114 115 116 117 118 119 120 121 122 11 78 | 65537 1048592 0 0: 71 72 123 124 110 111 112 113 114 115 116 117 118 119 120 121 122 11 79 | 4096 524352 0 0: 109 110 111 112 113 114 115 116 117 118 119 120 121 122 11 80 | 32769 1048608 0 0: 125 119 120 121 122 11 81 | 16385 1048640 0 0: 71 72 126 111 112 113 114 115 116 117 118 119 120 121 122 11 82 | 98305 1572888 0 0: 54 127 128 129 130 131 132 121 122 11 83 | 14044 1573032 0 0: 133 134 116 117 118 119 120 121 122 11 84 | 7283 2097728 0 0: 135 115 116 117 118 119 120 121 122 11 85 | 65537 1048592 0 0: 37 136 137 130 131 132 121 122 11 86 | 8192 524320 0 0: 138 128 129 130 131 132 121 122 11 87 | 4096 524352 0 0: 71 72 126 111 112 113 114 115 117 118 119 120 139 122 11 88 | 3641 524360 0 0: 71 140 141 130 131 132 139 122 11 89 | 2979 524376 0 0: 71 72 126 111 112 113 114 115 117 118 119 120 139 122 11 90 | 3641 524360 0 0: 142 130 131 132 139 122 11 91 | 32768 524296 0 0: 37 136 137 130 131 132 139 122 11 92 | 3641 1048864 0 0: 135 115 117 118 119 120 139 122 11 93 | 3277 524368 0 0: 71 140 141 130 131 132 139 122 11 94 | 10923 524312 0 0: 71 140 141 130 131 132 139 122 11 95 | 2731 524384 0 0: 133 134 117 118 119 120 139 122 11 96 | 16384 524304 0 0: 143 122 11 97 | Locations 98 | 1: 0x105ff4a M=1 sync.(*Pool).pinSlow /Users/prashant/go.versions/go1.8b2/src/sync/pool.go:205 s=0 99 | 2: 0x105fe2c M=1 sync.(*Pool).pin /Users/prashant/go.versions/go1.8b2/src/sync/pool.go:184 s=0 100 | 3: 0x105fabf M=1 sync.(*Pool).Get /Users/prashant/go.versions/go1.8b2/src/sync/pool.go:121 s=0 101 | 4: 0x1304bb3 M=1 github.com/uber-go/zap.(*jsonEncoder).WriteEntry /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:202 s=0 102 | 5: 0x130856a M=1 github.com/uber-go/zap.Meta.Encode /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:118 s=0 103 | 6: 0x1307acf M=1 github.com/uber-go/zap.(*logger).log /Users/prashant/gocode/src/github.com/uber-go/zap/logger.go:130 s=0 104 | 7: 0x13075d5 M=1 github.com/uber-go/zap.(*logger).Info /Users/prashant/gocode/src/github.com/uber-go/zap/logger.go:97 s=0 105 | 8: 0x134693e M=1 github.com/uber-go/zap_test.BenchmarkObjectField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:153 s=0 106 | 9: 0x1344ecc M=1 github.com/uber-go/zap_test.withBenchedLogger.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:59 s=0 107 | 10: 0x10dfa92 M=1 testing.(*B).RunParallel.func1 /Users/prashant/go.versions/go1.8b2/src/testing/benchmark.go:603 s=0 108 | 11: 0x10597a1 M=1 runtime.goexit /Users/prashant/go.versions/go1.8b2/src/runtime/asm_amd64.s:2185 s=0 109 | 12: 0x105f8cd M=1 sync.(*Pool).Put /Users/prashant/go.versions/go1.8b2/src/sync/pool.go:93 s=0 110 | 13: 0x1303b53 M=1 github.com/uber-go/zap.(*jsonEncoder).Free /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:96 s=0 111 | 14: 0x130859e M=1 github.com/uber-go/zap.Meta.Encode /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:120 s=0 112 | 15: 0x13255b1 M=1 github.com/uber-go/zap.glob..func2 /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:54 s=0 113 | 16: 0x105fb3d M=1 sync.(*Pool).Get /Users/prashant/go.versions/go1.8b2/src/sync/pool.go:144 s=0 114 | 17: 0x1075c91 M=1 time.Time.MarshalJSON /Users/prashant/go.versions/go1.8b2/src/time/time.go:956 s=0 115 | 18: 0x107c79d M=1 time.(*Time).MarshalJSON :35 s=0 116 | 19: 0x11444cf M=1 encoding/json.marshalerEncoder /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:451 s=0 117 | 20: 0x1145ea3 M=1 encoding/json.(*structEncoder).encode /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:637 s=0 118 | 21: 0x1151844 M=1 encoding/json.(*structEncoder).(encoding/json.encode)-fm /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:659 s=0 119 | 22: 0x11473d3 M=1 encoding/json.(*ptrEncoder).encode /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:787 s=0 120 | 23: 0x1151a44 M=1 encoding/json.(*ptrEncoder).(encoding/json.encode)-fm /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:791 s=0 121 | 24: 0x1143a82 M=1 encoding/json.(*encodeState).reflectValue /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:324 s=0 122 | 25: 0x1143768 M=1 encoding/json.(*encodeState).marshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:297 s=0 123 | 26: 0x114331e M=1 encoding/json.Marshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:161 s=0 124 | 27: 0x13047bf M=1 github.com/uber-go/zap.(*jsonEncoder).AddObject /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:173 s=0 125 | 28: 0x1303044 M=1 github.com/uber-go/zap.Field.AddTo /Users/prashant/gocode/src/github.com/uber-go/zap/field.go:218 s=0 126 | 29: 0x13032ee M=1 github.com/uber-go/zap.addFields /Users/prashant/gocode/src/github.com/uber-go/zap/field.go:240 s=0 127 | 30: 0x13082fc M=1 github.com/uber-go/zap.Meta.Encode /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:104 s=0 128 | 31: 0x130831d M=1 github.com/uber-go/zap.Meta.Encode /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:105 s=0 129 | 32: 0x1143b74 M=1 encoding/json.typeEncoder /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:349 s=0 130 | 33: 0x1143afc M=1 encoding/json.valueEncoder /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:344 s=0 131 | 34: 0x1143a3f M=1 encoding/json.(*encodeState).reflectValue /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:323 s=0 132 | 35: 0x1325583 M=1 github.com/uber-go/zap.glob..func2 /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:54 s=0 133 | 36: 0x13468aa M=1 github.com/uber-go/zap_test.BenchmarkObjectField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:152 s=0 134 | 37: 0x1011a7b M=1 reflect.unsafe_New /Users/prashant/go.versions/go1.8b2/src/runtime/malloc.go:806 s=0 135 | 38: 0x109c8ed M=1 reflect.packEface /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:111 s=0 136 | 39: 0x10a0584 M=1 reflect.valueInterface /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:941 s=0 137 | 40: 0x10a0464 M=1 reflect.Value.Interface /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:911 s=0 138 | 41: 0x114448a M=1 encoding/json.marshalerEncoder /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:446 s=0 139 | 42: 0x114acae M=1 encoding/json.compact /Users/prashant/go.versions/go1.8b2/src/encoding/json/indent.go:16 s=0 140 | 43: 0x1144622 M=1 encoding/json.marshalerEncoder /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:454 s=0 141 | 44: 0x10cb7c7 M=1 bytes.makeSlice /Users/prashant/go.versions/go1.8b2/src/bytes/buffer.go:201 s=0 142 | 45: 0x10cb0e7 M=1 bytes.(*Buffer).grow /Users/prashant/go.versions/go1.8b2/src/bytes/buffer.go:109 s=0 143 | 46: 0x10cb2e1 M=1 bytes.(*Buffer).Write /Users/prashant/go.versions/go1.8b2/src/bytes/buffer.go:137 s=0 144 | 47: 0x114b0be M=1 encoding/json.compact /Users/prashant/go.versions/go1.8b2/src/encoding/json/indent.go:57 s=0 145 | 48: 0x11432e1 M=1 encoding/json.Marshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:160 s=0 146 | 49: 0x1304971 M=1 github.com/uber-go/zap.(*jsonEncoder).Clone /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder.go:184 s=0 147 | 50: 0x1308289 M=1 github.com/uber-go/zap.Meta.Encode /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:102 s=0 148 | 51: 0x134675d M=1 github.com/uber-go/zap_test.BenchmarkStringsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:147 s=0 149 | 52: 0x13466c3 M=1 github.com/uber-go/zap_test.BenchmarkStringsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:146 s=0 150 | 53: 0x13465b2 M=1 github.com/uber-go/zap_test.BenchmarkStringsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:146 s=0 151 | 54: 0x100efc8 M=1 runtime.convT2E /Users/prashant/go.versions/go1.8b2/src/runtime/iface.go:198 s=0 152 | 55: 0x134661c M=1 github.com/uber-go/zap_test.BenchmarkStringsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:146 s=0 153 | 56: 0x10cba0c M=1 bytes.(*Buffer).WriteByte /Users/prashant/go.versions/go1.8b2/src/bytes/buffer.go:238 s=0 154 | 57: 0x11471c1 M=1 encoding/json.(*arrayEncoder).encode /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:763 s=0 155 | 58: 0x11519c4 M=1 encoding/json.(*arrayEncoder).(encoding/json.encode)-fm /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:774 s=0 156 | 59: 0x1146e61 M=1 encoding/json.(*sliceEncoder).encode /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:742 s=0 157 | 60: 0x1151944 M=1 encoding/json.(*sliceEncoder).(encoding/json.encode)-fm /Users/prashant/go.versions/go1.8b2/src/encoding/json/encode.go:753 s=0 158 | 61: 0x134651f M=1 github.com/uber-go/zap_test.BenchmarkIntsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:141 s=0 159 | 62: 0x132564d M=1 github.com/uber-go/zap.glob..func3 /Users/prashant/gocode/src/github.com/uber-go/zap/meta.go:33 s=0 160 | 63: 0x1346485 M=1 github.com/uber-go/zap_test.BenchmarkIntsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:140 s=0 161 | 64: 0x13463de M=1 github.com/uber-go/zap_test.BenchmarkIntsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:140 s=0 162 | 65: 0x1346382 M=1 github.com/uber-go/zap_test.BenchmarkIntsField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:140 s=0 163 | 66: 0x1346266 M=1 github.com/uber-go/zap_test.BenchmarkMarshalerField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:134 s=0 164 | 67: 0x1302905 M=1 github.com/uber-go/zap.Stack /Users/prashant/gocode/src/github.com/uber-go/zap/field.go:146 s=0 165 | 68: 0x1346094 M=1 github.com/uber-go/zap_test.BenchmarkStackField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:128 s=0 166 | 69: 0x13460bf M=1 github.com/uber-go/zap_test.BenchmarkStackField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:128 s=0 167 | 70: 0x1346138 M=1 github.com/uber-go/zap_test.BenchmarkStackField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:129 s=0 168 | 71: 0x1045818 M=1 runtime.rawstringtmp /Users/prashant/go.versions/go1.8b2/src/runtime/string.go:107 s=0 169 | 72: 0x104570e M=1 runtime.slicebytetostring /Users/prashant/go.versions/go1.8b2/src/runtime/string.go:89 s=0 170 | 73: 0x1308d2e M=1 github.com/uber-go/zap.takeStacktrace /Users/prashant/gocode/src/github.com/uber-go/zap/stacktrace.go:41 s=0 171 | 74: 0x1302843 M=1 github.com/uber-go/zap.Stack /Users/prashant/gocode/src/github.com/uber-go/zap/field.go:155 s=0 172 | 75: 0x1345ef9 M=1 github.com/uber-go/zap_test.BenchmarkErrorField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:122 s=0 173 | 76: 0x1345dcd M=1 github.com/uber-go/zap_test.BenchmarkDurationField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:116 s=0 174 | 77: 0x1345d33 M=1 github.com/uber-go/zap_test.BenchmarkDurationField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:115 s=0 175 | 78: 0x1345bc3 M=1 github.com/uber-go/zap_test.BenchmarkTimeField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:110 s=0 176 | 79: 0x1345b29 M=1 github.com/uber-go/zap_test.BenchmarkTimeField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:109 s=0 177 | 80: 0x134596e M=1 github.com/uber-go/zap_test.BenchmarkStringerField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:103 s=0 178 | 81: 0x100f081 M=1 runtime.convT2I /Users/prashant/go.versions/go1.8b2/src/runtime/iface.go:219 s=0 179 | 82: 0x1345824 M=1 github.com/uber-go/zap_test.BenchmarkStringerField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:102 s=0 180 | 83: 0x13458d4 M=1 github.com/uber-go/zap_test.BenchmarkStringerField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:102 s=0 181 | 84: 0x134578b M=1 github.com/uber-go/zap_test.BenchmarkStringField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:97 s=0 182 | 85: 0x13456f7 M=1 github.com/uber-go/zap_test.BenchmarkStringField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:96 s=0 183 | 86: 0x13455dc M=1 github.com/uber-go/zap_test.BenchmarkInt64Field.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:91 s=0 184 | 87: 0x1345548 M=1 github.com/uber-go/zap_test.BenchmarkInt64Field.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:90 s=0 185 | 88: 0x134543c M=1 github.com/uber-go/zap_test.BenchmarkIntField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:85 s=0 186 | 89: 0x13453a8 M=1 github.com/uber-go/zap_test.BenchmarkIntField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:84 s=0 187 | 90: 0x1345294 M=1 github.com/uber-go/zap_test.BenchmarkFloat64Field.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:79 s=0 188 | 91: 0x13451fa M=1 github.com/uber-go/zap_test.BenchmarkFloat64Field.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:78 s=0 189 | 92: 0x13450dc M=1 github.com/uber-go/zap_test.BenchmarkBoolField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:73 s=0 190 | 93: 0x1034a71 M=1 runtime.malg /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:2791 s=0 191 | 94: 0x1029be9 M=1 runtime.mpreinit /Users/prashant/go.versions/go1.8b2/src/runtime/os_darwin.go:172 s=0 192 | 95: 0x102f24c M=1 runtime.mcommoninit /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:531 s=0 193 | 96: 0x1030f29 M=1 runtime.allocm /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:1315 s=0 194 | 97: 0x10316a9 M=1 runtime.newm /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:1581 s=0 195 | 98: 0x1030736 M=1 runtime.startTheWorldWithSema /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:1094 s=0 196 | 99: 0x1056c69 M=1 runtime.systemstack /Users/prashant/go.versions/go1.8b2/src/runtime/asm_amd64.s:318 s=0 197 | 100: 0x10307b0 M=1 runtime.mstart /Users/prashant/go.versions/go1.8b2/src/runtime/proc.go:1102 s=0 198 | 101: 0x1345048 M=1 github.com/uber-go/zap_test.BenchmarkBoolField.func1 /Users/prashant/gocode/src/github.com/uber-go/zap/logger_bench_test.go:72 s=0 199 | 102: 0x121a842 M=1 container/list.(*List).PushFront /Users/prashant/go.versions/go1.8b2/src/container/list/list.go:133 s=0 200 | 103: 0x12d70f4 M=1 net/http.(*connLRU).add /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:2145 s=0 201 | 104: 0x12cd89b M=1 net/http.(*Transport).tryPutIdleConn /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:702 s=0 202 | 105: 0x12db7c0 M=1 net/http.(*persistConn).readLoop.func2 /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:1447 s=0 203 | 106: 0x12d3ec7 M=1 net/http.(*persistConn).readLoop /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:1606 s=0 204 | 107: 0x12d0bbb M=1 net/http.(*Transport).dialConn /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:1115 s=0 205 | 108: 0x12db4d8 M=1 net/http.(*Transport).getConn.func4 /Users/prashant/go.versions/go1.8b2/src/net/http/transport.go:908 s=0 206 | 109: 0x1142a47 M=1 encoding/json.unquoteBytes /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:1187 s=0 207 | 110: 0x11411e6 M=1 encoding/json.(*decodeState).literalStore /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:912 s=0 208 | 111: 0x113f1bd M=1 encoding/json.(*decodeState).literal /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:800 s=0 209 | 112: 0x113c03e M=1 encoding/json.(*decodeState).value /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:407 s=0 210 | 113: 0x113b47a M=1 encoding/json.(*decodeState).unmarshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:185 s=0 211 | 114: 0x113ae38 M=1 encoding/json.Unmarshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:104 s=0 212 | 115: 0x13162c3 M=1 github.com/uber-go/zap.roundTrip /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:49 s=0 213 | 116: 0x13164c5 M=1 github.com/uber-go/zap.roundTripASCII /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:68 s=0 214 | 117: 0x1057008 M=1 runtime.call32 /Users/prashant/go.versions/go1.8b2/src/runtime/asm_amd64.s:501 s=0 215 | 118: 0x109de3f M=1 reflect.Value.call /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:437 s=0 216 | 119: 0x109d404 M=1 reflect.Value.Call /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:302 s=0 217 | 120: 0x12ffd8d M=1 testing/quick.Check /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:285 s=0 218 | 121: 0x13165d2 M=1 github.com/uber-go/zap.TestJSONQuick /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:79 s=0 219 | 122: 0x10dd233 M=1 testing.tRunner /Users/prashant/go.versions/go1.8b2/src/testing/testing.go:657 s=0 220 | 123: 0x11427eb M=1 encoding/json.getu4 /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:1143 s=0 221 | 124: 0x1142cbf M=1 encoding/json.unquoteBytes /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:1233 s=0 222 | 125: 0x109de9c M=1 reflect.Value.call /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:453 s=0 223 | 126: 0x1141259 M=1 encoding/json.(*decodeState).literalStore /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:936 s=0 224 | 127: 0x131642e M=1 github.com/uber-go/zap.ASCII.Generate /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:64 s=0 225 | 128: 0x13369fa M=1 github.com/uber-go/zap.(*ASCII).Generate :126 s=0 226 | 129: 0x12ff80c M=1 testing/quick.sizedValue /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:65 s=0 227 | 130: 0x12fe3e8 M=1 testing/quick.Value /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:57 s=0 228 | 131: 0x1300113 M=1 testing/quick.arbitraryValues /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:346 s=0 229 | 132: 0x12ffd31 M=1 testing/quick.Check /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:280 s=0 230 | 133: 0x1316049 M=1 github.com/uber-go/zap.encodeString /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:37 s=0 231 | 134: 0x1316249 M=1 github.com/uber-go/zap.roundTrip /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:46 s=0 232 | 135: 0x113ad21 M=1 encoding/json.Unmarshal /Users/prashant/go.versions/go1.8b2/src/encoding/json/decode.go:97 s=0 233 | 136: 0x10a4866 M=1 reflect.Zero /Users/prashant/go.versions/go1.8b2/src/reflect/value.go:2113 s=0 234 | 137: 0x12fe480 M=1 testing/quick.sizedValue /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:64 s=0 235 | 138: 0x1316373 M=1 github.com/uber-go/zap.ASCII.Generate /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:59 s=0 236 | 139: 0x131656d M=1 github.com/uber-go/zap.TestJSONQuick /Users/prashant/gocode/src/github.com/uber-go/zap/json_encoder_quick_test.go:73 s=0 237 | 140: 0x1045bb6 M=1 runtime.slicerunetostring /Users/prashant/go.versions/go1.8b2/src/runtime/string.go:189 s=0 238 | 141: 0x12fefc9 M=1 testing/quick.sizedValue /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:149 s=0 239 | 142: 0x12fef05 M=1 testing/quick.sizedValue /Users/prashant/go.versions/go1.8b2/src/testing/quick/quick.go:145 s=0 240 | 143: 0x1315076 M=1 github.com/uber-go/zap.TestHooksNilEntry /Users/prashant/gocode/src/github.com/uber-go/zap/hook_test.go:81 s=0 241 | Mappings 242 | 1: 0x0/0x0/0x0 zap.test [FN][FL][LN][IN] 243 | -------------------------------------------------------------------------------- /pprof/testdata/pprof.1.pb.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber-archive/go-torch/86f327cc820ea7d6c5cb350dc99e01a718794f35/pprof/testdata/pprof.1.pb.gz -------------------------------------------------------------------------------- /pprof/testdata/pprof.raw.txt: -------------------------------------------------------------------------------- 1 | PeriodType: cpu nanoseconds 2 | Period: 10000000 3 | Time: 2015-09-10 13:53:30.696637683 -0700 PDT 4 | Duration: 3s 5 | Samples: 6 | samples/count cpu/nanoseconds 7 | 1 10000000: 1 2 2 2 3 3 2 2 3 3 2 2 2 3 3 3 2 3 2 3 2 2 3 2 2 3 4 5 6 8 | 1 10000000: 7 2 3 3 3 3 3 3 3 2 2 3 3 3 2 3 3 3 3 3 3 3 3 2 3 3 3 3 2 3 3 2 4 5 6 9 | 1 10000000: 8 2 2 3 3 3 2 3 3 3 3 3 2 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 4 5 6 10 | 1 10000000: 9 3 2 2 2 3 2 3 3 2 3 2 3 2 3 3 2 3 3 2 3 2 3 3 3 2 4 5 6 11 | 1 10000000: 10 2 3 3 3 3 3 3 3 2 3 2 2 3 3 3 3 2 3 2 3 2 2 3 3 3 2 4 5 6 12 | 1 10000000: 1 3 3 3 3 2 3 3 3 3 2 3 3 2 3 3 2 3 2 2 2 2 3 2 3 4 5 6 13 | 1 10000000: 1 2 2 2 2 3 2 2 3 2 2 3 2 3 2 2 3 3 3 2 3 2 3 3 3 3 4 5 6 14 | 1 10000000: 10 3 2 3 3 2 3 2 3 3 2 3 3 2 2 2 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 2 4 5 6 15 | 1 10000000: 11 3 2 3 3 3 3 2 2 3 3 3 2 2 3 3 3 2 3 2 3 3 2 3 3 3 2 4 5 6 16 | 1 10000000: 12 3 3 2 2 2 2 3 3 2 3 2 2 2 2 2 2 3 3 3 3 2 3 3 2 4 5 6 17 | 1 10000000: 10 3 3 3 2 3 2 2 3 2 3 2 3 3 3 2 3 2 2 2 3 3 3 3 3 3 3 3 2 3 2 4 5 6 18 | 1 10000000: 11 3 2 3 2 2 2 2 3 2 3 2 3 3 3 2 3 2 3 3 3 3 3 3 2 2 4 5 6 19 | 1 10000000: 13 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 3 2 2 3 3 3 3 3 3 3 2 3 3 2 2 2 4 5 6 20 | 1 10000000: 14 3 3 3 3 2 2 3 2 2 3 3 3 3 2 2 3 3 3 3 3 3 3 3 3 3 2 2 3 3 2 3 4 5 6 21 | 1 10000000: 11 3 3 3 3 3 3 2 3 2 3 3 2 2 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 4 5 6 22 | 1 10000000: 15 3 2 3 3 3 3 3 3 3 3 3 2 2 3 3 3 2 3 3 2 2 2 2 3 3 3 3 3 3 2 3 4 5 6 23 | 1 10000000: 13 3 3 3 3 3 3 3 3 3 3 3 2 3 2 3 3 3 3 2 2 3 3 3 3 2 3 3 3 2 3 3 3 3 3 4 5 6 24 | 1 10000000: 14 2 2 3 2 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 2 2 3 2 3 3 3 3 3 3 3 3 3 3 4 5 6 25 | 1 10000000: 14 2 2 3 2 2 3 2 2 3 3 3 2 2 2 3 3 2 3 3 3 3 3 2 4 5 6 26 | 1 10000000: 16 3 2 3 2 2 3 2 3 2 3 3 2 3 2 2 3 3 3 2 2 3 3 2 2 2 2 4 5 6 27 | 1 10000000: 1 2 3 2 3 2 3 3 3 2 3 3 2 2 3 3 2 2 3 2 2 3 3 3 2 2 4 5 6 28 | 1 10000000: 14 3 3 3 2 2 3 2 3 3 3 3 3 3 2 3 3 3 3 3 3 3 2 2 3 3 2 3 3 3 3 3 4 5 6 29 | 1 10000000: 13 3 3 2 2 2 3 3 3 3 3 2 3 2 3 2 2 2 3 3 3 3 2 2 3 3 2 2 4 5 6 30 | 1 10000000: 17 3 2 3 3 3 3 2 3 3 3 3 3 3 3 2 3 3 3 2 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 5 6 31 | 1 10000000: 7 2 3 2 3 3 2 2 3 2 3 3 3 3 3 3 3 3 3 3 3 3 2 3 2 3 2 3 2 3 3 3 3 4 5 6 32 | 1 10000000: 14 2 3 3 3 3 3 2 2 2 2 3 2 3 2 2 3 3 2 3 3 3 2 3 3 3 4 5 6 33 | 1 10000000: 11 3 3 3 2 3 3 3 3 2 3 3 3 2 2 2 3 2 3 2 2 2 3 2 3 2 3 3 4 5 6 34 | 1 10000000: 7 2 3 3 3 3 3 2 3 2 2 3 3 2 3 2 3 3 3 3 2 3 3 2 3 3 3 3 3 2 3 3 4 5 6 35 | 1 10000000: 11 3 3 3 3 2 2 3 3 3 3 3 3 2 2 3 3 3 3 2 3 3 3 3 3 2 3 2 2 3 3 3 3 4 5 6 36 | 1 10000000: 14 2 3 3 2 2 3 2 3 2 3 3 2 2 3 2 3 3 3 2 3 2 3 2 3 2 4 5 6 37 | 1 10000000: 18 3 2 2 2 3 2 3 2 2 2 2 3 2 3 3 2 2 3 2 2 3 3 2 3 3 3 4 5 6 38 | 1 10000000: 13 3 3 3 3 3 3 3 3 2 3 2 3 3 3 3 3 3 2 3 2 3 2 3 3 3 3 2 3 3 3 3 4 5 6 39 | 1 10000000: 10 2 2 3 3 3 3 3 2 2 3 2 3 3 2 3 3 2 3 3 2 2 2 3 3 3 2 3 3 3 2 3 4 5 6 40 | 1 10000000: 14 3 2 3 3 2 3 2 2 3 3 2 2 2 3 3 2 2 3 3 3 3 2 3 3 3 3 3 4 5 6 41 | 1 10000000: 13 2 3 3 3 3 3 3 2 3 3 2 2 3 3 3 2 3 3 3 2 2 2 3 3 3 3 2 3 2 3 3 3 4 5 6 42 | 1 10000000: 11 3 2 2 3 3 3 2 3 3 3 2 2 2 3 3 2 2 3 3 3 2 2 3 2 3 4 5 6 43 | 1 10000000: 19 3 2 3 2 2 3 2 3 3 3 3 3 3 2 2 3 3 3 3 3 2 3 3 3 3 2 3 2 3 2 3 4 5 6 44 | 1 10000000: 20 3 2 3 2 3 2 3 2 3 3 2 2 2 3 2 3 2 3 2 3 3 3 3 3 2 3 3 2 3 4 5 6 45 | 1 10000000: 7 2 3 3 3 3 3 3 3 2 2 3 2 2 3 3 3 2 3 3 2 3 2 3 2 3 2 3 3 3 4 5 6 46 | 1 10000000: 21 3 2 2 3 3 3 2 2 2 2 3 3 2 2 2 2 3 2 3 2 2 4 5 6 47 | 1 10000000: 22 3 3 3 2 3 3 3 3 2 3 3 2 3 2 3 3 3 3 3 3 2 3 3 2 3 2 2 2 2 3 4 5 6 48 | 1 10000000: 17 2 2 2 2 3 3 3 3 3 2 3 2 3 3 2 3 3 2 3 3 3 3 3 3 3 3 3 3 3 2 4 5 6 49 | 1 10000000: 9 2 2 3 2 2 3 2 3 2 2 3 3 3 3 3 3 3 3 2 3 3 3 3 3 2 2 2 2 3 4 5 6 50 | 1 10000000: 7 2 3 2 3 2 3 3 3 2 3 2 3 3 3 2 2 3 3 2 3 2 3 3 2 3 3 2 3 3 4 5 6 51 | 1 10000000: 1 2 2 3 2 2 3 2 3 2 3 3 3 3 3 3 3 3 3 3 3 2 2 3 2 2 3 2 3 3 4 5 6 52 | 12 120000000: 23 24 25 26 27 28 29 30 31 32 33 34 53 | 1 10000000: 13 2 3 2 3 3 2 3 3 3 2 2 2 3 3 3 3 2 3 3 3 3 3 3 2 2 2 3 2 3 4 5 6 54 | 1 10000000: 35 3 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2 2 3 3 2 3 3 3 3 3 3 3 3 3 3 4 5 6 55 | 1 10000000: 13 3 3 3 3 3 2 3 3 3 2 2 3 3 3 3 2 3 2 3 3 3 2 2 2 2 3 3 3 3 3 3 3 3 4 5 6 56 | 1 10000000: 1 3 3 3 3 2 3 3 3 3 3 3 2 3 2 2 3 3 3 3 2 2 2 3 3 3 3 2 3 2 3 4 5 6 57 | 1 10000000: 9 2 3 3 3 3 3 3 2 3 3 3 2 3 2 2 3 3 3 3 2 3 2 3 3 3 3 3 3 2 2 3 4 5 6 58 | 1 10000000: 17 3 2 3 3 3 3 2 2 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 2 3 2 3 2 2 3 3 3 4 5 6 59 | 1 10000000: 16 3 3 2 3 2 3 3 3 3 3 3 3 3 2 3 3 3 3 2 3 3 2 3 3 3 3 3 3 3 2 3 2 3 4 5 6 60 | 1 10000000: 14 2 3 3 2 3 3 3 3 3 3 3 3 2 3 3 2 2 2 3 3 3 2 3 3 2 3 3 3 3 3 3 3 4 5 6 61 | 1 10000000: 36 3 3 3 3 3 3 3 2 3 3 3 3 2 2 3 3 2 3 3 3 2 3 3 3 2 3 3 2 3 2 3 3 4 5 6 62 | 1 10000000: 37 3 3 3 3 3 2 3 3 3 3 3 3 2 3 2 2 2 3 2 3 2 3 3 3 3 3 2 3 2 3 2 4 5 6 63 | 1 10000000: 8 2 3 3 3 3 3 2 3 3 3 2 3 3 3 3 3 3 3 2 2 2 3 2 3 2 3 3 2 3 2 3 4 5 6 64 | 1 10000000: 13 2 3 3 3 3 3 3 3 3 3 3 3 2 3 2 2 3 3 3 3 2 3 3 3 2 3 2 3 2 3 3 3 2 4 5 6 65 | 1 10000000: 36 3 3 3 2 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 2 2 3 3 2 3 3 3 3 3 3 2 3 3 3 3 4 5 6 66 | 1 10000000: 9 3 3 3 3 2 3 2 3 3 2 2 3 3 2 3 2 2 3 2 3 3 3 3 3 2 3 2 2 2 4 5 6 67 | 1 10000000: 9 2 2 2 3 2 2 2 2 3 3 3 3 2 3 2 3 3 3 3 3 3 2 3 3 2 2 2 3 3 4 5 6 68 | 1 10000000: 9 2 2 3 3 3 3 3 3 2 2 2 2 3 3 3 3 2 2 2 3 3 3 3 3 2 3 4 5 6 69 | 1 10000000: 14 2 2 3 3 3 3 3 3 3 3 2 3 2 3 2 3 3 3 3 3 3 3 3 2 2 2 3 2 2 4 5 6 70 | 1 10000000: 15 3 2 2 3 3 3 2 3 2 3 3 2 3 3 3 2 2 3 3 3 3 3 2 2 3 2 3 3 2 4 5 6 71 | 1 10000000: 21 2 3 2 3 3 3 2 3 2 3 3 3 2 2 3 2 3 3 3 3 2 3 2 4 5 6 72 | 1 10000000: 21 3 3 3 2 2 3 3 3 2 3 3 3 2 2 3 3 2 3 3 3 3 2 2 2 4 5 6 73 | 1 10000000: 14 2 2 3 2 2 2 2 2 3 3 3 3 3 2 2 2 2 3 3 3 3 2 3 3 4 5 6 74 | 1 10000000: 11 2 2 3 3 3 3 3 3 2 3 3 3 3 3 2 3 3 3 3 3 2 3 2 3 3 4 5 6 75 | 1 10000000: 9 2 2 3 3 2 3 3 3 2 2 3 2 3 2 2 2 2 3 2 3 3 3 3 3 2 3 3 3 2 4 5 6 76 | 1 10000000: 14 2 3 2 3 3 3 3 2 2 2 3 3 2 3 3 3 3 3 3 3 2 2 3 2 3 4 5 6 77 | 1 10000000: 11 2 2 3 2 3 2 3 3 3 2 3 2 3 3 3 3 2 3 2 3 3 2 2 3 3 2 4 5 6 78 | 1 10000000: 14 3 2 2 3 3 3 3 3 3 2 3 3 3 3 3 3 3 2 3 3 2 3 2 3 3 2 2 4 5 6 79 | 1 10000000: 9 3 2 2 3 3 3 3 2 2 3 2 2 2 3 2 3 2 2 3 3 3 3 3 3 2 2 2 4 5 6 80 | 1 10000000: 13 3 3 3 3 3 3 3 3 2 3 2 2 2 3 2 2 3 3 2 2 2 3 3 2 2 3 2 4 5 6 81 | 1 10000000: 9 2 2 3 3 3 3 2 3 2 2 3 3 3 3 2 3 2 3 3 3 2 3 3 3 3 2 2 4 5 6 82 | 1 10000000: 11 3 2 2 3 3 2 3 2 3 3 2 3 3 2 2 2 3 2 4 5 6 83 | 1 10000000: 13 3 3 3 3 3 3 3 2 3 3 3 2 2 2 3 3 3 2 3 2 3 3 2 2 2 2 3 3 2 4 5 6 84 | 1 10000000: 13 2 3 3 3 2 2 3 2 3 3 3 3 2 3 2 3 2 3 2 3 3 2 3 2 2 3 2 3 2 4 5 6 85 | 1 10000000: 9 2 2 2 3 3 3 2 3 3 3 2 2 3 3 2 3 3 3 3 3 3 2 2 3 3 3 3 3 3 3 3 2 3 4 5 6 86 | 1 10000000: 14 3 3 3 2 3 3 3 2 2 3 3 3 3 3 3 2 2 3 3 2 2 3 3 3 2 2 3 3 3 3 3 4 5 6 87 | 1 10000000: 13 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 2 3 3 3 3 3 3 3 2 2 3 3 3 3 3 2 3 3 3 4 5 6 88 | 1 10000000: 37 2 3 3 3 2 3 3 2 2 3 3 3 3 3 2 3 2 3 2 3 3 2 3 3 3 2 3 3 3 2 3 3 4 5 6 89 | 1 10000000: 13 2 3 2 3 3 3 2 3 3 3 3 2 3 3 2 3 3 3 3 3 2 2 2 3 2 3 3 3 2 2 3 3 4 5 6 90 | 1 10000000: 9 2 3 3 3 3 3 3 2 3 3 3 2 2 2 2 3 2 2 3 3 3 3 3 3 3 2 2 3 3 3 3 3 4 5 6 91 | 1 10000000: 38 3 3 3 3 3 2 3 3 3 2 2 3 3 2 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 2 3 3 4 5 6 92 | 1 10000000: 19 3 2 3 2 3 3 3 3 3 3 2 3 2 2 3 2 2 3 2 3 3 3 2 2 3 3 3 3 2 3 4 5 6 93 | 1 10000000: 37 3 3 3 2 3 3 2 3 2 2 3 2 3 2 3 3 2 2 3 3 3 3 2 2 2 2 3 3 3 3 4 5 6 94 | 1 10000000: 9 3 2 3 3 3 3 3 2 3 2 3 3 3 3 2 3 3 2 3 3 2 3 3 3 3 3 3 2 2 3 3 2 3 4 5 6 95 | 1 10000000: 13 3 3 3 2 2 3 3 3 3 3 2 3 3 3 3 3 2 2 2 3 3 3 2 3 3 3 3 2 2 3 2 4 5 6 96 | 1 10000000: 39 3 2 2 3 2 2 3 3 3 3 2 3 3 3 3 3 3 2 2 3 3 2 3 2 2 3 3 2 3 3 4 5 6 97 | 1 10000000: 13 3 3 3 3 3 2 3 3 2 3 2 2 2 3 2 3 3 3 3 2 3 3 2 3 3 2 3 3 3 3 2 3 4 5 6 98 | 1 10000000: 9 2 2 3 2 3 3 3 3 3 3 3 3 3 3 2 3 2 3 3 2 3 2 3 2 3 3 2 2 3 2 3 3 4 5 6 99 | 1 10000000: 8 2 3 2 3 3 2 3 2 3 3 2 3 3 3 2 3 2 2 2 3 3 3 3 3 3 3 3 2 2 3 3 4 5 6 100 | 1 10000000: 10 2 2 2 3 3 2 3 3 3 3 2 3 3 2 3 3 2 3 2 3 2 3 2 2 3 3 3 3 3 3 3 4 5 6 101 | 1 10000000: 13 2 3 3 3 3 3 3 2 3 3 3 2 2 3 3 3 3 2 2 2 3 2 3 3 3 3 3 2 2 3 3 3 4 5 6 102 | 1 10000000: 9 2 2 3 3 2 3 3 3 3 3 2 3 3 3 3 2 2 3 3 2 3 2 3 2 3 2 3 3 2 4 5 6 103 | 1 10000000: 11 3 3 3 3 3 3 2 2 2 2 2 3 3 3 3 2 2 2 3 3 2 2 3 3 4 5 6 104 | 1 10000000: 11 2 3 2 2 3 3 3 3 3 2 3 2 3 3 2 2 2 2 3 3 2 3 3 3 3 2 4 5 6 105 | 1 10000000: 1 2 3 3 3 3 3 3 2 2 2 2 3 3 2 2 3 2 2 3 2 3 2 2 2 2 4 5 6 106 | 1 10000000: 11 3 2 2 3 3 3 2 2 2 2 3 2 3 2 2 3 3 3 2 3 3 2 3 2 2 4 5 6 107 | 1 10000000: 14 2 3 2 2 2 3 2 2 3 3 3 3 2 3 3 3 2 2 3 3 3 2 3 4 5 6 108 | 1 10000000: 10 2 2 2 2 2 2 3 2 2 3 3 3 3 3 3 3 3 2 3 3 3 3 2 2 3 2 4 5 6 109 | 1 10000000: 11 3 2 2 2 2 3 2 3 2 3 3 2 3 3 3 3 2 3 2 2 3 2 3 2 3 3 4 5 6 110 | 1 10000000: 9 2 2 2 3 3 2 3 3 3 2 2 2 3 3 3 3 3 2 3 3 3 3 3 3 3 2 3 4 5 6 111 | 1 10000000: 11 3 2 2 3 2 2 3 3 3 3 3 2 2 3 3 2 3 3 3 2 3 3 3 2 3 3 3 4 5 6 112 | 1 10000000: 1 2 2 3 2 3 3 3 3 3 3 2 3 3 2 3 2 3 3 3 3 2 2 2 3 2 3 4 5 6 113 | 1 10000000: 21 2 3 2 3 2 3 3 3 3 3 3 3 2 3 2 2 2 2 3 2 3 3 3 2 2 4 5 6 114 | 1 10000000: 7 3 3 3 3 3 3 2 3 3 2 3 2 2 2 3 3 2 3 2 2 3 2 2 2 4 5 6 115 | 1 10000000: 14 3 2 2 3 2 3 3 3 3 2 3 2 2 2 3 2 3 3 2 3 3 2 2 3 3 4 5 6 116 | 1 10000000: 21 3 3 3 2 2 3 3 3 3 3 2 2 2 2 2 3 3 2 2 2 3 3 3 2 3 2 3 4 5 6 117 | 1 10000000: 15 3 2 2 3 2 3 3 2 2 3 3 3 3 3 3 3 2 2 3 3 2 2 2 2 3 3 3 4 5 6 118 | 1 10000000: 7 3 2 2 3 3 3 3 2 3 3 3 3 3 3 2 3 3 3 3 3 3 2 3 3 3 2 3 4 5 6 119 | 1 10000000: 21 2 2 2 2 2 3 3 2 2 3 2 2 3 2 3 3 3 2 2 3 3 3 2 3 3 2 3 4 5 6 120 | 1 10000000: 8 2 3 3 2 3 2 2 3 2 3 3 2 2 3 3 2 2 2 3 3 2 3 2 2 2 3 3 4 5 6 121 | 1 10000000: 11 3 2 3 3 2 2 2 2 2 3 3 3 3 2 2 3 3 2 2 3 3 2 3 3 3 3 4 5 6 122 | 1 10000000: 13 3 3 2 2 3 2 3 2 2 2 3 2 3 3 2 3 3 2 2 3 3 3 2 2 3 3 3 4 5 6 123 | 1 10000000: 21 2 3 2 2 2 3 2 3 3 3 2 2 2 2 3 2 3 3 2 2 3 3 2 3 2 3 3 4 5 6 124 | 1 10000000: 22 3 2 2 3 2 2 3 3 2 3 3 3 3 2 3 3 3 2 2 3 3 3 3 3 3 2 3 2 3 4 5 6 125 | 1 10000000: 38 3 3 2 3 2 3 2 3 3 2 2 2 2 3 3 3 2 3 2 2 3 3 3 2 2 3 3 3 3 4 5 6 126 | 1 10000000: 38 3 2 3 2 3 3 3 3 2 3 3 3 3 2 3 3 2 3 2 2 3 2 2 2 2 2 3 3 3 4 5 6 127 | 1 10000000: 37 3 3 3 3 2 2 2 3 3 2 3 3 3 2 3 2 2 3 3 2 3 2 3 3 2 2 2 3 3 4 5 6 128 | 1 10000000: 10 3 3 3 3 3 3 3 3 3 2 3 2 3 3 3 3 3 2 3 2 3 3 2 2 2 3 3 3 3 3 2 2 3 4 5 6 129 | 1 10000000: 10 3 3 3 3 2 3 3 3 2 2 3 2 3 3 2 3 3 3 3 3 2 3 3 2 3 2 3 3 3 3 3 3 3 3 4 5 6 130 | 1 10000000: 13 2 3 3 3 3 3 3 2 2 3 3 2 3 3 3 3 3 2 3 2 2 3 3 3 2 2 3 2 2 3 3 4 5 6 131 | 1 10000000: 35 2 3 3 2 2 3 3 3 3 2 3 3 3 3 2 3 3 3 3 3 3 3 2 3 2 2 3 2 3 2 3 4 5 6 132 | 1 10000000: 10 2 3 2 3 3 3 3 3 3 3 3 3 2 2 3 3 3 2 3 3 3 3 2 3 2 3 2 2 3 3 2 3 4 5 6 133 | 1 10000000: 15 2 2 2 3 3 3 3 2 2 2 3 3 2 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 4 5 6 134 | 1 10000000: 16 2 3 3 3 3 2 3 2 3 3 3 2 3 3 2 3 3 2 2 3 2 2 3 3 2 3 3 3 3 3 4 5 6 135 | 1 10000000: 20 3 3 3 3 3 2 3 2 2 2 2 3 3 3 3 2 3 2 3 3 2 3 3 2 3 3 3 3 2 3 3 4 5 6 136 | 1 10000000: 14 3 3 2 3 3 3 2 3 3 2 3 2 3 3 3 3 3 3 2 3 3 3 3 3 3 2 3 2 2 3 2 3 4 5 6 137 | 1 10000000: 16 3 3 3 3 2 3 2 3 2 2 2 3 2 2 3 3 3 2 3 2 3 3 2 3 2 3 3 3 3 3 4 5 6 138 | 1 10000000: 12 3 3 3 2 3 2 2 3 2 3 2 3 3 3 3 3 3 3 3 3 2 3 3 3 3 2 2 2 3 3 3 3 4 5 6 139 | 1 10000000: 10 3 3 3 2 3 3 3 3 2 3 3 2 3 3 3 3 3 2 3 3 2 3 2 2 2 2 3 3 2 3 4 5 6 140 | 1 10000000: 21 3 3 2 2 3 3 2 2 3 3 3 3 3 3 3 3 3 3 2 3 2 3 2 2 3 2 3 3 3 3 4 5 6 141 | 1 10000000: 9 2 2 2 3 2 3 3 3 3 2 2 2 3 3 3 2 3 3 2 3 3 2 2 3 3 3 3 3 3 2 3 4 5 6 142 | 1 10000000: 14 3 3 3 3 2 3 3 3 3 2 3 2 2 3 2 3 3 3 2 3 3 3 3 3 3 2 3 3 2 3 4 5 6 143 | 1 10000000: 1 2 2 2 2 2 3 2 3 2 3 3 2 3 3 2 3 3 3 3 3 2 3 3 2 2 3 4 5 6 144 | 1 10000000: 1 2 2 2 2 3 3 2 3 3 2 2 3 3 3 3 3 2 3 2 2 3 2 3 2 3 4 5 6 145 | 1 10000000: 12 3 2 2 2 3 3 3 2 2 3 3 2 3 2 3 2 3 2 3 3 3 3 3 2 3 4 5 6 146 | 1 10000000: 11 3 3 2 3 2 3 3 3 2 3 3 2 2 3 2 3 2 2 2 2 3 3 3 3 2 3 4 5 6 147 | 1 10000000: 7 3 2 2 3 2 3 3 3 3 3 2 2 2 3 3 2 3 3 3 3 3 2 2 3 3 2 4 5 6 148 | 1 10000000: 14 2 2 2 2 2 3 3 3 2 2 3 3 2 2 3 3 2 3 3 2 2 2 3 4 5 6 149 | 1 10000000: 9 3 3 3 3 2 2 3 2 2 2 2 2 3 2 2 2 3 2 3 2 3 3 2 3 2 4 5 6 150 | 1 10000000: 14 2 3 3 2 2 3 2 3 2 3 3 3 3 2 3 3 2 3 3 2 2 3 3 2 2 2 4 5 6 151 | 1 10000000: 14 3 3 2 2 3 2 3 2 2 2 3 2 3 3 3 3 2 3 3 3 3 2 3 2 3 2 2 4 5 6 152 | 1 10000000: 12 2 3 3 3 2 2 3 3 3 3 3 3 3 2 3 2 3 3 3 3 2 3 3 2 3 3 3 4 5 6 153 | 1 10000000: 12 2 3 3 2 3 2 3 2 3 3 2 3 3 3 3 2 2 3 3 3 3 2 3 2 2 3 3 4 5 6 154 | 1 10000000: 11 3 2 3 2 2 2 3 2 2 3 2 3 2 2 2 3 3 2 3 2 3 2 3 3 3 4 5 6 155 | 1 10000000: 21 2 2 2 3 2 2 3 3 3 2 2 3 3 2 3 3 3 3 3 2 2 3 3 2 3 3 4 5 6 156 | 1 10000000: 14 2 2 2 3 3 2 3 3 2 3 3 2 3 3 3 2 3 3 2 2 3 2 2 3 4 5 6 157 | 1 10000000: 40 2 2 3 2 2 2 2 2 3 2 2 2 3 2 3 3 3 3 3 3 2 2 2 3 2 4 5 6 158 | 1 10000000: 9 3 2 2 2 3 2 3 3 3 3 3 3 2 2 2 3 3 3 3 3 2 3 3 3 3 2 3 4 5 6 159 | 1 10000000: 11 3 2 2 3 2 2 3 2 3 3 2 3 3 3 2 3 2 3 3 2 2 2 3 3 3 2 3 4 5 6 160 | 1 10000000: 11 2 3 2 3 3 2 3 3 3 3 3 2 3 3 2 3 3 2 2 3 3 2 3 2 3 2 3 4 5 6 161 | 1 10000000: 14 3 3 2 2 3 3 2 3 3 3 3 3 3 2 2 2 3 3 3 2 3 2 3 3 3 2 3 4 5 6 162 | 1 10000000: 9 2 3 3 2 3 2 3 3 2 3 3 2 2 2 2 2 2 2 3 3 3 3 2 3 3 3 3 4 5 6 163 | 1 10000000: 10 3 3 2 3 2 2 2 3 2 2 2 2 3 2 2 3 2 3 2 3 2 2 2 3 3 3 4 5 6 164 | 1 10000000: 16 3 2 3 2 3 2 2 2 2 3 2 2 3 2 2 2 2 2 2 2 2 2 3 3 4 5 6 165 | 1 10000000: 10 2 2 2 3 3 3 2 3 3 3 2 3 2 2 2 3 3 2 2 3 2 2 2 3 3 3 3 4 5 6 166 | 1 10000000: 12 3 3 3 2 3 3 2 2 3 2 2 2 2 2 2 3 3 3 2 3 2 2 2 3 2 3 3 4 5 6 167 | 1 10000000: 38 3 3 2 3 2 2 3 2 2 3 2 2 3 3 3 3 3 3 2 3 3 2 3 2 3 3 3 2 3 4 5 6 168 | 1 10000000: 38 3 3 3 3 3 3 2 2 2 2 2 2 2 2 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 5 6 169 | 1 10000000: 9 2 2 3 2 3 3 3 3 3 3 2 3 3 2 3 3 2 3 2 2 3 3 3 2 2 2 2 2 4 5 6 170 | 1 10000000: 9 2 2 3 3 3 2 3 3 2 2 2 2 3 3 3 2 3 2 2 3 3 2 3 3 2 3 3 3 4 5 6 171 | 1 10000000: 10 2 3 3 3 2 3 3 3 2 3 3 2 2 2 3 2 3 2 3 3 3 3 3 2 2 3 2 3 4 5 6 172 | 1 10000000: 21 3 3 3 2 3 2 3 3 3 2 2 3 3 3 3 3 3 2 3 3 3 3 3 3 2 3 2 2 4 5 6 173 | 1 10000000: 21 3 3 3 3 3 2 3 3 3 3 3 2 2 3 3 3 2 2 2 3 2 3 3 3 2 3 3 2 4 5 6 174 | 1 10000000: 21 2 3 2 2 3 3 3 3 3 3 3 3 2 2 3 3 3 2 3 3 3 2 3 3 2 3 3 2 4 5 6 175 | 1 10000000: 21 3 3 2 2 3 3 3 2 2 3 2 3 3 2 3 3 2 3 3 2 3 2 3 3 3 3 3 3 4 5 6 176 | 1 10000000: 21 3 2 2 2 3 3 3 3 3 3 2 2 3 2 3 2 2 3 3 3 2 2 3 3 3 3 3 3 4 5 6 177 | 1 10000000: 11 3 3 2 3 3 3 3 2 2 3 3 2 3 3 3 2 3 3 3 3 2 3 3 2 2 3 3 3 4 5 6 178 | 1 10000000: 13 3 3 2 3 3 3 3 3 2 3 3 3 3 3 3 3 3 2 2 3 3 3 2 3 2 2 2 3 3 3 3 3 4 5 6 179 | 1 10000000: 16 2 2 2 3 3 3 3 3 2 3 3 3 3 3 3 3 2 2 2 3 2 2 2 3 3 3 2 3 3 3 4 5 6 180 | 1 10000000: 36 3 2 3 2 2 2 3 3 3 3 3 2 3 2 3 3 2 3 2 2 2 3 3 3 3 3 3 3 3 3 4 5 6 181 | 1 10000000: 39 2 2 2 2 3 3 3 3 3 3 3 2 3 3 3 2 3 3 3 2 3 3 2 3 3 3 3 2 2 4 5 6 182 | 1 10000000: 11 3 2 3 2 3 3 3 2 2 3 3 3 2 3 3 2 3 2 3 3 3 3 2 3 3 3 2 3 3 3 4 5 6 183 | 1 10000000: 39 2 3 3 3 2 3 2 3 3 3 3 2 3 2 3 2 2 2 2 3 3 3 3 3 3 3 2 2 3 4 5 6 184 | 1 10000000: 38 3 3 2 2 2 3 3 2 3 3 2 3 2 3 2 3 3 2 2 3 2 3 3 2 2 3 4 5 6 185 | 1 10000000: 40 2 2 3 3 3 3 2 3 2 3 2 3 3 2 3 3 3 2 3 2 3 3 3 2 2 2 2 3 3 4 5 6 186 | 1 10000000: 14 3 2 3 2 2 3 3 2 2 2 3 3 2 2 3 3 3 3 3 3 2 3 2 3 3 2 4 5 6 187 | 1 10000000: 21 2 2 2 3 3 2 3 2 2 2 3 3 3 2 3 3 3 2 3 2 3 2 2 4 5 6 188 | 1 10000000: 21 2 3 2 2 2 3 2 3 2 2 2 3 3 2 2 2 2 3 2 2 2 3 2 3 4 5 6 189 | 1 10000000: 11 3 3 3 3 3 3 3 2 3 3 3 2 3 2 2 3 2 2 2 2 3 3 4 5 6 190 | 1 10000000: 11 3 2 3 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 2 3 3 2 4 5 6 191 | 1 10000000: 10 2 3 2 2 3 2 3 3 3 2 3 3 3 2 3 3 3 3 3 2 3 3 2 2 3 3 3 3 4 5 6 192 | 1 10000000: 8 3 3 2 3 3 2 3 2 3 2 3 3 3 3 3 2 2 3 2 3 2 3 2 3 2 2 3 2 4 5 6 193 | 1 10000000: 11 3 3 3 2 2 3 2 3 2 3 2 3 3 2 3 3 3 3 3 3 3 2 3 3 3 2 2 3 4 5 6 194 | 1 10000000: 11 3 3 3 2 2 2 3 3 3 3 3 3 3 3 2 3 2 2 3 3 3 3 3 3 3 3 3 2 4 5 6 195 | 1 10000000: 20 3 2 3 3 3 2 2 3 3 3 2 3 3 3 3 2 2 2 3 3 2 3 3 2 3 2 3 2 4 5 6 196 | 1 10000000: 13 3 2 2 3 3 2 3 2 2 3 2 3 2 2 2 3 3 3 2 3 2 3 3 2 3 3 3 3 4 5 6 197 | 1 10000000: 13 3 3 2 3 3 2 2 3 3 3 2 3 2 3 3 2 3 3 3 3 3 3 3 2 2 3 3 3 4 5 6 198 | 1 10000000: 13 3 3 3 3 2 3 3 2 3 2 3 2 2 2 2 2 2 3 2 3 2 2 3 3 3 2 3 3 4 5 6 199 | 1 10000000: 21 3 2 3 3 3 3 2 3 3 3 2 3 3 2 3 3 3 2 3 2 3 2 3 3 3 2 3 2 4 5 6 200 | 1 10000000: 21 2 2 2 3 2 3 3 3 3 3 3 2 3 2 2 3 2 3 2 3 3 3 3 3 2 3 3 3 4 5 6 201 | 1 10000000: 1 2 3 3 3 2 3 2 3 3 2 3 3 2 2 3 2 3 3 3 2 3 2 3 2 2 3 3 3 4 5 6 202 | 1 10000000: 35 2 3 3 3 3 2 3 2 2 2 2 3 3 2 2 3 3 2 2 3 3 3 3 3 3 3 2 2 4 5 6 203 | 1 10000000: 21 3 3 3 3 3 3 3 2 3 3 3 2 3 2 2 3 3 3 2 2 3 3 3 3 3 2 2 3 4 5 6 204 | 1 10000000: 14 3 3 3 3 2 3 2 3 3 2 3 3 3 3 3 2 2 3 2 2 2 3 3 3 3 3 3 2 4 5 6 205 | 1 10000000: 7 3 3 2 3 3 2 3 2 3 3 3 2 3 3 3 3 2 3 3 3 3 3 2 2 3 3 3 3 4 5 6 206 | 1 10000000: 12 2 3 2 2 3 3 3 3 3 2 3 2 2 3 2 3 3 2 3 3 2 3 3 2 3 2 2 3 4 5 6 207 | 1 10000000: 12 2 3 3 3 3 3 3 3 2 3 3 3 2 2 2 2 3 3 2 3 2 2 3 3 3 3 3 2 4 5 6 208 | 1 10000000: 40 2 3 3 3 3 2 3 3 2 2 3 2 2 3 3 2 3 2 2 3 3 2 2 3 3 3 3 2 4 5 6 209 | 1 10000000: 21 3 3 3 2 3 3 3 3 3 3 3 3 2 3 3 2 2 3 2 3 3 3 2 2 2 3 3 3 4 5 6 210 | 1 10000000: 14 3 3 3 2 2 3 2 3 3 3 2 2 3 3 3 2 3 2 2 2 3 3 3 2 3 3 3 3 4 5 6 211 | 1 10000000: 11 2 2 3 3 3 3 3 3 2 3 3 2 2 3 3 3 3 2 3 3 3 2 3 3 2 3 3 2 3 4 5 6 212 | 1 10000000: 20 3 2 3 2 3 3 3 3 3 3 3 2 3 3 2 2 3 2 2 2 4 5 6 213 | 1 10000000: 9 2 3 3 3 3 3 2 3 3 3 3 2 2 3 3 3 2 3 2 3 2 2 2 2 3 3 3 3 3 3 4 5 6 214 | 1 10000000: 41 2 2 2 3 3 2 2 3 2 3 3 3 3 3 3 3 2 3 2 3 2 3 3 3 2 2 3 3 3 2 4 5 6 215 | 1 10000000: 21 3 3 3 2 3 3 3 3 2 3 3 3 3 3 2 3 2 2 3 3 2 3 3 3 3 3 3 2 3 3 4 5 6 216 | 1 10000000: 36 3 2 3 3 2 3 2 2 3 3 3 3 2 3 3 3 2 3 3 3 2 3 3 2 2 3 3 3 2 3 4 5 6 217 | 1 10000000: 10 3 2 2 3 2 2 2 3 2 3 2 3 3 3 3 3 3 2 2 3 3 3 3 3 2 2 2 3 3 4 5 6 218 | 1 10000000: 13 2 3 3 2 3 3 3 2 3 2 3 2 2 2 3 3 2 2 3 3 3 3 2 3 3 3 2 3 2 3 4 5 6 219 | 1 10000000: 8 2 3 3 2 3 3 2 3 3 2 3 2 2 2 2 3 3 2 2 3 3 3 3 2 3 2 2 3 4 5 6 220 | 1 10000000: 10 2 3 3 3 3 3 2 2 2 3 3 2 3 2 3 3 2 2 3 3 3 3 2 3 3 3 2 2 4 5 6 221 | 1 10000000: 13 3 2 2 3 3 2 2 3 2 3 2 3 3 2 2 2 3 2 3 3 3 3 3 3 2 2 2 3 4 5 6 222 | 1 10000000: 11 3 3 2 2 2 3 2 3 3 2 3 2 3 3 2 3 3 2 3 3 3 2 3 3 3 3 3 3 4 5 6 223 | 1 10000000: 1 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 2 2 2 3 2 3 2 2 3 3 2 3 4 5 6 224 | 1 10000000: 38 3 3 2 3 2 2 2 3 2 3 3 2 3 3 2 2 3 3 3 3 2 2 3 3 2 3 3 3 4 5 6 225 | 1 10000000: 11 3 3 3 3 3 2 3 3 3 3 3 2 3 2 2 3 3 3 3 3 2 3 3 2 3 3 3 3 2 4 5 6 226 | 1 10000000: 13 3 3 2 2 3 3 3 2 3 2 3 2 2 2 2 2 3 2 3 2 2 3 2 3 3 2 3 3 4 5 6 227 | 1 10000000: 11 3 3 3 2 2 2 2 3 3 3 3 2 3 2 3 2 3 2 3 2 3 3 2 3 3 2 2 3 3 4 5 6 228 | 1 10000000: 21 3 3 2 3 3 3 3 3 2 2 3 3 3 3 2 3 3 3 2 3 2 3 2 3 3 2 3 2 4 5 6 229 | 1 10000000: 7 3 2 3 2 3 3 3 2 3 3 3 3 2 3 2 3 2 2 3 3 3 3 2 3 3 2 2 3 4 5 6 230 | 1 10000000: 11 3 2 3 2 2 3 2 2 2 3 3 3 3 2 2 3 3 3 2 3 3 2 2 3 3 3 3 3 3 4 5 6 231 | 1 10000000: 11 2 2 2 3 3 2 3 2 3 3 2 2 3 3 3 3 3 3 3 3 3 2 2 3 3 3 3 2 3 4 5 6 232 | 1 10000000: 14 3 3 3 2 2 2 2 2 2 3 3 3 2 3 2 3 3 3 3 3 3 3 3 3 2 3 2 2 4 5 6 233 | 1 10000000: 21 2 2 3 3 3 3 2 3 2 2 2 2 3 2 3 3 3 3 3 3 2 2 2 3 3 3 2 3 3 4 5 6 234 | 1 10000000: 13 2 3 3 3 3 3 2 3 2 3 3 3 3 2 2 3 3 3 3 3 3 2 3 2 2 3 2 2 2 3 4 5 6 235 | 1 10000000: 13 2 3 3 2 2 3 2 2 2 3 2 3 3 2 3 2 2 3 2 2 3 3 3 3 3 3 3 2 3 3 4 5 6 236 | 1 10000000: 10 2 2 3 2 3 3 2 2 3 2 3 3 2 2 3 3 3 3 2 3 3 3 2 3 3 2 2 2 3 4 5 6 237 | 1 10000000: 8 2 3 3 3 3 2 3 2 3 3 3 2 3 3 3 2 3 3 2 2 2 2 3 2 3 2 2 3 3 4 5 6 238 | 1 10000000: 9 2 2 2 3 3 2 2 3 2 3 3 3 2 3 3 2 2 3 2 2 3 3 2 3 3 3 3 2 3 3 4 5 6 239 | 1 10000000: 7 3 3 2 3 3 2 3 3 2 3 3 3 3 3 2 3 3 2 3 2 2 2 3 3 3 3 3 3 3 2 4 5 6 240 | 1 10000000: 7 3 3 3 3 3 3 3 2 3 3 3 3 2 3 3 3 2 3 3 3 2 3 3 2 3 2 3 3 2 3 4 5 6 241 | 1 10000000: 7 2 3 3 3 3 3 3 3 3 3 2 3 3 3 2 2 3 2 3 3 3 3 3 3 3 2 3 2 3 3 4 5 6 242 | 1 10000000: 36 3 3 3 2 2 3 3 3 2 3 3 3 2 3 2 3 3 2 2 3 3 3 3 3 2 2 3 2 2 4 5 6 243 | 1 10000000: 7 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 2 2 2 2 2 2 2 3 2 2 3 3 3 4 5 6 244 | 1 10000000: 11 3 2 2 3 3 2 3 2 3 3 3 2 2 3 3 2 2 3 3 2 3 3 2 3 2 3 3 3 3 4 5 6 245 | 1 10000000: 21 3 2 3 3 3 2 2 3 2 3 2 3 3 3 3 3 2 3 2 3 3 3 3 2 3 2 3 3 3 4 5 6 246 | 1 10000000: 10 3 3 3 3 2 3 3 2 3 2 2 3 2 2 3 3 3 2 3 3 2 3 2 2 2 2 3 2 3 4 5 6 247 | 1 10000000: 21 2 3 3 3 3 3 3 3 2 2 3 3 3 3 3 3 2 3 2 3 3 3 3 2 2 3 3 3 3 3 4 5 6 248 | 1 10000000: 35 3 3 3 3 2 2 3 3 3 3 3 3 3 3 3 2 3 3 2 3 2 3 2 3 3 2 3 2 3 3 4 5 6 249 | Locations 250 | 1: 0x206f main.fib :0 s=0 251 | 2: 0x2096 main.fib :0 s=0 252 | 3: 0x207a main.fib :0 s=0 253 | 4: 0x2134 main.main :0 s=0 254 | 5: 0x2df2f runtime.main :0 s=0 255 | 6: 0x5da90 runtime.goexit :0 s=0 256 | 7: 0x2085 main.fib :0 s=0 257 | 8: 0x2049 main.fib :0 s=0 258 | 9: 0x2040 main.fib :0 s=0 259 | 10: 0x204f main.fib :0 s=0 260 | 11: 0x2080 main.fib :0 s=0 261 | 12: 0x20a9 main.fib :0 s=0 262 | 13: 0x2058 main.fib :0 s=0 263 | 14: 0x20a4 main.fib :0 s=0 264 | 15: 0x20a1 main.fib :0 s=0 265 | 16: 0x2097 main.fib :0 s=0 266 | 17: 0x208a main.fib :0 s=0 267 | 18: 0x2072 main.fib :0 s=0 268 | 19: 0x206b main.fib :0 s=0 269 | 20: 0x2053 main.fib :0 s=0 270 | 21: 0x209c main.fib :0 s=0 271 | 22: 0x2092 main.fib :0 s=0 272 | 23: 0x5eecb runtime.mach_semaphore_signal :0 s=0 273 | 24: 0x29bef runtime.mach_semrelease :0 s=0 274 | 25: 0x28f29 runtime.semawakeup :0 s=0 275 | 26: 0xefae runtime.notewakeup :0 s=0 276 | 27: 0x32109 runtime.startm :0 s=0 277 | 28: 0x32468 runtime.wakep :0 s=0 278 | 29: 0x332ef runtime.resetspinning :0 s=0 279 | 30: 0x3374d runtime.schedule :0 s=0 280 | 31: 0x33b09 runtime.goschedImpl :0 s=0 281 | 32: 0x33ba1 runtime.gopreempt_m :0 s=0 282 | 33: 0x44511 runtime.newstack :0 s=0 283 | 34: 0x5b4fe runtime.morestack :0 s=0 284 | 35: 0x208e main.fib :0 s=0 285 | 36: 0x206c main.fib :0 s=0 286 | 37: 0x205e main.fib :0 s=0 287 | 38: 0x2076 main.fib :0 s=0 288 | 39: 0x207b main.fib :0 s=0 289 | 40: 0x20ad main.fib :0 s=0 290 | 41: 0x2067 main.fib :0 s=0 291 | Mappings 292 | -------------------------------------------------------------------------------- /renderer/flamegraph.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package renderer 22 | 23 | import ( 24 | "bytes" 25 | "errors" 26 | "os" 27 | "os/exec" 28 | ) 29 | 30 | var errNoPerlScript = errors.New("Cannot find flamegraph scripts in the PATH or current " + 31 | "directory. You can download the script at https://github.com/brendangregg/FlameGraph. " + 32 | "These scripts should be added to your PATH or in the directory where go-torch is executed. " + 33 | "Alternatively, you can run go-torch with the --raw flag.") 34 | 35 | var ( 36 | stackCollapseScripts = []string{"stackcollapse.pl", "./stackcollapse.pl", "./FlameGraph/stackcollapse.pl"} 37 | flameGraphScripts = []string{"flamegraph", "flamegraph.pl", "./flamegraph.pl", "./FlameGraph/flamegraph.pl", "flame-graph-gen"} 38 | ) 39 | 40 | // findInPath returns the first path that is found in PATH. 41 | func findInPath(paths []string) string { 42 | for _, v := range paths { 43 | if path, err := exec.LookPath(v); err == nil { 44 | return path 45 | } 46 | } 47 | return "" 48 | } 49 | 50 | // runScript runs scriptName with the given arguments, and stdin set to inData. 51 | // It returns the stdout on success. 52 | func runScript(scriptName string, args []string, inData []byte) ([]byte, error) { 53 | cmd := exec.Command(scriptName, args...) 54 | cmd.Stdin = bytes.NewReader(inData) 55 | cmd.Stderr = os.Stderr 56 | return cmd.Output() 57 | } 58 | 59 | // CollapseStacks runs the flamegraph's collapse stacks script. 60 | func CollapseStacks(stacks []byte, args ...string) ([]byte, error) { 61 | stackCollapse := findInPath(stackCollapseScripts) 62 | if stackCollapse == "" { 63 | return nil, errNoPerlScript 64 | } 65 | 66 | return runScript(stackCollapse, nil, stacks) 67 | } 68 | 69 | // GenerateFlameGraph runs the flamegraph script to generate a flame graph SVG. 70 | func GenerateFlameGraph(graphInput []byte, args ...string) ([]byte, error) { 71 | flameGraph := findInPath(flameGraphScripts) 72 | if flameGraph == "" { 73 | return nil, errNoPerlScript 74 | } 75 | 76 | return runScript(flameGraph, args, graphInput) 77 | } 78 | -------------------------------------------------------------------------------- /renderer/flamegraph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package renderer 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | "testing" 27 | ) 28 | 29 | const testData = "1 2 3 4 5\n" 30 | 31 | func TestFindInPatch(t *testing.T) { 32 | const realCmd1 = "ls" 33 | const realCmd2 = "cat" 34 | const fakeCmd1 = "should-not-find-this" 35 | const fakeCmd2 = "not-going-to-exist" 36 | 37 | tests := []struct { 38 | paths []string 39 | expected string 40 | }{ 41 | { 42 | paths: []string{}, 43 | }, 44 | { 45 | paths: []string{realCmd1}, 46 | expected: realCmd1, 47 | }, 48 | { 49 | paths: []string{fakeCmd1, realCmd1}, 50 | expected: realCmd1, 51 | }, 52 | { 53 | paths: []string{fakeCmd1, realCmd1, fakeCmd2, realCmd2}, 54 | expected: realCmd1, 55 | }, 56 | } 57 | 58 | for _, tt := range tests { 59 | got := findInPath(tt.paths) 60 | var gotFile string 61 | if got != "" { 62 | gotFile = filepath.Base(got) 63 | } 64 | if gotFile != tt.expected { 65 | t.Errorf("findInPaths(%v) got %v, want %v", tt.paths, gotFile, tt.expected) 66 | } 67 | 68 | // Verify that the returned path exists. 69 | if got != "" { 70 | _, err := os.Stat(got) 71 | if err != nil { 72 | t.Errorf("returned path %v failed to stat: %v", got, err) 73 | 74 | } 75 | } 76 | } 77 | } 78 | 79 | func TestRunScriptNoInput(t *testing.T) { 80 | out, err := runScript("echo", []string{"1", "2", "3"}, nil) 81 | if err != nil { 82 | t.Fatalf("run echo failed: %v", err) 83 | } 84 | 85 | const want = "1 2 3\n" 86 | if string(out) != want { 87 | t.Errorf("Got unexpected output:\n got %v\n want %v", string(out), want) 88 | } 89 | } 90 | 91 | type scriptFn func(input []byte, args ...string) ([]byte, error) 92 | 93 | func testScriptFound(t *testing.T, sliceToStub []string, f scriptFn) { 94 | // Stub out the scripts that it looks at for the test 95 | origVal := sliceToStub[0] 96 | sliceToStub[0] = "cat" 97 | defer func() { sliceToStub[0] = origVal }() 98 | 99 | out, err := f([]byte(testData)) 100 | if err != nil { 101 | t.Fatalf("Failed to run script: %v", err) 102 | } 103 | 104 | if string(out) != testData { 105 | t.Errorf("Got unexpected output:\n got %v\n want %v", string(out), testData) 106 | } 107 | } 108 | 109 | func testScriptNotFound(t *testing.T, sliceToStub *[]string, f scriptFn) { 110 | origVal := *sliceToStub 111 | *sliceToStub = []string{} 112 | defer func() { *sliceToStub = origVal }() 113 | 114 | _, err := f([]byte(testData)) 115 | if err != errNoPerlScript { 116 | t.Errorf("Unexpected error:\n got %v\n want %v", err, errNoPerlScript) 117 | } 118 | } 119 | 120 | func TestCollapseStacks(t *testing.T) { 121 | testScriptFound(t, stackCollapseScripts, CollapseStacks) 122 | testScriptNotFound(t, &stackCollapseScripts, CollapseStacks) 123 | } 124 | 125 | func TestGenerateFlameGraph(t *testing.T) { 126 | testScriptFound(t, flameGraphScripts, GenerateFlameGraph) 127 | testScriptNotFound(t, &flameGraphScripts, GenerateFlameGraph) 128 | } 129 | -------------------------------------------------------------------------------- /renderer/renderer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package renderer 22 | 23 | import ( 24 | "bytes" 25 | "fmt" 26 | "io" 27 | "strings" 28 | 29 | "github.com/uber/go-torch/stack" 30 | ) 31 | 32 | // ToFlameInput converts the given profile to flame graph input. 33 | func ToFlameInput(profile *stack.Profile, sampleIdx int) ([]byte, error) { 34 | buf := &bytes.Buffer{} 35 | for _, s := range profile.Samples { 36 | if err := renderSample(buf, s, sampleIdx); err != nil { 37 | return nil, err 38 | } 39 | } 40 | return buf.Bytes(), nil 41 | } 42 | 43 | // renderSample renders a single stack sample as flame graph input. 44 | func renderSample(w io.Writer, s *stack.Sample, sampleIdx int) error { 45 | _, err := fmt.Fprintf(w, "%s %v\n", strings.Join(s.Funcs, ";"), s.Counts[sampleIdx]) 46 | return err 47 | } 48 | -------------------------------------------------------------------------------- /renderer/renderer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package renderer 22 | 23 | import ( 24 | "reflect" 25 | "testing" 26 | 27 | "github.com/uber/go-torch/stack" 28 | ) 29 | 30 | func TestToFlameInput(t *testing.T) { 31 | profile := &stack.Profile{ 32 | SampleNames: []string{"samples/count"}, 33 | Samples: []*stack.Sample{ 34 | {Funcs: []string{"func1", "func2"}, Counts: []int64{10}}, 35 | {Funcs: []string{"func3"}, Counts: []int64{8}}, 36 | {Funcs: []string{"func4", "func5", "func6"}, Counts: []int64{3}}, 37 | }, 38 | } 39 | 40 | expected := "func1;func2 10\nfunc3 8\nfunc4;func5;func6 3\n" 41 | 42 | out, err := ToFlameInput(profile, 0) 43 | if err != nil { 44 | t.Fatalf("ToFlameInput failed: %v", err) 45 | } 46 | 47 | if !reflect.DeepEqual(expected, string(out)) { 48 | t.Errorf("ToFlameInput failed:\n got %s\n want %s", out, expected) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stack/sample.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package stack 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | ) 27 | 28 | var ( 29 | errProfileMustHaveSamples = errors.New("cannot create a profile with no samples") 30 | errProfileEmptySampleNames = errors.New("cannot have empty sample names in profile") 31 | ) 32 | 33 | // Profile represents a parsed pprof profile. 34 | type Profile struct { 35 | SampleNames []string 36 | Samples []*Sample 37 | } 38 | 39 | // Sample represents the sample count for a specific call stack. 40 | type Sample struct { 41 | // Funcs is parent first. 42 | Funcs []string 43 | Counts []int64 44 | } 45 | 46 | // NewProfile returns a new profile with the specified sample names. 47 | func NewProfile(names []string) (*Profile, error) { 48 | if len(names) == 0 { 49 | return nil, errProfileMustHaveSamples 50 | } 51 | for _, name := range names { 52 | if name == "" { 53 | return nil, errProfileEmptySampleNames 54 | } 55 | } 56 | return &Profile{SampleNames: names}, nil 57 | } 58 | 59 | // NewSample returns a new sample with a copy of the counts. 60 | func NewSample(funcs []string, counts []int64) *Sample { 61 | s := &Sample{ 62 | Funcs: funcs, 63 | Counts: make([]int64, len(counts)), 64 | } 65 | 66 | // We create a copy of counts, as we may modify them in Add. 67 | s.Add(counts) 68 | return s 69 | } 70 | 71 | // Add combines counts with the existing counts for this sample. 72 | func (s *Sample) Add(counts []int64) error { 73 | if len(s.Counts) != len(counts) { 74 | return fmt.Errorf("cannot add %v values to sample with %v values", len(counts), len(s.Counts)) 75 | } 76 | 77 | for i := range s.Counts { 78 | s.Counts[i] += counts[i] 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /stack/sample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package stack 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestNewProfile(t *testing.T) { 30 | tests := []struct { 31 | name string 32 | names []string 33 | wantErr error 34 | }{ 35 | { 36 | name: "valid profile", 37 | names: []string{"samples/count", "cpu/nanoseconds"}, 38 | }, 39 | { 40 | name: "no samples", 41 | names: nil, 42 | wantErr: errProfileMustHaveSamples, 43 | }, 44 | { 45 | name: "sample with empty name", 46 | names: []string{"samples/count", "", "cpu/nanoseconds"}, 47 | wantErr: errProfileEmptySampleNames, 48 | }, 49 | } 50 | 51 | for _, tt := range tests { 52 | profile, err := NewProfile(tt.names) 53 | if tt.wantErr != nil { 54 | assert.Equal(t, tt.wantErr, err, tt.name) 55 | continue 56 | } 57 | assert.NoError(t, err, tt.names) 58 | assert.NotNil(t, profile, "Expected profile for %v", tt.names) 59 | } 60 | } 61 | 62 | func TestSample(t *testing.T) { 63 | s := NewSample([]string{"a", "b"}, []int64{1, 2}) 64 | 65 | err := s.Add([]int64{3, 4}) 66 | assert.NoError(t, err) 67 | 68 | err = s.Add([]int64{5}) 69 | assert.Error(t, err, "should fail when sample counts mismatch") 70 | } 71 | -------------------------------------------------------------------------------- /torchlog/torchlog.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package torchlog 22 | 23 | import ( 24 | "fmt" 25 | "log" 26 | "time" 27 | 28 | "github.com/fatih/color" 29 | ) 30 | 31 | var ( 32 | redColor = color.New(color.FgRed) 33 | blueColor = color.New(color.FgBlue) 34 | ) 35 | 36 | func init() { 37 | log.SetFlags(0) // disable default flags 38 | } 39 | 40 | // getPrefix generates the log prefix in the given color 41 | func getPrefix(level string, color *color.Color) string { 42 | currentTime := time.Now().Format("15:04:05") 43 | toColoredString := color.SprintFunc() 44 | return toColoredString(fmt.Sprintf("%s[%s] ", level, currentTime)) 45 | } 46 | 47 | // Fatalf wraps log.Fatalf and adds the current time and color. 48 | func Fatalf(format string, v ...interface{}) { 49 | prefix := getPrefix("FATAL", redColor) 50 | log.Fatalf(prefix+format, v...) 51 | } 52 | 53 | // Printf wraps log.Printf and adds the current time and color. 54 | func Printf(format string, v ...interface{}) { 55 | prefix := getPrefix("INFO", blueColor) 56 | log.Printf(prefix+format, v...) 57 | } 58 | 59 | // Print wraps log.Print and adds the current time and color. 60 | func Print(v ...interface{}) { 61 | prefix := getPrefix("INFO", blueColor) 62 | log.Print(prefix + fmt.Sprint(v...)) 63 | } 64 | --------------------------------------------------------------------------------