@@ -26,6 +26,23 @@ type Stats struct {
26
26
Total time.Duration
27
27
}
28
28
29
+ // httpEndpointConfig represents the configuration for an HTTP endpoint.
30
+ type httpEndpointConfig struct {
31
+ client * http.Client
32
+ url string
33
+ }
34
+
35
+ // sgAuthTransport is an http.RoundTripper that adds an Authorization header to requests.
36
+ // It is used to add the Sourcegraph access token to requests to Sourcegraph endpoints.
37
+ type sgAuthTransport struct {
38
+ token string
39
+ base http.RoundTripper
40
+ }
41
+ func (t * sgAuthTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
42
+ req .Header .Add ("Authorization" , "token " + t .token )
43
+ return t .base .RoundTrip (req )
44
+ }
45
+
29
46
func init () {
30
47
usage := `
31
48
'src gateway benchmark' runs performance benchmarks against Cody Gateway endpoints.
@@ -38,7 +55,7 @@ Examples:
38
55
39
56
$ src gateway benchmark
40
57
$ src gateway benchmark --requests 50
41
- $ src gateway benchmark --gateway http://localhost:9992 --sourcegraph http://localhost:3082
58
+ $ src gateway benchmark --gateway http://localhost:9992 --sourcegraph http://localhost:3082 --sgp sgp_***** --requests 50
42
59
$ src gateway benchmark --requests 50 --csv results.csv
43
60
`
44
61
@@ -49,6 +66,7 @@ Examples:
49
66
csvOutput = flagSet .String ("csv" , "" , "Export results to CSV file (provide filename)" )
50
67
gatewayEndpoint = flagSet .String ("gateway" , "https://cody-gateway.sourcegraph.com" , "Cody Gateway endpoint" )
51
68
sgEndpoint = flagSet .String ("sourcegraph" , "https://sourcegraph.com" , "Sourcegraph endpoint" )
69
+ sgpToken = flagSet .String ("sgp" , "sgp_*****" , "Sourcegraph personal access token for the called instance" )
52
70
)
53
71
54
72
handler := func (args []string ) error {
@@ -63,43 +81,76 @@ Examples:
63
81
var (
64
82
gatewayWebsocket , sourcegraphWebsocket * websocket.Conn
65
83
err error
66
- httpClient = & http.Client {}
84
+ gatewayClient = & http.Client {}
85
+ sourcegraphClient = & http.Client {}
67
86
endpoints = map [string ]any {} // Values: URL `string`s or `*websocket.Conn`s
68
87
)
88
+
89
+ // Connect to endpoints
69
90
if * gatewayEndpoint != "" {
91
+ fmt .Println ("Benchmarking Cody Gateway instance:" , * gatewayEndpoint )
70
92
wsURL := strings .Replace (fmt .Sprint (* gatewayEndpoint , "/v2/websocket" ), "http" , "ws" , 1 )
93
+ fmt .Println ("Connecting to Cody Gateway via WebSocket.." , wsURL )
71
94
gatewayWebsocket , _ , err = websocket .DefaultDialer .Dial (wsURL , nil )
72
95
if err != nil {
73
96
return fmt .Errorf ("WebSocket dial(%s): %v" , wsURL , err )
74
97
}
98
+ fmt .Println ("Connected!" )
75
99
endpoints ["ws(s): gateway" ] = gatewayWebsocket
76
- endpoints ["http(s): gateway" ] = fmt .Sprint (* gatewayEndpoint , "/v2/http" )
100
+ endpoints ["http(s): gateway" ] = & httpEndpointConfig {
101
+ client : gatewayClient ,
102
+ url : fmt .Sprint (* gatewayEndpoint , "/v2/http" ),
103
+ }
104
+ } else {
105
+ fmt .Println ("warning: not benchmarking Cody Gateway (-gateway endpoint not provided)" )
77
106
}
78
107
if * sgEndpoint != "" {
108
+ // Add auth header to sourcegraphClient transport
109
+ if * sgpToken != "" {
110
+ sourcegraphClient .Transport = & sgAuthTransport {
111
+ token : * sgpToken ,
112
+ base : http .DefaultTransport ,
113
+ }
114
+ }
115
+ fmt .Println ("Benchmarking Sourcegraph instance:" , * sgEndpoint )
79
116
wsURL := strings .Replace (fmt .Sprint (* sgEndpoint , "/.api/gateway/websocket" ), "http" , "ws" , 1 )
80
- sourcegraphWebsocket , _ , err = websocket .DefaultDialer .Dial (wsURL , nil )
117
+ header := http.Header {}
118
+ header .Add ("Authorization" , "token " + * sgpToken )
119
+ fmt .Println ("Connecting to Sourcegraph instance via WebSocket.." , wsURL )
120
+ sourcegraphWebsocket , _ , err = websocket .DefaultDialer .Dial (wsURL , header )
81
121
if err != nil {
82
122
return fmt .Errorf ("WebSocket dial(%s): %v" , wsURL , err )
83
123
}
124
+ fmt .Println ("Connected!" )
125
+
84
126
endpoints ["ws(s): sourcegraph" ] = sourcegraphWebsocket
85
- endpoints ["http(s): sourcegraph" ] = fmt .Sprint (* sgEndpoint , "/.api/gateway/http" )
127
+ endpoints ["http(s): sourcegraph" ] = & httpEndpointConfig {
128
+ client : sourcegraphClient ,
129
+ url : fmt .Sprint (* sgEndpoint , "/.api/gateway/http" ),
130
+ }
131
+ endpoints ["http(s): http-then-ws" ] = & httpEndpointConfig {
132
+ client : sourcegraphClient ,
133
+ url : fmt .Sprint (* sgEndpoint , "/.api/gateway/http-then-websocket" ),
134
+ }
135
+ } else {
136
+ fmt .Println ("warning: not benchmarking Sourcegraph instance (-sourcegraph endpoint not provided)" )
86
137
}
87
138
88
139
fmt .Printf ("Starting benchmark with %d requests per endpoint...\n " , * requestCount )
89
140
90
141
var results []endpointResult
91
- for name , clientOrURL := range endpoints {
142
+ for name , clientOrEndpointConfig := range endpoints {
92
143
durations := make ([]time.Duration , 0 , * requestCount )
93
144
fmt .Printf ("\n Testing %s..." , name )
94
145
95
146
for i := 0 ; i < * requestCount ; i ++ {
96
- if ws , ok := clientOrURL .(* websocket.Conn ); ok {
147
+ if ws , ok := clientOrEndpointConfig .(* websocket.Conn ); ok {
97
148
duration := benchmarkEndpointWebSocket (ws )
98
149
if duration > 0 {
99
150
durations = append (durations , duration )
100
151
}
101
- } else if url , ok := clientOrURL .( string ); ok {
102
- duration := benchmarkEndpointHTTP (httpClient , url )
152
+ } else if epConf , ok := clientOrEndpointConfig .( httpEndpointConfig ); ok {
153
+ duration := benchmarkEndpointHTTP (epConf )
103
154
if duration > 0 {
104
155
durations = append (durations , duration )
105
156
}
@@ -161,11 +212,11 @@ type endpointResult struct {
161
212
successful int
162
213
}
163
214
164
- func benchmarkEndpointHTTP (client * http. Client , url string ) time.Duration {
215
+ func benchmarkEndpointHTTP (epConfig httpEndpointConfig ) time.Duration {
165
216
start := time .Now ()
166
- resp , err := client .Get ( url )
217
+ resp , err := epConfig . client .Post ( epConfig . url , "application/json" , strings . NewReader ( "ping" ) )
167
218
if err != nil {
168
- fmt .Printf ("Error calling %s: %v\n " , url , err )
219
+ fmt .Printf ("Error calling %s: %v\n " , epConfig . url , err )
169
220
return 0
170
221
}
171
222
defer func () {
@@ -174,12 +225,6 @@ func benchmarkEndpointHTTP(client *http.Client, url string) time.Duration {
174
225
fmt .Printf ("Error closing response body: %v\n " , err )
175
226
}
176
227
}()
177
-
178
- _ , err = io .ReadAll (resp .Body )
179
- if err != nil {
180
- fmt .Printf ("Error reading response body: %v\n " , err )
181
- return 0
182
- }
183
228
if resp .StatusCode != http .StatusOK {
184
229
fmt .Printf ("non-200 response: %v\n " , resp .Status )
185
230
return 0
@@ -267,7 +312,7 @@ func formatSuccessRate(successful, total int, best bool, worst bool) string {
267
312
268
313
func printResults (results []endpointResult , requestCount * int ) {
269
314
// Print header
270
- headerFmt := ansiColors ["blue" ] + "%-20s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s" + ansiColors ["nc" ] + "\n "
315
+ headerFmt := ansiColors ["blue" ] + "%-25s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s" + ansiColors ["nc" ] + "\n "
271
316
fmt .Printf ("\n " + headerFmt ,
272
317
"Endpoint " , "Average" , "Median" , "P5" , "P75" , "P80" , "P95" , "Total" , "Success" )
273
318
fmt .Println (ansiColors ["blue" ] + strings .Repeat ("-" , 121 ) + ansiColors ["nc" ])
0 commit comments