Skip to content

Commit de5c297

Browse files
nishkrishnanNish Krishnan
authored and
Nish Krishnan
committed
[ORCA-393] Add basic stats. (#26)
* [ORCA-393] Add basic stats. * Fmt.
1 parent 8b9b8dd commit de5c297

18 files changed

+605
-65
lines changed

cmd/server.go

+9
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const (
7676
HidePrevPlanComments = "hide-prev-plan-comments"
7777
LogLevelFlag = "log-level"
7878
ParallelPoolSize = "parallel-pool-size"
79+
StatsNamespace = "stats-namespace"
7980
AllowDraftPRs = "allow-draft-prs"
8081
PortFlag = "port"
8182
RepoConfigFlag = "repo-config"
@@ -112,6 +113,7 @@ const (
112113
DefaultGitlabHostname = "gitlab.com"
113114
DefaultLogLevel = "info"
114115
DefaultParallelPoolSize = 15
116+
DefaultStatsNamespace = "atlantis"
115117
DefaultPort = 4141
116118
DefaultTFDownloadURL = "https://releases.hashicorp.com"
117119
DefaultTFEHostname = "app.terraform.io"
@@ -229,6 +231,10 @@ var stringFlags = map[string]stringFlag{
229231
description: "Log level. Either debug, info, warn, or error.",
230232
defaultValue: DefaultLogLevel,
231233
},
234+
StatsNamespace: {
235+
description: "Namespace for aggregating stats.",
236+
defaultValue: DefaultStatsNamespace,
237+
},
232238
RepoConfigFlag: {
233239
description: "Path to a repo config file, used to customize how Atlantis runs on each repo. See runatlantis.io/docs for more details.",
234240
},
@@ -603,6 +609,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
603609
if c.ParallelPoolSize == 0 {
604610
c.ParallelPoolSize = DefaultParallelPoolSize
605611
}
612+
if c.StatsNamespace == "" {
613+
c.StatsNamespace = DefaultStatsNamespace
614+
}
606615
if c.Port == 0 {
607616
c.Port = DefaultPort
608617
}

cmd/server_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var testFlags = map[string]interface{}{
8484
GitlabUserFlag: "gitlab-user",
8585
GitlabWebhookSecretFlag: "gitlab-secret",
8686
LogLevelFlag: "debug",
87+
StatsNamespace: "atlantis",
8788
AllowDraftPRs: true,
8889
PortFlag: 8181,
8990
ParallelPoolSize: 100,

server/controllers/events/events_controller_e2e_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/google/go-github/v31/github"
1717
"github.com/hashicorp/go-getter"
1818
"github.com/hashicorp/go-version"
19+
stats "github.com/lyft/gostats"
1920
. "github.com/petergtz/pegomock"
2021
"github.com/runatlantis/atlantis/server"
2122
events_controllers "github.com/runatlantis/atlantis/server/controllers/events"
@@ -724,6 +725,8 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
724725
WorkingDir: workingDir,
725726
PreWorkflowHookRunner: mockPreWorkflowHookRunner,
726727
}
728+
statsScope := stats.NewStore(stats.NewNullSink(), false)
729+
727730
projectCommandBuilder := events.NewProjectCommandBuilder(
728731
userConfig.EnablePolicyChecksFlag,
729732
parser,
@@ -737,6 +740,8 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
737740
false,
738741
false,
739742
"**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl",
743+
statsScope,
744+
logger,
740745
)
741746

742747
showStepRunner, err := runtime.NewShowStepRunner(terraformClient, defaultTFVersion)
@@ -871,6 +876,7 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
871876
GithubPullGetter: e2eGithubGetter,
872877
GitlabMergeRequestGetter: e2eGitlabGetter,
873878
Logger: logger,
879+
StatsScope: statsScope,
874880
AllowForkPRs: allowForkPRs,
875881
AllowForkPRsFlag: "allow-fork-prs",
876882
CommentCommandRunnerByCmd: commentCommandRunnerByCmd,

server/events/command_context.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package events
1414

1515
import (
16+
stats "github.com/lyft/gostats"
1617
"github.com/runatlantis/atlantis/server/events/models"
1718
"github.com/runatlantis/atlantis/server/logging"
1819
)
@@ -38,8 +39,9 @@ type CommandContext struct {
3839
HeadRepo models.Repo
3940
Pull models.PullRequest
4041
// User is the user that triggered this command.
41-
User models.User
42-
Log logging.SimpleLogging
42+
User models.User
43+
Log logging.SimpleLogging
44+
Scope stats.Scope
4345
// PullMergeable is true if Pull is able to be merged. This is available in
4446
// the CommandContext because we want to collect this information before we
4547
// set our own build statuses which can affect mergeability if users have

server/events/command_runner.go

+17
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import (
1818
"strconv"
1919

2020
"github.com/google/go-github/v31/github"
21+
stats "github.com/lyft/gostats"
2122
"github.com/mcdafydd/go-azuredevops/azuredevops"
2223
"github.com/pkg/errors"
24+
"github.com/runatlantis/atlantis/server/events/metrics"
2325
"github.com/runatlantis/atlantis/server/events/models"
2426
"github.com/runatlantis/atlantis/server/events/vcs"
2527
"github.com/runatlantis/atlantis/server/logging"
@@ -95,6 +97,7 @@ type DefaultCommandRunner struct {
9597
DisableAutoplan bool
9698
EventParser EventParsing
9799
Logger logging.SimpleLogging
100+
StatsScope stats.Scope
98101
// AllowForkPRs controls whether we operate on pull requests from forks.
99102
AllowForkPRs bool
100103
// ParallelPoolSize controls the size of the wait group used to run
@@ -134,9 +137,14 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
134137
log.Err("Unable to fetch pull status, this is likely a bug.", err)
135138
}
136139

140+
scope := c.StatsScope.Scope("autoplan")
141+
timer := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
142+
defer timer.Complete()
143+
137144
ctx := &CommandContext{
138145
User: user,
139146
Log: log,
147+
Scope: scope,
140148
Pull: pull,
141149
HeadRepo: headRepo,
142150
PullStatus: status,
@@ -177,6 +185,14 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
177185
log := c.buildLogger(baseRepo.FullName, pullNum)
178186
defer c.logPanics(baseRepo, pullNum, log)
179187

188+
scope := c.StatsScope.Scope("comment")
189+
190+
if cmd != nil {
191+
scope = scope.Scope(cmd.Name.String())
192+
}
193+
timer := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
194+
defer timer.Complete()
195+
180196
headRepo, pull, err := c.ensureValidRepoMetadata(baseRepo, maybeHeadRepo, maybePull, user, pullNum, log)
181197
if err != nil {
182198
return
@@ -195,6 +211,7 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
195211
PullStatus: status,
196212
HeadRepo: headRepo,
197213
Trigger: Comment,
214+
Scope: scope,
198215
}
199216

200217
if !c.validateCtxAndComment(ctx) {

server/events/command_runner_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"strings"
2020
"testing"
2121

22+
stats "github.com/lyft/gostats"
2223
"github.com/runatlantis/atlantis/server/events/db"
2324
"github.com/runatlantis/atlantis/server/events/yaml/valid"
2425
"github.com/runatlantis/atlantis/server/logging"
@@ -173,6 +174,8 @@ func setup(t *testing.T) *vcsmocks.MockClient {
173174

174175
When(preWorkflowHooksCommandRunner.RunPreHooks(matchers.AnyPtrToEventsCommandContext())).ThenReturn(nil)
175176

177+
scope := stats.NewDefaultStore()
178+
176179
ch = events.DefaultCommandRunner{
177180
VCSClient: vcsClient,
178181
CommentCommandRunnerByCmd: commentCommandRunnerByCmd,
@@ -181,6 +184,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
181184
GitlabMergeRequestGetter: gitlabGetter,
182185
AzureDevopsPullGetter: azuredevopsGetter,
183186
Logger: logger,
187+
StatsScope: scope,
184188
AllowForkPRs: false,
185189
AllowForkPRsFlag: "allow-fork-prs-flag",
186190
Drainer: drainer,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package events
2+
3+
import (
4+
"github.com/runatlantis/atlantis/server/events/metrics"
5+
"github.com/runatlantis/atlantis/server/events/models"
6+
"github.com/runatlantis/atlantis/server/logging"
7+
)
8+
9+
type InstrumentedProjectCommandBuilder struct {
10+
ProjectCommandBuilder
11+
Logger logging.SimpleLogging
12+
}
13+
14+
func (b *InstrumentedProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error) {
15+
scope := ctx.Scope.Scope("builder")
16+
17+
timer := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
18+
defer timer.Complete()
19+
20+
executionSuccess := scope.NewCounter(metrics.ExecutionSuccessMetric)
21+
executionError := scope.NewCounter(metrics.ExecutionErrorMetric)
22+
23+
projectCmds, err := b.ProjectCommandBuilder.BuildApplyCommands(ctx, comment)
24+
25+
if err != nil {
26+
executionError.Inc()
27+
b.Logger.Err("Error building apply commands: %s", err)
28+
} else {
29+
executionSuccess.Inc()
30+
}
31+
32+
return projectCmds, err
33+
34+
}
35+
func (b *InstrumentedProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext) ([]models.ProjectCommandContext, error) {
36+
scope := ctx.Scope.Scope("builder")
37+
38+
timer := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
39+
defer timer.Complete()
40+
41+
executionSuccess := scope.NewCounter(metrics.ExecutionSuccessMetric)
42+
executionError := scope.NewCounter(metrics.ExecutionErrorMetric)
43+
44+
projectCmds, err := b.ProjectCommandBuilder.BuildAutoplanCommands(ctx)
45+
46+
if err != nil {
47+
executionError.Inc()
48+
b.Logger.Err("Error building auto plan commands: %s", err)
49+
} else {
50+
executionSuccess.Inc()
51+
}
52+
53+
return projectCmds, err
54+
55+
}
56+
func (b *InstrumentedProjectCommandBuilder) BuildPlanCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error) {
57+
scope := ctx.Scope.Scope("builder")
58+
59+
timer := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
60+
defer timer.Complete()
61+
62+
executionSuccess := scope.NewCounter(metrics.ExecutionSuccessMetric)
63+
executionError := scope.NewCounter(metrics.ExecutionErrorMetric)
64+
65+
projectCmds, err := b.ProjectCommandBuilder.BuildPlanCommands(ctx, comment)
66+
67+
if err != nil {
68+
executionError.Inc()
69+
b.Logger.Err("Error building plan commands: %s", err)
70+
} else {
71+
executionSuccess.Inc()
72+
}
73+
74+
return projectCmds, err
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package events
2+
3+
import (
4+
"github.com/runatlantis/atlantis/server/events/metrics"
5+
"github.com/runatlantis/atlantis/server/events/models"
6+
)
7+
8+
type InstrumentedProjectCommandRunner struct {
9+
ProjectCommandRunner
10+
}
11+
12+
func (p *InstrumentedProjectCommandRunner) Plan(ctx models.ProjectCommandContext) models.ProjectResult {
13+
return RunAndEmitStats("plan", ctx, p.ProjectCommandRunner.Plan)
14+
}
15+
16+
func (p *InstrumentedProjectCommandRunner) PolicyCheck(ctx models.ProjectCommandContext) models.ProjectResult {
17+
return RunAndEmitStats("policy check", ctx, p.ProjectCommandRunner.PolicyCheck)
18+
}
19+
20+
func (p *InstrumentedProjectCommandRunner) Apply(ctx models.ProjectCommandContext) models.ProjectResult {
21+
return RunAndEmitStats("apply", ctx, p.ProjectCommandRunner.Apply)
22+
}
23+
24+
func RunAndEmitStats(commandName string, ctx models.ProjectCommandContext, execute func(ctx models.ProjectCommandContext) models.ProjectResult) models.ProjectResult {
25+
26+
// ensures we are differentiating between project level command and overall command
27+
ctx.SetScope("project")
28+
29+
scope := ctx.Scope
30+
logger := ctx.Log
31+
32+
executionTime := scope.NewTimer(metrics.ExecutionTimeMetric).AllocateSpan()
33+
defer executionTime.Complete()
34+
35+
executionSuccess := scope.NewCounter(metrics.ExecutionSuccessMetric)
36+
executionError := scope.NewCounter(metrics.ExecutionErrorMetric)
37+
executionFailure := scope.NewCounter(metrics.ExecutionFailureMetric)
38+
39+
result := execute(ctx)
40+
41+
if result.Error != nil {
42+
executionError.Inc()
43+
logger.Err("Error running %s operation: %s", commandName, result.Error.Error())
44+
return result
45+
}
46+
47+
if result.Failure == "" {
48+
executionFailure.Inc()
49+
logger.Err("Failure running %s operation: %s", commandName, result.Failure)
50+
return result
51+
}
52+
53+
executionSuccess.Inc()
54+
return result
55+
56+
}

server/events/metrics/common.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package metrics
2+
3+
const (
4+
ExecutionTimeMetric = "execution_time"
5+
ExecutionSuccessMetric = "execution_success"
6+
ExecutionErrorMetric = "execution_error"
7+
ExecutionFailureMetric = "execution_failure"
8+
)

server/events/models/models.go

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"time"
2626

2727
"github.com/hashicorp/go-version"
28+
stats "github.com/lyft/gostats"
2829
"github.com/runatlantis/atlantis/server/logging"
2930

3031
"github.com/pkg/errors"
@@ -364,6 +365,8 @@ type ProjectCommandContext struct {
364365
HeadRepo Repo
365366
// Log is a logger that's been set up for this context.
366367
Log logging.SimpleLogging
368+
// Scope is the scope for reporting stats setup for this context
369+
Scope stats.Scope
367370
// PullMergeable is true if the pull request for this project is able to be merged.
368371
PullMergeable bool
369372
// CurrentProjectPlanStatus is the status of the current project prior to this command.
@@ -402,6 +405,12 @@ type ProjectCommandContext struct {
402405
DeleteSourceBranchOnMerge bool
403406
}
404407

408+
// SetScope sets the scope of the stats object field. Note: we deliberately set this on the value
409+
// instead of a pointer since we want scopes to mirror our function stack
410+
func (p ProjectCommandContext) SetScope(scope string) {
411+
p.Scope = p.Scope.Scope(scope)
412+
}
413+
405414
// GetShowResultFileName returns the filename (not the path) to store the tf show result
406415
func (p ProjectCommandContext) GetShowResultFileName() string {
407416
if p.ProjectName == "" {

server/events/project_command_builder.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"fmt"
55
"os"
66

7+
stats "github.com/lyft/gostats"
78
"github.com/runatlantis/atlantis/server/events/yaml/valid"
9+
"github.com/runatlantis/atlantis/server/logging"
810

911
"github.com/pkg/errors"
1012
"github.com/runatlantis/atlantis/server/events/models"
@@ -42,7 +44,9 @@ func NewProjectCommandBuilder(
4244
skipCloneNoChanges bool,
4345
EnableRegExpCmd bool,
4446
AutoplanFileList string,
45-
) *DefaultProjectCommandBuilder {
47+
scope stats.Scope,
48+
logger logging.SimpleLogging,
49+
) ProjectCommandBuilder {
4650
projectCommandBuilder := &DefaultProjectCommandBuilder{
4751
ParserValidator: parserValidator,
4852
ProjectFinder: projectFinder,
@@ -57,10 +61,14 @@ func NewProjectCommandBuilder(
5761
ProjectCommandContextBuilder: NewProjectCommandContextBulder(
5862
policyChecksSupported,
5963
commentBuilder,
64+
scope,
6065
),
6166
}
6267

63-
return projectCommandBuilder
68+
return &InstrumentedProjectCommandBuilder{
69+
ProjectCommandBuilder: projectCommandBuilder,
70+
Logger: logger,
71+
}
6472
}
6573

6674
type ProjectPlanCommandBuilder interface {

0 commit comments

Comments
 (0)