diff --git a/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index 776dfd0a1..bb347bee8 100644 --- a/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -18,15 +18,18 @@ package fieldmanager_test import ( "errors" + "fmt" "net/http" "testing" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" + "sigs.k8s.io/yaml" ) type fakeObjectConvertor struct{} @@ -48,7 +51,7 @@ type fakeObjectDefaulter struct{} func (d *fakeObjectDefaulter) Default(in runtime.Object) {} -func NewTestFieldManager(t *testing.T) *fieldmanager.FieldManager { +func NewTestFieldManager() *fieldmanager.FieldManager { gv := schema.GroupVersion{ Group: "apps", Version: "v1", @@ -63,13 +66,13 @@ func NewTestFieldManager(t *testing.T) *fieldmanager.FieldManager { } func TestFieldManagerCreation(t *testing.T) { - if NewTestFieldManager(t) == nil { + if NewTestFieldManager() == nil { t.Fatal("failed to create FieldManager") } } func TestApplyStripsFields(t *testing.T) { - f := NewTestFieldManager(t) + f := NewTestFieldManager() obj := &corev1.Pod{} @@ -114,7 +117,7 @@ func TestApplyStripsFields(t *testing.T) { } func TestVersionCheck(t *testing.T) { - f := NewTestFieldManager(t) + f := NewTestFieldManager() obj := &corev1.Pod{} @@ -147,7 +150,7 @@ func TestVersionCheck(t *testing.T) { } func TestApplyDoesNotStripLabels(t *testing.T) { - f := NewTestFieldManager(t) + f := NewTestFieldManager() obj := &corev1.Pod{} @@ -173,3 +176,266 @@ func TestApplyDoesNotStripLabels(t *testing.T) { t.Fatalf("labels shouldn't get stripped on apply: %v", m) } } + +func BenchmarkApplyNewObject(b *testing.B) { + f := NewTestFieldManager() + + obj := &corev1.Pod{} + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := f.Apply(obj, []byte(`{ + "apiVersion": "apps/v1", + "kind": "Pod", + "metadata": { + "name": "b", + "namespace": "b", + "creationTimestamp": "2016-05-19T09:59:00Z", + }, + "map": { + "fieldA": 1, + "fieldB": 1, + "fieldC": 1, + "fieldD": 1, + "fieldE": 1, + "fieldF": 1, + "fieldG": 1, + "fieldH": 1, + "fieldI": 1, + "fieldJ": 1, + "fieldK": 1, + "fieldL": 1, + "fieldM": 1, + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "value": true + }, + }, + }, + }, + }, + } + }`), "fieldmanager_test", false) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUpdateNewObject(b *testing.B) { + f := NewTestFieldManager() + + oldObj := &corev1.Pod{} + y := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "b", + "namespace": "b", + "creationTimestamp": "2016-05-19T09:59:00Z", + }, + "map": { + "fieldA": 1, + "fieldB": 1, + "fieldC": 1, + "fieldD": 1, + "fieldE": 1, + "fieldF": 1, + "fieldG": 1, + "fieldH": 1, + "fieldI": 1, + "fieldJ": 1, + "fieldK": 1, + "fieldL": 1, + "fieldM": 1, + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "value": true + }, + }, + }, + }, + }, + }, + + }` + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &newObj.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := f.Update(oldObj, newObj, "fieldmanager_test") + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkRepeatedUpdate(b *testing.B) { + f := NewTestFieldManager() + + var oldObj runtime.Object + oldObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + y1 := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "b", + "namespace": "b", + "creationTimestamp": "2016-05-19T09:59:00Z", + }, + "map": { + "fieldA": 1, + "fieldB": 1, + "fieldC": 1, + "fieldD": 1, + "fieldE": 1, + "fieldF": 1, + "fieldG": 1, + "fieldH": 1, + "fieldI": 1, + "fieldJ": 1, + "fieldK": 1, + "fieldL": 1, + "fieldM": 1, + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "value": true + }, + }, + }, + }, + }, + }, + + }` + obj1 := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y1), &obj1.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + y2 := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "b", + "namespace": "b", + "creationTimestamp": "2016-05-19T09:59:00Z", + }, + "map": { + "fieldA": 1, + "fieldB": 1, + "fieldC": 1, + "fieldD": 1, + "fieldE": 1, + "fieldF": 1, + "fieldG": 1, + "fieldH": 1, + "fieldI": 1, + "fieldJ": 1, + "fieldK": 1, + "fieldL": 1, + "fieldM": 1, + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "value": false + }, + }, + }, + }, + }, + }, + + }` + obj2 := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y2), &obj2.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + y3 := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "b", + "namespace": "b", + "creationTimestamp": "2016-05-19T09:59:00Z", + }, + "map": { + "fieldA": 1, + "fieldB": 1, + "fieldC": 1, + "fieldD": 1, + "fieldE": 1, + "fieldF": 1, + "fieldG": 1, + "fieldH": 1, + "fieldI": 1, + "fieldJ": 1, + "fieldK": 1, + "fieldL": 1, + "fieldM": 1, + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "fieldN": { + "value": true + }, + }, + }, + }, + }, + "fieldO": 1, + "fieldP": 1, + "fieldQ": 1, + "fieldR": 1, + "fieldS": 1, + }, + + }` + obj3 := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y3), &obj3.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + + objs := []*unstructured.Unstructured{obj1, obj2, obj3} + + var err error + oldObj, err = f.Update(oldObj, objs[0], "fieldmanager_0") + if err != nil { + b.Fatal(err) + } + + oldObj, err = f.Update(oldObj, objs[1], "fieldmanager_1") + if err != nil { + b.Fatal(err) + } + + oldObj, err = f.Update(oldObj, objs[2], "fieldmanager_2") + if err != nil { + b.Fatal(err) + } + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + oldObj, err = f.Update(oldObj, objs[n%3], fmt.Sprintf("fieldmanager_%d", n%3)) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go b/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go index 681c22cbc..b3c95d069 100644 --- a/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go +++ b/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go @@ -24,6 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/value" ) // TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set @@ -107,3 +108,52 @@ func TestSetToFieldsError(t *testing.T) { } } } + +func BenchmarkSetToFields(b *testing.B) { + set := fieldpath.NewSet( + fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), + fieldpath.MakePathOrDie("foo", 0, "bar"), + fieldpath.MakePathOrDie("foo", 0), + fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 1, "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("first"))), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("first")), "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("second")), "bar"), + ) + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := SetToFields(*set) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkFieldsToSet(b *testing.B) { + set := fieldpath.NewSet( + fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), + fieldpath.MakePathOrDie("foo", 0, "bar"), + fieldpath.MakePathOrDie("foo", 0), + fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 1, "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("first"))), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("first")), "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", value.StringValue("second")), "bar"), + ) + fields, err := SetToFields(*set) + if err != nil { + b.Fatal(err) + } + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := FieldsToSet(fields) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go b/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go index 8e41f0796..a29e25c85 100644 --- a/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go +++ b/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go @@ -23,6 +23,7 @@ import ( "strings" "testing" + "sigs.k8s.io/structured-merge-diff/typed" "sigs.k8s.io/yaml" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -175,3 +176,59 @@ Final object: %#v`, obj, newObj) } } + +var result typed.TypedValue + +func BenchmarkYAMLToTyped(b *testing.B) { + y := ` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +` + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + + d, err := fakeSchema.OpenAPISchema() + if err != nil { + b.Fatalf("Failed to parse OpenAPI schema: %v", err) + } + m, err := proto.NewOpenAPIData(d) + if err != nil { + b.Fatalf("Failed to build OpenAPI models: %v", err) + } + + tc, err := internal.NewTypeConverter(m) + if err != nil { + b.Fatalf("Failed to build TypeConverter: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + + var r typed.TypedValue + for i := 0; i < b.N; i++ { + var err error + r, err = tc.ObjectToTyped(obj) + if err != nil { + b.Fatalf("Failed to convert object to typed: %v", err) + } + } + result = r +}