istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/krt/bench_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package krt_test 16 17 import ( 18 "fmt" 19 "net" 20 "reflect" 21 "testing" 22 23 v1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 klabels "k8s.io/apimachinery/pkg/labels" 26 "k8s.io/apimachinery/pkg/types" 27 "k8s.io/apimachinery/pkg/watch" 28 29 "istio.io/istio/pkg/config/labels" 30 "istio.io/istio/pkg/kube" 31 "istio.io/istio/pkg/kube/controllers" 32 "istio.io/istio/pkg/kube/kclient" 33 "istio.io/istio/pkg/kube/kclient/clienttest" 34 "istio.io/istio/pkg/kube/krt" 35 "istio.io/istio/pkg/log" 36 "istio.io/istio/pkg/slices" 37 "istio.io/istio/pkg/test" 38 ) 39 40 type Workload struct { 41 krt.Named 42 ServiceNames []string 43 IP string 44 } 45 46 // GetLabelSelector defaults to using Reflection which is slow. Provide a specialized implementation that does it more efficiently. 47 type ServiceWrapper struct{ *v1.Service } 48 49 func (s ServiceWrapper) GetLabelSelector() map[string]string { 50 return s.Spec.Selector 51 } 52 53 var _ krt.LabelSelectorer = ServiceWrapper{} 54 55 func NewModern(c kube.Client, events chan string, _ <-chan struct{}) { 56 Pods := krt.NewInformer[*v1.Pod](c) 57 Services := krt.NewInformer[*v1.Service](c, krt.WithObjectAugmentation(func(o any) any { 58 return ServiceWrapper{o.(*v1.Service)} 59 })) 60 ServicesByNamespace := krt.NewNamespaceIndex(Services) 61 62 Workloads := krt.NewCollection(Pods, func(ctx krt.HandlerContext, p *v1.Pod) *Workload { 63 if p.Status.PodIP == "" { 64 return nil 65 } 66 services := krt.Fetch(ctx, Services, krt.FilterIndex(ServicesByNamespace, p.Namespace), krt.FilterSelectsNonEmpty(p.GetLabels())) 67 return &Workload{ 68 Named: krt.NewNamed(p), 69 IP: p.Status.PodIP, 70 ServiceNames: slices.Map(services, func(e *v1.Service) string { return e.Name }), 71 } 72 }) 73 Workloads.Register(func(e krt.Event[Workload]) { 74 events <- fmt.Sprintf(e.Latest().Name, e.Event) 75 }) 76 } 77 78 type legacy struct { 79 pods kclient.Client[*v1.Pod] 80 services kclient.Client[*v1.Service] 81 queue controllers.Queue 82 workloads map[types.NamespacedName]*Workload 83 handler func(event krt.Event[Workload]) 84 } 85 86 func getPodServices(allServices []*v1.Service, pod *v1.Pod) []*v1.Service { 87 var services []*v1.Service 88 for _, service := range allServices { 89 if labels.Instance(service.Spec.Selector).Match(pod.Labels) { 90 services = append(services, service) 91 } 92 } 93 94 return services 95 } 96 97 func (l *legacy) Reconcile(key types.NamespacedName) error { 98 pod := l.pods.Get(key.Name, key.Namespace) 99 if pod == nil || pod.Status.PodIP == "" { 100 old := l.workloads[key] 101 if old != nil { 102 ev := krt.Event[Workload]{ 103 Old: old, 104 Event: controllers.EventDelete, 105 } 106 l.handler(ev) 107 delete(l.workloads, key) 108 } 109 110 return nil 111 } 112 allServices := l.services.List(pod.Namespace, klabels.Everything()) 113 services := getPodServices(allServices, pod) 114 wl := &Workload{ 115 Named: krt.NewNamed(pod), 116 IP: pod.Status.PodIP, 117 ServiceNames: slices.Map(services, func(e *v1.Service) string { return e.Name }), 118 } 119 old := l.workloads[key] 120 if reflect.DeepEqual(old, wl) { 121 // No changes, NOP 122 return nil 123 } 124 // Changed. Update and call handlers 125 l.workloads[key] = wl 126 if old == nil { 127 l.handler(krt.Event[Workload]{ 128 New: wl, 129 Event: controllers.EventAdd, 130 }) 131 } else { 132 l.handler(krt.Event[Workload]{ 133 Old: old, 134 New: wl, 135 Event: controllers.EventUpdate, 136 }) 137 } 138 return nil 139 } 140 141 func NewLegacy(cl kube.Client, events chan string, stop <-chan struct{}) { 142 c := &legacy{ 143 workloads: map[types.NamespacedName]*Workload{}, 144 } 145 c.pods = kclient.New[*v1.Pod](cl) 146 c.services = kclient.New[*v1.Service](cl) 147 c.queue = controllers.NewQueue("pods", controllers.WithReconciler(c.Reconcile)) 148 c.pods.AddEventHandler(controllers.ObjectHandler(c.queue.AddObject)) 149 c.services.AddEventHandler(controllers.FromEventHandler(func(e controllers.Event) { 150 o := e.Latest() 151 for _, pod := range c.pods.List(o.GetNamespace(), klabels.SelectorFromValidatedSet(o.(*v1.Service).Spec.Selector)) { 152 c.queue.AddObject(pod) 153 } 154 })) 155 c.handler = func(e krt.Event[Workload]) { 156 events <- fmt.Sprintf(e.Latest().Name, e.Event) 157 } 158 go c.queue.Run(stop) 159 } 160 161 var nextIP = net.ParseIP("10.0.0.10") 162 163 func GetIP() string { 164 i := nextIP.To4() 165 ret := i.String() 166 v := uint(i[0])<<24 + uint(i[1])<<16 + uint(i[2])<<8 + uint(i[3]) 167 v++ 168 v3 := byte(v & 0xFF) 169 v2 := byte((v >> 8) & 0xFF) 170 v1 := byte((v >> 16) & 0xFF) 171 v0 := byte((v >> 24) & 0xFF) 172 nextIP = net.IPv4(v0, v1, v2, v3) 173 return ret 174 } 175 176 func drainN(c chan string, n int) { 177 for n > 0 { 178 n-- 179 <-c 180 } 181 } 182 183 func BenchmarkControllers(b *testing.B) { 184 log.FindScope("krt").SetOutputLevel(log.InfoLevel) 185 watch.DefaultChanSize = 100_000 186 initialPods := []*v1.Pod{} 187 for i := 0; i < 1000; i++ { 188 initialPods = append(initialPods, &v1.Pod{ 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: fmt.Sprintf("pod-%d", i), 191 Namespace: fmt.Sprintf("ns-%d", i%2), 192 Labels: map[string]string{ 193 "app": fmt.Sprintf("app-%d", i%25), 194 }, 195 }, 196 Spec: v1.PodSpec{ 197 ServiceAccountName: "fake-sa", 198 }, 199 Status: v1.PodStatus{ 200 Phase: v1.PodRunning, 201 PodIP: GetIP(), 202 }, 203 }) 204 } 205 initialServices := []*v1.Service{} 206 for i := 0; i < 50; i++ { 207 initialServices = append(initialServices, &v1.Service{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Name: fmt.Sprintf("pod-%d", i), 210 Namespace: fmt.Sprintf("ns-%d", i%2), 211 }, 212 Spec: v1.ServiceSpec{ 213 Selector: map[string]string{ 214 "app": fmt.Sprintf("app-%d", i%25), 215 }, 216 }, 217 }) 218 } 219 benchmark := func(b *testing.B, fn func(client kube.Client, events chan string, stop <-chan struct{})) { 220 c := kube.NewFakeClient() 221 events := make(chan string, 1000) 222 stop := test.NewStop(b) 223 fn(c, events, stop) 224 pods := clienttest.NewWriter[*v1.Pod](b, c) 225 services := clienttest.NewWriter[*v1.Service](b, c) 226 for _, p := range initialPods { 227 pods.Create(p) 228 } 229 for _, p := range initialServices { 230 services.Create(p) 231 } 232 b.ResetTimer() 233 c.RunAndWait(test.NewStop(b)) 234 drainN(events, 1000) 235 for n := 0; n < b.N; n++ { 236 for i := 0; i < 1000; i++ { 237 pods.Update(&v1.Pod{ 238 ObjectMeta: metav1.ObjectMeta{ 239 Name: fmt.Sprintf("pod-%d", i), 240 Namespace: fmt.Sprintf("ns-%d", i%2), 241 Labels: map[string]string{ 242 "app": fmt.Sprintf("app-%d", i%25), 243 }, 244 }, 245 Spec: v1.PodSpec{ 246 ServiceAccountName: "fake-sa", 247 }, 248 Status: v1.PodStatus{ 249 Phase: v1.PodRunning, 250 PodIP: GetIP(), 251 }, 252 }) 253 } 254 drainN(events, 1000) 255 } 256 } 257 b.Run("krt", func(b *testing.B) { 258 benchmark(b, NewModern) 259 }) 260 b.Run("legacy", func(b *testing.B) { 261 benchmark(b, NewLegacy) 262 }) 263 }