Skip to content

Commit 4844a25

Browse files
author
Dean Troyer
committed
Add basic timing support
Add support for --timing options. Use cliff via a pseudo-command 'Timing' to support multiple outputformats. If an output format other than the default 'table' is selected use CSV since the timing data is in list form. Will pick up timing data for any client object that has a method similar to novaclient's get_timings(). TODO: * Stop instantiating all of the clientmanager client objects just to check for timing data. Descriptor magic required? Change-Id: I7f1076b7a250fba6a8b24b2ae9353a7f51b792b2
1 parent ea938e8 commit 4844a25

5 files changed

Lines changed: 170 additions & 3 deletions

File tree

‎openstackclient/common/clientmanager.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(self, token=None, url=None, auth_url=None,
4949
user_domain_id=None, user_domain_name=None,
5050
project_domain_id=None, project_domain_name=None,
5151
region_name=None, api_version=None, verify=True,
52-
trust_id=None):
52+
trust_id=None, timing=None):
5353
self._token = token
5454
self._url = url
5555
self._auth_url = auth_url
@@ -67,6 +67,7 @@ def __init__(self, token=None, url=None, auth_url=None,
6767
self._api_version = api_version
6868
self._trust_id = trust_id
6969
self._service_catalog = None
70+
self.timing = timing
7071

7172
# verify is the Requests-compatible form
7273
self._verify = verify
@@ -116,7 +117,7 @@ def get_extension_modules(group):
116117

117118
setattr(
118119
ClientManager,
119-
ep.name,
120+
module.API_NAME,
120121
ClientCache(
121122
getattr(sys.modules[ep.module_name], 'make_client', None)
122123
),

‎openstackclient/common/timing.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
#
13+
14+
"""Timing Implementation"""
15+
16+
import logging
17+
18+
from cliff import lister
19+
20+
21+
class Timing(lister.Lister):
22+
"""Show timing data"""
23+
24+
log = logging.getLogger(__name__ + '.Timing')
25+
26+
def take_action(self, parsed_args):
27+
self.log.debug('take_action(%s)' % parsed_args)
28+
29+
column_headers = (
30+
'URL',
31+
'Seconds',
32+
)
33+
34+
results = []
35+
total = 0.0
36+
for url, start, end in self.app.timing_data:
37+
seconds = end - start
38+
total += seconds
39+
results.append((url, seconds))
40+
results.append(('Total', total))
41+
return (
42+
column_headers,
43+
results,
44+
)

‎openstackclient/compute/client.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ def make_client(instance):
5454
service_type=API_NAME,
5555
# FIXME(dhellmann): what is service_name?
5656
service_name='',
57-
http_log_debug=http_log_debug)
57+
http_log_debug=http_log_debug,
58+
timings=instance.timing,
59+
)
5860

5961
# Populate the Nova client to skip another auth query to Identity
6062
if instance._url:

‎openstackclient/shell.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from openstackclient.common import commandmanager
3333
from openstackclient.common import exceptions as exc
3434
from openstackclient.common import restapi
35+
from openstackclient.common import timing
3536
from openstackclient.common import utils
3637
from openstackclient.identity import client as identity_client
3738

@@ -60,6 +61,7 @@ class OpenStackShell(app.App):
6061
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
6162

6263
log = logging.getLogger(__name__)
64+
timing_data = []
6365

6466
def __init__(self):
6567
# Patch command.Command to add a default auth_required = True
@@ -303,6 +305,12 @@ def build_option_parser(self, description, version):
303305
metavar='<url>',
304306
default=env('OS_URL'),
305307
help='Defaults to env[OS_URL]')
308+
parser.add_argument(
309+
'--timing',
310+
default=False,
311+
action='store_true',
312+
help="Print API call timing info",
313+
)
306314

