go.etcd.io/etcd@v3.3.27+incompatible/clientv3/integration/metrics_test.go (about) 1 // Copyright 2016 The etcd 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 integration 16 17 import ( 18 "bufio" 19 "context" 20 "io" 21 "net" 22 "net/http" 23 "strconv" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/coreos/etcd/clientv3" 29 "github.com/coreos/etcd/integration" 30 "github.com/coreos/etcd/pkg/testutil" 31 "github.com/coreos/etcd/pkg/transport" 32 33 grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus" 34 "github.com/prometheus/client_golang/prometheus/promhttp" 35 "google.golang.org/grpc" 36 ) 37 38 func TestV3ClientMetrics(t *testing.T) { 39 defer testutil.AfterTest(t) 40 41 var ( 42 addr string = "localhost:27989" 43 ln net.Listener 44 err error 45 ) 46 47 // listen for all Prometheus metrics 48 donec := make(chan struct{}) 49 go func() { 50 defer close(donec) 51 52 srv := &http.Server{Handler: promhttp.Handler()} 53 srv.SetKeepAlivesEnabled(false) 54 55 ln, err = transport.NewUnixListener(addr) 56 if err != nil { 57 t.Fatalf("Error: %v occurred while listening on addr: %v", err, addr) 58 } 59 60 err = srv.Serve(ln) 61 if err != nil && !transport.IsClosedConnError(err) { 62 t.Fatalf("Err serving http requests: %v", err) 63 } 64 }() 65 66 url := "unix://" + addr + "/metrics" 67 68 clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, SkipCreatingClient: true}) 69 defer clus.Terminate(t) 70 71 cfg := clientv3.Config{ 72 Endpoints: []string{clus.Members[0].GRPCAddr()}, 73 DialOptions: []grpc.DialOption{ 74 grpc.WithUnaryInterceptor(grpcprom.UnaryClientInterceptor), 75 grpc.WithStreamInterceptor(grpcprom.StreamClientInterceptor), 76 }, 77 } 78 cli, cerr := clientv3.New(cfg) 79 if cerr != nil { 80 t.Fatal(cerr) 81 } 82 defer cli.Close() 83 84 wc := cli.Watch(context.Background(), "foo") 85 86 wBefore := sumCountersForMetricAndLabels(t, url, "grpc_client_msg_received_total", "Watch", "bidi_stream") 87 88 pBefore := sumCountersForMetricAndLabels(t, url, "grpc_client_started_total", "Put", "unary") 89 90 _, err = cli.Put(context.Background(), "foo", "bar") 91 if err != nil { 92 t.Errorf("Error putting value in key store") 93 } 94 95 pAfter := sumCountersForMetricAndLabels(t, url, "grpc_client_started_total", "Put", "unary") 96 if pBefore+1 != pAfter { 97 t.Errorf("grpc_client_started_total expected %d, got %d", 1, pAfter-pBefore) 98 } 99 100 // consume watch response 101 select { 102 case <-wc: 103 case <-time.After(10 * time.Second): 104 t.Error("Timeout occurred for getting watch response") 105 } 106 107 wAfter := sumCountersForMetricAndLabels(t, url, "grpc_client_msg_received_total", "Watch", "bidi_stream") 108 if wBefore+1 != wAfter { 109 t.Errorf("grpc_client_msg_received_total expected %d, got %d", 1, wAfter-wBefore) 110 } 111 112 ln.Close() 113 <-donec 114 } 115 116 func sumCountersForMetricAndLabels(t *testing.T, url string, metricName string, matchingLabelValues ...string) int { 117 count := 0 118 for _, line := range getHTTPBodyAsLines(t, url) { 119 ok := true 120 if !strings.HasPrefix(line, metricName) { 121 continue 122 } 123 124 for _, labelValue := range matchingLabelValues { 125 if !strings.Contains(line, `"`+labelValue+`"`) { 126 ok = false 127 break 128 } 129 } 130 131 if !ok { 132 continue 133 } 134 135 valueString := line[strings.LastIndex(line, " ")+1 : len(line)-1] 136 valueFloat, err := strconv.ParseFloat(valueString, 32) 137 if err != nil { 138 t.Fatalf("failed parsing value for line: %v and matchingLabelValues: %v", line, matchingLabelValues) 139 } 140 count += int(valueFloat) 141 } 142 return count 143 } 144 145 func getHTTPBodyAsLines(t *testing.T, url string) []string { 146 cfgtls := transport.TLSInfo{} 147 tr, err := transport.NewTransport(cfgtls, time.Second) 148 if err != nil { 149 t.Fatalf("Error getting transport: %v", err) 150 } 151 152 tr.MaxIdleConns = -1 153 tr.DisableKeepAlives = true 154 155 cli := &http.Client{Transport: tr} 156 157 resp, err := cli.Get(url) 158 if err != nil { 159 t.Fatalf("Error fetching: %v", err) 160 } 161 162 reader := bufio.NewReader(resp.Body) 163 lines := []string{} 164 for { 165 line, err := reader.ReadString('\n') 166 if err != nil { 167 if err == io.EOF { 168 break 169 } else { 170 t.Fatalf("error reading: %v", err) 171 } 172 } 173 lines = append(lines, line) 174 } 175 resp.Body.Close() 176 return lines 177 }