Completed
Push — master ( da3b73...3279ec )
by Kenny
01:17
created

SignalWaiter   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 51
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 51
rs 10
wmc 6

2 Methods

Rating   Name   Duplication   Size   Complexity  
A wait() 0 9 2
B __init__() 0 22 4
1
# -*- coding: utf-8 -*-
2
"""Various helper classes.
3
4
.. moduleauthor:: Kenny Freeman <[email protected]>
5
6
"""
7
8
__author__ = 'Kenny Freeman'
9
__email__ = '[email protected]'
10
__license__     = "ISCL"
11
__docformat__   = 'reStructuredText'
12
13
import os
14
import os.path
15
import time
16
import fcntl
17
import socket
18
import signal
19
20
21
def get_hostname():
22
    """
23
    Returns the hostname of the system the code is running on. Eventually this
24
    may include more checks.
25
26
    :rtype: str
27
    """
28
    return socket.gethostname().split(".")[0]
29
30
31
def escape_tag(tag):
32
    """Escapes a string for use in eg. an influxdb measurement tag.
33
    todo: regexp replacements
34
35
    :param tag: a string
36
    :type tag: str
37
    :rtype: str
38
    """
39
    rpls = [(" ", "\ "), (",", "\,"), ("\n", "\\n")]
40
    for rpl in rpls:
41
        tag = tag.replace(*rpl)
42
    return tag
43
44
45
def escape_field(field):
46
    """Escapes a string for use in eg. an influxdb measurement field.
47
    todo: regexp replacements
48
49
    :param field: a string
50
    :type field: str
51
    :rtype: str
52
    """
53
    rpls = [(" ", "\ "), (",", "\,"), ("\n", "\\n"), ('"', '\"')]
54
    for rpl in rpls:
55
        field = field.replace(*rpl)
56
    return '""{0}""'.format(field)
57
58
59
def replace_chars(tag, rpls=None, rpl_with=None):
60
    """Rewrites a string to make it suitable for use in eg. a graphite metric.
61
    todo: regexp replacements
62
63
    :param tag: the tag to process
64
    :type tag: str
65
    :param repl_chars: a list of strings to replace
66
    :type repl_chars: list
67
    :param repl_char: a string to replace each repl_chars with
68
    :type repl_char: str
69
    :rtype: str
70
    """
71
    if not rpls:
72
        rpls = [" ", ".", "\n", "="]
73
    if not rpl_with:
74
        rpl_with = "_"
75
    for rpl in rpls:
76
        tag = tag.replace(rpl, rpl_with)
77
    return tag
78
79
80
class Interval(object):
81
    """A simple helper class to ensure a loop occours every ltime seconds
82
    while optionally providing a means to interupt the loop with a
83
    :class:`threading.Event`.
84
85
86
    :param ltime: The target duration of a loop
87
    :type ltime: int or float
88
    :param evt: An event object that will cause the loop to complete early if
89
        the event is triggered while waiting.
90
    :type evt: threading.Event
91
    :raises: ValueError if the passed loop time is not an int or float or is < 0
92
93
94
    As an example, use it like this to loop on an interval:
95
96
    :Example:
97
98
        >>> import threading
99
        >>> evt = threading.Event()
100
        >>> while loop:
101
        >>>     with Interval(111.1, evt) as timer:
102
        >>>         do_things_here()
103
104
    .. note:: if the do_things_here() call returns before the loop timer
105
        completes the timer object will either evt.wait() if evt was defined or
106
        time.sleep() for the time remaining in the interval.
107
    """
108
109
    def __init__(self, ltime, evt=None):
110
        if ltime is None or ltime < 0:
111
            raise ValueError("an interval must be defined")
112
        self.ltime = ltime
113
        self.evt = evt
114
        self.lstart = None
115
116
117
    @property
118
    def remaining(self):
119
        """Return the remaining time in the loop.
120
121
        :rtype: float
122
        """
