Skip to content

Commit b826e4f

Browse files
swiatekmblakerouse
authored andcommitted
Add var generation benchmark (#6028)
1 parent 1481244 commit b826e4f

File tree

7 files changed

+506
-20
lines changed

7 files changed

+506
-20
lines changed
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
package composable
6+
7+
import (
8+
"maps"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"testing"
13+
14+
"gopkg.in/yaml.v3"
15+
"k8s.io/apimachinery/pkg/util/uuid"
16+
17+
"github.com/elastic/elastic-agent-libs/mapstr"
18+
"github.com/elastic/elastic-agent/pkg/core/logger"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
// BenchmarkGenerateVars100Pods checks the cost of generating vars with the kubernetes provider tracking 100 Pods.
24+
// This scenario does come up in reality, in particular in our internal Serverless clusters, and we've historically
25+
// had bad performance in it. Test data is taken almost directly from a real cluster.
26+
func BenchmarkGenerateVars100Pods(b *testing.B) {
27+
log, err := logger.New("", false)
28+
require.NoError(b, err)
29+
c := controller{
30+
contextProviders: make(map[string]*contextProviderState),
31+
dynamicProviders: make(map[string]*dynamicProviderState),
32+
logger: log,
33+
}
34+
podCount := 100
35+
36+
providerDataFiles, err := os.ReadDir("./testdata")
37+
require.NoError(b, err)
38+
39+
providerData := make(map[string]map[string]interface{}, len(providerDataFiles))
40+
for _, providerDataFile := range providerDataFiles {
41+
fileName := providerDataFile.Name()
42+
providerName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
43+
rawData, err := os.ReadFile(filepath.Join("./testdata", fileName))
44+
require.NoError(b, err)
45+
var data map[string]interface{}
46+
err = yaml.Unmarshal(rawData, &data)
47+
require.NoError(b, err)
48+
providerData[providerName] = data
49+
}
50+
51+
for providerName, providerMapping := range providerData {
52+
if providerName == "kubernetes" {
53+
providerState := &dynamicProviderState{
54+
mappings: make(map[string]dynamicProviderMapping),
55+
}
56+
for i := 0; i < podCount; i++ {
57+
podData := maps.Clone(providerMapping)
58+
podUID := uuid.NewUUID()
59+
podMapping := dynamicProviderMapping{
60+
mapping: podData,
61+
}
62+
providerState.mappings[string(podUID)] = podMapping
63+
}
64+
c.dynamicProviders[providerName] = providerState
65+
} else {
66+
providerState := &contextProviderState{
67+
mapping: providerData[providerName],
68+
}
69+
c.contextProviders[providerName] = providerState
70+
}
71+
}
72+
73+
b.ResetTimer()
74+
for i := 0; i < b.N; i++ {
75+
c.generateVars(mapstr.M{})
76+
}
77+
}

internal/pkg/composable/controller.go

+25-20
Original file line numberDiff line numberDiff line change
@@ -212,26 +212,7 @@ func (c *controller) Run(ctx context.Context) error {
212212

213213
c.logger.Debugf("Computing new variable state for composable inputs")
214214

215-
// build the vars list of mappings
216-
vars := make([]*transpiler.Vars, 1)
217-
mapping := map[string]interface{}{}
218-
for name, state := range c.contextProviders {
219-
mapping[name] = state.Current()
220-
}
221-
// this is ensured not to error, by how the mappings states are verified
222-
vars[0], _ = transpiler.NewVars("", mapping, fetchContextProviders)
223-
224-
// add to the vars list for each dynamic providers mappings
225-
for name, state := range c.dynamicProviders {
226-
for _, mappings := range state.Mappings() {
227-
local, _ := cloneMap(mapping) // will not fail; already been successfully cloned once
228-
local[name] = mappings.mapping
229-
id := fmt.Sprintf("%s-%s", name, mappings.id)
230-
// this is ensured not to error, by how the mappings states are verified
231-
v, _ := transpiler.NewVarsWithProcessors(id, local, name, mappings.processors, fetchContextProviders)
232-
vars = append(vars, v)
233-
}
234-
}
215+
vars := c.generateVars(fetchContextProviders)
235216

236217
UPDATEVARS:
237218
for {
@@ -291,6 +272,30 @@ func (c *controller) Close() {
291272
}
292273
}
293274

275+
func (c *controller) generateVars(fetchContextProviders mapstr.M) []*transpiler.Vars {
276+
// build the vars list of mappings
277+
vars := make([]*transpiler.Vars, 1)
278+
mapping := map[string]interface{}{}
279+
for name, state := range c.contextProviders {
280+
mapping[name] = state.Current()
281+
}
282+
// this is ensured not to error, by how the mappings states are verified
283+
vars[0], _ = transpiler.NewVars("", mapping, fetchContextProviders)
284+
285+
// add to the vars list for each dynamic providers mappings
286+
for name, state := range c.dynamicProviders {
287+
for _, mappings := range state.Mappings() {
288+
local, _ := cloneMap(mapping) // will not fail; already been successfully cloned once
289+
local[name] = mappings.mapping
290+
id := fmt.Sprintf("%s-%s", name, mappings.id)
291+
// this is ensured not to error, by how the mappings states are verified
292+
v, _ := transpiler.NewVarsWithProcessors(id, local, name, mappings.processors, fetchContextProviders)
293+
vars = append(vars, v)
294+
}
295+
}
296+
return vars
297+
}
298+
294299
type contextProviderState struct {
295300
context.Context
296301

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
id: 36806a7e-1981-4a55-a3b6-ec9dd1ad1a4c
2+
unprivileged: false
3+
version:
4+
build_time: 2024-09-12 21:13:27 +0000 UTC
5+
commit: d99b09b0769f6f34428321eedb00c0b4339c202b
6+
snapshot: true
7+
version: 9.0.0
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
API_KEY: ""
2+
BEAT_SETUID_AS: elastic-agent
3+
ELASTIC_CONTAINER: "true"
4+
ELASTIC_NETINFO: "false"
5+
ES_HOST: https://localhost:9243
6+
ES_PASSWORD:
7+
ES_USERNAME: elastic
8+
GODEBUG: madvdontneed=1
9+
HOME: /root
10+
HOSTNAME: kind
11+
KUBE_DNS_PORT: udp://10.96.0.10:53
12+
KUBE_DNS_PORT_53_TCP: tcp://10.96.0.10:53
13+
KUBE_DNS_PORT_53_TCP_ADDR: 10.96.0.10
14+
KUBE_DNS_PORT_53_TCP_PORT: "53"
15+
KUBE_DNS_PORT_53_TCP_PROTO: tcp
16+
KUBE_DNS_PORT_53_UDP: udp://10.96.0.10:53
17+
KUBE_DNS_PORT_53_UDP_ADDR: 10.96.0.10
18+
KUBE_DNS_PORT_53_UDP_PORT: "53"
19+
KUBE_DNS_PORT_53_UDP_PROTO: udp
20+
KUBE_DNS_PORT_9153_TCP: tcp://10.96.0.10:9153
21+
KUBE_DNS_PORT_9153_TCP_ADDR: 10.96.0.10
22+
KUBE_DNS_PORT_9153_TCP_PORT: "9153"
23+
KUBE_DNS_PORT_9153_TCP_PROTO: tcp
24+
KUBE_DNS_SERVICE_HOST: 10.96.0.10
25+
KUBE_DNS_SERVICE_PORT: "53"
26+
KUBE_DNS_SERVICE_PORT_DNS: "53"
27+
KUBE_DNS_SERVICE_PORT_DNS_TCP: "53"
28+
KUBE_DNS_SERVICE_PORT_METRICS: "9153"
29+
KUBERNETES_PORT: tcp://10.96.0.1:443
30+
KUBERNETES_PORT_443_TCP: tcp://10.96.0.1:443
31+
KUBERNETES_PORT_443_TCP_ADDR: 10.96.0.1
32+
KUBERNETES_PORT_443_TCP_PORT: "443"
33+
KUBERNETES_PORT_443_TCP_PROTO: tcp
34+
KUBERNETES_SERVICE_HOST: 10.96.0.1
35+
KUBERNETES_SERVICE_PORT: "443"
36+
KUBERNETES_SERVICE_PORT_HTTPS: "443"
37+
LIBBEAT_MONITORING_CGROUPS_HIERARCHY_OVERRIDE: /
38+
METRICS_SERVER_PORT: tcp://10.96.195.62:443
39+
METRICS_SERVER_PORT_443_TCP: tcp://10.96.195.62:443
40+
METRICS_SERVER_PORT_443_TCP_ADDR: 10.96.195.62
41+
METRICS_SERVER_PORT_443_TCP_PORT: "443"
42+
METRICS_SERVER_PORT_443_TCP_PROTO: tcp
43+
METRICS_SERVER_SERVICE_HOST: 10.96.195.62
44+
METRICS_SERVER_SERVICE_PORT: "443"
45+
METRICS_SERVER_SERVICE_PORT_HTTPS: "443"
46+
NODE_NAME: kind
47+
PATH: /usr/share/elastic-agent:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
48+
POD_NAME: elastic-agent-standalone-99pcr
49+
PWD: /usr/share/elastic-agent
50+
SHLVL: "0"

0 commit comments

Comments
 (0)