Skip to content

Commit 88a2765

Browse files
authored
DRIVERS-3254: Add perfcomp to drivers-evergreen-tools (#677)
1 parent 61eacf1 commit 88a2765

File tree

11 files changed

+1165
-0
lines changed

11 files changed

+1165
-0
lines changed

.evergreen/perfcomp/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# perfcomp
2+
3+
**perfcomp** is a performance analyzer on a PR commit basis.
4+
5+
## 📦 Installation
6+
7+
To install the latest version:
8+
9+
```bash
10+
go install github.com/mongodb-labs/drivers-evergreen-tools/perfcomp/cmd/perfcomp@latest
11+
```
12+
13+
Or build it locally in `bin/perfcomp`:
14+
15+
```bash
16+
bash build.sh
17+
```
18+
19+
## 🔧 Usage
20+
21+
### Parameters
22+
23+
To use `perfcomp`, you should have an analytics node URI env variable called `PERF_URI_PRIVATE_ENDPOINT`. You can request for it from the devprod performance team.
24+
25+
To run in your project repository, you need to create a [performance context](https://performance-monitoring-and-analysis.server-tig.prod.corp.mongodb.com/contexts) that captures all benchmarks in your project. This needs to be a triage context. Feel free to refer to the [Go Driver context](https://performance-monitoring-and-analysis.server-tig.prod.corp.mongodb.com/context/name/GoDriver%20perf%20task) as a template.
26+
27+
> _If you are creating a triage context for the first time, it may take a few hours for your project's data to be tagged._
28+
29+
You also need the name of the performance task and variant specific to your project. You can do a query in the analytics node `raw_results` collection:
30+
31+
```
32+
db.raw_results.find({
33+
“info.project”: “<project>”,
34+
“info.version”: “<random_evergreen_version>"
35+
})
36+
```
37+
38+
and look for the `variant` and `task_name` properties.
39+
40+
### perfcomp CLI
41+
42+
```bash
43+
perfcomp is a cli that reports stat-sig results between evergreen patches with the mainline commit
44+
45+
Usage:
46+
perfcomp [command]
47+
48+
Available Commands:
49+
compare compare evergreen patch to mainline commit
50+
mdreport generates markdown output after run
51+
```
52+
53+
### Commands
54+
55+
#### compare
56+
57+
```bash
58+
compare evergreen patch to mainline commit
59+
60+
Usage:
61+
perfcomp compare [version_id] [flags]
62+
63+
Flags:
64+
--perf-context string specify the performance triage context, ex. "GoDriver perf task" (required)
65+
--project string specify the name of an existing Evergreen project, ex. "mongo-go-driver" (required)
66+
--task string specify the evergreen perf task name, ex. "perf" (required)
67+
--variant string specify the perf task variant, ex. "perf" (required)
68+
```
69+
70+
#### mdreport
71+
72+
```bash
73+
generates markdown output after compare run (must be run after `compare`)
74+
75+
Usage:
76+
perfcomp mdreport
77+
```
78+
79+
### Run via shell script
80+
81+
Alternatively, you can run the perfcomp shell script. This script will run build and then run `compare`. From the root directory,
82+
83+
```bash
84+
PERF_URI_PRIVATE_ENDPOINT="<perf_uri>" VERSION_ID="<version>" PROJECT="<project>" CONTEXT="<context>" TASK="<task>" VARIANT="<variant>" .evergreen/run-perf-comp.sh
85+
```
86+
87+
If you would like to see a markdown preview of the report, you can also pass in `HEAD_SHA=""`. This will generate `.evergreen/perfcomp/perf-report.md`.

.evergreen/perfcomp/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
BIN_DIR="bin"
5+
mkdir -p $BIN_DIR
6+
go build -o $BIN_DIR/perfcomp ./cmd/perfcomp/
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"math"
8+
"os"
9+
"sort"
10+
"strings"
11+
"text/tabwriter"
12+
"time"
13+
14+
"github.com/mongodb-labs/drivers-evergreen-tools/perfcomp"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
func newCompareCommand() *cobra.Command {
19+
cmd := &cobra.Command{
20+
Use: "compare",
21+
Short: "compare evergreen patch to mainline commit",
22+
// Version id is a required argument
23+
Args: func(cmd *cobra.Command, args []string) error {
24+
if len(args) < 1 {
25+
return fmt.Errorf("this command requires an evergreen patch version ID")
26+
}
27+
return nil
28+
},
29+
}
30+
31+
var project, task, variant, perfcontext string
32+
cmd.Flags().StringVar(&project, "project", "", `specify the name of an existing Evergreen project, ex. "mongo-go-driver"`)
33+
cmd.Flags().StringVar(&perfcontext, "perf-context", "", `specify the performance triage context, ex. "GoDriver perf task"`)
34+
// TODO(DRIVERS-3264): Use first task / variant of the project by default for perf filtering
35+
cmd.Flags().StringVar(&task, "task", "", `specify the evergreen performance task name, ex. "perf"`)
36+
cmd.Flags().StringVar(&variant, "variant", "", `specify the performance variant, ex. "perf"`)
37+
38+
for _, flag := range []string{"project", "task", "variant", "context"} {
39+
cmd.MarkFlagRequired(flag)
40+
}
41+
42+
cmd.Run = func(cmd *cobra.Command, args []string) {
43+
// Check for variables
44+
uri := os.Getenv("PERF_URI_PRIVATE_ENDPOINT")
45+
if uri == "" {
46+
log.Fatal("PERF_URI_PRIVATE_ENDPOINT env variable is not set")
47+
}
48+
49+
// Validate all flags
50+
for _, flag := range []string{"project", "task", "variant", "perf-context"} {
51+
if flag == "" {
52+
log.Fatalf("must provide %s", flag)
53+
}
54+
}
55+
56+
// Run compare function
57+
err := runCompare(cmd, args,
58+
perfcomp.WithProject(project),
59+
perfcomp.WithTask(task),
60+
perfcomp.WithVariant(variant),
61+
perfcomp.WithContext(perfcontext),
62+
)
63+
if err != nil {
64+
log.Fatalf("failed to compare: %v", err)
65+
}
66+
}
67+
68+
return cmd
69+
}
70+
71+
func createComment(result perfcomp.CompareResult) string {
72+
var comment strings.Builder
73+
74+
if len(result.SigEnergyStats) == 0 {
75+
comment.Reset()
76+
fmt.Fprintf(&comment, "There were no significant changes to the performance to report for version %s.\n", result.Version)
77+
} else {
78+
fmt.Fprintf(&comment, "The following benchmark tests for version %s had statistically significant changes (i.e., |z-score| > 1.96):\n\n", result.Version)
79+
80+
w := tabwriter.NewWriter(&comment, 0, 0, 1, ' ', 0)
81+
82+
fmt.Fprintln(w, "| Benchmark\t| Measurement\t| % Change\t| Patch Value\t| Stable Region\t| H-Score\t| Z-Score\t| ")
83+
fmt.Fprintln(w, "| ---------\t| -----------\t| --------\t| -----------\t| -------------\t| -------\t| -------\t|")
84+
85+
sort.Slice(result.SigEnergyStats, func(i, j int) bool {
86+
return math.Abs(result.SigEnergyStats[i].PercentChange) > math.Abs(result.SigEnergyStats[j].PercentChange)
87+
})
88+
for _, es := range result.SigEnergyStats {
89+
fmt.Fprintf(w, "| %s\t| %s\t| %.4f\t| %.4f\t| Avg: %.4f, Med: %.4f, Stdev: %.4f\t| %.4f\t| %.4f\t|\n",
90+
es.Benchmark,
91+
es.Measurement,
92+
es.PercentChange,
93+
es.MeasurementVal,
94+
es.StableRegion.Mean,
95+
es.StableRegion.Median,
96+
es.StableRegion.Std,
97+
es.HScore,
98+
es.ZScore,
99+
)
100+
}
101+
102+
w.Flush()
103+
}
104+
105+
comment.WriteString("\n*For a comprehensive view of all microbenchmark results for this PR's commit, please check out the Evergreen perf task for this patch.*")
106+
return comment.String()
107+
108+
}
109+
110+
func runCompare(cmd *cobra.Command, args []string, opts ...perfcomp.CompareOption) error {
111+
perfAnalyticsConnString := os.Getenv("PERF_URI_PRIVATE_ENDPOINT")
112+
version := args[len(args)-1]
113+
opts = append(opts, perfcomp.WithVersion(version))
114+
115+
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
116+
defer cancel()
117+
118+
res, err := perfcomp.Compare(ctx, perfAnalyticsConnString, opts...)
119+
if err != nil {
120+
log.Fatalf("failed to compare: %v", err)
121+
}
122+
123+
res.CommitSHA = os.Getenv("HEAD_SHA")
124+
res.MainlineCommit = os.Getenv("BASE_SHA")
125+
126+
prComment := createComment(*res)
127+
log.Println("🧪 Performance Results")
128+
log.Println(prComment)
129+
130+
if res.CommitSHA != "" {
131+
// Write results to .txt file to parse into markdown comment
132+
fWrite, err := os.Create(perfReportFileTxt)
133+
if err != nil {
134+
log.Fatalf("Could not create %s: %v", perfReportFileTxt, err)
135+
}
136+
defer fWrite.Close()
137+
138+
fmt.Fprintf(fWrite, "Version ID: %s\n", version)
139+
fmt.Fprintf(fWrite, "Commit SHA: %s\n", res.CommitSHA)
140+
fmt.Fprintln(fWrite, prComment)
141+
log.Printf("PR commit %s: saved to %s for markdown comment.\n", res.CommitSHA, perfReportFileTxt)
142+
}
143+
144+
return nil
145+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func main() {
10+
cmd := &cobra.Command{
11+
Use: "perfcomp",
12+
Short: "perfcomp is a cli that reports stat-sig results between evergreen patches with the mainline commit",
13+
Version: "0.0.0-alpha",
14+
}
15+
16+
cmd.AddCommand(newCompareCommand())
17+
cmd.AddCommand(newMdCommand())
18+
19+
if err := cmd.Execute(); err != nil {
20+
log.Fatalf("error: %v", err)
21+
}
22+
}

0 commit comments

Comments
 (0)