Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pprof profiling in BadWolf #155

Merged
merged 5 commits into from
Nov 4, 2020
Merged
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
105 changes: 104 additions & 1 deletion tools/vcli/bw/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"fmt"
"io"
"os"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -88,10 +91,47 @@ func SimpleReadLine(done chan bool) <-chan string {
return c
}

// startProfiling tries to start pprof profiling, returning the two files to which the profiling metrics will be printed.
func startProfiling() (*os.File, *os.File, error) {
cpuProfile, err := os.Create("cpuprofile")
if err != nil {
return nil, nil, err
}
memProfile, err := os.Create("memprofile")
if err != nil {
return nil, nil, err
}

err = pprof.StartCPUProfile(cpuProfile)
if err != nil {
return nil, nil, err
}

return cpuProfile, memProfile, nil
}

// stopProfiling stops pprof profiling, writing the final profiling metrics to the correspondent files and closing them.
func stopProfiling(cpuProfile, memProfile *os.File) {
if cpuProfile == nil || memProfile == nil {
fmt.Println("cpuProfile and memProfile must be both non-nil to stop profiling.")
return
}

pprof.StopCPUProfile()
err := pprof.WriteHeapProfile(memProfile)
if err != nil {
fmt.Println(err)
}

cpuProfile.Close()
memProfile.Close()
}

// REPL starts a read-evaluation-print-loop to run BQL commands.
func REPL(od storage.Store, input *os.File, rl ReadLiner, chanSize, bulkSize, builderSize int, done chan bool) int {
var tracer io.Writer
ctx, isTracingToFile, sessionStart := context.Background(), false, time.Now()
ctx, isTracingToFile, isProfiling, sessionStart := context.Background(), false, false, time.Now()
var cpuProfile, memProfile *os.File

driverPlain := func() storage.Store {
return od
Expand Down Expand Up @@ -125,6 +165,11 @@ func REPL(od storage.Store, input *os.File, rl ReadLiner, chanSize, bulkSize, bu

for l := range rl(done) {
if strings.HasPrefix(l, "quit") {
if isProfiling {
fmt.Println("Stopping profiling and closing correspondent files.")
stopProfiling(cpuProfile, memProfile)
isProfiling = false
}
done <- true
break
}
Expand Down Expand Up @@ -175,6 +220,61 @@ func REPL(od storage.Store, input *os.File, rl ReadLiner, chanSize, bulkSize, bu
done <- false
continue
}
if strings.HasPrefix(l, "start profiling") {
Copy link

Choose a reason for hiding this comment

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

For a separate PR/Unralated to your changes:
Consider updating the loop to have a switch instead of several if strings.HasPrefix checks.
Remember: in Go, switch statements are more general than in other languages, so you can have cases checking for HasPrefix.
https://golang.org/doc/effective_go.html#switch
Besides being more readable, it can probably simplify the done <- false/continue repetitions. You might want to look at labels to control the flow here, rather than using the channel :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you Tati! It is cool that switch can be this general in Go, we will keep this in mind for a future PR as well. =]

if isProfiling {
fmt.Println("Profiling is already ongoing.")
done <- false
continue
}
args := strings.Split(strings.TrimSpace(l)[:len(l)-1], " ")
var err error
switch len(args) {
case 2:
cpuProfile, memProfile, err = startProfiling()
if err != nil {
fmt.Println(err)
fmt.Println("Profiling failed to start.")
break
}
isProfiling = true
fmt.Println("Profiling with pprof is on.")
case 4:
if args[2] != "-cpurate" {
fmt.Printf("Invalid syntax with %q.\n\tstart profiling -cpurate <samples_per_second>\n", args[2])
break
}
cpuProfRate, err := strconv.ParseInt(args[3], 10, 32)
if err != nil {
fmt.Println(err)
fmt.Println("Profiling failed to start.")
break
}
runtime.SetCPUProfileRate(int(cpuProfRate))
cpuProfile, memProfile, err = startProfiling()
if err != nil {
fmt.Println(err)
fmt.Println("Profiling failed to start.")
break
}
isProfiling = true
fmt.Printf("Profiling with pprof is on (CPU profiling rate: %d samples per second).\n", cpuProfRate)
default:
fmt.Println("Invalid syntax.\n\tstart profiling -cpurate <samples_per_second>")
}
done <- false
continue
}
if strings.HasPrefix(l, "stop profiling") {
if isProfiling {
stopProfiling(cpuProfile, memProfile)
isProfiling = false
fmt.Println("Profiling with pprof is turned off.")
} else {
fmt.Println("Profiling with pprof is already off.")
}
done <- false
rogerlucena marked this conversation as resolved.
Show resolved Hide resolved
continue
}
if strings.HasPrefix(l, "export") {
now := time.Now()
args := strings.Split("bw "+strings.TrimSpace(l)[:len(l)-1], " ")
Expand Down Expand Up @@ -255,6 +355,9 @@ func printHelp() {
fmt.Println("run <file_with_bql_statements> - runs all the BQL statements in the file.")
fmt.Println("start tracing [trace_file] - starts tracing queries.")
fmt.Println("stop tracing - stops tracing queries.")
fmt.Println("start profiling - starts pprof profiling for queries.")
fmt.Println("start profiling -cpurate <samples_per_second> - starts pprof profiling with customized CPU sampling rate.")
fmt.Println("stop profiling - stops pprof profiling for queries.")
fmt.Println("quit - quits the console.")
fmt.Println()
}
Expand Down