Skip to content
This repository was archived by the owner on Mar 7, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 87 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,70 +14,104 @@ synthesizes them into a flame graph. Uses Go's built in [pprof][] library.
## Basic Usage

```
$ go-torch --help
$ go-torch -h
Usage:
go-torch [options] [binary] <profile source>

NAME:
go-torch - go-torch collects stack traces of a Go application and synthesizes them into into a [flame graph](http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html)
pprof Options:
-u, --url= Base URL of your Go program (default: http://localhost:8080)
-s, --suffix= URL path of pprof profile (default: /debug/pprof/profile)
-b, --binaryinput= File path of previously saved binary profile. (binary profile is anything accepted by https://golang.org/cmd/pprof)
--binaryname= File path of the binary that the binaryinput is for, used for pprof inputs
-t, --seconds= Number of seconds to profile for (default: 30)
--pprofArgs= Extra arguments for pprof

USAGE:
go-torch [global options] command [command options] [arguments...]
Output Options:
-f, --file= Output file name (must be .svg) (default: torch.svg)
-p, --print Print the generated svg to stdout instead of writing to file
-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)
--title= Graph title to display in the output file (default: Flame Graph)

COMMANDS:
help, h Shows a list of commands or help for one command
Help Options:
-h, --help Show this help message
```

### Write flamegraph using /debug/pprof endpoint

The default options will hit `http://localhost:8080/debug/pprof/profile` for
a 30 second CPU profile, and write it out to torch.svg

GLOBAL OPTIONS:
--url, -u "http://localhost:8080" base url of your Go program
--suffix, -s "/debug/pprof/profile" url path of pprof profile
--binaryinput, -b file path of raw binary profile; alternative to having go-torch query pprof endpoint (binary profile is anything accepted by https://golang.org/cmd/pprof)
--binaryname file path of the binary that the binaryinput is for, used for pprof inputs
--time, -t "30" time in seconds to profile for
--file, -f "torch.svg" ouput file name (must be .svg)
--print, -p print the generated svg to stdout instead of writing to file
--raw, -r 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)
--title graph title to display in the output file
--help, -h show help
--version, -v print the version
```
$ go-torch
INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 30 http://localhost:8080/debug/pprof/profile
INFO[19:11:03] Writing svg to torch.svg
```

### File Example
You can customize the base URL by using `-u`

```
$ go-torch --time=15 --file "torch.svg" --url http://localhost:8080
INFO[0000] Profiling ...
INFO[0015] flame graph has been created as torch.svg
$ go-torch -u http://my-service:8080/
INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 30 http://my-service:8080/debug/pprof/profile
INFO[19:11:03] Writing svg to torch.svg
```

### Stdout Example
Or change the number of seconds to profile using `--seconds`:

```
$ go-torch --time=15 --print --url http://localhost:8080
INFO[0000] Profiling ...
<svg>
...
</svg>
INFO[0015] flame graph has been printed to stdout
$ go-torch --seconds 5
INFO[19:10:58] Run pprof command: go tool pprof -raw -seconds 5 http://localhost:8080/debug/pprof/profile
INFO[19:11:03] Writing svg to torch.svg
```


### Using pprof arguments

`go-torch` will pass through arguments to `go tool pprof`, which lets you take
existing pprof commands and easily make them work with `go-torch`.

For example, after creating a CPU profile from a benchmark:
```
$ go test -bench . -cpuprofile=cpu.prof

### Raw Example
# This creates a cpu.prof file, and the $PKG.test binary.
```

The same arguments that can be used with `go tool pprof` will also work
with `go-torch`:
```
$ go-torch --time=15 --raw --url http://localhost:8080
INFO[0000] Profiling ...
function1;function2 3
...
INFO[0015] raw call graph output been printed to stdout
$ go tool pprof main.test cpu.prof

# Same arguments work with go-torch
$ go-torch main.test cpu.pprof
INFO[19:00:29] Run pprof command: go tool pprof -raw -seconds 30 main.test prof.cpu
INFO[19:00:29] Writing svg to torch.svg
```

### Local pprof Example

