@@ -21,12 +21,14 @@ import (
21
21
"io"
22
22
"net"
23
23
"net/http"
24
+ "slices"
24
25
"strconv"
25
26
"strings"
26
27
"testing"
27
28
"time"
28
29
29
30
grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus"
31
+ "github.com/prometheus/client_golang/prometheus"
30
32
"github.com/prometheus/client_golang/prometheus/promhttp"
31
33
"google.golang.org/grpc"
32
34
@@ -176,3 +178,177 @@ func getHTTPBodyAsLines(t *testing.T, url string) []string {
176
178
resp .Body .Close ()
177
179
return lines
178
180
}
181
+
182
+ func TestAllMetricsGenerated (t * testing.T ) {
183
+ integration2 .BeforeTest (t )
184
+
185
+ var (
186
+ addr = "localhost:27989"
187
+ ln net.Listener
188
+ )
189
+
190
+ srv := & http.Server {Handler : promhttp .Handler ()}
191
+ srv .SetKeepAlivesEnabled (false )
192
+
193
+ ln , err := transport .NewUnixListener (addr )
194
+ if err != nil {
195
+ t .Errorf ("Error: %v occurred while listening on addr: %v" , err , addr )
196
+ }
197
+
198
+ donec := make (chan struct {})
199
+ defer func () {
200
+ ln .Close ()
201
+ <- donec
202
+ }()
203
+
204
+ // listen for all Prometheus metrics
205
+ go func () {
206
+ defer close (donec )
207
+
208
+ serr := srv .Serve (ln )
209
+ if serr != nil && ! transport .IsClosedConnError (serr ) {
210
+ t .Errorf ("Err serving http requests: %v" , serr )
211
+ }
212
+ }()
213
+
214
+ url := "unix://" + addr + "/metrics"
215
+
216
+ clus := integration2 .NewCluster (t , & integration2.ClusterConfig {Size : 1 })
217
+ defer clus .Terminate (t )
218
+
219
+ clientMetrics := grpcprom .NewClientMetrics ()
220
+ prometheus .Register (clientMetrics )
221
+
222
+ cfg := clientv3.Config {
223
+ Endpoints : []string {clus .Members [0 ].GRPCURL },
224
+ DialOptions : []grpc.DialOption {
225
+ grpc .WithUnaryInterceptor (clientMetrics .UnaryClientInterceptor ()),
226
+ grpc .WithStreamInterceptor (clientMetrics .StreamClientInterceptor ()),
227
+ },
228
+ }
229
+ cli , cerr := integration2 .NewClient (t , cfg )
230
+ if cerr != nil {
231
+ t .Fatal (cerr )
232
+ }
233
+ defer cli .Close ()
234
+
235
+ // Perform some operations to generate metrics
236
+ wc := cli .Watch (context .Background (), "foo" )
237
+ _ , err = cli .Put (context .Background (), "foo" , "bar" )
238
+ if err != nil {
239
+ t .Errorf ("Error putting value in key store" )
240
+ }
241
+
242
+ // consume watch response
243
+ select {
244
+ case <- wc :
245
+ case <- time .After (10 * time .Second ):
246
+ t .Error ("Timeout occurred for getting watch response" )
247
+ }
248
+
249
+ // Define the expected list of metrics
250
+ expectedMetrics := []string {
251
+ "etcd_cluster_version" ,
252
+ "etcd_disk_backend_commit_duration_seconds_bucket" ,
253
+ "etcd_disk_backend_commit_duration_seconds_count" ,
254
+ "etcd_disk_backend_commit_duration_seconds_sum" ,
255
+ "etcd_disk_backend_defrag_duration_seconds_bucket" ,
256
+ "etcd_disk_backend_defrag_duration_seconds_count" ,
257
+ "etcd_disk_backend_defrag_duration_seconds_sum" ,
258
+ "etcd_disk_backend_snapshot_duration_seconds_bucket" ,
259
+ "etcd_disk_backend_snapshot_duration_seconds_count" ,
260
+ "etcd_disk_backend_snapshot_duration_seconds_sum" ,
261
+ "etcd_disk_defrag_inflight" ,
262
+ "etcd_disk_wal_fsync_duration_seconds_bucket" ,
263
+ "etcd_disk_wal_fsync_duration_seconds_count" ,
264
+ "etcd_disk_wal_fsync_duration_seconds_sum" ,
265
+ "etcd_disk_wal_write_bytes_total" ,
266
+ "etcd_disk_wal_write_duration_seconds_bucket" ,
267
+ "etcd_disk_wal_write_duration_seconds_count" ,
268
+ "etcd_disk_wal_write_duration_seconds_sum" ,
269
+ "etcd_mvcc_db_open_read_transactions" ,
270
+ "etcd_mvcc_db_total_size_in_bytes" ,
271
+ "etcd_mvcc_db_total_size_in_use_in_bytes" ,
272
+ "etcd_mvcc_delete_total" ,
273
+ "etcd_mvcc_hash_duration_seconds_bucket" ,
274
+ "etcd_mvcc_hash_duration_seconds_count" ,
275
+ "etcd_mvcc_hash_duration_seconds_sum" ,
276
+ "etcd_mvcc_hash_rev_duration_seconds_bucket" ,
277
+ "etcd_mvcc_hash_rev_duration_seconds_count" ,
278
+ "etcd_mvcc_hash_rev_duration_seconds_sum" ,
279
+ "etcd_mvcc_put_total" ,
280
+ "etcd_mvcc_range_total" ,
281
+ "etcd_mvcc_txn_total" ,
282
+ "etcd_network_client_grpc_received_bytes_total" ,
283
+ "etcd_network_client_grpc_sent_bytes_total" ,
284
+ "etcd_network_known_peers" ,
285
+ "etcd_server_apply_duration_seconds_bucket" ,
286
+ "etcd_server_apply_duration_seconds_count" ,
287
+ "etcd_server_apply_duration_seconds_sum" ,
288
+ "etcd_server_client_requests_total" ,
289
+ "etcd_server_go_version" ,
290
+ "etcd_server_has_leader" ,
291
+ "etcd_server_health_failures" ,
292
+ "etcd_server_health_success" ,
293
+ "etcd_server_heartbeat_send_failures_total" ,
294
+ "etcd_server_id" ,
295
+ "etcd_server_is_leader" ,
296
+ "etcd_server_is_learner" ,
297
+ "etcd_server_leader_changes_seen_total" ,
298
+ "etcd_server_learner_promote_successes" ,
299
+ "etcd_server_proposals_applied_total" ,
300
+ "etcd_server_proposals_committed_total" ,
301
+ "etcd_server_proposals_failed_total" ,
302
+ "etcd_server_proposals_pending" ,
303
+ "etcd_server_quota_backend_bytes" ,
304
+ "etcd_server_read_indexes_failed_total" ,
305
+ "etcd_server_slow_apply_total" ,
306
+ "etcd_server_slow_read_indexes_total" ,
307
+ "etcd_server_snapshot_apply_in_progress_total" ,
308
+ "etcd_server_version" ,
309
+ "etcd_snap_db_fsync_duration_seconds_bucket" ,
310
+ "etcd_snap_db_fsync_duration_seconds_count" ,
311
+ "etcd_snap_db_fsync_duration_seconds_sum" ,
312
+ "etcd_snap_db_save_total_duration_seconds_bucket" ,
313
+ "etcd_snap_db_save_total_duration_seconds_count" ,
314
+ "etcd_snap_db_save_total_duration_seconds_sum" ,
315
+ "etcd_snap_fsync_duration_seconds_bucket" ,
316
+ "etcd_snap_fsync_duration_seconds_count" ,
317
+ "etcd_snap_fsync_duration_seconds_sum" ,
318
+ "grpc_client_handled_total" ,
319
+ "grpc_client_msg_received_total" ,
320
+ "grpc_client_msg_sent_total" ,
321
+ "grpc_client_started_total" ,
322
+ "grpc_server_handled_total" ,
323
+ "grpc_server_msg_received_total" ,
324
+ "grpc_server_msg_sent_total" ,
325
+ "grpc_server_started_total" ,
326
+ }
327
+
328
+ // Get the list of generated metrics
329
+ generatedMetrics := getMetricsList (t , url )
330
+ for _ , metric := range expectedMetrics {
331
+ if ! slices .Contains (generatedMetrics , metric ) {
332
+ t .Errorf ("Expected metric %s not found in generated metrics" , metric )
333
+ }
334
+ }
335
+ }
336
+
337
+ func getMetricsList (t * testing.T , url string ) []string {
338
+ lines := getHTTPBodyAsLines (t , url )
339
+ metrics := make (map [string ]struct {})
340
+ for _ , line := range lines {
341
+ if strings .Contains (line , "{" ) {
342
+ metric := line [:strings .Index (line , "{" )]
343
+ metrics [metric ] = struct {}{}
344
+ } else {
345
+ metric := line [:strings .Index (line , " " )]
346
+ metrics [metric ] = struct {}{}
347
+ }
348
+ }
349
+ var metricList []string
350
+ for metric := range metrics {
351
+ metricList = append (metricList , metric )
352
+ }
353
+ return metricList
354
+ }
0 commit comments