Skip to content

Commit

Permalink
Merge pull request #6 from codeperfio/publish.update
Browse files Browse the repository at this point in the history
Update bin name and references
  • Loading branch information
filipecosta90 authored Apr 29, 2024
2 parents 6f4e0b0 + 6454403 commit 974f011
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 55 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# pprof-exporter
Export and persist Go profiling data locally, or into https://codeperf.io for FREE.
# codeperf
Export and persist performance data into https://codeperf.io.


bash <(curl -s https://raw.githubusercontent.com/codeperfio/pprof-exporter/main/get.sh)
sudo install pprof-exporter /usr/local/bin/
bash <(curl -s https://raw.githubusercontent.com/codeperfio/codeperf/main/get.sh)
sudo install codeperf /usr/local/bin/

pprof-exporter --help
codeperf --help
89 changes: 48 additions & 41 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,59 +33,66 @@ type TextReport struct {
}

func exportLogic() func(cmd *cobra.Command, args []string) {
granularityOptions := []string{"lines", "functions"}

return func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
log.Fatalf("Exactly one profile file is required")
}
if local {
err, finalTree := generateFlameGraph(args[0])
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
postBody, err := json.Marshal(finalTree)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
fmt.Println(string(postBody))
inputName := args[0]
granularityOptions := []string{"lines", "functions"}
exportFromPprof(inputName, bench, granularityOptions)
}
}

for _, granularity := range granularityOptions {
err, report := generateTextReports(granularity, args[0])
if err == nil {
var w io.Writer
// open output file
localExportLogic(w, report)
} else {
log.Fatal(err)
}
}
} else {
for _, granularity := range granularityOptions {
err, report := generateTextReports(granularity, args[0])
if err == nil {
remoteExportLogic(report, granularity)
} else {
log.Fatal(err)
}
func exportFromPprof(inputName string, benchmark string, granularityOptions []string) {
if local {
err, finalTree := generateFlameGraph(inputName)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
postBody, err := json.Marshal(finalTree)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
fmt.Println(string(postBody))

for _, granularity := range granularityOptions {
err, report := generateTextReports(granularity, inputName)
if err == nil {
var w io.Writer
// open output file
localExportLogic(w, report)
} else {
log.Fatal(err)
}
err, finalTree := generateFlameGraph(args[0])
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
} else {
for _, granularity := range granularityOptions {
err, report := generateTextReports(granularity, inputName)
if err == nil {
remoteExportLogic(report, benchmark, granularity)
} else {
log.Fatal(err)
}
remoteFlameGraphExport(finalTree)
log.Printf("Successfully published profile data")
log.Printf("Check it at: %s/gh/%s/%s/commit/%s/bench/%s/cpu", codeperfUrl, gitOrg, gitRepo, gitCommit, bench)
}
err, finalTree := generateFlameGraph(inputName)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
remoteFlameGraphExport(finalTree, benchmark)
log.Printf("Successfully published profile data")
link := fmt.Sprintf("%s/gh/%s/%s/commit/%s/bench/%s/cpu", codeperfUrl, gitOrg, gitRepo, gitCommit, benchmark)
log.Printf(link)
}
}

func remoteFlameGraphExport(tree treeNodeSlice) {
func remoteFlameGraphExport(tree treeNodeSlice, benchmark string) {
postBody, err := json.Marshal(tree)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
responseBody := bytes.NewBuffer(postBody)
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/commit/%s/bench/%s/cpu/flamegraph", codeperfApiUrl, gitOrg, gitRepo, gitCommit, bench)
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/commit/%s/bench/%s/cpu/flamegraph", codeperfApiUrl, gitOrg, gitRepo, gitCommit, benchmark)
resp, err := http.Post(endPoint, "application/json", responseBody)
//Handle Error
if err != nil {
Expand All @@ -99,18 +106,18 @@ func remoteFlameGraphExport(tree treeNodeSlice) {
log.Fatalln(err)
}
if resp.StatusCode != 200 {
log.Fatalf("An error ocurred while phusing data to remote %s. Status code %d. Reply: %s", codeperfApiUrl, resp.StatusCode, string(reply))
log.Fatalf("An error ocurred while phusing flamegraph data to remote %s. Status code %d. Reply: %s", codeperfApiUrl, resp.StatusCode, string(reply))
}

}

func remoteExportLogic(report TextReport, granularity string) {
func remoteExportLogic(report TextReport, benchmark string, granularity string) {
postBody, err := json.Marshal(report)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
responseBody := bytes.NewBuffer(postBody)
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/commit/%s/bench/%s/cpu/%s", codeperfApiUrl, gitOrg, gitRepo, gitCommit, bench, granularity)
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/commit/%s/bench/%s/cpu/%s", codeperfApiUrl, gitOrg, gitRepo, gitCommit, benchmark, granularity)
resp, err := http.Post(endPoint, "application/json", responseBody)
//Handle Error
if err != nil {
Expand All @@ -124,7 +131,7 @@ func remoteExportLogic(report TextReport, granularity string) {
log.Fatalln(err)
}
if resp.StatusCode != 200 {
log.Fatalf("An error ocurred while phusing data to remote %s. Status code %d. Reply: %s", codeperfApiUrl, resp.StatusCode, string(reply))
log.Fatalf("An error ocurred while pushing cpu data to remote %s.\nEndpoint %s. Status code %d. Reply: %s", codeperfApiUrl, endPoint, resp.StatusCode, string(reply))
}
}

Expand Down
54 changes: 54 additions & 0 deletions cmd/project_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
)

func funcNames(filename string, exportOnly bool) []string {
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, filename, nil, 0)
if exportOnly {
ast.FileExports(file) // trim AST
}

funcNames := []string{}
for _, decl := range file.Decls {
fn, ok := decl.(*ast.FuncDecl) // assert type of declaration
if !ok {
continue
}
funcNames = append(funcNames, fn.Name.Name)
}
return funcNames
}

func GetBenchmarks(root string) ([]string, error) {
var data []string
fileSystem := os.DirFS(root)
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.Fatal(err)
}
if filepath.Ext(path) != ".go" {
return nil
}
if !strings.HasSuffix(filepath.Base(path), "_test.go") {
return nil
}
fnames := funcNames(path, false)
for _, fname := range fnames {
if strings.HasPrefix(fname, "Benchmark") {
data = append(data, fname)
}
}
return nil
})
return data, err
}
105 changes: 98 additions & 7 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ limitations under the License.
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"github.com/go-git/go-git/v5"
"io/ioutil"
"log"
"net/http"
"os/exec"
"strings"

"github.com/spf13/cobra"
Expand All @@ -31,6 +36,7 @@ var cfgFile string
var bench string
var gitOrg string
var gitRepo string
var gitBranch string
var gitCommit string
var localFilename string
var codeperfUrl string
Expand All @@ -43,19 +49,99 @@ var longDescription = ` __ ____ _
\___/\____/\__,_/\___/ .___/\___/_/ /_/ (_) /_/\____/
/_/
Export and persist Go's profiling data locally, or into https://codeperf.io.`
Export and persist Go's performance data into https://codeperf.io.`

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "pprof-exporter",
Short: "Export and persist Go's profiling data locally, or into https://codeperf.io.",
Use: "codeperf",
Short: "Export and persist Go's performance into https://codeperf.io.",
Long: longDescription,
Run: exportLogic(),
}

var cmdPrint = &cobra.Command{
Use: "test",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: cobra.MinimumNArgs(0),
Run: testLogic,
}

func testLogic(cmd *cobra.Command, args []string) {
// TODO: Check pprof is available on path
const shell = "/bin/bash"
benchmarks, _ := GetBenchmarks(".")
benchtime := "1s"
var err error = nil

goPath, err := exec.LookPath("go")

log.Println(fmt.Sprintf("Detected %d distinct benchmarks.", len(benchmarks)))
for _, benchmark := range benchmarks {

cpuProfileName := fmt.Sprintf("cpuprofile-%s.out", benchmark)
cmdS := fmt.Sprintf("%s test -bench=%s -benchtime=%s -cpuprofile %s .", goPath, benchmark, benchtime, cpuProfileName)
log.Println(fmt.Sprintf("Running benchmark %s with the following command: %s.", benchmark, cmdS))
err := exec.Command(shell, "-c", cmdS).Run()
if err != nil {
log.Fatal(err)
}
granularityOptions := []string{"lines", "functions"}
exportFromPprof(cpuProfileName, benchmark, granularityOptions)
}
coverprofile := "coverage.out"
cmdS := fmt.Sprintf("%s test -cover -bench=. -benchtime=0.01s -coverprofile %s .", goPath, coverprofile)
log.Println(fmt.Sprintf("Calculating the project benchmark coverage with the following command: %s.", cmdS))
c := exec.Command(shell, "-c", cmdS)
var outb, errb bytes.Buffer
c.Stdout = &outb
c.Stderr = &errb
err = c.Run()
if err != nil {
log.Fatal(err)
}

cleaned := strings.TrimSpace(string(outb.String()))
lines := strings.Split(cleaned, "\n")
coverageLine := lines[len(lines)-2]
coverageLineS := strings.Split(coverageLine, ":")
coverageVs := strings.Split(strings.TrimSpace(coverageLineS[1]), " ")[0]
coverageVs = coverageVs[0 : len(coverageVs)-1]
fmt.Println(coverageLine, coverageVs)
exportCoverage(coverageVs)
}

func exportCoverage(vs string) {
postBody, err := json.Marshal(map[string]string{"coverage": vs})
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
fmt.Println(string(postBody))

responseBody := bytes.NewBuffer(postBody)
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/branch/%s/graph", codeperfApiUrl, gitOrg, gitRepo, gitBranch)
resp, err := http.Post(endPoint, "application/json", responseBody)
//Handle Error
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
defer resp.Body.Close()

//Read the response body
reply, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
if resp.StatusCode != 200 {
log.Fatalf("An error ocurred while pushing cpu data to remote %s.\nEndpoint %s. Status code %d. Reply: %s", codeperfApiUrl, endPoint, resp.StatusCode, string(reply))
}

}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
rootCmd.AddCommand(cmdPrint)
cobra.CheckErr(rootCmd.Execute())
}

