Skip to content

Commit

Permalink
feat: Add informer metrics (#209)
Browse files Browse the repository at this point in the history
Signed-off-by: jannfis <[email protected]>
  • Loading branch information
jannfis authored Nov 1, 2024
1 parent 61613f0 commit 46b9eea
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 107 deletions.
10 changes: 0 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ jobs:
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Download all Go modules
run: |
go mod download
Expand All @@ -104,11 +99,6 @@ jobs:
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Download all Go modules
run: |
go mod download
Expand Down
153 changes: 85 additions & 68 deletions internal/informer/appproject/projectinformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/watch"

"github.com/argoproj-labs/argocd-agent/internal/informer"
"github.com/argoproj-labs/argocd-agent/internal/metrics"
)

type AppProjectInformer struct {
Expand All @@ -41,6 +42,8 @@ type AppProjectInformer struct {
addFunc func(proj *v1alpha1.AppProject)
updateFunc func(oldProj *v1alpha1.AppProject, newProj *v1alpha1.AppProject)
deleteFunc func(proj *v1alpha1.AppProject)

metrics *metrics.AppProjectWatcherMetrics
}

type AppProjectInformerOption func(pi *AppProjectInformer) error
Expand Down Expand Up @@ -98,6 +101,15 @@ func WithNamespaces(namespaces ...string) AppProjectInformerOption {
}
}

// WithMetrics sets the AppProject watcher metrics to be used with this
// informer.
func WithMetrics(m *metrics.AppProjectWatcherMetrics) AppProjectInformerOption {
return func(pi *AppProjectInformer) error {
pi.metrics = m
return nil
}
}

// NewAppProjectInformer returns a new instance of a GenericInformer set up to
// handle AppProjects. It will be configured with the given options, using the
// given appclientset.
Expand All @@ -114,77 +126,82 @@ func NewAppProjectInformer(ctx context.Context, client appclientset.Interface, n
if pi.logger == nil {
pi.logger = logrus.WithField("module", "AppProjectInformer")
}
iopts := []informer.InformerOption{}
if pi.metrics != nil {
iopts = append(iopts, informer.WithMetrics(pi.metrics.ProjectsAdded, pi.metrics.ProjectsUpdated, pi.metrics.ProjectsRemoved, pi.metrics.ProjectsWatched))
}
i, err := informer.NewGenericInformer(&v1alpha1.AppProject{},
informer.WithListCallback(func(options v1.ListOptions, namespace string) (runtime.Object, error) {
log().Infof("Listing AppProjects in namespace %s", namespace)
projects, err := client.ArgoprojV1alpha1().AppProjects(namespace).List(ctx, options)
log().Infof("Lister returned %d AppProjects", len(projects.Items))
if pi.filterFunc != nil {
newItems := make([]v1alpha1.AppProject, 0)
for _, p := range projects.Items {
if pi.filterFunc(&p) {
newItems = append(newItems, p)
append([]informer.InformerOption{
informer.WithListCallback(func(options v1.ListOptions, namespace string) (runtime.Object, error) {
log().Infof("Listing AppProjects in namespace %s", namespace)
projects, err := client.ArgoprojV1alpha1().AppProjects(namespace).List(ctx, options)
log().Infof("Lister returned %d AppProjects", len(projects.Items))
if pi.filterFunc != nil {
newItems := make([]v1alpha1.AppProject, 0)
for _, p := range projects.Items {
if pi.filterFunc(&p) {
newItems = append(newItems, p)
}
}
pi.logger.Debugf("Lister has %d AppProjects after filtering", len(newItems))
projects.Items = newItems
}
return projects, err
}),
informer.WithNamespaces(pi.namespaces...),
informer.WithWatchCallback(func(options v1.ListOptions, namespace string) (watch.Interface, error) {
log().Info("Watching AppProjects")
return client.ArgoprojV1alpha1().AppProjects(namespace).Watch(ctx, options)
}),
informer.WithAddCallback(func(obj interface{}) {
log().Info("Add AppProject Callback")
proj, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Received add event for unknown type %T", obj)
return
}
pi.logger.Debugf("AppProject add event: %s", proj.Name)
if pi.addFunc != nil {
pi.addFunc(proj)
}
}),
informer.WithUpdateCallback(func(oldObj, newObj interface{}) {
log().Info("Update AppProject Callback")
oldProj, oldProjOk := oldObj.(*v1alpha1.AppProject)
newProj, newProjOk := newObj.(*v1alpha1.AppProject)
if !newProjOk || !oldProjOk {
pi.logger.Errorf("Received update event for unknown type old:%T new:%T", oldObj, newObj)
return
}
pi.logger.Debugf("AppProject update event: old:%s new:%s", oldProj.Name, newProj.Name)
if pi.updateFunc != nil {
pi.updateFunc(oldProj, newProj)
}
}),
informer.WithDeleteCallback(func(obj interface{}) {
log().Info("Delete AppProject Callback")
proj, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Received delete event for unknown type %T", obj)
return
}
pi.logger.Debugf("AppProject delete event: %s", proj.Name)
if pi.deleteFunc != nil {
pi.deleteFunc(proj)
}
}),
informer.WithFilterFunc(func(obj interface{}) bool {
if pi.filterFunc == nil {
return true
}
o, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Failed type conversion for unknown type %T", obj)
return false
}
pi.logger.Debugf("Lister has %d AppProjects after filtering", len(newItems))
projects.Items = newItems
}
return projects, err
}),
informer.WithNamespaces(pi.namespaces...),
informer.WithWatchCallback(func(options v1.ListOptions, namespace string) (watch.Interface, error) {
log().Info("Watching AppProjects")
return client.ArgoprojV1alpha1().AppProjects(namespace).Watch(ctx, options)
}),
informer.WithAddCallback(func(obj interface{}) {
log().Info("Add AppProject Callback")
proj, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Received add event for unknown type %T", obj)
return
}
pi.logger.Debugf("AppProject add event: %s", proj.Name)
if pi.addFunc != nil {
pi.addFunc(proj)
}
}),
informer.WithUpdateCallback(func(oldObj, newObj interface{}) {
log().Info("Update AppProject Callback")
oldProj, oldProjOk := oldObj.(*v1alpha1.AppProject)
newProj, newProjOk := newObj.(*v1alpha1.AppProject)
if !newProjOk || !oldProjOk {
pi.logger.Errorf("Received update event for unknown type old:%T new:%T", oldObj, newObj)
return
}
pi.logger.Debugf("AppProject update event: old:%s new:%s", oldProj.Name, newProj.Name)
if pi.updateFunc != nil {
pi.updateFunc(oldProj, newProj)
}
}),
informer.WithDeleteCallback(func(obj interface{}) {
log().Info("Delete AppProject Callback")
proj, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Received delete event for unknown type %T", obj)
return
}
pi.logger.Debugf("AppProject delete event: %s", proj.Name)
if pi.deleteFunc != nil {
pi.deleteFunc(proj)
}
}),
informer.WithFilterFunc(func(obj interface{}) bool {
if pi.filterFunc == nil {
return true
}
o, ok := obj.(*v1alpha1.AppProject)
if !ok {
pi.logger.Errorf("Failed type conversion for unknown type %T", obj)
return false
}
return pi.filterFunc(o)
}),
)
return pi.filterFunc(o)
}),
}, iopts...)...)
pi.projectInformer = i
pi.projectLister = applisters.NewAppProjectLister(i.Indexer())
return pi, err
Expand Down
51 changes: 51 additions & 0 deletions internal/informer/informer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ type GenericInformer struct {
fieldSelector string
// mutex should be owned before accessing namespaces
namespaces map[string]interface{}
// metrics is the metrics provider for this informer
metrics metrics
}

