From 6454403607dad6d44a23d790a1e282a725ab6e20 Mon Sep 17 00:00:00 2001 From: filipecosta90 Date: Mon, 29 Apr 2024 01:36:31 +0100 Subject: [PATCH] Update bin name and references --- README.md | 10 ++-- cmd/export.go | 89 ++++++++++++++++++----------------- cmd/project_parser.go | 54 ++++++++++++++++++++++ cmd/root.go | 105 +++++++++++++++++++++++++++++++++++++++--- get.sh | 2 +- main.go | 2 +- 6 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 cmd/project_parser.go diff --git a/README.md b/README.md index 117a9fe..2c4ba7c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmd/export.go b/cmd/export.go index 9c56e05..dc05879 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -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 { @@ -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 { @@ -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)) } } diff --git a/cmd/project_parser.go b/cmd/project_parser.go new file mode 100644 index 0000000..90636d7 --- /dev/null +++ b/cmd/project_parser.go @@ -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 +} diff --git a/cmd/root.go b/cmd/root.go index a4bcb98..9fb64dd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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" @@ -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 @@ -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()) } @@ -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() @@ -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) diff --git a/get.sh b/get.sh index 97c4535..178f60b 100755 --- a/get.sh +++ b/get.sh @@ -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" diff --git a/main.go b/main.go index 74fb0e4..5bc5fce 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ /* -Copyright © 2021 codeperf.io hello codeperf io +Copyright © 2021-present codeperf.io hello codeperf io Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.