Skip to content

Commit 3d206a7

Browse files
authored
Add performance monitoring support (#233)
* Add input * Add monitoring * Add test * Fix linting
1 parent f6cdf8f commit 3d206a7

File tree

9 files changed

+155
-10
lines changed

9 files changed

+155
-10
lines changed

gitclone/command_runner.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,24 @@ type CommandRunner interface {
1919
RunForOutput(c *command.Model) (string, error)
2020
Run(c *command.Model) error
2121
RunWithRetry(getCommmand func() *command.Model) error
22+
SetPerformanceMonitoring(enable bool)
23+
PausePerformanceMonitoring()
24+
ResumePerformanceMonitoring()
2225
}
2326

2427
// DefaultRunner ...
2528
type DefaultRunner struct {
29+
performanceMonitoringEnabled bool
30+
performanceMonitoringTemporarilyDisabled bool
2631
}
2732

2833
// RunForOutput ...
29-
func (r DefaultRunner) RunForOutput(c *command.Model) (string, error) {
34+
func (r *DefaultRunner) RunForOutput(c *command.Model) (string, error) {
3035
fmt.Println()
3136
log.Infof("$ %s &> out", c.PrintableCommandArgs())
3237

38+
r.setupPerformanceMonitoring(c)
39+
3340
out, err := c.RunAndReturnTrimmedCombinedOutput()
3441
if err != nil && errorutil.IsExitStatusError(err) {
3542
return out, errors.New(out)
@@ -39,11 +46,13 @@ func (r DefaultRunner) RunForOutput(c *command.Model) (string, error) {
3946
}
4047

4148
// Run ...
42-
func (r DefaultRunner) Run(c *command.Model) error {
49+
func (r *DefaultRunner) Run(c *command.Model) error {
4350
fmt.Println()
4451
log.Infof("$ %s", c.PrintableCommandArgs())
4552
var buffer bytes.Buffer
4653

54+
r.setupPerformanceMonitoring(c)
55+
4756
err := c.SetStdout(os.Stdout).SetStderr(io.MultiWriter(os.Stderr, &buffer)).Run()
4857
if err != nil {
4958
if errorutil.IsExitStatusError(err) {
@@ -60,7 +69,7 @@ func (r DefaultRunner) Run(c *command.Model) error {
6069
}
6170

6271
// RunWithRetry ...
63-
func (r DefaultRunner) RunWithRetry(getCommand func() *command.Model) error {
72+
func (r *DefaultRunner) RunWithRetry(getCommand func() *command.Model) error {
6473
return retry.Times(2).Wait(5).Try(func(attempt uint) error {
6574
if attempt > 0 {
6675
log.Warnf("Retrying...")
@@ -75,3 +84,26 @@ func (r DefaultRunner) RunWithRetry(getCommand func() *command.Model) error {
7584
return err
7685
})
7786
}
87+
88+
func (r *DefaultRunner) SetPerformanceMonitoring(enable bool) {
89+
r.performanceMonitoringEnabled = enable
90+
}
91+
92+
func (r *DefaultRunner) PausePerformanceMonitoring() {
93+
r.performanceMonitoringTemporarilyDisabled = true
94+
}
95+
96+
func (r *DefaultRunner) ResumePerformanceMonitoring() {
97+
r.performanceMonitoringTemporarilyDisabled = false
98+
}
99+
100+
func (r *DefaultRunner) setupPerformanceMonitoring(c *command.Model) {
101+
if r.performanceMonitoringTemporarilyDisabled {
102+
c.AppendEnvs("GIT_TRACE2_PERF=0")
103+
return
104+
}
105+
106+
if r.performanceMonitoringEnabled {
107+
c.AppendEnvs("GIT_TRACE2_PERF=1")
108+
}
109+
}

gitclone/command_runner_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package gitclone
2+
3+
import (
4+
"os/exec"
5+
"strings"
6+
"testing"
7+
8+
"github.com/bitrise-io/go-utils/command"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestPerformanceMonitoring(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
initialState *bool
16+
shouldDisable bool
17+
want *string
18+
}{
19+
{
20+
name: "Normal case",
21+
initialState: nil,
22+
want: nil,
23+
},
24+
{
25+
name: "Enable performance monitoring",
26+
initialState: pointer(true),
27+
want: pointer("1"),
28+
},
29+
{
30+
name: "Disable performance monitoring",
31+
initialState: pointer(false),
32+
},
33+
{
34+
name: "Temporarily disable performance monitoring",
35+
initialState: pointer(true),
36+
shouldDisable: true,
37+
want: pointer("0"),
38+
},
39+
}
40+
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
r := DefaultRunner{}
44+
45+
if tt.initialState != nil {
46+
r.SetPerformanceMonitoring(*tt.initialState)
47+
}
48+
49+
if tt.shouldDisable {
50+
r.PausePerformanceMonitoring()
51+
}
52+
53+
cmd := command.New("echo", "hello")
54+
err := r.Run(cmd)
55+
require.NoError(t, err)
56+
57+
value, ok := getEnv(cmd.GetCmd(), "GIT_TRACE2_PERF")
58+
59+
if tt.want == nil {
60+
require.Equal(t, "", value)
61+
require.False(t, ok)
62+
return
63+
}
64+
65+
if tt.shouldDisable {
66+
require.Equal(t, "0", value)
67+
require.True(t, ok)
68+
return
69+
}
70+
71+
require.Equal(t, *tt.want, value)
72+
require.True(t, ok)
73+
})
74+
}
75+
}
76+
77+
func pointer[T any](d T) *T {
78+
return &d
79+
}
80+
81+
func getEnv(cmd *exec.Cmd, key string) (string, bool) {
82+
for _, env := range cmd.Env {
83+
if strings.HasPrefix(env, key) {
84+
return strings.TrimPrefix(env, key+"="), true
85+
}
86+
}
87+
return "", false
88+
}

gitclone/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616
jobsFlag = "--jobs=10"
1717
)
1818

19-
var runner CommandRunner = DefaultRunner{}
19+
var runner CommandRunner = &DefaultRunner{}
2020

2121
func isOriginPresent(gitCmd git.Git, dir, repoURL string) (bool, error) {
2222
absDir, err := pathutil.AbsPath(dir)

gitclone/gitclone.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ type GitCloner struct {
5151
mergeRefChecker bitriseapi.MergeRefChecker
5252
}
5353

54-
func NewGitCloner(logger log.Logger, tracker tracker.StepTracker, cmdFactory command.Factory, patchSource bitriseapi.PatchSource, mergeRefChecker bitriseapi.MergeRefChecker) GitCloner {
54+
func NewGitCloner(logger log.Logger, tracker tracker.StepTracker, cmdFactory command.Factory, patchSource bitriseapi.PatchSource, mergeRefChecker bitriseapi.MergeRefChecker, performanceMonitoring bool) GitCloner {
55+
runner.SetPerformanceMonitoring(performanceMonitoring)
56+
5557
return GitCloner{
5658
logger: logger,
5759
tracker: tracker,

gitclone/gitclone_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ func Test_checkoutState(t *testing.T) {
495495
envRepo := env.NewRepository()
496496
logger := log.NewLogger()
497497
tracker := tracker.NewStepTracker(envRepo, logger)
498-
cloner := NewGitCloner(log.NewLogger(), tracker, command.NewFactory(envRepo), tt.patchSource, tt.mergeRefChecker)
498+
cloner := NewGitCloner(log.NewLogger(), tracker, command.NewFactory(envRepo), tt.patchSource, tt.mergeRefChecker, false)
499499
_, _, actualErr := cloner.checkoutState(git.Git{}, tt.cfg)
500500

501501
// Then

gitclone/mock_command_runner_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ func (m *MockRunner) GivenRunWithRetryFailsAfter(times int) *MockRunner {
8989
return m
9090
}
9191

92+
func (m *MockRunner) SetPerformanceMonitoring(enable bool) {
93+
}
94+
95+
func (m *MockRunner) PausePerformanceMonitoring() {
96+
}
97+
98+
func (m *MockRunner) ResumePerformanceMonitoring() {
99+
}
100+
92101
func (m *MockRunner) rememberCommand(args mock.Arguments) {
93102
var cmdModel *command.Model
94103
switch res := args[0].(type) {

gitclone/output.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ func (e *OutputExporter) gitOutputs(gitRef string, isPR bool) []gitOutput {
112112
}
113113

114114
func (e *OutputExporter) printLogAndExportEnv(command *v1command.Model, env string, maxEnvLength int) error {
115+
runner.PausePerformanceMonitoring()
116+
defer runner.ResumePerformanceMonitoring()
117+
115118
l, err := runner.RunForOutput(command)
116119
if err != nil {
117120
return fmt.Errorf("command failed: %s", err)

step.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ inputs:
227227
- "No"
228228
- "Yes"
229229

230+
- performance_monitoring: "no"
231+
opts:
232+
category: Debug
233+
title: Performance monitoring
234+
summary: Enable performance monitoring.
235+
description: Prints extra performance related information for every git operation.
236+
value_options:
237+
- "no"
238+
- "yes"
239+
230240
- build_url: $BITRISE_BUILD_URL
231241
opts:
232242
category: Debug

step/step.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ type Input struct {
3939
PRUnverifiedMergeBranch string `env:"pull_request_unverified_merge_branch"`
4040
PRHeadBranch string `env:"pull_request_head_branch"`
4141

42-
ResetRepository bool `env:"reset_repository,opt[Yes,No]"`
43-
BuildURL string `env:"build_url"`
44-
BuildAPIToken string `env:"build_api_token"`
42+
ResetRepository bool `env:"reset_repository,opt[Yes,No]"`
43+
PerformanceMonitoring bool `env:"performance_monitoring,opt[yes,no]"`
44+
BuildURL string `env:"build_url"`
45+
BuildAPIToken string `env:"build_api_token"`
4546
}
4647

4748
// Config is the git clone step configuration
@@ -105,7 +106,7 @@ func (g GitCloneStep) Run(cfg Config) (gitclone.CheckoutStateResult, error) {
105106
gitCloneCfg := convertConfig(cfg)
106107
patchSource := bitriseapi.NewPatchSource(cfg.BuildURL, cfg.BuildAPIToken)
107108
mergeRefChecker := bitriseapi.NewMergeRefChecker(cfg.BuildURL, cfg.BuildAPIToken, retry.NewHTTPClient(), g.logger, g.tracker)
108-
cloner := gitclone.NewGitCloner(g.logger, g.tracker, g.cmdFactory, patchSource, mergeRefChecker)
109+
cloner := gitclone.NewGitCloner(g.logger, g.tracker, g.cmdFactory, patchSource, mergeRefChecker, cfg.PerformanceMonitoring)
109110
return cloner.CheckoutState(gitCloneCfg)
110111
}
111112

0 commit comments

Comments
 (0)