Skip to content

Commit dd36be1

Browse files
authored
/metrics: prometheus metrics for git syncs and bundle builds (#118)
This change adds a couple of bundle build and git sync related metrics to the service, and exposes them via a /metrics endpoint for consumption by Prometheus. Related to #90. Signed-off-by: bdjgs <jgs@bankdata.dk>
1 parent 0d5f454 commit dd36be1

File tree

7 files changed

+159
-8
lines changed

7 files changed

+159
-8
lines changed

e2e/cli/run_git_bundle_build.txtar

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# local example git repo
2+
cd repo
3+
env HOME=.
4+
exec git init .
5+
exec git branch -m main
6+
exec git config --global user.name "botbotbot"
7+
exec git config --global user.email "bot@bot.bot"
8+
exec git add .
9+
exec git commit -m initial-commit
10+
cd ..
11+
12+
exec $OPACTL run --addr 127.0.0.1:8283 --config config.d/bundle.yml --data-dir tmp1 &opactl&
13+
! stderr .
14+
! stdout .
15+
16+
exec curl --retry 5 --retry-all-errors http://127.0.0.1:8283/metrics
17+
18+
# assert git sync and bundle build metrics
19+
stdout 'ocp_git_sync_count_total{repo="./repo/",source="git-data",state="SUCCESS"} 1'
20+
stdout 'ocp_git_sync_duration_seconds_count{repo="./repo/",source="git-data"} 1'
21+
stdout 'ocp_git_sync_duration_seconds_sum.*'
22+
stdout 'ocp_git_sync_duration_seconds_bucket.*'
23+
stdout 'ocp_bundle_build_duration_seconds_count{bundle="hello-world"} 1'
24+
stdout 'ocp_bundle_build_duration_seconds_sum.*'
25+
stdout 'ocp_bundle_build_duration_seconds_bucket.*'
26+
stdout 'ocp_bundle_build_count_total{bundle="hello-world",state="SUCCESS"} 1'
27+
28+
kill opactl
29+
30+
-- repo/users/data.json --
31+
[
32+
{
33+
"id": "alice"
34+
},
35+
{
36+
"id": "bob"
37+
}
38+
]
39+
-- config.d/bundle.yml --
40+
bundles:
41+
hello-world:
42+
object_storage:
43+
filesystem:
44+
path: bundles/hello-world/bundle.tar.gz
45+
requirements:
46+
- source: git-data
47+
sources:
48+
git-data:
49+
git:
50+
repo: ./repo/
51+
reference: main

internal/gitsync/gitsync.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"strings"
1616
"sync"
17+
"time"
1718

1819
"github.com/bradleyfalzon/ghinstallation/v2"
1920
"github.com/go-git/go-git/v5"
@@ -24,6 +25,7 @@ import (
2425
"github.com/go-git/go-git/v5/plumbing/transport/http"
2526
gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
2627
"github.com/styrainc/opa-control-plane/internal/config"
28+
"github.com/styrainc/opa-control-plane/internal/metrics"
2729
"golang.org/x/crypto/ssh"
2830
"gopkg.in/yaml.v3"
2931
)
@@ -59,9 +61,14 @@ func New(path string, config config.Git, sourceName string) *Synchronizer {
5961
// Execute performs the synchronization of the configured Git repository. If the repository does not exist
6062
// on disk, clone it. If it does exist, pull the latest changes and rebase the local branch onto the remote branch.
6163
func (s *Synchronizer) Execute(ctx context.Context) error {
64+
startTime := time.Now()
65+
6266
if err := s.execute(ctx); err != nil {
67+
metrics.GitSyncFailed(s.sourceName, s.config.Repo)
6368
return fmt.Errorf("source %q: git synchronizer: %v: %w", s.sourceName, s.config.Repo, err)
6469
}
70+
71+
metrics.GitSyncSucceeded(s.sourceName, s.config.Repo, startTime)
6572
return nil
6673
}
6774

internal/metrics/metrics.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package metrics
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/prometheus/client_golang/prometheus/promhttp"
7+
)
8+
9+
func Handler() http.Handler {
10+
return promhttp.Handler()
11+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package metrics
2+
3+
import (
4+
"time"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
"github.com/prometheus/client_golang/prometheus/promauto"
8+
)
9+
10+
var (
11+
GitSyncCount = promauto.NewCounterVec(
12+
prometheus.CounterOpts{
13+
Name: "ocp_git_sync_count_total",
14+
Help: "Number of times a git sync has been performed and its state",
15+
},
16+
[]string{"source", "repo", "state"},
17+
)
18+
19+
GitSyncDuration = promauto.NewHistogramVec(
20+
prometheus.HistogramOpts{
21+
Name: "ocp_git_sync_duration_seconds",
22+
Help: "Git sync duration in seconds",
23+
Buckets: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.5, 2, 5, 10, 30, 60},
24+
},
25+
[]string{"source", "repo"},
26+
)
27+
)
28+
29+
func GitSyncFailed(source string, repo string) {
30+
GitSyncCount.WithLabelValues(source, repo, "FAILED").Inc()
31+
}
32+
33+
func GitSyncSucceeded(source string, repo string, startTime time.Time) {
34+
GitSyncCount.WithLabelValues(source, repo, "SUCCESS").Inc()
35+
GitSyncDuration.WithLabelValues(source, repo).Observe(float64(time.Since(startTime).Seconds()))
36+
}

internal/metrics/metrics_worker.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package metrics
2+
3+
import (
4+
"time"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
"github.com/prometheus/client_golang/prometheus/promauto"
8+
)
9+
10+
var (
11+
BundleBuildCount = promauto.NewCounterVec(
12+
prometheus.CounterOpts{
13+
Name: "ocp_bundle_build_count_total",
14+
Help: "Number of times a bundle build has been performed and its state",
15+
},
16+
[]string{"bundle", "state"},
17+
)
18+
19+
BundleBuildDuration = promauto.NewHistogramVec(
20+
prometheus.HistogramOpts{
21+
Name: "ocp_bundle_build_duration_seconds",
22+
Help: "Bundle build duration in seconds",
23+
Buckets: []float64{0.1, 0.2, 0.5, 1, 1.5, 2, 5, 10, 30, 60},
24+
},
25+
[]string{"bundle"},
26+
)
27+
)
28+
29+
func BundleBuildFailed(bundle string, state string) {
30+
BundleBuildCount.WithLabelValues(bundle, state).Inc()
31+
}
32+
33+
func BundleBuildSucceeded(bundle string, state string, startTime time.Time) {
34+
BundleBuildCount.WithLabelValues(bundle, state).Inc()
35+
BundleBuildDuration.WithLabelValues(bundle).Observe(float64(time.Since(startTime).Seconds()))
36+
}

internal/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/styrainc/opa-control-plane/internal/config"
1717
"github.com/styrainc/opa-control-plane/internal/database"
18+
"github.com/styrainc/opa-control-plane/internal/metrics"
1819
"github.com/styrainc/opa-control-plane/internal/server/types"
1920
)
2021