// metrics is a simplified and specialized metrics provider for the informer
type metrics struct {
added metricsCounter
updated metricsCounter
removed metricsCounter
watched metricsGauge
}

// metricsCounter is an interface for a metrics implementation of type counter.
// The reason we use an oversimplified abstraction is for testing purposes.
type metricsCounter interface {
Inc()
}

// metricsGauge is an interface for a metrics implementation of type gauge.
// The reason we use an oversimplified abstraction is for testing purposes.
type metricsGauge interface {
Inc()
Dec()
Set(float64)
}

type InformerOption func(i *GenericInformer) error
Expand Down Expand Up @@ -124,6 +148,17 @@ func WithFieldSelector(sel string) InformerOption {
}
}

// WithMetrics sets the metrics functions to use with this informer.
func WithMetrics(added, updated, removed metricsCounter, watched metricsGauge) InformerOption {
return func(i *GenericInformer) error {
i.metrics.added = added
i.metrics.updated = updated
i.metrics.removed = removed
i.metrics.watched = watched
return nil
}
}

// WithNamespaces sets the namespaces for which the informer will process any
// event. If an event is seen for an object in a namespace that is not in this
// list, the event will be ignored. If either zero or multiple namespaces are
Expand Down Expand Up @@ -166,6 +201,9 @@ func NewGenericInformer(objType runtime.Object, options ...InformerOption) (*Gen
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
logCtx.Trace("Executing list")
if i.metrics.watched != nil {
i.metrics.watched.Set(0)
}
return i.listFunc(options, i.watchAndListNamespace())
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
Expand Down Expand Up @@ -202,6 +240,10 @@ func NewGenericInformer(objType runtime.Object, options ...InformerOption) (*Gen
if i.addFunc != nil {
i.addFunc(obj)
}
if i.metrics.added != nil && i.metrics.watched != nil {
i.metrics.added.Inc()
i.metrics.watched.Inc()
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
mobj, err := meta.Accessor(newObj)
Expand All @@ -221,6 +263,9 @@ func NewGenericInformer(objType runtime.Object, options ...InformerOption) (*Gen
if i.updateFunc != nil {
i.updateFunc(oldObj, newObj)
}
if i.metrics.updated != nil {
i.metrics.updated.Inc()
}
},
DeleteFunc: func(obj interface{}) {
mobj, err := meta.Accessor(obj)
Expand All @@ -240,6 +285,10 @@ func NewGenericInformer(objType runtime.Object, options ...InformerOption) (*Gen
if i.deleteFunc != nil {
i.deleteFunc(obj)
}
if i.metrics.removed != nil && i.metrics.watched != nil {
i.metrics.removed.Inc()
i.metrics.watched.Dec()
}
},
})
if err != nil {
Expand Down Expand Up @@ -342,6 +391,8 @@ func (i *GenericInformer) watchAndListNamespace() string {
// isNamespaceAllowed returns whether the namespace of an event's object is
// permitted.
func (i *GenericInformer) isNamespaceAllowed(obj v1.Object) bool {
i.mutex.RLock()
defer i.mutex.RUnlock()
if len(i.namespaces) == 0 {
return true
}
Expand Down
Loading

0 comments on commit 46b9eea

Please sign in to comment.