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.

Kubernetes-commit: 92cf3764f979e63317c8f483d8e841e0358599f4
This commit is contained in:
Antoine Pelisse 2019-07-09 14:00:21 -07:00 committed by Kubernetes Publisher
parent c925c445dd
commit 2f450e5e39
3 changed files with 378 additions and 5 deletions

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

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