Source code for zhmcclient._timestats

# Copyright 2016-2017 IBM Corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The :class:`~zhmcclient.TimeStatsKeeper` class allows measuring the elapsed
time of accordingly instrumented code and keeps a statistics of these times.

The :class:`~zhmcclient.Session` class uses this class for keeping
statistics about the time to issue HTTP requests against the HMC API (see its
:attr:`~zhmcclient.Session.time_stats_keeper` property).

The :class:`~zhmcclient.TimeStats` class is a helper class that contains the
actual measurement data for all invocations of a particular HTTP request. Its
objects are under control of the :class:`~zhmcclient.TimeStatsKeeper` class.

Example::

    import zhmcclient

    session = zhmcclient.Session(hmc, userid, password)
    session.time_stats_keeper.enable()

    # Some operations that are being measured
    client = zhmcclient.Client(session)
    cpcs = client.cpcs.list()

    print(session.time_stats_keeper)
"""

from __future__ import absolute_import

import time
import copy

from ._logging import logged_api_call

__all__ = ['TimeStatsKeeper', 'TimeStats']


[docs]class TimeStats(object): """ Elapsed time statistics for all invocations of a particular named operation. All invocations of the operation will be accumulated into the statistics data kept by an object of this class. Objects of this class don't need to (and in fact, are not supposed to) be created by the user. Instead, the :meth:`zhmcclient.TimeStatsKeeper.get_stats` method should be used to create objects of this class. """ def __init__(self, keeper, name): """ Parameters: keeper (TimeStatsKeeper): The statistics keeper that holds this time statistics. name (string): Name of the operation. """ self._keeper = keeper self._name = name self._count = 0 self._sum = float(0) self._min = float('inf') self._max = float(0) self._begin_time = None @property def name(self): """ :term:`string`: Name of the operation this time statistics has data for. This name is used by the :class:`~zhmcclient.TimeStatsKeeper` object holding this time statistics as a key. """ return self._name @property def keeper(self): """ :class:`~zhmcclient.TimeStatsKeeper`: The time statistics keeper holding this time statistics. """ return self._keeper @property def count(self): """ :term:`integer`: The number of invocations of the operation. """ return self._count @property def avg_time(self): """ float: The average elapsed time for invoking the operation, in seconds. """ try: return self._sum / self._count except ZeroDivisionError: return 0 @property def min_time(self): """ float: The minimum elapsed time for invoking the operation, in seconds. """ return self._min @property def max_time(self): """ float: The maximum elapsed time for invoking the operation, in seconds. """ return self._max
[docs] @logged_api_call def reset(self): """ Reset the time statistics data for the operation. """ self._count = 0 self._sum = float(0) self._min = float('inf') self._max = float(0)
[docs] @logged_api_call def begin(self): """ This method must be called before invoking the operation. Note that this method is not to be invoked by the user; it is invoked by the implementation of the :class:`~zhmcclient.Session` class. If the statistics keeper holding this time statistics is enabled, this method takes the current time, so that :meth:`~zhmcclient.TimeStats.end` can calculate the elapsed time between the two method calls. If the statistics keeper holding this time statistics is disabled, this method does nothing, in order to save resources. """ if self.keeper.enabled: self._begin_time = time.time()
[docs] @logged_api_call def end(self): """ This method must be called after the operation returns. Note that this method is not to be invoked by the user; it is invoked by the implementation of the :class:`~zhmcclient.Session` class. If the statistics keeper holding this time statistics is enabled, this method takes the current time, calculates the duration of the operation since the last call to :meth:`~zhmcclient.TimeStats.begin`, and updates the time statistics to reflect the new operation. If the statistics keeper holding this time statistics is disabled, this method does nothing, in order to save resources. If this method is called without a preceding call to :meth:`~zhmcclient.TimeStats.begin`, a :exc:`py:RuntimeError` is raised. Raises: RuntimeError """ if self.keeper.enabled: if self._begin_time is None: raise RuntimeError("end() called without preceding begin()") dt = time.time() - self._begin_time self._begin_time = None self._count += 1 self._sum += dt if dt > self._max: self._max = dt if dt < self._min: self._min = dt
[docs] def __str__(self): """ Return a human readable string with the time statistics for this operation. Example result: .. code-block:: text TimeStats: count=1 avg=1.000s min=1.000s max=1.000s get /api/cpcs """ return "TimeStats: count={:d} avg={:.3f}s min={:.3f}s "\ "max={:.3f}s {}".format( self.count, self.avg_time, self.min_time, self.max_time, self.name)
[docs]class TimeStatsKeeper(object): """ Statistics keeper for elapsed times. The statistics keeper can hold multiple time statistics (see :class:`~zhmcclient.TimeStats`), that are identified by a name. The statistics keeper can be in a state of enabled or disabled. If enabled, it accumulates the elapsed times between subsequent calls to the :meth:`~zhmcclient.TimeStats.begin` and :meth:`~zhmcclient.TimeStats.end` methods of class :class:`~zhmcclient.TimeStats`. If disabled, calls to these methods do not accumulate any time. Initially, the statistics keeper is disabled. """ def __init__(self): self._enabled = False self._time_stats = {} # TimeStats objects self._disabled_stats = TimeStats(self, "disabled") @property def enabled(self): """ Indicates whether the statistics keeper is enabled. """ return self._enabled
[docs] @logged_api_call def enable(self): """ Enable the statistics keeper. """ self._enabled = True
[docs] @logged_api_call def disable(self): """ Disable the statistics keeper. """ self._enabled = False
[docs] @logged_api_call def get_stats(self, name): """ Get the time statistics for a name. If a time statistics for that name does not exist yet, create one. Parameters: name (string): Name of the time statistics. Returns: TimeStats: The time statistics for the specified name. If the statistics keeper is disabled, a dummy time statistics object is returned, in order to save resources. """ if not self.enabled: return self._disabled_stats if name not in self._time_stats: self._time_stats[name] = TimeStats(self, name) return self._time_stats[name]
[docs] @logged_api_call def snapshot(self): """ Return a snapshot of the time statistics of this keeper. The snapshot represents the statistics data at the time this method is called, and remains unchanged even if the statistics of this keeper continues to be updated. Returns: dict: A dictionary of the time statistics by operation, where: - key (:term:`string`): Name of the operation - value (:class:`~zhmcclient.TimeStats`): Time statistics for the operation """ return copy.deepcopy(self._time_stats)
[docs] def __str__(self): """ Return a human readable string with the time statistics for this keeper. The operations are sorted by decreasing average time. Example result, if keeper is enabled: .. code-block:: text Time statistics (times in seconds): Count Average Minimum Maximum Operation name 1 0.024 0.024 0.024 get /api/cpcs 1 0.009 0.009 0.009 get /api/version """ ret = "Time statistics (times in seconds):\n" if self.enabled: ret += "Count Average Minimum Maximum Operation name\n" stats_dict = self.snapshot() snapshot_by_avg = sorted(stats_dict.items(), key=lambda item: item[1].avg_time, reverse=True) for name, stats in snapshot_by_avg: ret += "{:5d} {:7.3f} {:7.3f} {:7.3f} {}\n".format( stats.count, stats.avg_time, stats.min_time, stats.max_time, name) else: ret += "Disabled.\n" return ret.strip()