@@ -33,6 +34,7 @@ func (s *Server) Init() *Server {
3334
s.router = http.NewServeMux()
3435
}
3536

37+
s.router.Handle("/metrics", metrics.Handler())
3638
s.router.HandleFunc("GET /health", s.health)
3739
s.router.HandleFunc("GET /v1/sources/{source}/data/{path...}", authenticationMiddleware(s.db, s.v1SourcesDataGet))
3840
s.router.HandleFunc("POST /v1/sources/{source}/data/{path...}", authenticationMiddleware(s.db, s.v1SourcesDataPut))

internal/service/worker.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/styrainc/opa-control-plane/internal/builder"
1010
"github.com/styrainc/opa-control-plane/internal/config"
1111
"github.com/styrainc/opa-control-plane/internal/logging"
12+
"github.com/styrainc/opa-control-plane/internal/metrics"
1213
"github.com/styrainc/opa-control-plane/internal/progress"
1314
"github.com/styrainc/opa-control-plane/internal/s3"
1415
)
@@ -93,6 +94,7 @@ func (worker *BundleWorker) UpdateConfig(b *config.Bundle, sources []*config.Sou
9394
// Execute runs a bundle synchronization iteration: git sync, bundle construct
9495
// and then push bundles to object storage.
9596
func (w *BundleWorker) Execute(ctx context.Context) time.Time {
97+
startTime := time.Now() // Used for timing metric
9698

9799
defer w.bar.Add(1)
98100

@@ -106,22 +108,22 @@ func (w *BundleWorker) Execute(ctx context.Context) time.Time {
106108
for _, src := range w.sources {
107109
if err := src.Wipe(); err != nil {
108110
w.log.Warnf("failed to remove a directory for bundle %q: %v", w.bundleConfig.Name, err)
109-
return w.report(ctx, BuildStateInternalError, err)
111+
return w.report(ctx, BuildStateInternalError, startTime, err)
110112
}
111113
}
112114

113115
for _, synchronizer := range w.synchronizers {
114116
err := synchronizer.Execute(ctx)
115117
if err != nil {
116118
w.log.Warnf("failed to synchronize bundle %q: %v", w.bundleConfig.Name, err)
117-
return w.report(ctx, BuildStateSyncFailed, err)
119+
return w.report(ctx, BuildStateSyncFailed, startTime, err)
118120
}
119121
}
120122

121123
for _, src := range w.sources {
122124
if err := src.Transform(ctx); err != nil {
123125
w.log.Warnf("failed to evaluate source %q for bundle %q: %v", src.Name, w.bundleConfig.Name, err)
124-
return w.report(ctx, BuildStateTransformFailed, err)
126+
return w.report(ctx, BuildStateTransformFailed, startTime, err)
125127
}
126128
}
127129

@@ -135,24 +137,24 @@ func (w *BundleWorker) Execute(ctx context.Context) time.Time {
135137
err := b.Build(ctx)
136138
if err != nil {
137139
w.log.Warnf("failed to build a bundle %q: %v", w.bundleConfig.Name, err)
138-
return w.report(ctx, BuildStateBuildFailed, err)
140+
return w.report(ctx, BuildStateBuildFailed, startTime, err)
139141
}
140142

141143
if w.storage != nil {
142144
if err := w.storage.Upload(ctx, bytes.NewReader(buffer.Bytes())); err != nil {
143145
w.log.Warnf("failed to upload bundle %q: %v", w.bundleConfig.Name, err)
144-
return w.report(ctx, BuildStatePushFailed, err)
146+
return w.report(ctx, BuildStatePushFailed, startTime, err)
145147
}
146148

147149
w.log.Debugf("Bundle %q built and uploaded.", w.bundleConfig.Name)
148-
return w.report(ctx, BuildStateSuccess, nil)
150+
return w.report(ctx, BuildStateSuccess, startTime, nil)
149151
}
150152

151153
w.log.Debugf("Bundle %q built.", w.bundleConfig.Name)
152-
return w.report(ctx, BuildStateSuccess, nil)
154+
return w.report(ctx, BuildStateSuccess, startTime, nil)
153155
}
154156

155-
func (w *BundleWorker) report(ctx context.Context, state BuildState, err error) time.Time {
157+
func (w *BundleWorker) report(ctx context.Context, state BuildState, startTime time.Time, err error) time.Time {
156158
w.status.State = state
157159
if err != nil {
158160
if _, ok := err.(ast.Errors); ok {
@@ -162,6 +164,12 @@ func (w *BundleWorker) report(ctx context.Context, state BuildState, err error)
162164
}
163165
}
164166

167+
if state == BuildStateSuccess {
168+
metrics.BundleBuildSucceeded(w.bundleConfig.Name, state.String(), startTime)
169+
} else {
170+
metrics.BundleBuildFailed(w.bundleConfig.Name, state.String())
171+
}
172+
165173
if w.singleShot {
166174
return w.die(ctx)
167175
}

0 commit comments

Comments
 (0)