Skip to content

Commit e1ef0f6

Browse files
committed
chore: Add config rendering pipeline benchmarks
1 parent 41ed8a6 commit e1ef0f6

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package renderer
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
discoveryv1 "k8s.io/api/discovery/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
11+
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
12+
13+
"github.com/l7mp/stunner-gateway-operator/internal/config"
14+
"github.com/l7mp/stunner-gateway-operator/internal/event"
15+
"github.com/l7mp/stunner-gateway-operator/internal/store"
16+
"github.com/l7mp/stunner-gateway-operator/internal/testutils"
17+
opdefault "github.com/l7mp/stunner-gateway-operator/pkg/config"
18+
19+
stnrgwv1 "github.com/l7mp/stunner-gateway-operator/api/v1"
20+
)
21+
22+
// Config rendering pipeline benchmarks:
23+
//
24+
// # Run with longer time for more accurate results
25+
// go test -bench=BenchmarkRenderPipeline ./internal/renderer -benchtime=5 -benchmem -run=^$
26+
//
27+
// # Using go tool pprof directly (modern Go versions)
28+
// go test -bench=BenchmarkRenderPipeline -benchtime=5s -run=^$ -cpuprofile=cpu.prof -memprofile=mem
29+
// go tool pprof -http=:8080 cpu.prof
30+
31+
// generateGateway creates a unique gateway based on the test template.
32+
func generateGateway(index int) gwapiv1.Gateway {
33+
gw := testutils.TestGw.DeepCopy()
34+
gw.Name = fmt.Sprintf("gateway-%d", index)
35+
gw.Namespace = fmt.Sprintf("testnamespace-%d", index)
36+
return *gw
37+
}
38+
39+
// generateUDPRoute creates a unique UDP route based on the test template.
40+
func generateUDPRoute(index int) stnrgwv1.UDPRoute {
41+
route := testutils.TestUDPRoute.DeepCopy()
42+
route.Name = fmt.Sprintf("udproute-%d", index)
43+
route.Namespace = fmt.Sprintf("testnamespace-%d", index)
44+
// Update parent ref to match the gateway
45+
route.Spec.ParentRefs[0].Name = gwapiv1.ObjectName(fmt.Sprintf("gateway-%d", index))
46+
// Update backend ref to match the service
47+
route.Spec.Rules[0].BackendRefs[0].Name = gwapiv1.ObjectName(fmt.Sprintf("testservice-%d", index))
48+
return *route
49+
}
50+
51+
// generateService creates a unique service based on the test template.
52+
func generateService(index int) corev1.Service {
53+
svc := testutils.TestSvc.DeepCopy()
54+
svc.Name = fmt.Sprintf("testservice-%d", index)
55+
svc.Namespace = fmt.Sprintf("testnamespace-%d", index)
56+
// Update owner reference to match the gateway.
57+
svc.SetOwnerReferences([]metav1.OwnerReference{{
58+
APIVersion: gwapiv1.GroupVersion.String(),
59+
Kind: "Gateway",
60+
UID: "test-uid",
61+
Name: fmt.Sprintf("gateway-%d", index),
62+
}})
63+
svc.Spec.ClusterIP = fmt.Sprintf("10.0.%d.%d", index/256, index%256)
64+
return *svc
65+
}
66+
67+
// generateEndpointSlice creates a unique endpoint slice based on the test template.
68+
func generateEndpointSlice(index int) discoveryv1.EndpointSlice {
69+
esl := testutils.TestEndpointSlice.DeepCopy()
70+
esl.Name = fmt.Sprintf("testendpointslice-%d", index)
71+
esl.Namespace = fmt.Sprintf("testnamespace-%d", index)
72+
// Update label to bind to the service.
73+
esl.Labels["kubernetes.io/service-name"] = fmt.Sprintf("testservice-%d", index)
74+
return *esl
75+
}
76+
77+
// benchmarkSetup prepares the stores with N gateways, routes, services, and endpoint slices.
78+
func benchmarkSetup(n int) {
79+
// Flush all stores.
80+
store.GatewayClasses.Flush()
81+
store.GatewayConfigs.Flush()
82+
store.Gateways.Flush()
83+
store.UDPRoutes.Flush()
84+
store.Services.Flush()
85+
store.EndpointSlices.Flush()
86+
store.Dataplanes.Flush()
87+
88+
// Add the single GatewayClass and GatewayConfig.
89+
store.GatewayClasses.Upsert(&testutils.TestGwClass)
90+
store.GatewayConfigs.Upsert(&testutils.TestGwConfig)
91+
store.Dataplanes.Upsert(&testutils.TestDataplane)
92+
93+
// Generate and add N gateways, routes, services, and endpoint slices.
94+
for i := 0; i < n; i++ {
95+
gw := generateGateway(i)
96+
store.Gateways.Upsert(&gw)
97+
98+
route := generateUDPRoute(i)
99+
store.UDPRoutes.Upsert(&route)
100+
101+
svc := generateService(i)
102+
store.Services.Upsert(&svc)
103+
104+
esl := generateEndpointSlice(i)
105+
store.EndpointSlices.Upsert(&esl)
106+
}
107+
}
108+
109+
// BenchmarkRenderPipeline benchmarks the rendering pipeline with varying numbers of gateways.
110+
func BenchmarkRenderPipeline(b *testing.B) {
111+
// Test with N=1,2,4,8,16,32,64,128,256,512 gateways.
112+
sizes := []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}
113+
114+
for _, n := range sizes {
115+
b.Run(fmt.Sprintf("N=%d", n), func(b *testing.B) {
116+
// Setup: prepare stores with N resources.
117+
benchmarkSetup(n)
118+
119+
// Configure managed mode with EDS enabled (like the reference test).
120+
config.DataplaneMode = config.DataplaneModeManaged
121+
config.EnableEndpointDiscovery = true
122+
config.EnableRelayToClusterIP = true
123+
config.EndpointSliceAvailable = true
124+
125+
// Create the renderer.
126+
r := NewDefaultRenderer(RendererConfig{
127+
Scheme: scheme,
128+
Logger: log.WithName("benchmark-renderer"),
129+
}).(*renderer)
130+
131+
// Start the renderer.
132+
err := r.Start(b.Context())
133+
if err != nil {
134+
b.Fatalf("failed to start renderer: %v", err)
135+
}
136+
137+
// Prepare the render context (outside the benchmark loop).
138+
gc, err := r.getGatewayClass()
139+
if err != nil {
140+
b.Fatalf("failed to get gateway class: %v", err)
141+
}
142+
143+
gwConf, err := r.getGatewayConfig4Class(&RenderContext{gc: gc, log: log})
144+
if err != nil {
145+
b.Fatalf("failed to get gateway config: %v", err)
146+
}
147+
148+
gws := r.getGateways4Class(&RenderContext{gc: gc, log: log})
149+
if len(gws) != n {
150+
b.Fatalf("expected %d gateways, got %d", n, len(gws))
151+
}
152+
153+
// Reset the benchmark timer to exclude setup time.
154+
b.ResetTimer()
155+
156+
// Run the benchmark: measure how many full rendering cycles we can do.
157+
for i := 0; i < b.N; i++ {
158+
// Create a fresh render context for each iteration.
159+
c := &RenderContext{
160+
gc: gc,
161+
gwConf: gwConf,
162+
gws: store.NewGatewayStore(),
163+
log: log,
164+
update: event.NewEventUpdate(0),
165+
}
166+
167+
// Reset gateways in the context.
168+
c.gws.ResetGateways(gws)
169+
170+
// Perform the rendering.
171+
err := r.renderForGateways(c)
172+
if err != nil {
173+
b.Fatalf("render failed: %v", err)
174+
}
175+
}
176+
177+
// Stop the timer before cleanup.
178+
b.StopTimer()
179+
180+
// Restore default config.
181+
config.EnableEndpointDiscovery = opdefault.DefaultEnableEndpointDiscovery
182+
config.EnableRelayToClusterIP = opdefault.DefaultEnableRelayToClusterIP
183+
config.DataplaneMode = config.NewDataplaneMode(opdefault.DefaultDataplaneMode)
184+
})
185+
}
186+
}

0 commit comments

Comments
 (0)