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 }