Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add informer metrics #209

Merged
merged 2 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }}
Comment on lines -86 to -90
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache was not used anyway.

- 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 }}
Comment on lines -107 to -111
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache was not used anyway

- 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
Loading