github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-twemproxy/lib/twemproxy_test.go (about)

     1  package mptwemproxy
     2  
     3  import (
     4  	"flag"
     5  	"log"
     6  	"net"
     7  	"os"
     8  	"reflect"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/lestrrat-go/tcptest"
    17  )
    18  
    19  var (
    20  	statsServer *tcptest.TCPTest
    21  	reply       string
    22  	replyLock   sync.RWMutex
    23  )
    24  
    25  func getReply() string {
    26  	replyLock.RLock()
    27  	defer replyLock.RUnlock()
    28  	return reply
    29  }
    30  
    31  func setReply(s string) {
    32  	replyLock.Lock()
    33  	reply = s
    34  	replyLock.Unlock()
    35  }
    36  
    37  func TestMain(m *testing.M) {
    38  	os.Exit(mainTest(m))
    39  }
    40  
    41  func mainTest(m *testing.M) int {
    42  	flag.Parse()
    43  
    44  	// run a test server returning a dummy stats json
    45  	startStatsServer := func(port int) {
    46  		err := startTCPServer(port)
    47  		if err != nil {
    48  			panic("Failed to run a test server: " + err.Error())
    49  		}
    50  	}
    51  	server, err := tcptest.Start(startStatsServer, 10*time.Second)
    52  	if err != nil {
    53  		panic("Failed to start a stats server: " + err.Error())
    54  	}
    55  	statsServer = server
    56  
    57  	log.Printf("Started a stats server. port=%d", statsServer.Port())
    58  
    59  	return m.Run()
    60  }
    61  
    62  func startTCPServer(port int) error {
    63  	server, err := net.Listen("tcp", ":"+strconv.Itoa(port))
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	ch := make(chan net.Conn)
    69  	go func() {
    70  		for {
    71  			client, err := server.Accept()
    72  			if err != nil {
    73  				log.Printf("Failed to accept: err=%v", err)
    74  			}
    75  			ch <- client
    76  		}
    77  	}()
    78  
    79  	for {
    80  		go func(client net.Conn) {
    81  			stats := getReply()
    82  			_, err := client.Write([]byte(stats))
    83  			if err != nil {
    84  				log.Printf("Failed to write %v: err=%v", stats, err)
    85  			}
    86  		}(<-ch)
    87  	}
    88  }
    89  
    90  var jsonStr = `{
    91      "service": "nutcracker",
    92      "source": "ip-10-0-1-10",
    93      "version": "0.4.1",
    94      "uptime": 74635,
    95      "timestamp": 1477054533,
    96      "total_connections": 3895,
    97      "curr_connections": 272,
    98      "redis-index": {
    99          "index1.cache:6379": {
   100              "out_queue_bytes": 80,
   101              "out_queue": 1,
   102              "in_queue_bytes": 50,
   103              "in_queue": 2,
   104              "requests": 3908,
   105              "request_bytes": 170558,
   106              "responses": 3908,
   107              "response_bytes": 176918,
   108              "server_connections": 4,
   109              "server_eof": 2,
   110              "server_timedout": 3,
   111              "server_ejected_at": 0,
   112              "server_err": 5
   113          },
   114          "client_eof": 1716,
   115          "client_err": 10,
   116          "client_connections": 121,
   117          "server_ejects": 20,
   118          "forward_error": 1,
   119          "fragments": 0
   120      },
   121      "redis/budget": {
   122          "budget1.cache:6379": {
   123              "out_queue_bytes": 81,
   124              "out_queue": 2,
   125              "in_queue_bytes": 51,
   126              "in_queue": 3,
   127              "requests": 3909,
   128              "request_bytes": 170559,
   129              "responses": 3909,
   130              "response_bytes": 176919,
   131              "server_connections": 5,
   132              "server_eof": 3,
   133              "server_timedout": 4,
   134              "server_ejected_at": 0,
   135              "server_err": 3
   136          },
   137          "budget2.cache:6379": {
   138              "out_queue_bytes": 83,
   139              "out_queue": 4,
   140              "in_queue_bytes": 53,
   141              "in_queue": 5,
   142              "requests": 3911,
   143              "request_bytes": 170561,
   144              "responses": 3911,
   145              "response_bytes": 176921,
   146              "server_connections": 7,
   147              "server_eof": 5,
   148              "server_timedout": 6,
   149              "server_ejected_at": 2,
   150              "server_err": 5
   151          },
   152          "client_eof": 2716,
   153          "client_err": 30,
   154          "client_connections": 221,
   155          "server_ejects": 40,
   156          "forward_error": 2,
   157          "fragments": 0
   158      }
   159  }`
   160  
   161  func TestFetchMetrics(t *testing.T) {
   162  	// response a valid stats json
   163  	setReply(jsonStr)
   164  
   165  	// get metrics
   166  	p := TwemproxyPlugin{
   167  		Address:           "localhost:" + strconv.Itoa(statsServer.Port()),
   168  		Prefix:            "twemproxy",
   169  		Timeout:           5,
   170  		EachServerMetrics: true,
   171  	}
   172  	metrics, err := p.FetchMetrics()
   173  	if err != nil {
   174  		t.Errorf("Failed to FetchMetrics: %s", err)
   175  		return
   176  	}
   177  
   178  	// check the metrics
   179  	expected := map[string]uint64{
   180  		"total_connections":                                                         3895,
   181  		"curr_connections":                                                          272,
   182  		"pool_error.redis-index.client_err":                                         10,
   183  		"pool_error.redis-index.server_ejects":                                      20,
   184  		"pool_error.redis-index.forward_error":                                      1,
   185  		"pool_client_connections.redis-index.client_eof":                            1716,
   186  		"pool_client_connections.redis-index.client_connections":                    121,
   187  		"server_error.redis-index_index1_cache_6379.server_err":                     5,
   188  		"server_error.redis-index_index1_cache_6379.server_timedout":                3,
   189  		"server_connections.redis-index_index1_cache_6379.server_eof":               2,
   190  		"server_connections.redis-index_index1_cache_6379.server_connections":       4,
   191  		"server_queue.redis-index_index1_cache_6379.out_queue":                      1,
   192  		"server_queue.redis-index_index1_cache_6379.in_queue":                       2,
   193  		"server_queue_bytes.redis-index_index1_cache_6379.out_queue_bytes":          80,
   194  		"server_queue_bytes.redis-index_index1_cache_6379.in_queue_bytes":           50,
   195  		"server_communications.redis-index_index1_cache_6379.requests":              3908,
   196  		"server_communications.redis-index_index1_cache_6379.responses":             3908,
   197  		"server_communication_bytes.redis-index_index1_cache_6379.request_bytes":    170558,
   198  		"server_communication_bytes.redis-index_index1_cache_6379.response_bytes":   176918,
   199  		"pool_error.redis_budget.client_err":                                        30,
   200  		"pool_error.redis_budget.server_ejects":                                     40,
   201  		"pool_error.redis_budget.forward_error":                                     2,
   202  		"pool_client_connections.redis_budget.client_eof":                           2716,
   203  		"pool_client_connections.redis_budget.client_connections":                   221,
   204  		"server_error.redis_budget_budget1_cache_6379.server_err":                   3,
   205  		"server_error.redis_budget_budget1_cache_6379.server_timedout":              4,
   206  		"server_connections.redis_budget_budget1_cache_6379.server_eof":             3,
   207  		"server_connections.redis_budget_budget1_cache_6379.server_connections":     5,
   208  		"server_queue.redis_budget_budget1_cache_6379.out_queue":                    2,
   209  		"server_queue.redis_budget_budget1_cache_6379.in_queue":                     3,
   210  		"server_queue_bytes.redis_budget_budget1_cache_6379.out_queue_bytes":        81,
   211  		"server_queue_bytes.redis_budget_budget1_cache_6379.in_queue_bytes":         51,
   212  		"server_communications.redis_budget_budget1_cache_6379.requests":            3909,
   213  		"server_communications.redis_budget_budget1_cache_6379.responses":           3909,
   214  		"server_communication_bytes.redis_budget_budget1_cache_6379.request_bytes":  170559,
   215  		"server_communication_bytes.redis_budget_budget1_cache_6379.response_bytes": 176919,
   216  		"server_error.redis_budget_budget2_cache_6379.server_err":                   5,
   217  		"server_error.redis_budget_budget2_cache_6379.server_timedout":              6,
   218  		"server_connections.redis_budget_budget2_cache_6379.server_eof":             5,
   219  		"server_connections.redis_budget_budget2_cache_6379.server_connections":     7,
   220  		"server_queue.redis_budget_budget2_cache_6379.out_queue":                    4,
   221  		"server_queue.redis_budget_budget2_cache_6379.in_queue":                     5,
   222  		"server_queue_bytes.redis_budget_budget2_cache_6379.out_queue_bytes":        83,
   223  		"server_queue_bytes.redis_budget_budget2_cache_6379.in_queue_bytes":         53,
   224  		"server_communications.redis_budget_budget2_cache_6379.requests":            3911,
   225  		"server_communications.redis_budget_budget2_cache_6379.responses":           3911,
   226  		"server_communication_bytes.redis_budget_budget2_cache_6379.request_bytes":  170561,
   227  		"server_communication_bytes.redis_budget_budget2_cache_6379.response_bytes": 176921,
   228  	}
   229  
   230  	for k, v := range expected {
   231  		value, ok := metrics[k]
   232  		if !ok {
   233  			t.Errorf("metric of %s cannot be fetched", k)
   234  			continue
   235  		}
   236  		if v != value {
   237  			t.Errorf("metric of %s should be %v, but %v", k, v, value)
   238  		}
   239  	}
   240  }
   241  
   242  func TestFetchMetrics_disableEachMetrics(t *testing.T) {
   243  	// response a valid stats json
   244  	setReply(jsonStr)
   245  
   246  	// get metrics
   247  	p := TwemproxyPlugin{
   248  		Address: "localhost:" + strconv.Itoa(statsServer.Port()),
   249  		Prefix:  "twemproxy",
   250  		Timeout: 5,
   251  	}
   252  	metrics, err := p.FetchMetrics()
   253  	if err != nil {
   254  		t.Errorf("Failed to FetchMetrics: %s", err)
   255  		return
   256  	}
   257  
   258  	expectedNum := 17
   259  	if len(metrics) != expectedNum {
   260  		t.Errorf("%d metrics are expected to be collected, but it was %d", expectedNum, len(metrics))
   261  	}
   262  
   263  	// check the metrics
   264  	expected := map[string]uint64{
   265  		"total_connections": 3895,
   266  		"curr_connections":  272,
   267  	}
   268  
   269  	for k, v := range expected {
   270  		value, ok := metrics[k]
   271  		if !ok {
   272  			t.Errorf("metric of %s cannot be fetched", k)
   273  			continue
   274  		}
   275  		if v != value {
   276  			t.Errorf("metric of %s should be %v, but %v", k, v, value)
   277  		}
   278  	}
   279  }
   280  
   281  func TestFetchMetricsFail(t *testing.T) {
   282  	assertPanic := func(t *testing.T, f func() (map[string]interface{}, error)) {
   283  		defer func() {
   284  			if r := recover(); r == nil {
   285  				t.Errorf("FetchMetrics should be panic: stats=%v", getReply())
   286  			}
   287  		}()
   288  		f()
   289  	}
   290  
   291  	p := TwemproxyPlugin{
   292  		Address:           "localhost:" + strconv.Itoa(statsServer.Port()),
   293  		Prefix:            "twemproxy",
   294  		Timeout:           5,
   295  		EachServerMetrics: true,
   296  	}
   297  
   298  	noClientErrJSONStr := strings.Replace(
   299  		jsonStr, "\"client_err\": 10,\n", "", 1)
   300  	setReply(noClientErrJSONStr)
   301  	assertPanic(t, p.FetchMetrics)
   302  
   303  	noOutQueueJSONStr := strings.Replace(
   304  		jsonStr, "\"out_queue\": 1,\n", "", 1)
   305  	setReply(noOutQueueJSONStr)
   306  	assertPanic(t, p.FetchMetrics)
   307  
   308  	// return error against a stats json with addition
   309  	addInvalidParamJSONStr := strings.Replace(
   310  		jsonStr, "3895,", "3895, \"hoge\": 1,", 1)
   311  	setReply(addInvalidParamJSONStr)
   312  	_, err := p.FetchMetrics()
   313  	if err == nil {
   314  		t.Errorf("FetchMetrics should return error: stats=%v", getReply())
   315  	}
   316  }
   317  
   318  func TestGraphDefinition(t *testing.T) {
   319  	p := TwemproxyPlugin{
   320  		Address: "",
   321  		Prefix:  "twemproxy",
   322  		Timeout: 5,
   323  	}
   324  	graphdef := p.GraphDefinition()
   325  
   326  	expectedNames := map[string]([]string){
   327  		"connections": {
   328  			"total_connections",
   329  			"curr_connections",
   330  		},
   331  		"pool_error.#": {
   332  			"client_err",
   333  			"server_ejects",
   334  			"forward_error",
   335  		},
   336  		"pool_client_connections.#": {
   337  			"client_connections",
   338  			"client_eof",
   339  		},
   340  		"server_error.#": {
   341  			"server_err",
   342  			"server_timedout",
   343  		},
   344  		"server_connections.#": {
   345  			"server_connections",
   346  			"server_eof",
   347  		},
   348  		"server_queue.#": {
   349  			"out_queue",
   350  			"in_queue",
   351  		},
   352  		"server_queue_bytes.#": {
   353  			"out_queue_bytes",
   354  			"in_queue_bytes",
   355  		},
   356  		"server_communications.#": {
   357  			"requests",
   358  			"responses",
   359  		},
   360  		"server_communication_bytes.#": {
   361  			"request_bytes",
   362  			"response_bytes",
   363  		},
   364  	}
   365  
   366  	expectedLabels := map[string]([]string){
   367  		"connections": {
   368  			"New Connections",
   369  			"Current Connections",
   370  		},
   371  		"pool_error.#": {
   372  			"Client Error",
   373  			"Server Ejects",
   374  			"Forward Error",
   375  		},
   376  		"pool_client_connections.#": {
   377  			"Client Connections",
   378  			"Client EOF",
   379  		},
   380  		"server_error.#": {
   381  			"Server Error",
   382  			"Server Timedout",
   383  		},
   384  		"server_connections.#": {
   385  			"Server Connections",
   386  			"Server EOF",
   387  		},
   388  		"server_queue.#": {
   389  			"Out Queue",
   390  			"In Queue",
   391  		},
   392  		"server_queue_bytes.#": {
   393  			"Out Queue Bytes",
   394  			"In Queue Bytes",
   395  		},
   396  		"server_communications.#": {
   397  			"Requests",
   398  			"Responses",
   399  		},
   400  		"server_communication_bytes.#": {
   401  			"Request Bytes",
   402  			"Response Bytes",
   403  		},
   404  	}
   405  
   406  	for k, names := range expectedNames {
   407  		value, ok := graphdef[k]
   408  		if !ok {
   409  			t.Errorf("graphdef of %s cannot be fetched", k)
   410  			continue
   411  		}
   412  
   413  		var metricNames []string
   414  		var metricLabels []string
   415  		for _, metric := range value.Metrics {
   416  			metricNames = append(metricNames, metric.Name)
   417  			metricLabels = append(metricLabels, metric.Label)
   418  		}
   419  
   420  		sort.Strings(names)
   421  		sort.Strings(metricNames)
   422  		if !reflect.DeepEqual(names, metricNames) {
   423  			t.Errorf("graphdef of %s should contain names %v, but %v",
   424  				k, names, metricNames)
   425  		}
   426  
   427  		labels := expectedLabels[k]
   428  		sort.Strings(labels)
   429  		sort.Strings(metricLabels)
   430  		if !reflect.DeepEqual(labels, metricLabels) {
   431  			t.Errorf("graphdef of %s should contain labels %v, but %v",
   432  				k, labels, metricLabels)
   433  		}
   434  	}
   435  }