istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/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 xds_test 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "path" 22 "strconv" 23 "strings" 24 "testing" 25 "text/template" 26 "time" 27 28 "github.com/Masterminds/sprig/v3" 29 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 30 31 meshconfig "istio.io/api/mesh/v1alpha1" 32 networking "istio.io/api/networking/v1alpha3" 33 security "istio.io/api/security/v1beta1" 34 "istio.io/api/type/v1beta1" 35 "istio.io/istio/pilot/pkg/config/kube/crd" 36 "istio.io/istio/pilot/pkg/features" 37 "istio.io/istio/pilot/pkg/model" 38 "istio.io/istio/pilot/pkg/networking/core/route" 39 v3 "istio.io/istio/pilot/pkg/xds/v3" 40 "istio.io/istio/pilot/test/xds" 41 "istio.io/istio/pilot/test/xdstest" 42 "istio.io/istio/pkg/config" 43 "istio.io/istio/pkg/config/constants" 44 "istio.io/istio/pkg/config/host" 45 "istio.io/istio/pkg/config/mesh" 46 "istio.io/istio/pkg/config/schema/gvk" 47 "istio.io/istio/pkg/env" 48 istiolog "istio.io/istio/pkg/log" 49 "istio.io/istio/pkg/spiffe" 50 "istio.io/istio/pkg/test" 51 "istio.io/istio/pkg/test/util/yml" 52 "istio.io/istio/pkg/util/protomarshal" 53 ) 54 55 // ConfigInput defines inputs passed to the test config templates 56 // This allows tests to do things like create a virtual service for each service, for example 57 type ConfigInput struct { 58 // Name of the test 59 Name string 60 // Name of the test config file to use. If not set, <Name> is used 61 ConfigName string 62 // Number of services to make 63 Services int 64 // Number of instances to make 65 Instances int 66 // If set, only run for this config type 67 OnlyRunType string 68 // If set, skip runs for this config type 69 SkipType string 70 // ResourceType of proxy to generate configs for. If not set, sidecar is used 71 ProxyType model.NodeType 72 } 73 74 var testCases = []ConfigInput{ 75 { 76 // Gateways provides an example config for a large Ingress deployment. This will create N 77 // virtual services and gateways, where routing is determined by hostname, meaning we generate N routes for HTTPS. 78 Name: "gateways", 79 Services: 1000, 80 ProxyType: model.Router, 81 }, 82 { 83 // Gateways-shared provides an example config for a large Ingress deployment. This will create N 84 // virtual services and gateways, where routing is determined by path. This means there will be a single large route. 85 Name: "gateways-shared", 86 Services: 1000, 87 ProxyType: model.Router, 88 }, 89 { 90 // Knative Gateway simulates a full Knative routing setup. There have been a variety of performance issues and optimizations 91 // around Knative's somewhat abnormal usage, so its good to keep track and measure 92 Name: "knative-gateway", 93 Services: 100, 94 ProxyType: model.Router, 95 }, 96 97 // Testing different port types 98 { 99 Name: "http", 100 Services: 100, 101 }, 102 { 103 Name: "tcp", 104 Services: 100, 105 SkipType: v3.RouteType, // no routes for tcp 106 }, 107 { 108 Name: "tls", 109 Services: 100, 110 SkipType: v3.RouteType, // no routes for tls 111 }, 112 { 113 Name: "auto", 114 Services: 100, 115 }, 116 117 // Test different TLS modes. This only impacts listeners 118 { 119 Name: "strict", 120 OnlyRunType: v3.ListenerType, 121 }, 122 { 123 Name: "disabled", 124 OnlyRunType: v3.ListenerType, 125 }, 126 127 { 128 Name: "externalname", 129 Services: 100, 130 }, 131 132 // Test usage of various APIs 133 { 134 Name: "telemetry-api", 135 Services: 100, 136 }, 137 { 138 Name: "virtualservice", 139 Services: 100, 140 }, 141 { 142 Name: "authorizationpolicy", 143 Services: 100, 144 OnlyRunType: v3.ListenerType, 145 }, 146 { 147 Name: "serviceentry-workloadentry", 148 Services: 100, 149 Instances: 1000, 150 }, 151 } 152 153 func configureBenchmark(t test.Failer) { 154 for _, s := range istiolog.Scopes() { 155 if s.Name() == benchmarkScope.Name() { 156 continue 157 } 158 s.SetOutputLevel(istiolog.NoneLevel) 159 } 160 test.SetForTest(t, &features.EnableXDSCaching, false) 161 } 162 163 func BenchmarkInitPushContext(b *testing.B) { 164 configureBenchmark(b) 165 for _, tt := range testCases { 166 b.Run(tt.Name, func(b *testing.B) { 167 s, proxy := setupTest(b, tt) 168 b.ResetTimer() 169 for n := 0; n < b.N; n++ { 170 s.Env().PushContext().InitDone.Store(false) 171 initPushContext(s.Env(), proxy) 172 } 173 }) 174 } 175 } 176 177 func BenchmarkRouteGeneration(b *testing.B) { 178 runBenchmark(b, v3.RouteType, testCases) 179 } 180 181 func TestRouteGeneration(t *testing.T) { 182 testBenchmark(t, v3.RouteType, testCases) 183 } 184 185 func BenchmarkClusterGeneration(b *testing.B) { 186 runBenchmark(b, v3.ClusterType, testCases) 187 } 188 189 func TestClusterGeneration(t *testing.T) { 190 testBenchmark(t, v3.ClusterType, testCases) 191 } 192 193 func BenchmarkListenerGeneration(b *testing.B) { 194 runBenchmark(b, v3.ListenerType, testCases) 195 } 196 197 func TestListenerGeneration(t *testing.T) { 198 testBenchmark(t, v3.ListenerType, testCases) 199 } 200 201 // NDS isn't really impacted by anything beyond number of services, so just run these separately 202 var ndsCases = []ConfigInput{ 203 { 204 Name: "tcp", 205 Services: 1000, 206 }, 207 } 208 209 func BenchmarkNameTableGeneration(b *testing.B) { 210 runBenchmark(b, v3.NameTableType, ndsCases) 211 } 212 213 func TestNameTableGeneration(t *testing.T) { 214 testBenchmark(t, v3.NameTableType, ndsCases) 215 } 216 217 var secretCases = []ConfigInput{ 218 { 219 Name: "secrets", 220 Services: 10, 221 ProxyType: model.Router, 222 }, 223 { 224 Name: "secrets", 225 Services: 1000, 226 ProxyType: model.Router, 227 }, 228 } 229 230 func TestSecretGeneration(t *testing.T) { 231 testBenchmark(t, v3.SecretType, secretCases) 232 } 233 234 func BenchmarkSecretGeneration(b *testing.B) { 235 runBenchmark(b, v3.SecretType, secretCases) 236 } 237 238 func createGateways(n int) map[string]*meshconfig.Network { 239 out := make(map[string]*meshconfig.Network, n) 240 for i := 0; i < n; i++ { 241 out[fmt.Sprintf("network-%d", i)] = &meshconfig.Network{ 242 Gateways: []*meshconfig.Network_IstioNetworkGateway{{ 243 Gw: &meshconfig.Network_IstioNetworkGateway_Address{Address: fmt.Sprintf("35.0.0.%d", i)}, 244 Port: 15443, 245 }}, 246 } 247 } 248 return out 249 } 250 251 // BenchmarkEDS measures performance of EDS config generation 252 // TODO Add more variables, such as different services 253 func BenchmarkEndpointGeneration(b *testing.B) { 254 configureBenchmark(b) 255 256 const numNetworks = 4 257 tests := []struct { 258 endpoints int 259 services int 260 }{ 261 {1, 100}, 262 {10, 10}, 263 {100, 10}, 264 {1000, 1}, 265 } 266 267 for _, tt := range tests { 268 b.Run(fmt.Sprintf("%d/%d", tt.endpoints, tt.services), func(b *testing.B) { 269 s := xds.NewFakeDiscoveryServer(b, xds.FakeOptions{ 270 Configs: createEndpointsConfig(tt.endpoints, tt.services, numNetworks), 271 NetworksWatcher: mesh.NewFixedNetworksWatcher(&meshconfig.MeshNetworks{ 272 Networks: createGateways(numNetworks), 273 }), 274 }) 275 proxy := &model.Proxy{ 276 Type: model.SidecarProxy, 277 IPAddresses: []string{"10.3.3.3"}, 278 ID: "random", 279 ConfigNamespace: "default", 280 Metadata: &model.NodeMetadata{}, 281 } 282 push := s.PushContext() 283 proxy.SetSidecarScope(push) 284 b.ResetTimer() 285 var c model.Resources 286 for n := 0; n < b.N; n++ { 287 watchedResources := []string{} 288 for svc := 0; svc < tt.services; svc++ { 289 watchedResources = append(watchedResources, fmt.Sprintf("outbound|80||foo-%d.com", svc)) 290 } 291 wr := &model.WatchedResource{ResourceNames: watchedResources} 292 c, _, _ = s.Discovery.Generators[v3.EndpointType].Generate(proxy, wr, &model.PushRequest{Full: true, Push: s.PushContext()}) 293 } 294 logDebug(b, c) 295 }) 296 } 297 } 298 299 func runBenchmark(b *testing.B, tpe string, testCases []ConfigInput) { 300 configureBenchmark(b) 301 for _, tt := range testCases { 302 if tt.OnlyRunType != "" && tt.OnlyRunType != tpe { 303 // Not applicable for this type 304 continue 305 } 306 if tt.SkipType != "" && tt.SkipType == tpe { 307 continue 308 } 309 b.Run(tt.Name, func(b *testing.B) { 310 s, proxy := setupAndInitializeTest(b, tt) 311 wr := getWatchedResources(tpe, tt, s, proxy) 312 b.ResetTimer() 313 var c model.Resources 314 for n := 0; n < b.N; n++ { 315 c, _, _ = s.Discovery.Generators[tpe].Generate(proxy, wr, &model.PushRequest{Full: true, Push: s.PushContext()}) 316 if len(c) == 0 { 317 b.Fatalf("Got no %v's!", tpe) 318 } 319 } 320 logDebug(b, c) 321 }) 322 } 323 } 324 325 func testBenchmark(t *testing.T, tpe string, testCases []ConfigInput) { 326 for _, tt := range testCases { 327 if tt.OnlyRunType != "" && tt.OnlyRunType != tpe { 328 // Not applicable for this type 329 continue 330 } 331 if tt.SkipType != "" && tt.SkipType == tpe { 332 continue 333 } 334 t.Run(tt.Name, func(t *testing.T) { 335 // No need for large test here 336 tt.Services = 1 337 tt.Instances = 1 338 s, proxy := setupAndInitializeTest(t, tt) 339 wr := getWatchedResources(tpe, tt, s, proxy) 340 c, _, _ := s.Discovery.Generators[tpe].Generate(proxy, wr, &model.PushRequest{Full: true, Push: s.PushContext()}) 341 if len(c) == 0 { 342 t.Fatalf("Got no %v's!", tpe) 343 } 344 }) 345 } 346 } 347 348 func getWatchedResources(tpe string, tt ConfigInput, s *xds.FakeDiscoveryServer, proxy *model.Proxy) *model.WatchedResource { 349 switch tpe { 350 case v3.SecretType: 351 watchedResources := []string{} 352 for i := 0; i < tt.Services; i++ { 353 watchedResources = append(watchedResources, fmt.Sprintf("kubernetes://default/sds-credential-%d", i)) 354 } 355 return &model.WatchedResource{ResourceNames: watchedResources} 356 case v3.RouteType: 357 l := s.ConfigGen.BuildListeners(proxy, s.PushContext()) 358 routeNames := xdstest.ExtractRoutesFromListeners(l) 359 return &model.WatchedResource{ResourceNames: routeNames} 360 } 361 return nil 362 } 363 364 // Setup test builds a mock test environment. Note: push context is not initialized, to be able to benchmark separately 365 // most should just call setupAndInitializeTest 366 func setupTest(t testing.TB, config ConfigInput) (*xds.FakeDiscoveryServer, *model.Proxy) { 367 proxyType := config.ProxyType 368 if proxyType == "" { 369 proxyType = model.SidecarProxy 370 } 371 proxy := &model.Proxy{ 372 Type: proxyType, 373 IPAddresses: []string{"1.1.1.1"}, 374 ID: "v0.default", 375 DNSDomain: "default.example.org", 376 Labels: map[string]string{ 377 "istio.io/benchmark": "true", 378 }, 379 Metadata: &model.NodeMetadata{ 380 Namespace: "default", 381 Labels: map[string]string{ 382 "istio.io/benchmark": "true", 383 }, 384 ClusterID: constants.DefaultClusterName, 385 IstioVersion: "1.23.0", 386 }, 387 ConfigNamespace: "default", 388 VerifiedIdentity: &spiffe.Identity{Namespace: "default"}, 389 } 390 proxy.IstioVersion = model.ParseIstioVersion(proxy.Metadata.IstioVersion) 391 // need to call DiscoverIPMode to check the ipMode of the proxy 392 proxy.DiscoverIPMode() 393 394 configs, k8sConfig := getConfigsWithCache(t, config) 395 m := mesh.DefaultMeshConfig() 396 m.ExtensionProviders = append(m.ExtensionProviders, &meshconfig.MeshConfig_ExtensionProvider{ 397 Name: "envoy-json", 398 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 399 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 400 Path: "/dev/stdout", 401 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat{ 402 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Labels{}, 403 }, 404 }, 405 }, 406 }) 407 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{ 408 Configs: configs, 409 KubernetesObjectString: k8sConfig, 410 // Allow debounce to avoid overwhelming with writes 411 DebounceTime: time.Millisecond * 10, 412 DisableSecretAuthorization: true, 413 MeshConfig: m, 414 }) 415 416 return s, proxy 417 } 418 419 var ( 420 configCache = map[ConfigInput][]config.Config{} 421 k8sConfigCache = map[ConfigInput]string{} 422 ) 423 424 func getConfigsWithCache(t testing.TB, input ConfigInput) ([]config.Config, string) { 425 // Config setup is slow for large tests. Cache this and return from Cache. 426 // This improves even running a single test, as go will run the full test (including setup) at least twice. 427 if cached, f := configCache[input]; f { 428 return cached, k8sConfigCache[input] 429 } 430 configName := input.ConfigName 431 if configName == "" { 432 configName = input.Name 433 } 434 tmpl := template.Must(template.New("").Funcs(sprig.TxtFuncMap()).ParseFiles(path.Join("testdata", "benchmarks", configName+".yaml"))) 435 var buf bytes.Buffer 436 if err := tmpl.ExecuteTemplate(&buf, configName+".yaml", input); err != nil { 437 t.Fatalf("failed to execute template: %v", err) 438 } 439 extra := path.Join("testdata", "benchmarks", configName+".extra.yaml") 440 inputYAML := buf.String() 441 if _, err := os.Stat(extra); err == nil { 442 bdata, err := os.ReadFile(extra) 443 if err != nil { 444 t.Fatal(err) 445 } 446 447 inputYAML += "\n---\n" + yml.SplitYamlByKind(string(bdata))[gvk.EnvoyFilter.Kind] 448 } 449 450 configs, badKinds, err := crd.ParseInputs(inputYAML) 451 if err != nil { 452 t.Fatalf("failed to read config: %v", err) 453 } 454 k8sTypes, count := parseKubernetesTypes(inputYAML) 455 if len(badKinds) != count { 456 t.Fatalf("Got unknown resources: %v", badKinds) 457 } 458 // setup default namespace if not defined 459 for i, c := range configs { 460 if c.Namespace == "" { 461 c.Namespace = "default" 462 } 463 configs[i] = c 464 } 465 configCache[input] = configs 466 k8sConfigCache[input] = k8sTypes 467 return configs, k8sTypes 468 } 469 470 func parseKubernetesTypes(inputs string) (string, int) { 471 matches := 0 472 sb := strings.Builder{} 473 for _, text := range strings.Split(inputs, "\n---") { 474 if strings.Contains(text, "kind: Secret") { 475 sb.WriteString(text + "\n---\n") 476 matches++ 477 } 478 if strings.Contains(text, "kind: Service\n") { 479 sb.WriteString(text + "\n---\n") 480 matches++ 481 } 482 } 483 return sb.String(), matches 484 } 485 486 func setupAndInitializeTest(t testing.TB, config ConfigInput) (*xds.FakeDiscoveryServer, *model.Proxy) { 487 s, proxy := setupTest(t, config) 488 initPushContext(s.Env(), proxy) 489 return s, proxy 490 } 491 492 func initPushContext(env *model.Environment, proxy *model.Proxy) { 493 pushContext := env.PushContext() 494 pushContext.InitContext(env, nil, nil) 495 proxy.SetSidecarScope(pushContext) 496 proxy.SetGatewaysForProxy(pushContext) 497 proxy.SetServiceTargets(env.ServiceDiscovery) 498 } 499 500 var debugGeneration = env.Register("DEBUG_CONFIG_DUMP", false, "if enabled, print a full config dump of the generated config") 501 502 var benchmarkScope = istiolog.RegisterScope("benchmark", "") 503 504 // Add additional debug info for a test 505 func logDebug(b *testing.B, m model.Resources) { 506 b.Helper() 507 b.StopTimer() 508 509 if debugGeneration.Get() { 510 for i, r := range m { 511 s, err := protomarshal.MarshalIndent(r, " ") 512 if err != nil { 513 b.Fatal(err) 514 } 515 // Cannot use b.Logf, it truncates 516 benchmarkScope.Infof("Generated: %d %s", i, s) 517 } 518 } 519 bytes := 0 520 for _, r := range m { 521 bytes += len(r.GetResource().Value) 522 } 523 b.ReportMetric(float64(bytes)/1000, "kb/msg") 524 b.ReportMetric(float64(len(m)), "resources/msg") 525 b.StartTimer() 526 } 527 528 func createEndpointsConfig(numEndpoints, numServices, numNetworks int) []config.Config { 529 result := make([]config.Config, 0, numServices) 530 for s := 0; s < numServices; s++ { 531 endpoints := make([]*networking.WorkloadEntry, 0, numEndpoints) 532 for e := 0; e < numEndpoints; e++ { 533 endpoints = append(endpoints, &networking.WorkloadEntry{ 534 Labels: map[string]string{ 535 "type": "eds-benchmark", 536 "app": "foo-" + strconv.Itoa(s), 537 }, 538 Address: fmt.Sprintf("111.%d.%d.%d", e/(256*256), (e/256)%256, e%256), 539 Network: fmt.Sprintf("network-%d", e%numNetworks), 540 ServiceAccount: "something", 541 }) 542 } 543 result = append(result, config.Config{ 544 Meta: config.Meta{ 545 GroupVersionKind: gvk.ServiceEntry, 546 Name: "foo-" + strconv.Itoa(s), 547 Namespace: "default", 548 CreationTimestamp: time.Now(), 549 }, 550 Spec: &networking.ServiceEntry{ 551 Hosts: []string{fmt.Sprintf("foo-%d.com", s)}, 552 Ports: []*networking.ServicePort{ 553 {Number: 80, Name: "http-port", Protocol: "http"}, 554 }, 555 Endpoints: endpoints, 556 Resolution: networking.ServiceEntry_STATIC, 557 }, 558 }) 559 } 560 // EDS looks up PA, so add a few... 561 result = append(result, config.Config{ 562 Meta: config.Meta{ 563 GroupVersionKind: gvk.PeerAuthentication, 564 Name: "global", 565 Namespace: "istio-system", 566 CreationTimestamp: time.Now(), 567 }, 568 Spec: &security.PeerAuthentication{ 569 Mtls: &security.PeerAuthentication_MutualTLS{Mode: security.PeerAuthentication_MutualTLS_PERMISSIVE}, 570 }, 571 }, 572 config.Config{ 573 Meta: config.Meta{ 574 GroupVersionKind: gvk.PeerAuthentication, 575 Name: "namespace", 576 Namespace: "default", 577 CreationTimestamp: time.Now(), 578 }, 579 Spec: &security.PeerAuthentication{ 580 Mtls: &security.PeerAuthentication_MutualTLS{Mode: security.PeerAuthentication_MutualTLS_DISABLE}, 581 }, 582 }, 583 config.Config{ 584 Meta: config.Meta{ 585 GroupVersionKind: gvk.PeerAuthentication, 586 Name: "selector", 587 Namespace: "default", 588 CreationTimestamp: time.Now(), 589 }, 590 Spec: &security.PeerAuthentication{ 591 Selector: &v1beta1.WorkloadSelector{MatchLabels: map[string]string{"type": "eds-benchmark"}}, 592 Mtls: &security.PeerAuthentication_MutualTLS{Mode: security.PeerAuthentication_MutualTLS_DISABLE}, 593 }, 594 }) 595 return result 596 } 597 598 func makeCacheKey(n int) model.XdsCacheEntry { 599 ns := strconv.Itoa(n) 600 601 // 100 services 602 services := make([]*model.Service, 0, 100) 603 // 100 destinationrules 604 drs := make([]*model.ConsolidatedDestRule, 0, 100) 605 for i := 0; i < 100; i++ { 606 index := strconv.Itoa(i) 607 services = append(services, &model.Service{ 608 Hostname: host.Name(ns + "some" + index + ".example.com"), 609 Attributes: model.ServiceAttributes{Namespace: "test" + index}, 610 }) 611 drs = append(drs, model.ConvertConsolidatedDestRule(&config.Config{Meta: config.Meta{Name: index, Namespace: index}})) 612 } 613 614 key := &route.Cache{ 615 RouteName: "something", 616 ClusterID: "my-cluster", 617 DNSDomain: "some.domain.example.com", 618 DNSCapture: true, 619 DNSAutoAllocate: false, 620 ListenerPort: 1234, 621 Services: services, 622 DestinationRules: drs, 623 EnvoyFilterKeys: []string{ns + "1/a", ns + "2/b", ns + "3/c"}, 624 } 625 return key 626 } 627 628 func BenchmarkCache(b *testing.B) { 629 // Ensure cache doesn't grow too large 630 test.SetForTest(b, &features.XDSCacheMaxSize, 1_000) 631 res := &discovery.Resource{Name: "test"} 632 zeroTime := time.Time{} 633 b.Run("key", func(b *testing.B) { 634 key := makeCacheKey(1) 635 for n := 0; n < b.N; n++ { 636 _ = key.Key() 637 } 638 }) 639 b.Run("insert", func(b *testing.B) { 640 c := model.NewXdsCache() 641 stop := make(chan struct{}) 642 defer close(stop) 643 c.Run(stop) 644 for n := 0; n < b.N; n++ { 645 key := makeCacheKey(n) 646 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(n))} 647 c.Add(key, req, res) 648 } 649 }) 650 // to trigger clear index on old dependents 651 b.Run("insert same key", func(b *testing.B) { 652 c := model.NewXdsCache() 653 stop := make(chan struct{}) 654 defer close(stop) 655 c.Run(stop) 656 // First occupy cache capacity 657 for i := 0; i < features.XDSCacheMaxSize; i++ { 658 key := makeCacheKey(i) 659 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(i))} 660 c.Add(key, req, res) 661 } 662 b.ResetTimer() 663 for n := 0; n < b.N; n++ { 664 key := makeCacheKey(1) 665 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(features.XDSCacheMaxSize + n))} 666 c.Add(key, req, res) 667 } 668 }) 669 b.Run("get", func(b *testing.B) { 670 c := model.NewXdsCache() 671 key := makeCacheKey(1) 672 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(1))} 673 c.Add(key, req, res) 674 for n := 0; n < b.N; n++ { 675 c.Get(key) 676 } 677 }) 678 679 b.Run("insert and get", func(b *testing.B) { 680 c := model.NewXdsCache() 681 stop := make(chan struct{}) 682 defer close(stop) 683 c.Run(stop) 684 // First occupy cache capacity 685 for i := 0; i < features.XDSCacheMaxSize; i++ { 686 key := makeCacheKey(i) 687 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(i))} 688 c.Add(key, req, res) 689 } 690 b.ResetTimer() 691 for n := 0; n < b.N; n++ { 692 key := makeCacheKey(n) 693 req := &model.PushRequest{Start: zeroTime.Add(time.Duration(features.XDSCacheMaxSize + n))} 694 c.Add(key, req, res) 695 c.Get(key) 696 } 697 }) 698 }