| 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 |
| 11 import gae_ts_mon | 12 import gae_ts_mon |
| 12 import mock | 13 import mock |
| 13 import webapp2 | 14 import webapp2 |
| 14 | 15 |
| 15 from infra_libs.ts_mon import config | 16 from infra_libs.ts_mon import config |
| 16 from infra_libs.ts_mon import shared | 17 from infra_libs.ts_mon import shared |
| 17 from infra_libs.ts_mon.common import http_metrics | 18 from infra_libs.ts_mon.common import http_metrics |
| 18 from infra_libs.ts_mon.common import interface | 19 from infra_libs.ts_mon.common import interface |
| 19 from infra_libs.ts_mon.common import monitors | 20 from infra_libs.ts_mon.common import monitors |
| 20 from infra_libs.ts_mon.common import targets | 21 from infra_libs.ts_mon.common import targets |
| 21 from infra_libs.ts_mon.common.test import stubs | 22 from infra_libs.ts_mon.common.test import stubs |
| 23 from protorpc import message_types |
| 24 from protorpc import remote |
| 22 from testing_utils import testing | 25 from testing_utils import testing |
| 23 | 26 |
| 24 | 27 |
| 25 class InitializeTest(testing.AppengineTestCase): | 28 class InitializeTest(testing.AppengineTestCase): |
| 26 def setUp(self): | 29 def setUp(self): |
| 27 super(InitializeTest, self).setUp() | 30 super(InitializeTest, self).setUp() |
| 28 | 31 |
| 29 config.reset_for_unittest() | 32 config.reset_for_unittest() |
| 30 target = targets.TaskTarget('test_service', 'test_job', | 33 target = targets.TaskTarget('test_service', 'test_job', |
| 31 'test_region', 'test_host') | 34 'test_region', 'test_host') |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 counter.increment() | 114 counter.increment() |
| 112 self.assertEqual(5, gauge.get()) | 115 self.assertEqual(5, gauge.get()) |
| 113 self.assertEqual(1, counter.get()) | 116 self.assertEqual(1, counter.get()) |
| 114 | 117 |
| 115 config._reset_cumulative_metrics() | 118 config._reset_cumulative_metrics() |
| 116 self.assertEqual(5, gauge.get()) | 119 self.assertEqual(5, gauge.get()) |
| 117 self.assertIsNone(counter.get()) | 120 self.assertIsNone(counter.get()) |
| 118 | 121 |
| 119 def test_flush_metrics_no_task_num(self): | 122 def test_flush_metrics_no_task_num(self): |
| 120 # We are not assigned task_num yet; cannot send metrics. | 123 # We are not assigned task_num yet; cannot send metrics. |
| 121 time_now = datetime.datetime(2016, 2, 8, 1, 0) | 124 time_now = 10000 |
| 122 more_than_min_ago = time_now - datetime.timedelta(seconds=61) | 125 datetime_now = datetime.datetime.utcfromtimestamp(time_now) |
| 126 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) |
| 123 interface.state.last_flushed = more_than_min_ago | 127 interface.state.last_flushed = more_than_min_ago |
| 124 entity = shared.get_instance_entity() | 128 entity = shared.get_instance_entity() |
| 125 entity.task_num = -1 | 129 entity.task_num = -1 |
| 126 interface.state.target.task_num = -1 | 130 interface.state.target.task_num = -1 |
| 127 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) | 131 self.assertFalse(config.flush_metrics_if_needed(time_now)) |
| 128 | 132 |
| 129 def test_flush_metrics_no_task_num_too_long(self): | 133 def test_flush_metrics_no_task_num_too_long(self): |
| 130 # We are not assigned task_num for too long; cannot send metrics. | 134 # We are not assigned task_num for too long; cannot send metrics. |
| 131 time_now = datetime.datetime(2016, 2, 8, 1, 0) | 135 time_now = 10000 |
| 132 too_long_ago = time_now - datetime.timedelta( | 136 datetime_now = datetime.datetime.utcfromtimestamp(time_now) |
| 137 too_long_ago = datetime_now - datetime.timedelta( |
| 133 seconds=shared.INSTANCE_EXPECTED_TO_HAVE_TASK_NUM_SEC+1) | 138 seconds=shared.INSTANCE_EXPECTED_TO_HAVE_TASK_NUM_SEC+1) |
| 134 interface.state.last_flushed = too_long_ago | 139 interface.state.last_flushed = too_long_ago |
| 135 entity = shared.get_instance_entity() | 140 entity = shared.get_instance_entity() |
| 136 entity.task_num = -1 | 141 entity.task_num = -1 |
| 137 entity.last_updated = too_long_ago | 142 entity.last_updated = too_long_ago |
| 138 interface.state.target.task_num = -1 | 143 interface.state.target.task_num = -1 |
| 139 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) | 144 self.assertFalse(config.flush_metrics_if_needed(time_now)) |
| 140 | 145 |
| 141 def test_flush_metrics_purged(self): | 146 def test_flush_metrics_purged(self): |
| 142 # We lost our task_num; cannot send metrics. | 147 # We lost our task_num; cannot send metrics. |
| 143 time_now = datetime.datetime(2016, 2, 8, 1, 0) | 148 time_now = 10000 |
| 144 more_than_min_ago = time_now - datetime.timedelta(seconds=61) | 149 datetime_now = datetime.datetime.utcfromtimestamp(time_now) |
| 150 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) |
| 145 interface.state.last_flushed = more_than_min_ago | 151 interface.state.last_flushed = more_than_min_ago |
| 146 entity = shared.get_instance_entity() | 152 entity = shared.get_instance_entity() |
| 147 entity.task_num = -1 | 153 entity.task_num = -1 |
| 148 interface.state.target.task_num = 2 | 154 interface.state.target.task_num = 2 |
| 149 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) | 155 self.assertFalse(config.flush_metrics_if_needed(time_now)) |
| 150 | 156 |
| 151 def test_flush_metrics_too_early(self): | 157 def test_flush_metrics_too_early(self): |
| 152 # Too early to send metrics. | 158 # Too early to send metrics. |
| 153 time_now = datetime.datetime(2016, 2, 8, 1, 0) | 159 time_now = 10000 |
| 154 less_than_min_ago = time_now - datetime.timedelta(seconds=59) | 160 datetime_now = datetime.datetime.utcfromtimestamp(time_now) |
| 161 less_than_min_ago = datetime_now - datetime.timedelta(seconds=59) |
| 155 interface.state.last_flushed = less_than_min_ago | 162 interface.state.last_flushed = less_than_min_ago |
| 156 entity = shared.get_instance_entity() | 163 entity = shared.get_instance_entity() |
| 157 entity.task_num = 2 | 164 entity.task_num = 2 |
| 158 self.assertFalse(config.flush_metrics_if_needed(time_fn=lambda: time_now)) | 165 self.assertFalse(config.flush_metrics_if_needed(time_now)) |
| 159 | 166 |
| 160 @mock.patch('infra_libs.ts_mon.common.interface.flush', autospec=True) | 167 @mock.patch('infra_libs.ts_mon.common.interface.flush', autospec=True) |
| 161 def test_flush_metrics_successfully(self, mock_flush): | 168 def test_flush_metrics_successfully(self, mock_flush): |
| 162 # We have task_num and due for sending metrics. | 169 # We have task_num and due for sending metrics. |
| 163 time_now = datetime.datetime(2016, 2, 8, 1, 0) | 170 time_now = 10000 |
| 164 more_than_min_ago = time_now - datetime.timedelta(seconds=61) | 171 datetime_now = datetime.datetime.utcfromtimestamp(time_now) |
| 172 more_than_min_ago = datetime_now - datetime.timedelta(seconds=61) |
| 165 interface.state.last_flushed = more_than_min_ago | 173 interface.state.last_flushed = more_than_min_ago |
| 166 entity = shared.get_instance_entity() | 174 entity = shared.get_instance_entity() |
| 167 entity.task_num = 2 | 175 entity.task_num = 2 |
| 168 # Global metrics must be erased after flush. | 176 # Global metrics must be erased after flush. |
| 169 test_global_metric = gae_ts_mon.GaugeMetric('test') | 177 test_global_metric = gae_ts_mon.GaugeMetric('test') |
| 170 test_global_metric.set(42) | 178 test_global_metric.set(42) |
| 171 shared.register_global_metrics([test_global_metric]) | 179 shared.register_global_metrics([test_global_metric]) |
| 172 self.assertEqual(42, test_global_metric.get()) | 180 self.assertEqual(42, test_global_metric.get()) |
| 173 self.assertTrue(config.flush_metrics_if_needed(time_fn=lambda: time_now)) | 181 self.assertTrue(config.flush_metrics_if_needed(time_now)) |
| 174 self.assertEqual(None, test_global_metric.get()) | 182 self.assertEqual(None, test_global_metric.get()) |
| 175 mock_flush.assert_called_once_with() | 183 mock_flush.assert_called_once_with() |
| 176 | 184 |
| 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 |
| 177 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, | 198 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, |
| 178 return_value=True) | 199 return_value=True) |
| 179 def test_shutdown_hook_flushed(self, _mock_flush): | 200 def test_shutdown_hook_flushed(self, _mock_flush): |
| 201 time_now = 10000 |
| 180 id = shared.get_instance_entity().key.id() | 202 id = shared.get_instance_entity().key.id() |
| 181 with shared.instance_namespace_context(): | 203 with shared.instance_namespace_context(): |
| 182 self.assertIsNotNone(shared.Instance.get_by_id(id)) | 204 self.assertIsNotNone(shared.Instance.get_by_id(id)) |
| 183 config._shutdown_hook() | 205 config._shutdown_hook(time_fn=lambda: time_now) |
| 184 with shared.instance_namespace_context(): | 206 with shared.instance_namespace_context(): |
| 185 self.assertIsNone(shared.Instance.get_by_id(id)) | 207 self.assertIsNone(shared.Instance.get_by_id(id)) |
| 186 | 208 |
| 187 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, | 209 @mock.patch('gae_ts_mon.config.flush_metrics_if_needed', autospec=True, |
| 188 return_value=False) | 210 return_value=False) |
| 189 def test_shutdown_hook_not_flushed(self, _mock_flush): | 211 def test_shutdown_hook_not_flushed(self, _mock_flush): |
| 212 time_now = 10000 |
| 190 id = shared.get_instance_entity().key.id() | 213 id = shared.get_instance_entity().key.id() |
| 191 with shared.instance_namespace_context(): | 214 with shared.instance_namespace_context(): |
| 192 self.assertIsNotNone(shared.Instance.get_by_id(id)) | 215 self.assertIsNotNone(shared.Instance.get_by_id(id)) |
| 193 config._shutdown_hook() | 216 config._shutdown_hook(time_fn=lambda: time_now) |
| 194 with shared.instance_namespace_context(): | 217 with shared.instance_namespace_context(): |
| 195 self.assertIsNone(shared.Instance.get_by_id(id)) | 218 self.assertIsNone(shared.Instance.get_by_id(id)) |
| 196 | 219 |
| 197 def test_internal_callback(self): | 220 def test_internal_callback(self): |
| 198 # Smoke test. | 221 # Smoke test. |
| 199 config._internal_callback() | 222 config._internal_callback() |
| 200 | 223 |
| 201 | 224 |
| 202 class InstrumentTest(testing.AppengineTestCase): | 225 class InstrumentTest(testing.AppengineTestCase): |
| 203 def setUp(self): | 226 def setUp(self): |
| (...skipping 14 matching lines...) Expand all Loading... |
| 218 def get(self): | 241 def get(self): |
| 219 self.response.write('success!') | 242 self.response.write('success!') |
| 220 | 243 |
| 221 app = webapp2.WSGIApplication([('/', Handler)]) | 244 app = webapp2.WSGIApplication([('/', Handler)]) |
| 222 config.instrument_wsgi_application(app, time_fn=self.fake_time) | 245 config.instrument_wsgi_application(app, time_fn=self.fake_time) |
| 223 | 246 |
| 224 app.get_response('/') | 247 app.get_response('/') |
| 225 | 248 |
| 226 fields = {'name': '^/$', 'status': 200, 'is_robot': False} | 249 fields = {'name': '^/$', 'status': 200, 'is_robot': False} |
| 227 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | 250 self.assertEqual(1, http_metrics.server_response_status.get(fields)) |
| 228 self.assertEqual(3000, http_metrics.server_durations.get(fields).sum) | 251 self.assertLessEqual(3000, http_metrics.server_durations.get(fields).sum) |
| 229 self.assertEqual( | 252 self.assertEqual( |
| 230 len('success!'), http_metrics.server_response_bytes.get(fields).sum) | 253 len('success!'), http_metrics.server_response_bytes.get(fields).sum) |
| 231 | 254 |
| 232 def test_abort(self): | 255 def test_abort(self): |
| 233 class Handler(webapp2.RequestHandler): | 256 class Handler(webapp2.RequestHandler): |
| 234 def get(self): | 257 def get(self): |
| 235 self.abort(417) | 258 self.abort(417) |
| 236 | 259 |
| 237 app = webapp2.WSGIApplication([('/', Handler)]) | 260 app = webapp2.WSGIApplication([('/', Handler)]) |
| 238 config.instrument_wsgi_application(app) | 261 config.instrument_wsgi_application(app) |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 341 | 364 |
| 342 app = webapp2.WSGIApplication([('/', Handler)]) | 365 app = webapp2.WSGIApplication([('/', Handler)]) |
| 343 config.instrument_wsgi_application(app) | 366 config.instrument_wsgi_application(app) |
| 344 | 367 |
| 345 app.get_response('/', POST='foo') | 368 app.get_response('/', POST='foo') |
| 346 | 369 |
| 347 fields = {'name': '^/$', 'status': 200, 'is_robot': False} | 370 fields = {'name': '^/$', 'status': 200, 'is_robot': False} |
| 348 self.assertEqual(1, http_metrics.server_response_status.get(fields)) | 371 self.assertEqual(1, http_metrics.server_response_status.get(fields)) |
| 349 self.assertEqual( | 372 self.assertEqual( |
| 350 len('foo'), http_metrics.server_request_bytes.get(fields).sum) | 373 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 |