Skip to content

Commit

Permalink
cmd/pprof: add readline support similar to upstream
Browse files Browse the repository at this point in the history
The upstream pprof implements the readline feature using
the github.com/chzyer/readline package in its pprof.go main.

It would be ideal to use the same readline support package as
the upstream for better user experience and code maintenance.
However, bringing in third-party packages requires more work
than I envisioned (e.g. clean up the vendored code to meet the
expected standard - iow don't break builders).

As a result, this change implements the similar feature
for the pprof command included in the go distribution
(cmd/pprof/pprof.go) using golang.org/x/crypto/ssh/terminal
for now.

Auto-completion is not yet supported (same in the upstream).

The feature is enabled only in linux, windows, darwin, and
only when terminal support is available.

This change brings in new vendored packages,
golang.org/x/crypto/ssh/terminal and
golang.org/x/sys/{unix,windows}.

For #14041

Change-Id: If4a790796acf2ab20f7e81268b9d9354c5a5cd2b
Reviewed-on: https://go-review.googlesource.com/112436
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
hyangah committed May 23, 2018
1 parent 392ff18 commit 3f89214
Show file tree
Hide file tree
Showing 304 changed files with 168,533 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/cmd/pprof/pprof.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func main() {
options := &driver.Options{
Fetch: new(fetcher),
Obj: new(objTool),
UI: newUI(),
}
if err := driver.PProf(options); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
Expand Down Expand Up @@ -369,3 +370,7 @@ func (f *file) Close() error {
f.file.Close()
return nil
}

// newUI will be set in readlineui.go in some platforms
// for interactive readline functionality.
var newUI = func() driver.UI { return nil }
116 changes: 116 additions & 0 deletions src/cmd/pprof/readlineui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains an driver.UI implementation
// that provides the readline functionality if possible.

// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
// +build !appengine
// +build !android

package main

import (
"fmt"
"io"
"os"
"strings"

"github.com/google/pprof/driver"
"golang.org/x/crypto/ssh/terminal"
)

func init() {
newUI = newReadlineUI
}

// readlineUI implements driver.UI interface using the
// golang.org/x/crypto/ssh/terminal package.
// The upstream pprof command implements the same functionality
// using the github.com/chzyer/readline package.
type readlineUI struct {
term *terminal.Terminal
}

func newReadlineUI() driver.UI {
// test if we can use terminal.ReadLine
// that assumes operation in the raw mode.
oldState, err := terminal.MakeRaw(0)
if err != nil {
return nil
}
terminal.Restore(0, oldState)

rw := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stderr}
return &readlineUI{term: terminal.NewTerminal(rw, "")}
}

// Read returns a line of text (a command) read from the user.
// prompt is printed before reading the command.
func (r *readlineUI) ReadLine(prompt string) (string, error) {
r.term.SetPrompt(prompt)

// skip error checking because we tested it
// when creating this readlineUI initially.
oldState, _ := terminal.MakeRaw(0)
defer terminal.Restore(0, oldState)

s, err := r.term.ReadLine()
return s, err
}

// Print shows a message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, Print writes to standard error.
// (Standard output is reserved for report data.)
func (r *readlineUI) Print(args ...interface{}) {
r.print(false, args...)
}

// PrintErr shows an error message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, PrintErr writes to standard error.
func (r *readlineUI) PrintErr(args ...interface{}) {
r.print(true, args...)
}

func (r *readlineUI) print(withColor bool, args ...interface{}) {
text := fmt.Sprint(args...)
if !strings.HasSuffix(text, "\n") {
text += "\n"
}
if withColor {
text = colorize(text)
}
fmt.Fprintf(r.term, text)
}

// colorize prints the msg in red using ANSI color escapes.
func colorize(msg string) string {
const red = 31
var colorEscape = fmt.Sprintf("\033[0;%dm", red)
var colorResetEscape = "\033[0m"
return colorEscape + msg + colorResetEscape
}

// IsTerminal returns whether the UI is known to be tied to an
// interactive terminal (as opposed to being redirected to a file).
func (r *readlineUI) IsTerminal() bool {
const stdout = 1
return terminal.IsTerminal(stdout)
}

// WantBrowser indicates whether browser should be opened with the -http option.
func (r *readlineUI) WantBrowser() bool {
return r.IsTerminal()
}