| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Report ts_mon metrics for HTTP requests made by the `requests` library. | |
| 6 | |
| 7 This module provides get(), post(), etc. methods that wrap the corresponding | |
| 8 methods in the requests module. They take an additional first 'name' argument | |
| 9 which is an identifier for the type of request being made. | |
| 10 | |
| 11 Example:: | |
| 12 | |
| 13 from infra_libs import instrumented_requests | |
| 14 r = instrumented_requests.get('myapi', 'https://example.com/api') | |
| 15 | |
| 16 Alternatively you can add the hook manually:: | |
| 17 | |
| 18 import requests | |
| 19 from infra_libs import instrumented_requests | |
| 20 r = requests.get( | |
| 21 'https://example.com/api', | |
| 22 hooks={'response': instrumented_requests.instrumentation_hook('myapi')}) | |
| 23 """ | |
| 24 | |
| 25 import functools | |
| 26 | |
| 27 import requests | |
| 28 | |
| 29 from infra_libs.ts_mon.common import http_metrics | |
| 30 | |
| 31 | |
| 32 def instrumentation_hook(name): | |
| 33 """Returns a hook function that records ts_mon metrics about the request. | |
| 34 | |
| 35 Usage:: | |
| 36 | |
| 37 r = requests.get( | |
| 38 'https://example.com/api', | |
| 39 hooks={'response': instrumented_requests.instrumentation_hook('myapi')}) | |
| 40 | |
| 41 Args: | |
| 42 name: An identifier for the HTTP requests made by this object. | |
| 43 """ | |
| 44 | |
| 45 def _content_length(headers): | |
| 46 if headers is None or 'content-length' not in headers: | |
| 47 return 0 | |
| 48 return int(headers['content-length']) | |
| 49 | |
| 50 def hook(response, *_args, **_kwargs): | |
| 51 request_bytes = _content_length(response.request.headers) | |
| 52 response_bytes = _content_length(response.headers) | |
| 53 duration_msec = response.elapsed.total_seconds() * 1000 | |
| 54 | |
| 55 fields = {'name': name, 'client': 'requests'} | |
| 56 http_metrics.request_bytes.add(request_bytes, fields=fields) | |
| 57 http_metrics.response_bytes.add(response_bytes, fields=fields) | |
| 58 http_metrics.durations.add(duration_msec, fields=fields) | |
| 59 | |
| 60 _update_status(name, response.status_code) | |
| 61 | |
| 62 return hook | |
| 63 | |
| 64 | |
| 65 def _update_status(name, status): | |
| 66 fields = {'status': status, 'name': name, 'client': 'requests'} | |
| 67 http_metrics.response_status.increment(fields=fields) | |
| 68 | |
| 69 | |
| 70 def _wrap(method, name, url, *args, **kwargs): | |
| 71 hooks = {'response': instrumentation_hook(name)} | |
| 72 if 'hooks' in kwargs: | |
| 73 hooks.update(kwargs['hooks']) | |
| 74 kwargs['hooks'] = hooks | |
| 75 | |
| 76 try: | |
| 77 return getattr(requests, method)(url, *args, **kwargs) | |
| 78 except requests.exceptions.ReadTimeout: | |
| 79 _update_status(name, http_metrics.STATUS_TIMEOUT) | |
| 80 raise | |
| 81 except requests.exceptions.ConnectionError: | |
| 82 _update_status(name, http_metrics.STATUS_ERROR) | |
| 83 raise | |
| 84 except requests.exceptions.RequestException: | |
| 85 _update_status(name, http_metrics.STATUS_EXCEPTION) | |
| 86 raise | |
| 87 | |
| 88 | |
| 89 request = functools.partial(_wrap, 'request') | |
| 90 get = functools.partial(_wrap, 'get') | |
| 91 head = functools.partial(_wrap, 'head') | |
| 92 post = functools.partial(_wrap, 'post') | |
| 93 patch = functools.partial(_wrap, 'patch') | |
| 94 put = functools.partial(_wrap, 'put') | |
| 95 delete = functools.partial(_wrap, 'delete') | |
| 96 options = functools.partial(_wrap, 'options') | |
| OLD | NEW |