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  }