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  }