Skip to content

Commit c50fc29

Browse files
perf: reduce remaining hot-path overhead (#4661)
Signed-off-by: Sertac Ozercan <sozercan@gmail.com> Signed-off-by: Jaydip Gabani <gabanijaydip@gmail.com> Co-authored-by: Jaydip Gabani <gabanijaydip@gmail.com>
1 parent 16b4c2e commit c50fc29

45 files changed

Lines changed: 2859 additions & 216 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, tracker *readiness.
432432
case *externaldataProviderResponseCacheTTL > 0:
433433
providerResponseCache := frameworksexternaldata.NewProviderResponseCache(ctx, *externaldataProviderResponseCacheTTL)
434434
args = append(args, rego.AddExternalDataProviderResponseCache(providerResponseCache))
435+
mutationOpts.ProviderResponseCache = providerResponseCache
435436
case *externaldataProviderResponseCacheTTL == 0:
436437
setupLog.Info("external data provider response cache is disabled")
437438
default:

pkg/audit/manager.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ func (am *Manager) auditResources(
493493
for gv, gvKinds := range clusterAPIResources {
494494
kindsLoop:
495495
for kind := range gvKinds {
496+
if !shouldAuditKind(matchedKinds, kind) {
497+
continue
498+
}
496499
am.log.V(logging.DebugLevel).Info("Listing objects for GVK", "group", gv.Group, "version", gv.Version, "kind", kind)
497500
// delete all existing folders from cache dir before starting next kind
498501
err := am.removeAllFromDir(*apiCacheDir, *auditChunkSize)
@@ -502,10 +505,6 @@ func (am *Manager) auditResources(
502505
}
503506
// tracking number of folders created for this kind
504507
folderCount := 0
505-
_, matchAll := matchedKinds["*"]
506-
if _, found := matchedKinds[kind]; !found && !matchAll {
507-
continue
508-
}
509508

510509
objList := &unstructured.UnstructuredList{}
511510
opts := &client.ListOptions{
@@ -585,6 +584,13 @@ func (am *Manager) auditResources(
585584
return nil
586585
}
587586

587+
func shouldAuditKind(matchedKinds map[string]bool, kind string) bool {
588+
if matchedKinds["*"] {
589+
return true
590+
}
591+
return matchedKinds[kind]
592+
}
593+
588594
func (am *Manager) auditFromCache(ctx context.Context) ([]Result, []error) {
589595
objs, err := am.auditCache.ListObjects(ctx)
590596
if err != nil {

pkg/audit/manager_benchmark_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,71 @@ func writeBenchmarkPerObjectFiles(tb testing.TB, directory string, objects []uns
148148
require.NoError(tb, os.WriteFile(path.Join(directory, strconv.Itoa(i)), jsonBytes, 0o600))
149149
}
150150
}
151+
152+
func TestShouldAuditKind(t *testing.T) {
153+
for _, tc := range []struct {
154+
name string
155+
matchedKinds map[string]bool
156+
kind string
157+
want bool
158+
}{
159+
{name: "explicit match", matchedKinds: map[string]bool{"Pod": true}, kind: "Pod", want: true},
160+
{name: "explicit mismatch", matchedKinds: map[string]bool{"Pod": true}, kind: "ConfigMap"},
161+
{name: "wildcard", matchedKinds: map[string]bool{"*": true}, kind: "ConfigMap", want: true},
162+
} {
163+
t.Run(tc.name, func(t *testing.T) {
164+
if got := shouldAuditKind(tc.matchedKinds, tc.kind); got != tc.want {
165+
t.Fatalf("shouldAuditKind(%v, %q) = %t, want %t", tc.matchedKinds, tc.kind, got, tc.want)
166+
}
167+
})
168+
}
169+
}
170+
171+
func BenchmarkAuditSkippedKindAvoidsCacheCleanup(b *testing.B) {
172+
const staleDirs = 100
173+
am := benchmarkAuditManager(b)
174+
matchedKinds := map[string]bool{"Pod": true}
175+
176+
b.Run("old_order_cleanup_before_skip", func(b *testing.B) {
177+
root := b.TempDir()
178+
b.ReportAllocs()
179+
for i := 0; i < b.N; i++ {
180+
b.StopTimer()
181+
populateAuditCacheDirs(b, root, staleDirs)
182+
b.StartTimer()
183+
if err := am.removeAllFromDir(root, *auditChunkSize); err != nil {
184+
b.Fatal(err)
185+
}
186+
if shouldAuditKind(matchedKinds, "ConfigMap") {
187+
b.Fatal("expected ConfigMap to be skipped")
188+
}
189+
}
190+
})
191+
192+
b.Run("new_order_skip_before_cleanup", func(b *testing.B) {
193+
root := b.TempDir()
194+
populateAuditCacheDirs(b, root, staleDirs)
195+
b.ReportAllocs()
196+
for i := 0; i < b.N; i++ {
197+
if !shouldAuditKind(matchedKinds, "ConfigMap") {
198+
continue
199+
}
200+
if err := am.removeAllFromDir(root, *auditChunkSize); err != nil {
201+
b.Fatal(err)
202+
}
203+
}
204+
})
205+
}
206+
207+
func populateAuditCacheDirs(tb testing.TB, root string, count int) {
208+
tb.Helper()
209+
for i := 0; i < count; i++ {
210+
dir := path.Join(root, "stale-"+strconv.Itoa(i))
211+
if err := os.MkdirAll(dir, 0o750); err != nil {
212+
tb.Fatal(err)
213+
}
214+
if err := os.WriteFile(path.Join(dir, auditObjectsFile), []byte("[]"), 0o600); err != nil {
215+
tb.Fatal(err)
216+
}
217+
}
218+
}

pkg/audit/manager_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ package audit
33
import (
44
"container/heap"
55
"context"
6+
"encoding/json"
67
"flag"
78
"fmt"
9+
"net/http"
10+
"net/http/httptest"
811
"os"
912
"path"
1013
"reflect"
1114
"strconv"
15+
"sync/atomic"
1216
"testing"
1317
"time"
1418

@@ -37,8 +41,10 @@ import (
3741
"k8s.io/apimachinery/pkg/runtime/schema"
3842
"k8s.io/apimachinery/pkg/types"
3943
"k8s.io/client-go/kubernetes/scheme"
44+
"k8s.io/client-go/rest"
4045
"sigs.k8s.io/controller-runtime/pkg/client"
4146
"sigs.k8s.io/controller-runtime/pkg/client/fake"
47+
controllerruntimemanager "sigs.k8s.io/controller-runtime/pkg/manager"
4248
)
4349

4450
func Test_SVQueue(t *testing.T) {
@@ -608,6 +614,130 @@ func Test_removeAllFromDir(t *testing.T) {
608614
})
609615
}
610616

617+
type auditResourcesTestManager struct {
618+
controllerruntimemanager.Manager
619+
config *rest.Config
620+
}
621+
622+
func (m *auditResourcesTestManager) GetConfig() *rest.Config {
623+
return m.config
624+
}
625+
626+
type auditResourcesTestClient struct {
627+
client.Client
628+
cacheDir string
629+
constraintListCalls int
630+
resourceListCalls int
631+
}
632+
633+
func (c *auditResourcesTestClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
634+
switch list.GetObjectKind().GroupVersionKind().Kind {
635+
case "ConfigMapList":
636+
c.resourceListCalls++
637+
return nil
638+
case "K8sRequiredLabelsList":
639+
c.constraintListCalls++
640+
default:
641+
return fmt.Errorf("unexpected list GVK %s", list.GetObjectKind().GroupVersionKind())
642+
}
643+
644+
constraintList, ok := list.(*unstructured.UnstructuredList)
645+
if !ok {
646+
return fmt.Errorf("unexpected list type %T", list)
647+
}
648+
constraintList.Items = []unstructured.Unstructured{{Object: map[string]interface{}{
649+
"spec": map[string]interface{}{
650+
"match": map[string]interface{}{
651+
"kinds": []interface{}{
652+
map[string]interface{}{"kinds": []interface{}{"Pod"}},
653+
},
654+
},
655+
},
656+
}}}
657+
658+
sentinelDir := path.Join(c.cacheDir, "sentinel")
659+
if err := os.Mkdir(sentinelDir, 0o750); err != nil {
660+
return err
661+
}
662+
return os.WriteFile(path.Join(sentinelDir, auditObjectsFile), []byte("[]"), 0o600)
663+
}
664+
665+
func TestAuditResourcesSkipsUnmatchedKindBeforeCleanup(t *testing.T) {
666+
oldAPICacheDir := *apiCacheDir
667+
oldAuditMatchKindOnly := *auditMatchKindOnly
668+
t.Cleanup(func() {
669+
*apiCacheDir = oldAPICacheDir
670+
*auditMatchKindOnly = oldAuditMatchKindOnly
671+
})
672+
673+
cacheDir := t.TempDir()
674+
*apiCacheDir = cacheDir
675+
*auditMatchKindOnly = true
676+
677+
var coreResourcesDiscovered atomic.Bool
678+
discoveryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
679+
w.Header().Set("Content-Type", "application/json")
680+
var response interface{}
681+
switch r.URL.Path {
682+
case "/api":
683+
response = metav1.APIVersions{
684+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "APIVersions"},
685+
Versions: []string{"v1"},
686+
}
687+
case "/api/v1":
688+
coreResourcesDiscovered.Store(true)
689+
response = metav1.APIResourceList{
690+
GroupVersion: "v1",
691+
APIResources: []metav1.APIResource{{
692+
Name: "configmaps",
693+
Namespaced: true,
694+
Kind: "ConfigMap",
695+
Verbs: metav1.Verbs{"list"},
696+
}},
697+
}
698+
case "/apis":
699+
response = metav1.APIGroupList{
700+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "APIGroupList"},
701+
}
702+
default:
703+
http.NotFound(w, r)
704+
return
705+
}
706+
if err := json.NewEncoder(w).Encode(response); err != nil {
707+
t.Errorf("encoding discovery response: %v", err)
708+
}
709+
}))
710+
t.Cleanup(discoveryServer.Close)
711+
712+
testClient := &auditResourcesTestClient{
713+
Client: fake.NewClientBuilder().Build(),
714+
cacheDir: cacheDir,
715+
}
716+
am := &Manager{
717+
client: testClient,
718+
mgr: &auditResourcesTestManager{
719+
config: &rest.Config{Host: discoveryServer.URL},
720+
},
721+
log: logr.Discard(),
722+
}
723+
724+
err := am.auditResources(
725+
context.Background(),
726+
[]schema.GroupVersionKind{{Group: "constraints.gatekeeper.sh", Version: "v1beta1", Kind: "K8sRequiredLabelsList"}},
727+
map[util.KindVersionName]*LimitQueue{},
728+
map[util.KindVersionName]int64{},
729+
map[util.EnforcementAction]int64{},
730+
"test-timestamp",
731+
&auditExportPublishingState{Errors: map[string]error{}},
732+
)
733+
require.NoError(t, err)
734+
require.True(t, coreResourcesDiscovered.Load(), "core resources were not discovered")
735+
require.Equal(t, 1, testClient.constraintListCalls)
736+
require.Zero(t, testClient.resourceListCalls, "resource List called for unmatched ConfigMap")
737+
_, err = os.Stat(path.Join(cacheDir, "sentinel", auditObjectsFile))
738+
require.NoError(t, err, "cache cleanup ran for unmatched ConfigMap")
739+
}
740+
611741
func Test_readUnstructured(t *testing.T) {
612742
am := Manager{}
613743

0 commit comments

Comments
 (0)