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:
parent
c925c445dd
commit
2f450e5e39
|
@ -18,15 +18,18 @@ package fieldmanager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeObjectConvertor struct{}
|
type fakeObjectConvertor struct{}
|
||||||
|
@ -48,7 +51,7 @@ type fakeObjectDefaulter struct{}
|
||||||
|
|
||||||
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
|
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
|
||||||
|
|
||||||
func NewTestFieldManager(t *testing.T) *fieldmanager.FieldManager {
|
func NewTestFieldManager() *fieldmanager.FieldManager {
|
||||||
gv := schema.GroupVersion{
|
gv := schema.GroupVersion{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
|
@ -63,13 +66,13 @@ func NewTestFieldManager(t *testing.T) *fieldmanager.FieldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFieldManagerCreation(t *testing.T) {
|
func TestFieldManagerCreation(t *testing.T) {
|
||||||
if NewTestFieldManager(t) == nil {
|
if NewTestFieldManager() == nil {
|
||||||
t.Fatal("failed to create FieldManager")
|
t.Fatal("failed to create FieldManager")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyStripsFields(t *testing.T) {
|
func TestApplyStripsFields(t *testing.T) {
|
||||||
f := NewTestFieldManager(t)
|
f := NewTestFieldManager()
|
||||||
|
|
||||||
obj := &corev1.Pod{}
|
obj := &corev1.Pod{}
|
||||||
|
|
||||||
|
@ -114,7 +117,7 @@ func TestApplyStripsFields(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVersionCheck(t *testing.T) {
|
func TestVersionCheck(t *testing.T) {
|
||||||
f := NewTestFieldManager(t)
|
f := NewTestFieldManager()
|
||||||
|
|
||||||
obj := &corev1.Pod{}
|
obj := &corev1.Pod{}
|
||||||
|
|
||||||
|
@ -147,7 +150,7 @@ func TestVersionCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyDoesNotStripLabels(t *testing.T) {
|
func TestApplyDoesNotStripLabels(t *testing.T) {
|
||||||
f := NewTestFieldManager(t)
|
f := NewTestFieldManager()
|
||||||
|
|
||||||
obj := &corev1.Pod{}
|
obj := &corev1.Pod{}
|
||||||
|
|
||||||
|
@ -173,3 +176,266 @@ func TestApplyDoesNotStripLabels(t *testing.T) {
|
||||||
t.Fatalf("labels shouldn't get stripped on apply: %v", m)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
"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
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/structured-merge-diff/typed"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -175,3 +176,59 @@ Final object:
|
||||||
%#v`, obj, newObj)
|
%#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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue