Add benchmarks for FieldManager handling

We don't have a lot of data on allocations and how much time it takes to
run apply or update on objects, so adding some benchmark will help us
investigate possible improvements.
This commit is contained in:
Antoine Pelisse 2019-07-09 14:00:21 -07:00
parent 5a7e336689
commit 92cf3764f9
5 changed files with 382 additions and 5 deletions

View File

@ -47,7 +47,9 @@ go_test(
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -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)
}
}
}

View File

@ -53,6 +53,8 @@ go_test(
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/typed:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -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)
}
}
}

View File

@ -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
}