Expand All @@ -64,12 +150,16 @@ func init() {
defaultGitOrg := ""
defaultGitRepo := ""
defaultGitCommit := ""
defaultGitBranch := ""

r, err := git.PlainOpen(".")
if err != nil {
log.Println("Unable to retrieve current repo git info. Use the --git-org, --git-repo, and --git-hash to properly fill the git info.")
log.Println("Unable to retrieve current repo git info. Use the --git-org, --git-repo, --git-branch and --git-hash to properly fill the git info.")
} else {
ref, _ := r.Head()
if ref.Name().IsBranch() {
defaultGitBranch = ref.Name().Short()
}
refHash := ref.Hash().String()
defaultGitCommit = getShortHash(refHash, 7)
remotes, _ := r.Remotes()
Expand All @@ -87,14 +177,15 @@ func init() {

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.codeperf.yaml)")
rootCmd.PersistentFlags().BoolVar(&local, "local", false, "don't push the data to https://codeperf.io")
rootCmd.PersistentFlags().StringVar(&bench, "bench", "", "Benchmark name")
rootCmd.PersistentFlags().StringVar(&gitOrg, "git-org", defaultGitOrg, "git org")
rootCmd.PersistentFlags().StringVar(&gitRepo, "git-repo", defaultGitRepo, "git repo")
rootCmd.PersistentFlags().StringVar(&gitCommit, "git-hash", defaultGitCommit, "git commit hash")
rootCmd.PersistentFlags().StringVar(&gitBranch, "git-branch", defaultGitBranch, "git branch")
rootCmd.PersistentFlags().StringVar(&localFilename, "local-filename", "profile.json", "Local file to export the json to. Only used when the --local flag is set")
rootCmd.PersistentFlags().StringVar(&codeperfUrl, "codeperf-url", "https://codeperf.io", "codeperf URL")
rootCmd.PersistentFlags().StringVar(&codeperfApiUrl, "codeperf-api-url", "https://api.codeperf.io", "codeperf API URL")
rootCmd.MarkPersistentFlagRequired("bench")
rootCmd.PersistentFlags().StringVar(&bench, "bench", "", "Benchmark name")
//rootCmd.MarkPersistentFlagRequired("bench")
}

// Abbreviate the long hash to a short hash (7 digits)
Expand Down
2 changes: 1 addition & 1 deletion get.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
export VERIFY_CHECKSUM=0
export ALIAS=""
export OWNER=codeperfio
export REPO=pprof-exporter
export REPO=codeperf
export SUCCESS_CMD="$REPO version"
export BINLOCATION="/usr/local/bin"

Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright © 2021 codeperf.io hello <at> codeperf <dot> io
Copyright © 2021-present codeperf.io hello <at> codeperf <dot> io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down

0 comments on commit 974f011

Please sign in to comment.