307315
parser.add_argument(
308316
'--os-identity-api-version',
@@ -410,6 +418,7 @@ def authenticate_user(self):
410418
password=self.options.os_password,
411419
region_name=self.options.os_region_name,
412420
verify=self.verify,
421+
timing=self.options.timing,
413422
api_version=self.api_version,
414423
trust_id=self.options.os_trust_id,
415424
)
@@ -499,9 +508,33 @@ def prepare_to_run_command(self, cmd):
499508

500509
def clean_up(self, cmd, result, err):
501510
self.log.debug('clean_up %s', cmd.__class__.__name__)
511+
502512
if err:
503513
self.log.debug('got an error: %s', err)
504514

515+
# Process collected timing data
516+
if self.options.timing:
517+
# Loop through extensions
518+
for mod in self.ext_modules:
519+
client = getattr(self.client_manager, mod.API_NAME)
520+
if hasattr(client, 'get_timings'):
521+
self.timing_data.extend(client.get_timings())
522+
523+
# Use the Timing pseudo-command to generate the output
524+
tcmd = timing.Timing(self, self.options)
525+
tparser = tcmd.get_parser('Timing')
526+
527+
# If anything other than prettytable is specified, force csv
528+
format = 'table'
529+
# Check the formatter used in the actual command
530+
if hasattr(cmd, 'formatter') \
531+
and cmd.formatter != cmd._formatter_plugins['table'].obj:
532+
format = 'csv'
533+
534+
sys.stdout.write('\n')
535+
targs = tparser.parse_args(['-f', format])
536+
tcmd.run(targs)
537+
505538
def interact(self):
506539
# NOTE(dtroyer): Maintain the old behaviour for interactive use as
507540
# this path does not call prepare_to_run_command()
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
#
13+
14+
"""Test Timing pseudo-command"""
15+
16+
from openstackclient.common import timing
17+
from openstackclient.tests import fakes
18+
from openstackclient.tests import utils
19+
20+
21+
timing_url = 'GET http://localhost:5000'
22+
timing_start = 1404802774.872809
23+
timing_end = 1404802775.724802
24+
25+
26+
class FakeGenericClient(object):
27+
28+
def __init__(self, **kwargs):
29+
self.auth_token = kwargs['token']
30+
self.management_url = kwargs['endpoint']
31+
32+
33+
class TestTiming(utils.TestCommand):
34+
35+
def setUp(self):
36+
super(TestTiming, self).setUp()
37+
38+
self.app.timing_data = []
39+
40+
self.app.client_manager.compute = FakeGenericClient(
41+
endpoint=fakes.AUTH_URL,
42+
token=fakes.AUTH_TOKEN,
43+
)
44+
45+
self.app.client_manager.volume = FakeGenericClient(
46+
endpoint=fakes.AUTH_URL,
47+
token=fakes.AUTH_TOKEN,
48+
)
49+
50+
# Get the command object to test
51+
self.cmd = timing.Timing(self.app, None)
52+
53+
def test_timing_list_no_data(self):
54+
arglist = []
55+
verifylist = []
56+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
57+
58+
# DisplayCommandBase.take_action() returns two tuples
59+
columns, data = self.cmd.take_action(parsed_args)
60+
61+
collist = ('URL', 'Seconds')
62+
self.assertEqual(collist, columns)
63+
datalist = [
64+
('Total', 0.0,)
65+
]
66+
self.assertEqual(datalist, data)
67+
68+
def test_timing_list(self):
69+
self.app.timing_data = [
70+
(timing_url, timing_start, timing_end),
71+
]
72+
73+
arglist = []
74+
verifylist = []
75+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
76+
77+
# DisplayCommandBase.take_action() returns two tuples
78+
columns, data = self.cmd.take_action(parsed_args)
79+
80+
collist = ('URL', 'Seconds')
81+
self.assertEqual(collist, columns)
82+
timing_sec = timing_end - timing_start
83+
datalist = [
84+
(timing_url, timing_sec),
85+
('Total', timing_sec)
86+
]
87+
self.assertEqual(datalist, data)

0 commit comments

Comments
 (0)