404 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
###############################################################################
 | 
						|
#
 | 
						|
# The MIT License (MIT)
 | 
						|
#
 | 
						|
# Copyright (c) Tavendo GmbH
 | 
						|
#
 | 
						|
# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
# of this software and associated documentation files (the "Software"), to deal
 | 
						|
# in the Software without restriction, including without limitation the rights
 | 
						|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						|
# copies of the Software, and to permit persons to whom the Software is
 | 
						|
# furnished to do so, subject to the following conditions:
 | 
						|
#
 | 
						|
# The above copyright notice and this permission notice shall be included in
 | 
						|
# all copies or substantial portions of the Software.
 | 
						|
#
 | 
						|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
						|
# THE SOFTWARE.
 | 
						|
#
 | 
						|
###############################################################################
 | 
						|
 | 
						|
from __future__ import absolute_import
 | 
						|
 | 
						|
import time
 | 
						|
import random
 | 
						|
import sys
 | 
						|
import re
 | 
						|
import six
 | 
						|
from datetime import datetime, timedelta
 | 
						|
from pprint import pformat
 | 
						|
 | 
						|
if six.PY3:
 | 
						|
    # Python 3
 | 
						|
    # noinspection PyShadowingBuiltins
 | 
						|
    xrange = range
 | 
						|
 | 
						|
__all__ = ("utcnow",
 | 
						|
           "parseutc",
 | 
						|
           "utcstr",
 | 
						|
           "id",
 | 
						|
           "newid",
 | 
						|
           "rtime",
 | 
						|
           "Stopwatch",
 | 
						|
           "Tracker",
 | 
						|
           "EqualityMixin")
 | 
						|
 | 
						|
 | 
						|