Flags that are not handled by `go-torch` are passed through as well:
```
$ go test -cpuprofile=cpu.pprof
# This creates a cpu.pprof file, and the golang.test binary.
$ go-torch --binaryinput cpu.pprof --binaryname golang.test
INFO[0000] Profiling ...
INFO[0000] flame graph has been created as torch.svg
$ go-torch --alloc_objects main.test mem.prof
INFO[19:00:29] Run pprof command: go tool pprof -raw -seconds 30 --alloc_objects main.test prof.mem
INFO[19:00:29] Writing svg to torch.svg
```

## Integrating With Your Application

To add profiling endpoints in your application, follow the official
Go docs [here][].
If your application is already running a server on the DefaultServeMux,
just add this import to your application.

[here]: https://golang.org/pkg/net/http/pprof/

```go
import _ "net/http/pprof"
```

If your application is not using the DefaultServeMux, you can still easily
expose pprof endpoints by manually registering the net/http/pprof handlers or by
using a library like [this one](https://github.com/e-dard/netbug).

## Installation

```
Expand All @@ -92,36 +126,27 @@ $ docker run uber/go-torch -u http://[address-of-host] -p > torch.svg
Using `-p` will print the SVG to standard out, which can then be redirected
to a file. This avoids mounting volumes to a container.

### Install the Go dependencies:

```
$ go get github.com/Masterminds/glide
$ glide install
```

### Get the flame graph script:

When using the `go-torch` binary locally, you will need the Flamegraph scripts
in your `PATH`:

```
$ cd $GOPATH/src/github.com/uber/go-torch
$ git clone https://github.com/brendangregg/FlameGraph.git
```

## Integrating With Your Application

Expose a pprof endpoint. Official Go docs are [here][]. If your application is
already running a server on the DefaultServeMux, just add this import to your
application.
## Development and Testing

[here]: https://golang.org/pkg/net/http/pprof/
### Install the Go dependencies:

```go
import _ "net/http/pprof"
```
$ go get github.com/Masterminds/glide
$ cd $GOPATH/src/github.com/uber/go-torch
$ glide install
```

If your application is not using the DefaultServeMux, you can still easily
expose pprof endpoints by manually registering the net/http/pprof handlers or by
using a library like [this one](https://github.com/e-dard/netbug).

## Run the Tests
### Run the Tests

```
$ go test ./...
Expand Down
4 changes: 2 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 19 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,31 @@ import (

// options are the parameters for go-torch.
type options struct {
PProfOptions pprof.Options
File string `short:"f" long:"file" default:"torch.svg" description:"Output file name (must be .svg)"`
Print bool `short:"p" long:"print" description:"Print the generated svg to stdout instead of writing to file"`
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)"`
Title string `long:"title" default:"Flame Graph" description:"Graph title to display in the output file"`
PProfOptions pprof.Options `group:"pprof Options"`
OutputOpts outputOptions `group:"Output Options"`
}

type outputOptions struct {
File string `short:"f" long:"file" default:"torch.svg" description:"Output file name (must be .svg)"`
Print bool `short:"p" long:"print" description:"Print the generated svg to stdout instead of writing to file"`
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)"`
Title string `long:"title" default:"Flame Graph" description:"Graph title to display in the output file"`
}

