| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import copy | 5 import copy |
| 6 import datetime | 6 import datetime |
| 7 import os | 7 import os |
| 8 import time | 8 import time |
| 9 import unittest | 9 import unittest |
| 10 | 10 |
| 11 import endpoints | |
| 12 import gae_ts_mon | 11 import gae_ts_mon |
| 13 import mock | 12 import mock |
| 14 import webapp2 | 13 import webapp2 |
| 15 | 14 |
| 16 from infra_libs.ts_mon import config | 15 from infra_libs.ts_mon import config |
| 17 from infra_libs.ts_mon import shared | 16 from infra_libs.ts_mon import shared |
| 18 from infra_libs.ts_mon.common import http_metrics | 17 from infra_libs.ts_mon.common import http_metrics |
| 19 from infra_libs.ts_mon.common import interface | 18 from infra_libs.ts_mon.common import interface |
| 20 from infra_libs.ts_mon.common import monitors | 19 from infra_libs.ts_mon.common import monitors |
| 21 from infra_libs.ts_mon.common import targets | 20 from infra_libs.ts_mon.common import targets |
| 22 from infra_libs.ts_mon.common.test import stubs | 21 from infra_libs.ts_mon.common.test import stubs |
| 23 from protorpc import message_types | |
| 24 from protorpc import remote | |
| 25 from testing_utils import testing | 22 from testing_utils import testing |
| 26 | 23 |
| 27 | 24 |
| 28 class InitializeTest(testing.AppengineTestCase): | 25 class InitializeTest(testing.AppengineTestCase): |
| 29 def setUp(self): | 26 def setUp(self): |
| 30 super(InitializeTest, self).setUp() | 27 super(InitializeTest, self).setUp() |
| 31 | 28 |
| 32 config.reset_for_unittest() | 29 config.reset_for_unittest() |
| 33 target = targets.TaskTarget('test_service', 'test_job', | 30 target = targets.TaskTarget('test_service', 'test_job', |
| 34 'test_region', 'test_host') | 31 'test_region', 'test_host') |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 counter.increment() | 111 counter.increment() |
| 115 self.assertEqual(5, gauge.get()) | 112 self.assertEqual(5, gauge.get()) |
| 116 self.assertEqual(1, counter.get()) | 113 self.assertEqual(1, counter.get()) |
| 117 | 114 |
| 118 config._reset_cumulative_metrics() | 115 config._reset_cumulative_metrics() |
| 119 self.assertEqual(5, gauge.get()) | 116 self.assertEqual(5, gauge.get()) |
| 120 self.assertIsNone(counter.get()) | 117 self.assertIsNone(counter.get()) |
| 121 | 118 |
| 122 def test_flush_metrics_no_task_num(self): | 119 def test_flush_metrics_no_task_num(self): |
| 123 # We are not assigned task_num yet; cannot send metrics. | 120 # We are not assigned task_num yet; cannot send metrics. |
| 124 time_now = 10000 | 121 time_now = datetime.datetime(2016, 2, 8, 1, 0) |
| 125 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | 122 more_than_min_ago = time_now - datetime.timedelta(seconds=61) |
| 126 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) | |
| 127 interface.state.last_flushed = more_than_min_ago | 123 interface.state.last_flushed = more_than_min_ago |
| 128 entity = shared.get_instance_entity() | 124 entity = shared.get_instance_entity() |
| 129 entity.task_num = -1 | 125 entity.task_num = -1 |
| 130 interface.state.target.task_num = -1 | 126 interface.state.target.task_num = -1 |
| 131 self.assertFalse(config.flush_metrics_if_needed(time_now)) | 127 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) |
| 132 | 128 |
| 133 def test_flush_metrics_no_task_num_too_long(self): | 129 def test_flush_metrics_no_task_num_too_long(self): |
| 134 # We are not assigned task_num for too long; cannot send metrics. | 130 # We are not assigned task_num for too long; cannot send metrics. |
| 135 time_now = 10000 | 131 time_now = datetime.datetime(2016, 2, 8, 1, 0) |
| 136 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | 132 too_long_ago = time_now - datetime.timedelta( |
| 137 too_long_ago = datetime_now - datetime.timedelta( | |
| 138 seconds=shared.INSTANCE_EXPECTED_TO_HAVE_TASK_NUM_SEC+1) | 133 seconds=shared.INSTANCE_EXPECTED_TO_HAVE_TASK_NUM_SEC+1) |
| 139 interface.state.last_flushed = too_long_ago | 134 interface.state.last_flushed = too_long_ago |
| 140 entity = shared.get_instance_entity() | 135 entity = shared.get_instance_entity() |
| 141 entity.task_num = -1 | 136 entity.task_num = -1 |
| 142 entity.last_updated = too_long_ago | 137 entity.last_updated = too_long_ago |
| 143 interface.state.target.task_num = -1 | 138 interface.state.target.task_num = -1 |
| 144 self.assertFalse(config.flush_metrics_if_needed(time_now)) | 139 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) |
| 145 | 140 |
| 146 def test_flush_metrics_purged(self): | 141 def test_flush_metrics_purged(self): |
| 147 # We lost our task_num; cannot send metrics. | 142 # We lost our task_num; cannot send metrics. |
| 148 time_now = 10000 | 143 time_now = datetime.datetime(2016, 2, 8, 1, 0) |
| 149 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | 144 more_than_min_ago = time_now - datetime.timedelta(seconds=61) |
| 150 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) | |
| 151 interface.state.last_flushed = more_than_min_ago | 145 interface.state.last_flushed = more_than_min_ago |
| 152 entity = shared.get_instance_entity() | 146 entity = shared.get_instance_entity() |
| 153 entity.task_num = -1 | 147 entity.task_num = -1 |
| 154 interface.state.target.task_num = 2 | 148 interface.state.target.task_num = 2 |
| 155 self.assertFalse(config.flush_metrics_if_needed(time_now)) | 149 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) |
| 156 | 150 |
| 157 def test_flush_metrics_too_early(self): | 151 def test_flush_metrics_too_early(self): |
| 158 # Too early to send metrics. | 152 # Too early to send metrics. |
| 159 time_now = 10000 | 153 time_now = datetime.datetime(2016, 2, 8, 1, 0) |
| 160 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | 154 less_than_min_ago = time_now - datetime.timedelta(seconds=59) |
| 161 less_than_min_ago = datetime_now - datetime.timedelta(seconds=59) | |
| 162 interface.state.last_flushed = less_than_min_ago | 155 interface.state.last_flushed = less_than_min_ago |
| 163 entity = shared.get_instance_entity() | 156 entity = shared.get_instance_entity() |
| 164 entity.task_num = 2 | 157 entity.task_num = 2 |
| 165 self.assertFalse(config.flush_metrics_if_needed(time_now)) | 158 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) |
| 166 | 159 |
| 167 @mock.patch('infra_libs.ts_mon.common.interface.flush', autospec=True) | 160 @mock.patch('infra_libs.ts_mon.common.interface.flush', autospec=True) |
| 168 def test_flush_metrics_successfully(self, mock_flush): | 161 def test_flush_metrics_successfully(self, mock_flush): |
| 169 # We have task_num and due for sending metrics. | 162 # We have task_num and due for sending metrics. |
| 170 time_now = 10000 | 163 time_now = datetime.datetime(2016, 2, 8, 1, 0) |
| 171 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | 164 more_than_min_ago = time_now - datetime.timedelta(seconds=61) |
| 172 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) | |
| 173 interface.state.last_flushed = more_than_min_ago | 165 interface.state.last_flushed = more_than_min_ago |
| 174 entity = shared.get_instance_entity() | 166 entity = shared.get_instance_entity() |
| 175 entity.task_num = 2 | 167 entity.task_num = 2 |
| 176 # Global metrics must be erased after flush. | 168 # Global metrics must be erased after flush. |
| 177 test_global_metric = gae_ts_mon.GaugeMetric('test') | 169 test_global_metric = gae_ts_mon.GaugeMetric('test') |
| 178 test_global_metric.set(42) | 170 test_global_metric.set(42) |
| 179 shared.register_global_metrics([test_global_metric]) | 171 shared.register_global_metrics([test_global_metric]) |
| 180 self.assertEqual(42, test_global_metric.get()) | 172 self.assertEqual(42, test_global_metric.get()) |
| 181 self.assertTrue(config.flush_metrics_if_needed(time_now)) | 173 self.assertTrue(config.flush_metrics_if_needed(time_fn=lambda: time_now)) |
| 182 self.assertEqual(None, test_global_metric.get()) | 174 self.assertEqual(None, test_global_metric.get()) |
| 183 mock_flush.assert_called_once_with() | 175 mock_flush.assert_called_once_with() |
| 184 | 176 |
| 185 @mock.patch('infra_libs.ts_mon.common.interface.flush', autospec=True) | |
| 186 def test_flush_metrics_disabled(self, mock_flush): | |
| 187 # We have task_num and due for sending metrics, but ts_mon is disabled. | |
| 188 time_now = 10000 | |
| 189 datetime_now = datetime.datetime.utcfromtimestamp(time_now) | |
| 190 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) | |
| 191 interface.state.last_flushed = more_than_min_ago | |
| 192 interface.state.flush_enabled_fn = lambda: False | |
| 193 entity = shared.get_instance_entity() | |
| 194 entity.task_num = 2 | |
| 195 self.assertFalse(config.flush_metrics_if_needed(time_now)) | |
| 196 self.assertEqual(0, mock_flush.call_count) | |
| 197 | |
| 198 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, | 177 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, |
| 199 return_value=True) | 178 return_value=True) |
| 200 def test_shutdown_hook_flushed(self, _mock_flush): | 179 def test_shutdown_hook_flushed(self, _mock_flush): |
| 201 time_now = 10000 | |
| 202 id = shared.get_instance_entity().key.id() | 180 id = shared.get_instance_entity().key.id() |
| 203 with shared.instance_namespace_context(): | 181 with shared.instance_namespace_context(): |
| 204 self.assertIsNotNone(shared.Instance.get_by_id(id)) | 182 self.assertIsNotNone(shared.Instance.get_by_id(id)) |
| 205 config._shutdown_hook(time_fn=lambda: time_now) | 183 config._shutdown_hook() |
| 206 with shared.instance_namespace_context(): | 184 with shared.instance_namespace_context(): |
| 207 self.assertIsNone(shared.Instance.get_by_id(id)) | 185 self.assertIsNone(shared.Instance.get_by_id(id)) |
| 208 | 186 |
| 209 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, | 187 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, |
| 210 return_value=False) | 188 return_value=False) |
| 211 def test_shutdown_hook_not_flushed(self, _mock_flush): | 189 def test_shutdown_hook_not_flushed(self, _mock_flush): |
| 212 time_now = 10000 | |
| 213 id = shared.get_instance_entity().key.id() | 190 id = shared.get_instance_entity().key.id() |
| 214 with shared.instance_namespace_context(): | 191 with shared.instance_namespace_context(): |
| 215 self.assertIsNotNone(shared.Instance.get_by_id(id)) | 192 self.assertIsNotNone(shared.Instance.get_by_id(id)) |
| 216 config._shutdown_hook(time_fn=lambda: time_now) | 193 config._shutdown_hook() |
| 217 with shared.instance_namespace_context(): | 194 with shared.instance_namespace_context(): |
| 218 self.assertIsNone(shared.Instance.get_by_id(id)) | 195 self.assertIsNone(shared.Instance.get_by_id(id)) |
| 219 | 196 |
| 220 def test_internal_callback(self): | 197 def test_internal_callback(self): |
| 221 # Smoke test. | 198 # Smoke test. |
| 222 config._internal_callback() | 199 config._internal_callback() |
| 223 | 200 |
| 224 | 201 |
| 225 class InstrumentTest(testing.AppengineTestCase): | 202 class InstrumentTest(testing.AppengineTestCase): |
| 226 def setUp(self): | 203 def setUp(self): |
| (...skipping 14 matching lines...) Expand all Loading... |
| 241 def get(self): | 218 def get(self): |
| 242 self.response.write('success!') | 219 self.response.write('success!') |
| 243 | 220 |
| 244 app = webapp2.WSGIApplication([('/', Handler)]) | 221 app = webapp2.WSGIApplication([('/', Handler)]) |
| 245 config.instrument_wsgi_application(app, time_fn=self.fake_time) | 222 config.instrument_wsgi_application(app, time_fn=self.fake_time) |
| 246 | 223 |
| 247 app.get_response('/') | 224 app.get_response('/') |
| 248 | 225 |
| 249 fields = {'name': '^/$', 'status': 200, 'is_robot': False} | 226 fields = {'name': '^/$', 'status': 200, 'is_robot': False} |
| 250 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | 227 self.assertEqual(1, http_metrics.server_response_status.get(fields)) |
| 251 self.assertLessEqual(3000, http_metrics.server_durations.get(fields).sum) | 228 self.assertEqual(3000, http_metrics.server_durations.get(fields).sum) |
| 252 self.assertEqual( | 229 self.assertEqual( |
| 253 len('success!'), http_metrics.server_response_bytes.get(fields).sum) | 230 len('success!'), http_metrics.server_response_bytes.get(fields).sum) |
| 254 | 231 |
| 255 def test_abort(self): | 232 def test_abort(self): |
| 256 class Handler(webapp2.RequestHandler): | 233 class Handler(webapp2.RequestHandler): |
| 257 def get(self): | 234 def get(self): |
| 258 self.abort(417) | 235 self.abort(417) |
| 259 | 236 |
| 260 app = webapp2.WSGIApplication([('/', Handler)]) | 237 app = webapp2.WSGIApplication([('/', Handler)]) |
| 261 config.instrument_wsgi_application(app) | 238 config.instrument_wsgi_application(app) |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 364 | 341 |
| 365 app = webapp2.WSGIApplication([('/', Handler)]) | 342 app = webapp2.WSGIApplication([('/', Handler)]) |
| 366 config.instrument_wsgi_application(app) | 343 config.instrument_wsgi_application(app) |
| 367 | 344 |
| 368 app.get_response('/', POST='foo') | 345 app.get_response('/', POST='foo') |
| 369 | 346 |
| 370 fields = {'name': '^/$', 'status': 200, 'is_robot': False} | 347 fields = {'name': '^/$', 'status': 200, 'is_robot': False} |
| 371 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | 348 self.assertEqual(1, http_metrics.server_response_status.get(fields)) |
| 372 self.assertEqual( | 349 self.assertEqual( |
| 373 len('foo'), http_metrics.server_request_bytes.get(fields).sum) | 350 len('foo'), http_metrics.server_request_bytes.get(fields).sum) |
| 374 | |
| 375 | |
| 376 class FakeTime(object): | |
| 377 def __init__(self): | |
| 378 self.timestamp_now = 1000.0 | |
| 379 | |
| 380 def time_fn(self): | |
| 381 self.timestamp_now += 0.2 | |
| 382 return self.timestamp_now | |
| 383 | |
| 384 | |
| 385 @endpoints.api(name='testapi', version='v1') | |
| 386 class TestEndpoint(remote.Service): | |
| 387 | |
| 388 @gae_ts_mon.instrument_endpoint(time_fn=FakeTime().time_fn) | |
| 389 @endpoints.method(message_types.VoidMessage, message_types.VoidMessage, | |
| 390 name='method_good') | |
| 391 def do_good(self, request): | |
| 392 return request | |
| 393 | |
| 394 @gae_ts_mon.instrument_endpoint(time_fn=FakeTime().time_fn) | |
| 395 @endpoints.method(message_types.VoidMessage, message_types.VoidMessage, | |
| 396 name='method_bad') | |
| 397 def do_bad(self, request): | |
| 398 raise Exception | |
| 399 | |
| 400 @gae_ts_mon.instrument_endpoint(time_fn=FakeTime().time_fn) | |
| 401 @endpoints.method(message_types.VoidMessage, message_types.VoidMessage, | |
| 402 name='method_400') | |
| 403 def do_400(self, request): | |
| 404 raise endpoints.BadRequestException('Bad request') | |
| 405 | |
| 406 | |
| 407 class InstrumentEndpointTest(testing.EndpointsTestCase): | |
| 408 api_service_cls = TestEndpoint | |
| 409 | |
| 410 def setUp(self): | |
| 411 super(InstrumentEndpointTest, self).setUp() | |
| 412 | |
| 413 config.reset_for_unittest() | |
| 414 target = targets.TaskTarget('test_service', 'test_job', | |
| 415 'test_region', 'test_host') | |
| 416 self.mock_state = interface.State(target=target) | |
| 417 self.mock_state.metrics = copy.copy(interface.state.metrics) | |
| 418 self.endpoint_name = '/_ah/spi/TestEndpoint.%s' | |
| 419 mock.patch('infra_libs.ts_mon.common.interface.state', | |
| 420 new=self.mock_state).start() | |
| 421 | |
| 422 mock.patch('infra_libs.ts_mon.common.monitors.PubSubMonitor', | |
| 423 autospec=True).start() | |
| 424 | |
| 425 def tearDown(self): | |
| 426 config.reset_for_unittest() | |
| 427 self.assertEqual([], list(shared.global_metrics_callbacks)) | |
| 428 mock.patch.stopall() | |
| 429 super(InstrumentEndpointTest, self).tearDown() | |
| 430 | |
| 431 def test_good(self): | |
| 432 self.call_api('do_good') | |
| 433 fields = {'name': self.endpoint_name % 'method_good', | |
| 434 'status': 200, 'is_robot': False} | |
| 435 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | |
| 436 self.assertLessEqual(200, http_metrics.server_durations.get(fields).sum) | |
| 437 | |
| 438 def test_bad(self): | |
| 439 with self.call_should_fail('500 Internal Server Error'): | |
| 440 self.call_api('do_bad') | |
| 441 fields = {'name': self.endpoint_name % 'method_bad', | |
| 442 'status': 500, 'is_robot': False} | |
| 443 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | |
| 444 self.assertLessEqual(200, http_metrics.server_durations.get(fields).sum) | |
| 445 | |
| 446 def test_400(self): | |
| 447 with self.call_should_fail('400 Bad Request'): | |
| 448 self.call_api('do_400') | |
| 449 fields = {'name': self.endpoint_name % 'method_400', | |
| 450 'status': 400, 'is_robot': False} | |
| 451 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | |
| 452 self.assertLessEqual(200, http_metrics.server_durations.get(fields).sum) | |
| 453 | |
| 454 @mock.patch('gae_ts_mon.config.need_to_flush_metrics', autospec=True, | |
| 455 return_value=False) | |
| 456 def test_no_flush(self, _fake): | |
| 457 # For branch coverage. | |
| 458 self.call_api('do_good') | |
| 459 fields = {'name': self.endpoint_name % 'method_good', | |
| 460 'status': 200, 'is_robot': False} | |
| 461 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | |
| 462 self.assertLessEqual(200, http_metrics.server_durations.get(fields).sum) | |
| OLD | NEW |