github.com/arthur-befumo/witchcraft-go-server@v1.12.0/integration/metrics_test.go (about) 1 // Copyright (c) 2018 Palantir Technologies. All rights reserved. 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 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 "net/http" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/palantir/pkg/httpserver" 29 "github.com/palantir/pkg/metrics" 30 "github.com/palantir/witchcraft-go-logging/conjure/witchcraft/api/logging" 31 "github.com/palantir/witchcraft-go-server/config" 32 "github.com/palantir/witchcraft-go-server/witchcraft" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 // TestEmitMetrics verifies that metrics are printed periodically by a Witchcraft server and that the emitted values 38 // respect the default blacklist. We verify both custom metrics set in the InitFunc (with tags) and server.response 39 // metrics from the metrics middleware. 40 func TestEmitMetrics(t *testing.T) { 41 logOutputBuffer := &bytes.Buffer{} 42 port, err := httpserver.AvailablePort() 43 require.NoError(t, err) 44 45 // ensure that registry used in this test is unique/does not have any past metrics registered on it 46 metrics.DefaultMetricsRegistry = metrics.NewRootMetricsRegistry() 47 server, serverErr, cleanup := createAndRunCustomTestServer(t, port, port, func(ctx context.Context, info witchcraft.InitInfo) (deferFn func(), rErr error) { 48 ctx = metrics.AddTags(ctx, metrics.MustNewTag("key", "val")) 49 metrics.FromContext(ctx).Counter("my-counter").Inc(13) 50 return nil, info.Router.Post("/error", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 51 rw.WriteHeader(500) 52 })) 53 }, logOutputBuffer, func(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) *witchcraft.Server { 54 installCfg.MetricsEmitFrequency = 100 * time.Millisecond 55 return createTestServer(t, initFn, installCfg, logOutputBuffer) 56 }) 57 defer func() { 58 require.NoError(t, server.Close()) 59 }() 60 defer cleanup() 61 62 // Make POST that will 404 to trigger request size and error rate metrics 63 _, err = testServerClient().Post(fmt.Sprintf("https://localhost:%d/%s/%s", port, basePath, "error"), "application/json", strings.NewReader("{}")) 64 require.NoError(t, err) 65 66 // Allow the metric emitter to do its thing. 67 time.Sleep(150 * time.Millisecond) 68 69 parts := strings.Split(logOutputBuffer.String(), "\n") 70 var metricLogs []logging.MetricLogV1 71 for _, curr := range parts { 72 if strings.Contains(curr, `"metric.1"`) { 73 var currLog logging.MetricLogV1 74 require.NoError(t, json.Unmarshal([]byte(curr), &currLog)) 75 metricLogs = append(metricLogs, currLog) 76 } 77 } 78 79 var seenMyCounter, seenResponseTimer, seenUptime, seenResponseSize, seenRequestSize, seenResponseError bool 80 for _, metricLog := range metricLogs { 81 switch metricLog.MetricName { 82 case "my-counter": 83 seenMyCounter = true 84 assert.Equal(t, "counter", metricLog.MetricType, "my-counter metric had incorrect type") 85 assert.Equal(t, map[string]interface{}{"count": json.Number("13")}, metricLog.Values) 86 assert.Equal(t, map[string]string{"key": "val"}, metricLog.Tags) 87 case "server.response": 88 seenResponseTimer = true 89 assert.Equal(t, "timer", metricLog.MetricType, "server.response metric had incorrect type") 90 assert.NotNil(t, metricLog.Values["count"]) 91 assert.NotNil(t, metricLog.Values["max"]) 92 assert.NotNil(t, metricLog.Values["p95"]) 93 assert.NotNil(t, metricLog.Values["p99"]) 94 95 // keys are part of the default blacklist and should thus be nil 96 assert.Nil(t, metricLog.Values["1m"]) 97 assert.Nil(t, metricLog.Values["5m"]) 98 assert.Nil(t, metricLog.Values["15m"]) 99 assert.Nil(t, metricLog.Values["meanRate"]) 100 assert.Nil(t, metricLog.Values["min"]) 101 assert.Nil(t, metricLog.Values["mean"]) 102 assert.Nil(t, metricLog.Values["stddev"]) 103 assert.Nil(t, metricLog.Values["p50"]) 104 case "server.request.size": 105 seenRequestSize = true 106 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 107 assert.NotNil(t, metricLog.Values["max"]) 108 assert.NotNil(t, metricLog.Values["p95"]) 109 assert.NotNil(t, metricLog.Values["p99"]) 110 assert.NotNil(t, metricLog.Values["count"]) 111 112 // keys are part of the default blacklist and should thus be nil 113 assert.Nil(t, metricLog.Values["min"]) 114 assert.Nil(t, metricLog.Values["mean"]) 115 assert.Nil(t, metricLog.Values["stddev"]) 116 assert.Nil(t, metricLog.Values["p50"]) 117 case "server.response.size": 118 seenResponseSize = true 119 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 120 assert.NotNil(t, metricLog.Values["max"]) 121 assert.NotNil(t, metricLog.Values["p95"]) 122 assert.NotNil(t, metricLog.Values["p99"]) 123 assert.NotNil(t, metricLog.Values["count"]) 124 125 // keys are part of the default blacklist and should thus be nil 126 assert.Nil(t, metricLog.Values["min"]) 127 assert.Nil(t, metricLog.Values["mean"]) 128 assert.Nil(t, metricLog.Values["stddev"]) 129 assert.Nil(t, metricLog.Values["p50"]) 130 case "server.response.error": 131 seenResponseError = true 132 assert.Equal(t, "meter", metricLog.MetricType, "server.response metric had incorrect type") 133 assert.NotNil(t, metricLog.Values["count"]) 134 135 // keys are part of the default blacklist and should thus be nil 136 assert.Nil(t, metricLog.Values["1m"]) 137 assert.Nil(t, metricLog.Values["5m"]) 138 assert.Nil(t, metricLog.Values["15m"]) 139 assert.Nil(t, metricLog.Values["mean"]) 140 case "server.uptime": 141 seenUptime = true 142 assert.Equal(t, "gauge", metricLog.MetricType, "server.uptime metric had incorrect type") 143 assert.NotZero(t, metricLog.Values["value"]) 144 default: 145 assert.Fail(t, "unexpected metric encountered: %s", metricLog.MetricName) 146 } 147 } 148 assert.True(t, seenMyCounter, "my-counter metric was not emitted") 149 assert.True(t, seenResponseTimer, "server.response metric was not emitted") 150 assert.True(t, seenRequestSize, "server.request.size metric was not emitted") 151 assert.True(t, seenResponseSize, "server.response.size metric was not emitted") 152 assert.True(t, seenResponseError, "server.response.error metric was not emitted") 153 assert.True(t, seenUptime, "server.uptime metric was not emitted") 154 155 select { 156 case err := <-serverErr: 157 require.NoError(t, err) 158 default: 159 } 160 } 161 162 // TestEmitMetricsEmptyBlacklist verifies that metrics are printed periodically by a Witchcraft server and that, if the 163 // blacklist is empty, all values are emitted. We verify both custom metrics set in the InitFunc (with tags) and 164 // server.response metrics from the metrics middleware. 165 func TestEmitMetricsEmptyBlacklist(t *testing.T) { 166 logOutputBuffer := &bytes.Buffer{} 167 port, err := httpserver.AvailablePort() 168 require.NoError(t, err) 169 170 // ensure that registry used in this test is unique/does not have any past metrics registered on it 171 metrics.DefaultMetricsRegistry = metrics.NewRootMetricsRegistry() 172 server, serverErr, cleanup := createAndRunCustomTestServer(t, port, port, func(ctx context.Context, info witchcraft.InitInfo) (deferFn func(), rErr error) { 173 ctx = metrics.AddTags(ctx, metrics.MustNewTag("key", "val")) 174 metrics.FromContext(ctx).Counter("my-counter").Inc(13) 175 return nil, info.Router.Post("/error", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 176 rw.WriteHeader(500) 177 })) 178 }, logOutputBuffer, func(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) *witchcraft.Server { 179 installCfg.MetricsEmitFrequency = 100 * time.Millisecond 180 return createTestServer(t, initFn, installCfg, logOutputBuffer).WithMetricTypeValuesBlacklist(map[string]map[string]struct{}{}) 181 }) 182 defer func() { 183 require.NoError(t, server.Close()) 184 }() 185 defer cleanup() 186 187 // Make POST that will 404 to trigger request size and error rate metrics 188 _, err = testServerClient().Post(fmt.Sprintf("https://localhost:%d/%s/%s", port, basePath, "error"), "application/json", strings.NewReader("{}")) 189 require.NoError(t, err) 190 191 // Allow the metric emitter to do its thing. 192 time.Sleep(150 * time.Millisecond) 193 194 parts := strings.Split(logOutputBuffer.String(), "\n") 195 var metricLogs []logging.MetricLogV1 196 for _, curr := range parts { 197 if strings.Contains(curr, `"metric.1"`) { 198 var currLog logging.MetricLogV1 199 require.NoError(t, json.Unmarshal([]byte(curr), &currLog)) 200 metricLogs = append(metricLogs, currLog) 201 } 202 } 203 204 var seenMyCounter, seenResponseTimer, seenResponseSize, seenRequestSize, seenResponseError, seenUptime bool 205 for _, metricLog := range metricLogs { 206 switch metricLog.MetricName { 207 case "my-counter": 208 seenMyCounter = true 209 assert.Equal(t, "counter", metricLog.MetricType, "my-counter metric had incorrect type") 210 assert.Equal(t, map[string]interface{}{"count": json.Number("13")}, metricLog.Values) 211 assert.Equal(t, map[string]string{"key": "val"}, metricLog.Tags) 212 case "server.response": 213 seenResponseTimer = true 214 assert.Equal(t, "timer", metricLog.MetricType, "server.response metric had incorrect type") 215 216 // blacklist is set to empty, so all keys should be non-nil 217 assert.NotNil(t, metricLog.Values["count"]) 218 assert.NotNil(t, metricLog.Values["1m"]) 219 assert.NotNil(t, metricLog.Values["5m"]) 220 assert.NotNil(t, metricLog.Values["15m"]) 221 assert.NotNil(t, metricLog.Values["meanRate"]) 222 assert.NotNil(t, metricLog.Values["min"]) 223 assert.NotNil(t, metricLog.Values["max"]) 224 assert.NotNil(t, metricLog.Values["mean"]) 225 assert.NotNil(t, metricLog.Values["stddev"]) 226 assert.NotNil(t, metricLog.Values["p50"]) 227 assert.NotNil(t, metricLog.Values["p95"]) 228 assert.NotNil(t, metricLog.Values["p99"]) 229 case "server.request.size": 230 seenRequestSize = true 231 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 232 233 // blacklist is set to empty, so all keys should be non-nil 234 assert.NotNil(t, metricLog.Values["min"]) 235 assert.NotNil(t, metricLog.Values["max"]) 236 assert.NotNil(t, metricLog.Values["mean"]) 237 assert.NotNil(t, metricLog.Values["stddev"]) 238 assert.NotNil(t, metricLog.Values["p50"]) 239 assert.NotNil(t, metricLog.Values["p95"]) 240 assert.NotNil(t, metricLog.Values["p99"]) 241 assert.NotNil(t, metricLog.Values["count"]) 242 case "server.response.size": 243 seenResponseSize = true 244 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 245 246 // blacklist is set to empty, so all keys should be non-nil 247 assert.NotNil(t, metricLog.Values["min"]) 248 assert.NotNil(t, metricLog.Values["max"]) 249 assert.NotNil(t, metricLog.Values["mean"]) 250 assert.NotNil(t, metricLog.Values["stddev"]) 251 assert.NotNil(t, metricLog.Values["p50"]) 252 assert.NotNil(t, metricLog.Values["p95"]) 253 assert.NotNil(t, metricLog.Values["p99"]) 254 assert.NotNil(t, metricLog.Values["count"]) 255 case "server.response.error": 256 seenResponseError = true 257 assert.Equal(t, "meter", metricLog.MetricType, "server.response metric had incorrect type") 258 259 // blacklist is set to empty, so all keys should be non-nil 260 assert.NotNil(t, metricLog.Values["count"]) 261 assert.NotNil(t, metricLog.Values["1m"]) 262 assert.NotNil(t, metricLog.Values["5m"]) 263 assert.NotNil(t, metricLog.Values["15m"]) 264 assert.NotNil(t, metricLog.Values["mean"]) 265 case "server.uptime": 266 seenUptime = true 267 assert.Equal(t, "gauge", metricLog.MetricType, "server.uptime metric had incorrect type") 268 assert.NotZero(t, metricLog.Values["value"]) 269 default: 270 assert.Fail(t, "unexpected metric encountered: %s", metricLog.MetricName) 271 } 272 } 273 assert.True(t, seenMyCounter, "my-counter metric was not emitted") 274 assert.True(t, seenResponseTimer, "server.response metric was not emitted") 275 assert.True(t, seenRequestSize, "server.request.size metric was not emitted") 276 assert.True(t, seenResponseSize, "server.response.size metric was not emitted") 277 assert.True(t, seenResponseError, "server.response.error metric was not emitted") 278 assert.True(t, seenUptime, "server.uptime metric was not emitted") 279 280 select { 281 case err := <-serverErr: 282 require.NoError(t, err) 283 default: 284 } 285 } 286 287 // TestMetricTypeValueBlacklist tests that if a metric type value is blacklisted, all metric of that type does not 288 // contain any of the blacklisted keys. 289 func TestMetricTypeValueBlacklist(t *testing.T) { 290 logOutputBuffer := &bytes.Buffer{} 291 port, err := httpserver.AvailablePort() 292 require.NoError(t, err) 293 294 // ensure that registry used in this test is unique/does not have any past metrics registered on it 295 metrics.DefaultMetricsRegistry = metrics.NewRootMetricsRegistry() 296 server, serverErr, cleanup := createAndRunCustomTestServer(t, port, port, func(ctx context.Context, info witchcraft.InitInfo) (deferFn func(), rErr error) { 297 ctx = metrics.AddTags(ctx, metrics.MustNewTag("key", "val")) 298 metrics.FromContext(ctx).Counter("my-counter").Inc(13) 299 return nil, info.Router.Post("/error", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 300 rw.WriteHeader(500) 301 })) 302 }, logOutputBuffer, func(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) *witchcraft.Server { 303 installCfg.MetricsEmitFrequency = 100 * time.Millisecond 304 return createTestServer(t, initFn, installCfg, logOutputBuffer).WithMetricTypeValuesBlacklist(map[string]map[string]struct{}{ 305 "histogram": {"count": {}}, 306 }) 307 }) 308 defer func() { 309 require.NoError(t, server.Close()) 310 }() 311 defer cleanup() 312 313 // Make POST that will 404 to trigger request size and error rate metrics 314 _, err = testServerClient().Post(fmt.Sprintf("https://localhost:%d/%s/%s", port, basePath, "error"), "application/json", strings.NewReader("{}")) 315 require.NoError(t, err) 316 317 // Allow the metric emitter to do its thing. 318 time.Sleep(150 * time.Millisecond) 319 320 parts := strings.Split(logOutputBuffer.String(), "\n") 321 var metricLogs []logging.MetricLogV1 322 for _, curr := range parts { 323 if strings.Contains(curr, `"metric.1"`) { 324 var currLog logging.MetricLogV1 325 require.NoError(t, json.Unmarshal([]byte(curr), &currLog)) 326 metricLogs = append(metricLogs, currLog) 327 } 328 } 329 330 var seenMyCounter, seenResponseTimer, seenResponseSize, seenRequestSize, seenResponseError, seenUptime bool 331 for _, metricLog := range metricLogs { 332 switch metricLog.MetricName { 333 case "my-counter": 334 seenMyCounter = true 335 assert.Equal(t, "counter", metricLog.MetricType, "my-counter metric had incorrect type") 336 assert.Equal(t, map[string]interface{}{"count": json.Number("13")}, metricLog.Values) 337 assert.Equal(t, map[string]string{"key": "val"}, metricLog.Tags) 338 case "server.response": 339 seenResponseTimer = true 340 assert.Equal(t, "timer", metricLog.MetricType, "server.response metric had incorrect type") 341 assert.NotNil(t, metricLog.Values["count"]) 342 assert.NotNil(t, metricLog.Values["mean"]) 343 assert.NotNil(t, metricLog.Values["max"]) 344 assert.NotNil(t, metricLog.Values["min"]) 345 case "server.request.size": 346 seenRequestSize = true 347 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 348 // there should be no value for "count" because it is blacklisted for the histogram type 349 assert.Nil(t, metricLog.Values["count"]) 350 case "server.response.size": 351 seenResponseSize = true 352 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 353 // there should be no value for "count" because it is blacklisted for the histogram type 354 assert.Nil(t, metricLog.Values["count"]) 355 case "server.response.error": 356 seenResponseError = true 357 assert.Equal(t, "meter", metricLog.MetricType, "server.response metric had incorrect type") 358 assert.NotNil(t, metricLog.Values["count"]) 359 case "server.uptime": 360 seenUptime = true 361 assert.Equal(t, "gauge", metricLog.MetricType, "server.uptime metric had incorrect type") 362 assert.NotZero(t, metricLog.Values["value"]) 363 default: 364 assert.Fail(t, "unexpected metric encountered: %s", metricLog.MetricName) 365 } 366 } 367 assert.True(t, seenMyCounter, "my-counter metric was not emitted") 368 assert.True(t, seenResponseTimer, "server.response metric was not emitted") 369 assert.True(t, seenRequestSize, "server.request.size metric was not emitted") 370 assert.True(t, seenResponseSize, "server.response.size metric was not emitted") 371 assert.True(t, seenResponseError, "server.response.error metric was not emitted") 372 assert.True(t, seenUptime, "server.uptime metric was not emitted") 373 374 select { 375 case err := <-serverErr: 376 require.NoError(t, err) 377 default: 378 } 379 } 380 381 // TestMetricsBlacklist verifies that blacklisted metrics are not emitted. 382 func TestMetricsBlacklist(t *testing.T) { 383 logOutputBuffer := &bytes.Buffer{} 384 port, err := httpserver.AvailablePort() 385 require.NoError(t, err) 386 server, serverErr, cleanup := createAndRunCustomTestServer(t, port, port, func(ctx context.Context, info witchcraft.InitInfo) (deferFn func(), rErr error) { 387 ctx = metrics.AddTags(ctx, metrics.MustNewTag("key", "val")) 388 metrics.FromContext(ctx).Counter("my-counter").Inc(13) 389 return nil, info.Router.Post("/error", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 390 rw.WriteHeader(500) 391 })) 392 }, logOutputBuffer, func(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) *witchcraft.Server { 393 installCfg.MetricsEmitFrequency = 100 * time.Millisecond 394 server := createTestServer(t, initFn, installCfg, logOutputBuffer) 395 server.WithMetricsBlacklist(map[string]struct{}{ 396 "my-counter": {}, 397 "server.request.size": {}, 398 "server.uptime": {}, 399 }) 400 return server 401 }) 402 defer func() { 403 require.NoError(t, server.Close()) 404 }() 405 defer cleanup() 406 407 // Make POST that will 404 to trigger request size and error rate metrics 408 _, err = testServerClient().Post(fmt.Sprintf("https://localhost:%d/%s/%s", port, basePath, "error"), "application/json", strings.NewReader("{}")) 409 require.NoError(t, err) 410 411 // Allow the metric emitter to do its thing. 412 time.Sleep(150 * time.Millisecond) 413 414 parts := strings.Split(logOutputBuffer.String(), "\n") 415 var metricLogs []logging.MetricLogV1 416 for _, curr := range parts { 417 if strings.Contains(curr, `"metric.1"`) { 418 var currLog logging.MetricLogV1 419 require.NoError(t, json.Unmarshal([]byte(curr), &currLog)) 420 metricLogs = append(metricLogs, currLog) 421 } 422 } 423 424 var seenMyCounter, seenResponseTimer, seenResponseSize, seenRequestSize, seenResponseError bool 425 for _, metricLog := range metricLogs { 426 switch metricLog.MetricName { 427 case "my-counter": 428 assert.Fail(t, "my-counter metric should not be emitted") 429 case "server.response": 430 seenResponseTimer = true 431 assert.Equal(t, "timer", metricLog.MetricType, "server.response metric had incorrect type") 432 assert.NotZero(t, metricLog.Values["count"]) 433 case "server.request.size": 434 assert.Fail(t, "server.request.size metric should not be emitted") 435 case "server.response.size": 436 seenResponseSize = true 437 assert.Equal(t, "histogram", metricLog.MetricType, "server.response metric had incorrect type") 438 assert.NotZero(t, metricLog.Values["count"]) 439 case "server.response.error": 440 seenResponseError = true 441 assert.Equal(t, "meter", metricLog.MetricType, "server.response metric had incorrect type") 442 assert.NotZero(t, metricLog.Values["count"]) 443 default: 444 assert.Fail(t, "unexpected metric encountered: %s", metricLog.MetricName) 445 } 446 } 447 assert.False(t, seenMyCounter, "my-counter metric was emitted") 448 assert.False(t, seenRequestSize, "server.request.size metric was emitted") 449 450 assert.True(t, seenResponseTimer, "server.response metric was not emitted") 451 assert.True(t, seenResponseSize, "server.response.size metric was not emitted") 452 assert.True(t, seenResponseError, "server.response.error metric was not emitted") 453 454 select { 455 case err := <-serverErr: 456 require.NoError(t, err) 457 default: 458 } 459 }