def utcnow():
 | 
						|
    """
 | 
						|
    Get current time in UTC as ISO 8601 string.
 | 
						|
 | 
						|
    :returns: Current time as string in ISO 8601 format.
 | 
						|
    :rtype: unicode
 | 
						|
    """
 | 
						|
    now = datetime.utcnow()
 | 
						|
    return u"{0}Z".format(now.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
 | 
						|
 | 
						|
 | 
						|
def utcstr(ts):
 | 
						|
    """
 | 
						|
    Format UTC timestamp in ISO 8601 format.
 | 
						|
 | 
						|
    :param ts: The timestamp to format.
 | 
						|
    :type ts: instance of :py:class:`datetime.datetime`
 | 
						|
 | 
						|
    :returns: Timestamp formatted in ISO 8601 format.
 | 
						|
    :rtype: unicode
 | 
						|
    """
 | 
						|
    if ts:
 | 
						|
        return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
 | 
						|
    else:
 | 
						|
        return ts
 | 
						|
 | 
						|
 | 
						|
def parseutc(datestr):
 | 
						|
    """
 | 
						|
    Parse an ISO 8601 combined date and time string, like i.e. ``"2011-11-23T12:23:00Z"``
 | 
						|
    into a UTC datetime instance.
 | 
						|
 | 
						|
    .. deprecated:: 0.8.12
 | 
						|
       Use the **iso8601** module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``)
 | 
						|
 | 
						|
    :param datestr: The datetime string to parse.
 | 
						|
    :type datestr: unicode
 | 
						|
 | 
						|
    :returns: The converted datetime object.
 | 
						|
    :rtype: instance of :py:class:`datetime.datetime`
 | 
						|
    """
 | 
						|
    try:
 | 
						|
        return datetime.strptime(datestr, u"%Y-%m-%dT%H:%M:%SZ")
 | 
						|
    except ValueError:
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class IdGenerator(object):
 | 
						|
    """
 | 
						|
    ID generator for WAMP request IDs.
 | 
						|
 | 
						|
    WAMP request IDs are sequential per WAMP session, starting at 0 and
 | 
						|
    wrapping around at 2**53 (both value are inclusive [0, 2**53]).
 | 
						|
 | 
						|
    See https://github.com/tavendo/WAMP/blob/master/spec/basic.md#ids
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self._next = -1
 | 
						|
 | 
						|
    def next(self):
 | 
						|
        self._next += 1
 | 
						|
        if self._next > 9007199254740992:
 | 
						|
            self._next = 0
 | 
						|
        return self._next
 | 
						|
 | 
						|
    # generator protocol
 | 
						|
    def __next__(self):
 | 
						|
        return self.next()
 | 
						|
 | 
						|
 | 
						|
# noinspection PyShadowingBuiltins
 | 
						|
def id():
 | 
						|
    """
 | 
						|
    Generate a new random object ID from range **[0, 2**53]**.
 | 
						|
 | 
						|
    The upper bound **2**53** is chosen since it is the maximum integer that can be
 | 
						|
    represented as a IEEE double such that all smaller integers are representable as well.
 | 
						|
 | 
						|
    Hence, IDs can be safely used with languages that use IEEE double as their
 | 
						|
    main (or only) number type (JavaScript, Lua, etc).
 | 
						|
 | 
						|
    :returns: A random object ID.
 | 
						|
    :rtype: int
 | 
						|
    """
 | 
						|
    return random.randint(0, 9007199254740992)
 | 
						|
 | 
						|
 | 
						|
def newid(length=16):
 | 
						|
    """
 | 
						|
    Generate a new random object ID.
 | 
						|
 | 
						|
    :param length: The length (in chars) of the ID to generate.
 | 
						|
    :type length: int
 | 
						|
 | 
						|
    :returns: A random object ID.
 | 
						|
    :rtype: str
 | 
						|
    """
 | 
						|
    return ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for _ in xrange(length)])
 | 
						|
 | 
						|
 | 
						|
# Select the most precise walltime measurement function available
 | 
						|
# on the platform
 | 
						|
##
 | 
						|
if sys.platform.startswith('win'):
 | 
						|
    # On Windows, this function returns wall-clock seconds elapsed since the
 | 
						|
    # first call to this function, as a floating point number, based on the
 | 
						|
    # Win32 function QueryPerformanceCounter(). The resolution is typically
 | 
						|
    # better than one microsecond
 | 
						|
    _rtime = time.clock
 | 
						|
    _ = _rtime()  # this starts wallclock
 | 
						|
else:
 | 
						|
    # On Unix-like platforms, this used the first available from this list:
 | 
						|
    # (1) gettimeofday() -- resolution in microseconds
 | 
						|
    # (2) ftime() -- resolution in milliseconds
 | 
						|
    # (3) time() -- resolution in seconds
 | 
						|
    _rtime = time.time
 | 
						|
 | 
						|
 | 
						|
rtime = _rtime
 | 
						|
"""
 | 
						|
Precise wallclock time.
 | 
						|
 | 
						|
:returns: The current wallclock in seconds. Returned values are only guaranteed
 | 
						|
   to be meaningful relative to each other.
 | 
						|
:rtype: float
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
class Stopwatch(object):
 | 
						|
    """
 | 
						|
    Stopwatch based on walltime.
 | 
						|
 | 
						|
    This can be used to do code timing and uses the most precise walltime measurement
 | 
						|
    available on the platform. This is a very light-weight object,
 | 
						|
    so create/dispose is very cheap.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, start=True):
 | 
						|
        """
 | 
						|
        :param start: If ``True``, immediately start the stopwatch.
 | 
						|
        :type start: bool
 | 
						|
        """
 | 
						|
        self._elapsed = 0
 | 
						|
        if start:
 | 
						|
            self._started = rtime()
 | 
						|
            self._running = True
 | 
						|
        else:
 | 
						|
            self._started = None
 | 
						|
            self._running = False
 | 
						|
 | 
						|
    def elapsed(self):
 | 
						|
        """
 | 
						|
        Return total time elapsed in seconds during which the stopwatch was running.
 | 
						|
 | 
						|
        :returns: The elapsed time in seconds.
 | 
						|
        :rtype: float
 | 
						|
        """
 | 
						|
        if self._running:
 | 
						|
            now = rtime()
 | 
						|
            return self._elapsed + (now - self._started)
 | 
						|
        else:
 | 
						|
            return self._elapsed
 | 
						|
 | 
						|
    def pause(self):
 | 
						|
        """
 | 
						|
        Pauses the stopwatch and returns total time elapsed in seconds during which
 | 
						|
        the stopwatch was running.
 | 
						|
 | 
						|
        :returns: The elapsed time in seconds.
 | 
						|
        :rtype: float
 | 
						|
        """
 | 
						|
        if self._running:
 | 
						|
            now = rtime()
 | 
						|
            self._elapsed += now - self._started
 | 
						|
            self._running = False
 | 
						|
            return self._elapsed
 | 
						|
        else:
 | 
						|
            return self._elapsed
 | 
						|
 | 
						|
    def resume(self):
 | 
						|
        """
 | 
						|
        Resumes a paused stopwatch and returns total elapsed time in seconds
 | 
						|
        during which the stopwatch was running.
 | 
						|
 | 
						|
        :returns: The elapsed time in seconds.
 | 
						|
        :rtype: float
 | 
						|
        """
 | 
						|
        if not self._running:
 | 
						|
            self._started = rtime()
 | 
						|
            self._running = True
 | 
						|
            return self._elapsed
 | 
						|
        else:
 | 
						|
            now = rtime()
 | 
						|
            return self._elapsed + (now - self._started)
 | 
						|
 | 
						|
    def stop(self):
 | 
						|
        """
 | 
						|
        Stops the stopwatch and returns total time elapsed in seconds during which
 | 
						|
        the stopwatch was (previously) running.
 | 
						|
 | 
						|
        :returns: The elapsed time in seconds.
 | 
						|
        :rtype: float
 | 
						|
        """
 | 
						|
        elapsed = self.pause()
 | 
						|
        self._elapsed = 0
 | 
						|
        self._started = None
 | 
						|
        self._running = False
 | 
						|
        return elapsed
 | 
						|
 | 
						|
 | 
						|
class Tracker(object):
 | 
						|
    """
 | 
						|
    A key-based statistics tracker.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, tracker, tracked):
 | 
						|
        """
 | 
						|
        """
 | 
						|
        self.tracker = tracker
 | 
						|
        self.tracked = tracked
 | 
						|
        self._timings = {}
 | 
						|
        self._offset = rtime()
 | 
						|
        self._dt_offset = datetime.utcnow()
 | 
						|
 | 
						|
    def track(self, key):
 | 
						|
        """
 | 
						|
        Track elapsed for key.
 | 
						|
 | 
						|
        :param key: Key under which to track the timing.
 | 
						|
        :type key: str
 | 
						|
        """
 | 
						|
        self._timings[key] = rtime()
 | 
						|
 | 
						|
    def diff(self, startKey, endKey, formatted=True):
 | 
						|
        """
 | 
						|
        Get elapsed difference between two previously tracked keys.
 | 
						|
 | 
						|
        :param startKey: First key for interval (older timestamp).
 | 
						|
        :type startKey: str
 | 
						|
        :param endKey: Second key for interval (younger timestamp).
 | 
						|
        :type endKey: str
 | 
						|
        :param formatted: If ``True``, format computed time period and return string.
 | 
						|
        :type formatted: bool
 | 
						|
 | 
						|
        :returns: Computed time period in seconds (or formatted string).
 | 
						|
        :rtype: float or str
 | 
						|
        """
 | 
						|
        if endKey in self._timings and startKey in self._timings:
 | 
						|
            d = self._timings[endKey] - self._timings[startKey]
 | 
						|
            if formatted:
 | 
						|
                if d < 0.00001:  # 10us
 | 
						|
                    s = "%d ns" % round(d * 1000000000.)
 | 
						|
                elif d < 0.01:  # 10ms
 | 
						|
                    s = "%d us" % round(d * 1000000.)
 | 
						|
                elif d < 10:  # 10s
 | 
						|
                    s = "%d ms" % round(d * 1000.)
 | 
						|
                else:
 | 
						|
                    s = "%d s" % round(d)
 | 
						|
                return s.rjust(8)
 | 
						|
            else:
 | 
						|
                return d
 | 
						|
        else:
 | 
						|
            if formatted:
 | 
						|
                return "n.a.".rjust(8)
 | 
						|
            else:
 | 
						|
                return None
 | 
						|
 | 
						|
    def absolute(self, key):
 | 
						|
        """
 | 
						|
        Return the UTC wall-clock time at which a tracked event occurred.
 | 
						|
 | 
						|
        :param key: The key
 | 
						|
        :type key: str
 | 
						|
 | 
						|
        :returns: Timezone-naive datetime.
 | 
						|
        :rtype: instance of :py:class:`datetime.datetime`
 | 
						|
        """
 | 
						|
        elapsed = self[key]
 | 
						|
        if elapsed is None:
 | 
						|
            raise KeyError("No such key \"%s\"." % elapsed)
 | 
						|
        return self._dt_offset + timedelta(seconds=elapsed)
 | 
						|
 | 
						|
    def __getitem__(self, key):
 | 
						|
        if key in self._timings:
 | 
						|
            return self._timings[key] - self._offset
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    def __iter__(self):
 | 
						|
        return self._timings.__iter__()
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return pformat(self._timings)
 | 
						|
 | 
						|
 | 
						|
