summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-05-17 21:06:52 +0000
committerGerrit Code Review <review@openstack.org>2018-05-17 21:06:52 +0000
commitcc912f050052c5970a0672c0ae75facad2c844a8 (patch)
tree4708c04878ccdc2ac766b26b047a8624c451f48d
parent775550d25628a9cb3ce56f9518b4239e6a2bca75 (diff)
parent244780fba84f008ddb2892b4c24ca2eb3fbcb0db (diff)
Merge "Collect timing information for API calls"
-rw-r--r--keystoneauth1/loading/session.py15
-rw-r--r--keystoneauth1/session.py51
-rw-r--r--keystoneauth1/tests/unit/loading/test_session.py9
-rw-r--r--keystoneauth1/tests/unit/test_session.py21
-rw-r--r--releasenotes/notes/collect-timing-85f007f0d86c8b26.yaml8
5 files changed, 102 insertions, 2 deletions
diff --git a/keystoneauth1/loading/session.py b/keystoneauth1/loading/session.py
index 5d21f03..1e58064 100644
--- a/keystoneauth1/loading/session.py
+++ b/keystoneauth1/loading/session.py
@@ -118,12 +118,19 @@ class Session(base.BaseLoader):
118 metavar='<seconds>', 118 metavar='<seconds>',
119 help='Set request timeout (in seconds).') 119 help='Set request timeout (in seconds).')
120 120
121 session_group.add_argument(
122 '--collect-timing',
123 default=False,
124 action='store_true',
125 help='Collect per-API call timing information.')
126
121 def load_from_argparse_arguments(self, namespace, **kwargs): 127 def load_from_argparse_arguments(self, namespace, **kwargs):
122 kwargs.setdefault('insecure', namespace.insecure) 128 kwargs.setdefault('insecure', namespace.insecure)
123 kwargs.setdefault('cacert', namespace.os_cacert) 129 kwargs.setdefault('cacert', namespace.os_cacert)
124 kwargs.setdefault('cert', namespace.os_cert) 130 kwargs.setdefault('cert', namespace.os_cert)
125 kwargs.setdefault('key', namespace.os_key) 131 kwargs.setdefault('key', namespace.os_key)
126 kwargs.setdefault('timeout', namespace.timeout) 132 kwargs.setdefault('timeout', namespace.timeout)
133 kwargs.setdefault('collect_timing', namespace.collect_timing)
127 134
128 return self.load_from_options(**kwargs) 135 return self.load_from_options(**kwargs)
129 136
@@ -139,6 +146,7 @@ class Session(base.BaseLoader):
139 :keyfile: The key for the client certificate. 146 :keyfile: The key for the client certificate.
140 :insecure: Whether to ignore SSL verification. 147 :insecure: Whether to ignore SSL verification.
141 :timeout: The max time to wait for HTTP connections. 148 :timeout: The max time to wait for HTTP connections.
149 :collect-timing: Whether to collect API timing information.
142 150
143 :param dict deprecated_opts: Deprecated options that should be included 151 :param dict deprecated_opts: Deprecated options that should be included
144 in the definition of new options. This should be a dict from the 152 in the definition of new options. This should be a dict from the
@@ -175,6 +183,11 @@ class Session(base.BaseLoader):
175 cfg.IntOpt('timeout', 183 cfg.IntOpt('timeout',
176 deprecated_opts=deprecated_opts.get('timeout'), 184 deprecated_opts=deprecated_opts.get('timeout'),
177 help='Timeout value for http requests'), 185 help='Timeout value for http requests'),
186 cfg.BoolOpt('collect-timing',
187 deprecated_opts=deprecated_opts.get(
188 'collect-timing'),
189 default=False,
190 help='Collect per-API call timing information.'),
178 ] 191 ]
179 192
180 def register_conf_options(self, conf, group, deprecated_opts=None): 193 def register_conf_options(self, conf, group, deprecated_opts=None):
@@ -186,6 +199,7 @@ class Session(base.BaseLoader):
186 :keyfile: The key for the client certificate. 199 :keyfile: The key for the client certificate.
187 :insecure: Whether to ignore SSL verification. 200 :insecure: Whether to ignore SSL verification.
188 :timeout: The max time to wait for HTTP connections. 201 :timeout: The max time to wait for HTTP connections.
202 :collect-timing: Whether to collect API timing information.
189 203
190 :param oslo_config.Cfg conf: config object to register with. 204 :param oslo_config.Cfg conf: config object to register with.
191 :param string group: The ini group to register options in. 205 :param string group: The ini group to register options in.
@@ -227,6 +241,7 @@ class Session(base.BaseLoader):
227 kwargs.setdefault('cert', c.certfile) 241 kwargs.setdefault('cert', c.certfile)
228 kwargs.setdefault('key', c.keyfile) 242 kwargs.setdefault('key', c.keyfile)
229 kwargs.setdefault('timeout', c.timeout) 243 kwargs.setdefault('timeout', c.timeout)
244 kwargs.setdefault('collect_timing', c.collect_timing)
230 245
231 return self.load_from_options(**kwargs) 246 return self.load_from_options(**kwargs)
232 247
diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py
index d9e99d3..4edc382 100644
--- a/keystoneauth1/session.py
+++ b/keystoneauth1/session.py
@@ -203,6 +203,24 @@ def _determine_user_agent():
203 return name 203 return name
204 204
205 205
206class RequestTiming(object):
207 """Contains timing information for an HTTP interaction."""
208
209 #: HTTP method used for the call (GET, POST, etc)
210 method = None
211
212 #: URL against which the call was made
213 url = None
214
215 #: Elapsed time information
216 elapsed = None # type: datetime.timedelta
217
218 def __init__(self, method, url, elapsed):
219 self.method = method
220 self.url = url
221 self.elapsed = elapsed
222
223
206class Session(object): 224class Session(object):
207 """Maintains client communication state and common functionality. 225 """Maintains client communication state and common functionality.
208 226
@@ -264,6 +282,9 @@ class Session(object):
264 None which means automatically manage) 282 None which means automatically manage)
265 :param bool split_loggers: Split the logging of requests across multiple 283 :param bool split_loggers: Split the logging of requests across multiple
266 loggers instead of just one. Defaults to False. 284 loggers instead of just one. Defaults to False.
285 :param bool collect_timing: Whether or not to collect per-method timing
286 information for each API call. (optional,
287 defaults to False)
267 """ 288 """
268 289
269 user_agent = None 290 user_agent = None
@@ -276,7 +297,8 @@ class Session(object):
276 cert=None, timeout=None, user_agent=None, 297 cert=None, timeout=None, user_agent=None,
277 redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None, 298 redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None,
278 app_name=None, app_version=None, additional_user_agent=None, 299 app_name=None, app_version=None, additional_user_agent=None,
279 discovery_cache=None, split_loggers=None): 300 discovery_cache=None, split_loggers=None,
301 collect_timing=False):
280 302
281 self.auth = auth 303 self.auth = auth
282 self.session = _construct_session(session) 304 self.session = _construct_session(session)
@@ -296,6 +318,8 @@ class Session(object):
296 # NOTE(mordred) split_loggers kwarg default is None rather than False 318 # NOTE(mordred) split_loggers kwarg default is None rather than False
297 # so we can distinguish between the value being set or not. 319 # so we can distinguish between the value being set or not.
298 self._split_loggers = split_loggers 320 self._split_loggers = split_loggers
321 self._collect_timing = collect_timing
322 self._api_times = []
299 323
300 if timeout is not None: 324 if timeout is not None:
301 self.timeout = float(timeout) 325 self.timeout = float(timeout)
@@ -832,6 +856,19 @@ class Session(object):
832 resp.status_code) 856 resp.status_code)
833 raise exceptions.from_response(resp, method, url) 857 raise exceptions.from_response(resp, method, url)
834 858
859 if self._collect_timing:
860 for h in resp.history:
861 self._api_times.append(RequestTiming(
862 method=h.request.method,
863 url=h.request.url,
864 elapsed=h.elapsed,
865 ))
866 self._api_times.append(RequestTiming(
867 method=resp.request.method,
868 url=resp.request.url,
869 elapsed=resp.elapsed,
870 ))
871
835 return resp 872 return resp
836 873
837 def _send_request(self, url, method, redirect, log, logger, split_loggers, 874 def _send_request(self, url, method, redirect, log, logger, split_loggers,
@@ -1196,6 +1233,18 @@ class Session(object):
1196 auth = self._auth_required(auth, 'get project_id') 1233 auth = self._auth_required(auth, 'get project_id')
1197 return auth.get_project_id(self) 1234 return auth.get_project_id(self)
1198 1235
1236 def get_timings(self):
1237 """Return collected API timing information.
1238
1239 :returns: List of `RequestTiming` objects.
1240 """
1241 return self._api_times
1242
1243 def reset_timings(self):
1244 """Clear API timing information."""
1245 self._api_times = []
1246
1247
1199REQUESTS_VERSION = tuple(int(v) for v in requests.__version__.split('.')) 1248REQUESTS_VERSION = tuple(int(v) for v in requests.__version__.split('.'))
1200 1249
1201 1250
diff --git a/keystoneauth1/tests/unit/loading/test_session.py b/keystoneauth1/tests/unit/loading/test_session.py
index 253ec34..bc42a59 100644
--- a/keystoneauth1/tests/unit/loading/test_session.py
+++ b/keystoneauth1/tests/unit/loading/test_session.py
@@ -70,7 +70,14 @@ class ConfLoadingTests(utils.TestCase):
70 def new_deprecated(): 70 def new_deprecated():
71 return cfg.DeprecatedOpt(uuid.uuid4().hex, group=uuid.uuid4().hex) 71 return cfg.DeprecatedOpt(uuid.uuid4().hex, group=uuid.uuid4().hex)
72 72
73 opt_names = ['cafile', 'certfile', 'keyfile', 'insecure', 'timeout'] 73 opt_names = [
74 'cafile',
75 'certfile',
76 'keyfile',
77 'insecure',
78 'timeout',
79 'collect-timing',
80 ]
74 depr = dict([(n, [new_deprecated()]) for n in opt_names]) 81 depr = dict([(n, [new_deprecated()]) for n in opt_names])
75 opts = loading.get_session_conf_options(deprecated_opts=depr) 82 opts = loading.get_session_conf_options(deprecated_opts=depr)
76 83
diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py
index a7165e1..0fea99a 100644
--- a/keystoneauth1/tests/unit/test_session.py
+++ b/keystoneauth1/tests/unit/test_session.py
@@ -10,6 +10,7 @@
10# License for the specific language governing permissions and limitations 10# License for the specific language governing permissions and limitations
11# under the License. 11# under the License.
12 12
13import datetime
13import itertools 14import itertools
14import json 15import json
15import logging 16import logging
@@ -1017,6 +1018,26 @@ class SessionAuthTests(utils.TestCase):
1017 key=response_key, val=response_val), 1018 key=response_key, val=response_val),
1018 body_output) 1019 body_output)
1019 1020
1021 def test_collect_timing(self):
1022 auth = AuthPlugin()
1023 sess = client_session.Session(auth=auth, collect_timing=True)
1024 response = {uuid.uuid4().hex: uuid.uuid4().hex}
1025
1026 self.stub_url('GET',
1027 json=response,
1028 headers={'Content-Type': 'application/json'})
1029
1030 resp = sess.get(self.TEST_URL)
1031
1032 self.assertEqual(response, resp.json())
1033 timings = sess.get_timings()
1034 self.assertEqual(timings[0].method, 'GET')
1035 self.assertEqual(timings[0].url, self.TEST_URL)
1036 self.assertIsInstance(timings[0].elapsed, datetime.timedelta)
1037 sess.reset_timings()
1038 timings = sess.get_timings()
1039 self.assertEqual(len(timings), 0)
1040
1020 1041
1021class AdapterTest(utils.TestCase): 1042class AdapterTest(utils.TestCase):
1022 1043
diff --git a/releasenotes/notes/collect-timing-85f007f0d86c8b26.yaml b/releasenotes/notes/collect-timing-85f007f0d86c8b26.yaml
new file mode 100644
index 0000000..0e09c9e
--- /dev/null
+++ b/releasenotes/notes/collect-timing-85f007f0d86c8b26.yaml
@@ -0,0 +1,8 @@
1---
2features:
3 - |
4 Added ``collect_timing`` option to ``keystoneauth1.session.Session``.
5 The option, which is off by default, causes the ``Session`` to collect
6 API timing information for every call it makes. Methods ``get_timings``
7 and ``reset_timings`` have been added to allow getting and clearing the
8 data.