├── LICENSE ├── README.md └── gopprof.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 chzyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gopprof 2 | A go pprof wrapper with full readline supported 3 | 4 | ### Get 5 | ``` 6 | $ go get github.com/chzyer/gopprof 7 | $ gopprof [options] [binary] ... 8 | # just like anything with "go tool pprof" 9 | ``` 10 | 11 | [![asciicast](https://asciinema.org/a/c2ijwhevuxhic8j1n1eylgqx3.png)](https://asciinema.org/a/c2ijwhevuxhic8j1n1eylgqx3) 12 | 13 | ``` 14 | $ gopprof http://localhost:6060/debug/pprof/heap 15 | Fetching profile from http://localhost:6060/debug/pprof/heap 16 | Saved profile in /Users/xxx/pprof/pprof.localhost:6060.inuse_objects.inuse_space.028.pb.gz 17 | Entering interactive mode (type "help" for commands) 18 | (gopprof) _ 19 | ``` 20 | -------------------------------------------------------------------------------- /gopprof.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "os" 8 | "os/exec" 9 | 10 | "github.com/chzyer/readline" 11 | ) 12 | 13 | func checkGo() error { 14 | cmd := exec.Command("go") 15 | if err := cmd.Start(); err != nil { 16 | return err 17 | } 18 | // it's exit code should return 2 19 | return nil 20 | } 21 | 22 | func runPprof(args []string) error { 23 | newArgs := make([]string, 2+len(args)) 24 | copy(newArgs[2:], args) 25 | newArgs[0] = "tool" 26 | newArgs[1] = "pprof" 27 | cmd := exec.Command("go", newArgs...) 28 | 29 | pr, pw := io.Pipe() 30 | cmd.Stderr = pw 31 | cmd.Stdout = pw 32 | pprofIn, err := cmd.StdinPipe() 33 | if err != nil { 34 | return err 35 | } 36 | _ = pprofIn 37 | 38 | if err := cmd.Start(); err != nil { 39 | return err 40 | } 41 | 42 | errChan := make(chan error) 43 | go func() { 44 | errChan <- cmd.Wait() 45 | }() 46 | go process(pr, pprofIn) 47 | 48 | err = <-errChan 49 | pr.CloseWithError(err) 50 | return err 51 | } 52 | 53 | func process(pr io.ReadCloser, pprofIn io.Writer) { 54 | r := bufio.NewReader(pr) 55 | defer pr.Close() 56 | 57 | rl := newrl() 58 | defer rl.Close() 59 | buffer := bytes.NewBuffer(nil) 60 | 61 | for { 62 | line, err := lineOrEOF(r, buffer) 63 | if err != nil { 64 | break 65 | } 66 | if line != "(pprof) " { 67 | os.Stdout.Write([]byte(line)) 68 | continue 69 | } 70 | 71 | reread: 72 | ret := rl.Line() 73 | if ret.CanBreak() { 74 | pprofIn.Write([]byte("quit\n")) 75 | break 76 | } else if ret.CanContinue() { 77 | goto reread 78 | } 79 | 80 | pprofIn.Write([]byte(ret.Line)) 81 | pprofIn.Write([]byte("\n")) 82 | } 83 | 84 | } 85 | 86 | func lineOrEOF(r *bufio.Reader, buf *bytes.Buffer) (string, error) { 87 | buf.Reset() 88 | for { 89 | c, err := r.ReadByte() 90 | if err != nil { 91 | break 92 | } 93 | buf.WriteByte(c) 94 | if c == '\n' { 95 | break 96 | } 97 | if bytes.Equal(buf.Bytes(), []byte("(pprof) ")) { 98 | break 99 | } 100 | } 101 | return buf.String(), nil 102 | } 103 | 104 | func newrl() *readline.Instance { 105 | rl, err := readline.NewEx(&readline.Config{ 106 | Prompt: "(gopprof) ", 107 | }) 108 | if err != nil { 109 | panic(err) 110 | } 111 | return rl 112 | } 113 | 114 | func main() { 115 | if err := checkGo(); err != nil { 116 | println(err.Error()) 117 | os.Exit(2) 118 | } 119 | 120 | if err := runPprof(os.Args[1:]); err != nil { 121 | println(err.Error()) 122 | os.Exit(1) 123 | } 124 | } 125 | --------------------------------------------------------------------------------