123
        if self.lstart is not None:
124
            return self.ltime - (time.time() - self.lstart)
125
        return 0.0
126
127
128
    @property
129
    def elapsed(self):
130
        """Return the elapsed time of the loop.
131
132
        :rtype: float
133
        """
134
        if self.lstart is not None:
135
            return time.time() - self.lstart
136
        return 0.0
137
138
139
    def __str__(self):
140
        """Return a nicely formatted string.
141
142
        :rtype: str
143
        """
144
        elapsed = None
145
        if self.lstart is not None:
146
            elapsed = time.time() - self.lstart
147
        s = "Interval {0} seconds, {1} elapsed, {2} started"
148
        return s.format(self.ltime, elapsed, self.lstart)
149
150
151
    def __repr__(self):
152
        """Return a nicely formatted string.
153
154
        :rtype: str
155
        """
156
        elapsed = None
157
        if self.lstart is not None:
158
            elapsed = time.time() - self.lstart
159
        s = "Interval {0} seconds, {1} elapsed, {2} started"
160
        return s.format(self.ltime, elapsed, self.lstart)
161
162
163
    def __enter__(self):
164
        """Called on entrance to with() statement - simply records loop start
165
        time.
166
167
        :rtype: Interval"""
168
        self.lstart = time.time()
169
        return self
170
171
172
    def __exit__(self, *args):
173
        """Called on exit from a with() statement and either waits on the
174
        self.evt :class:`threading.Event` event if defined or time.sleep()'s
175
        the remaining interval time."""
176
        if self.lstart is None:
177
            self.ltstart = time.time()
178
        # block on the event for the remaining time, if any
179
        wtime = self.ltime - (time.time() - self.lstart)
180
        if wtime > 0:
181
            if self.evt is not None:
182
                self.evt.wait(wtime)
183
            else:
184
                time.sleep(wtime)
185
186
187
class SignalWaiter(object):
188
    """A helper class that makes waiting on signals very easy. Define the
189
    list of signals to wait for and to ignore and then call wait(). Instead of
190
    using signal handler functions it uses the signal.set_wakeup_fd() function
191
    and blocks on read().
192
193
    See http://www.pocketnix.org/doc/Fighting_set__wakeup__fd/ and others for
194
    more details.
195
196
    :param sig_wait: :class:`list` signals to wait for
197
    :type sig_wait: list
198
    :param sig_ignore: :class:`list` signals to ignore
199
    :type sig_ignore: list
200
    """
201
202
    def __init__(self, sig_wait, sig_ignore):
203
        """Create a :class:`SignalWaiter`"""
204
        self.sig_wait = sig_wait
205
        self.sig_ignore = sig_ignore
206
207
        # use a pipe to listen for signals
208
        self.rpipe, self.wpipe = os.pipe()
209
        # set non blocking mode on the write end of the pipe
210
        flags = fcntl.fcntl(self.wpipe, fcntl.F_GETFL, 0)
211
        flags = flags | os.O_NONBLOCK
212
        flags = fcntl.fcntl(self.wpipe, fcntl.F_SETFL, flags)
213
214
        # set the write end as a signal wakeup fd - we read signals from it
215
        signal.set_wakeup_fd(self.wpipe)
216
217
        # install dummy signal handlers that do nothing
218
        for signum in self.sig_wait:
219
            signal.signal(signum, lambda x,y: None)
220
221
        # install the ignore signal handler for sig_ignore signals
222
        for signum in self.sig_ignore:
223
            signal.signal(signum, signal.SIG_IGN)
224
225
        # now a call to wait() blocks the caller until a signal is received
226
        # only the signals we've registered handlers for will trigger
227
228
229
    def wait(self):
230
        """Blocks the calling thread until signal(s) are received.
231
232
        :rtype: :class:`int` or :class:`Exception`
233
        """
234
        try:
235
            return os.read(self.rpipe, 1)
236
        except Exception as e:
237
            return e
238