// main is the entry point of the application
func main() {
if err := runWithArgs(os.Args...); err != nil {
if err := runWithArgs(os.Args[1:]...); err != nil {
torchlog.Fatalf("Failed: %v", err)
}
}

func runWithArgs(args ...string) error {
opts := &options{}

remaining, err := gflags.ParseArgs(opts, args)
parser := gflags.NewParser(opts, gflags.Default|gflags.IgnoreUnknown)
parser.Usage = "[options] [binary] <profile source>"

remaining, err := parser.ParseArgs(args)
if err != nil {
if flagErr, ok := err.(*gflags.Error); ok && flagErr.Type == gflags.ErrHelp {
os.Exit(0)
Expand All @@ -65,15 +72,11 @@ func runWithArgs(args ...string) error {
return fmt.Errorf("invalid options: %v", err)
}

// If there are remaining arguments, the first argument is the binary name.
if len(remaining) > 0 {
remaining = remaining[1:]
}
return runWithOptions(opts, remaining)
}

func runWithOptions(opts *options, remaining []string) error {
pprofRawOutput, err := pprof.GetRaw(opts.PProfOptions, remaining)
func runWithOptions(allOpts *options, remaining []string) error {
pprofRawOutput, err := pprof.GetRaw(allOpts.PProfOptions, remaining)
if err != nil {
return fmt.Errorf("could not get raw output from pprof: %v", err)
}
Expand All @@ -88,6 +91,7 @@ func runWithOptions(opts *options, remaining []string) error {
return fmt.Errorf("could not convert stacks to flamegraph input: %v", err)
}

opts := allOpts.OutputOpts
if opts.Raw {
torchlog.Print("Printing raw flamegraph input to stdout")
fmt.Printf("%s\n", flameInput)
Expand All @@ -114,7 +118,8 @@ func runWithOptions(opts *options, remaining []string) error {
}

func validateOptions(opts *options) error {
if opts.File != "" && !strings.HasSuffix(opts.File, ".svg") {
file := opts.OutputOpts.File
if file != "" && !strings.HasSuffix(file, ".svg") {
return fmt.Errorf("output file must end in .svg")
}
if opts.PProfOptions.TimeSeconds < 1 {
Expand Down
10 changes: 5 additions & 5 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestInvalidOptions(t *testing.T) {

func TestRunRaw(t *testing.T) {
opts := getDefaultOptions()
opts.Raw = true
opts.OutputOpts.Raw = true

if err := runWithOptions(opts, nil); err != nil {
t.Fatalf("Run with Raw failed: %v", err)
Expand All @@ -120,14 +120,14 @@ func getTempFilename(t *testing.T, suffix string) string {

func TestRunFile(t *testing.T) {
opts := getDefaultOptions()
opts.File = getTempFilename(t, ".svg")
opts.OutputOpts.File = getTempFilename(t, ".svg")

withScriptsInPath(t, func() {
if err := runWithOptions(opts, nil); err != nil {
t.Fatalf("Run with Print failed: %v", err)
}

f, err := os.Open(opts.File)
f, err := os.Open(opts.OutputOpts.File)
if err != nil {
t.Errorf("Failed to open output file: %v", err)
}
Expand All @@ -147,7 +147,7 @@ func TestRunFile(t *testing.T) {

func TestRunBadFile(t *testing.T) {
opts := getDefaultOptions()
opts.File = "/dev/zero/invalid/file"
opts.OutputOpts.File = "/dev/zero/invalid/file"

withScriptsInPath(t, func() {
if err := runWithOptions(opts, nil); err == nil {
Expand All @@ -158,7 +158,7 @@ func TestRunBadFile(t *testing.T) {

func TestRunPrint(t *testing.T) {
opts := getDefaultOptions()
opts.Print = true
opts.OutputOpts.Print = true

withScriptsInPath(t, func() {
if err := runWithOptions(opts, nil); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion torchlog/torchlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func init() {
func getPrefix(level string, color *color.Color) string {
currentTime := time.Now().Format("15:04:05")
toColoredString := color.SprintFunc()
return toColoredString(fmt.Sprintf("%s[%s]", level, currentTime))
return toColoredString(fmt.Sprintf("%s[%s] ", level, currentTime))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor formatting change to make the output a little nicer. The previous output looked like:

INFO[19:00:29]Run pprof command: go tool pprof -raw -seconds 30 main.test prof.cpu
INFO[19:00:29]Writing svg to torch.svg

I thought adding a space would make the output a little more readable:

INFO[19:00:29] Run pprof command: go tool pprof -raw -seconds 30 main.test prof.cpu
INFO[19:00:29] Writing svg to torch.svg

Caught this while updating the examples in README

Copy link
Contributor

@ascandella ascandella Jun 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, agree it's better. Wasn't sure whether you meant to include it here or
another diff. This is good though.

}

// Fatalf wraps log.Fatalf and adds the current time and color.
Expand Down