class EqualityMixin(object):
 | 
						|
    """
 | 
						|
    Mixing to add equality comparison operators to a class.
 | 
						|
 | 
						|
    Two objects are identical under this mixin, if and only if:
 | 
						|
 | 
						|
    1. both object have the same class
 | 
						|
    2. all non-private object attributes are equal
 | 
						|
    """
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        """
 | 
						|
        Compare this object to another object for equality.
 | 
						|
 | 
						|
        :param other: The other object to compare with.
 | 
						|
        :type other: obj
 | 
						|
 | 
						|
        :returns: ``True`` iff the objects are equal.
 | 
						|
        :rtype: bool
 | 
						|
        """
 | 
						|
        if not isinstance(other, self.__class__):
 | 
						|
            return False
 | 
						|
        # we only want the actual message data attributes (not eg _serialize)
 | 
						|
        for k in self.__dict__:
 | 
						|
            if not k.startswith('_'):
 | 
						|
                if not self.__dict__[k] == other.__dict__[k]:
 | 
						|
                    return False
 | 
						|
        return True
 | 
						|
        # return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__)
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        """
 | 
						|
        Compare this object to another object for inequality.
 | 
						|
 | 
						|
        :param other: The other object to compare with.
 | 
						|
        :type other: obj
 | 
						|
 | 
						|
        :returns: ``True`` iff the objects are not equal.
 | 
						|
        :rtype: bool
 | 
						|
        """
 | 
						|
        return not self.__eq__(other)
 | 
						|
 | 
						|
 | 
						|
def wildcards2patterns(wildcards):
 | 
						|
    """
 | 
						|
    Compute a list of regular expression patterns from a list of
 | 
						|
    wildcard strings. A wildcard string uses '*' as a wildcard character
 | 
						|
    matching anything.
 | 
						|
 | 
						|
    :param wildcards: List of wildcard strings to compute regular expression patterns for.
 | 
						|
    :type wildcards: list of str
 | 
						|
    :returns: Computed regular expressions.
 | 
						|
    :rtype: list of obj
 | 
						|
    """
 | 
						|
    return [re.compile(wc.replace('.', '\.').replace('*', '.*')) for wc in wildcards]
 |