| 1 | """Python Daemon Management -- PERF utilities |
| 2 | |
| 3 | This module serves two purposes: It has a few utility classes |
| 4 | for implementing PERF interfaces in common ways, and uses those |
| 5 | classes to implement some standard PERF objects that can be used by |
| 6 | PERF clients connecting to any PERF server. |
| 7 | |
| 8 | See the documentation for L{pdm.srv.perf} for a description of the |
| 9 | various PERF interfaces. |
| 10 | |
| 11 | It contains two named PERF objects: |
| 12 | |
| 13 | - sysres -- A directory containing the following objects pertaining |
| 14 | to the resource usage of the server process: |
| 15 | |
| 16 | - realtime -- An attribute returning the amount of real time since |
| 17 | the PDM module was imported (which likely coincides with the |
| 18 | amount of time the server process has been running). |
| 19 | |
| 20 | - sysinfo -- A directory containing the following objects pertaining |
| 21 | to the environment of the server process: |
| 22 | |
| 23 | - pid -- An attribute returning the PID of the server process. |
| 24 | |
| 25 | - hostname -- An attribute returning the hostname of the system. |
| 26 | |
| 27 | - platform -- An attribute returning the Python build platform. |
| 28 | """ |
| 29 | |
| 30 | import os, sys, time, socket, threading |
| 31 | |
| 32 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
| 33 | "staticdir", "event", "procevent", "startevent", |
| 34 | "finishevent"] |
| 35 | |
| 36 | class attrinfo(object): |
| 37 | """The return value of the `attrinfo' method on `attr' objects as |
| 38 | described in L{pdm.srv.perf}. |
| 39 | |
| 40 | Currently contains a single data field, `desc', which should have |
| 41 | a human-readable description of the purpose of the attribute. |
| 42 | """ |
| 43 | def __init__(self, desc = None): |
| 44 | self.desc = desc |
| 45 | |
| 46 | class perfobj(object): |
| 47 | def __init__(self, *args, **kwargs): |
| 48 | super(perfobj, self).__init__() |
| 49 | |
| 50 | def pdm_protocols(self): |
| 51 | return [] |
| 52 | |
| 53 | class simpleattr(perfobj): |
| 54 | """An implementation of the `attr' interface, which is initialized |
| 55 | with a function, and returns whatever that function returns when |
| 56 | read. |
| 57 | """ |
| 58 | def __init__(self, func, info = None, *args, **kwargs): |
| 59 | super(simpleattr, self).__init__(*args, **kwargs) |
| 60 | self.func = func |
| 61 | if info is None: |
| 62 | info = attrinfo() |
| 63 | self.info = info |
| 64 | |
| 65 | def readattr(self): |
| 66 | return self.func() |
| 67 | |
| 68 | def attrinfo(self): |
| 69 | return self.info |
| 70 | |
| 71 | def pdm_protocols(self): |
| 72 | return super(simpleattr, self).pdm_protocols() + ["attr"] |
| 73 | |
| 74 | class valueattr(perfobj): |
| 75 | """An implementation of the `attr' interface, which is initialized |
| 76 | with a single value, and returns that value when read. Subsequent |
| 77 | updates to the value are reflected in subsequent reads. |
| 78 | """ |
| 79 | def __init__(self, init, info = None, *args, **kwargs): |
| 80 | super(valueattr, self).__init__(*args, **kwargs) |
| 81 | self.value = init |
| 82 | if info is None: |
| 83 | info = attrinfo() |
| 84 | self.info = info |
| 85 | |
| 86 | def readattr(self): |
| 87 | return self.value |
| 88 | |
| 89 | def attrinfo(self): |
| 90 | return self.info |
| 91 | |
| 92 | def pdm_protocols(self): |
| 93 | return super(valueattr, self).pdm_protocols() + ["attr"] |
| 94 | |
| 95 | class eventobj(perfobj): |
| 96 | """An implementation of the `event' interface. It keeps track of |
| 97 | subscribers, and will multiplex any event to all current |
| 98 | subscribers when submitted with the `notify' method. |
| 99 | """ |
| 100 | def __init__(self, *args, **kwargs): |
| 101 | super(eventobj, self).__init__(*args, **kwargs) |
| 102 | self.subscribers = set() |
| 103 | |
| 104 | def subscribe(self, cb): |
| 105 | if cb in self.subscribers: |
| 106 | raise ValueError("Already subscribed") |
| 107 | self.subscribers.add(cb) |
| 108 | |
| 109 | def unsubscribe(self, cb): |
| 110 | self.subscribers.remove(cb) |
| 111 | |
| 112 | def notify(self, event): |
| 113 | """Notify all subscribers with the given event object.""" |
| 114 | for cb in self.subscribers: |
| 115 | try: |
| 116 | cb(event) |
| 117 | except: pass |
| 118 | |
| 119 | def pdm_protocols(self): |
| 120 | return super(eventobj, self).pdm_protocols() + ["event"] |
| 121 | |
| 122 | class staticdir(perfobj): |
| 123 | """An implementation of the `dir' interface. Put other PERF |
| 124 | objects in it using the normal dict assignment syntax, and it will |
| 125 | return them to requesting clients. |
| 126 | """ |
| 127 | def __init__(self, *args, **kwargs): |
| 128 | super(staticdir, self).__init__(*args, **kwargs) |
| 129 | self.map = {} |
| 130 | |
| 131 | def __setitem__(self, name, ob): |
| 132 | self.map[name] = ob |
| 133 | |
| 134 | def __delitem__(self, name): |
| 135 | del self.map[name] |
| 136 | |
| 137 | def __getitem__(self, name): |
| 138 | return self.map[name] |
| 139 | |
| 140 | def get(self, name, default = None): |
| 141 | return self.map.get(name, default) |
| 142 | |
| 143 | def listdir(self): |
| 144 | return self.map.keys() |
| 145 | |
| 146 | def lookup(self, name): |
| 147 | return self.map[name] |
| 148 | |
| 149 | def pdm_protocols(self): |
| 150 | return super(staticdir, self).pdm_protocols() + ["dir"] |
| 151 | |
| 152 | class event(object): |
| 153 | """This class should be subclassed by all event objects sent via |
| 154 | the `event' interface. Its main utility is that it keeps track of |
| 155 | the time it was created, so that listening clients can precisely |
| 156 | measure the time between event notifications. |
| 157 | |
| 158 | Subclasses should make sure to call the __init__ method if they |
| 159 | override it. |
| 160 | """ |
| 161 | def __init__(self): |
| 162 | self.time = time.time() |
| 163 | |
| 164 | idlock = threading.Lock() |
| 165 | procevid = 0 |
| 166 | |
| 167 | def getprocid(): |
| 168 | global procevid |
| 169 | idlock.acquire() |
| 170 | try: |
| 171 | ret = procevid |
| 172 | procevid += 1 |
| 173 | return ret |
| 174 | finally: |
| 175 | idlock.release() |
| 176 | |
| 177 | class procevent(event): |
| 178 | """A subclass of the `event' class meant to group several events |
| 179 | related to the same process. Create a new process by creating (a |
| 180 | subclass of) the `startevent' class, and subsequent events in the |
| 181 | same process by passing that startevent as the `id' parameter. |
| 182 | |
| 183 | It works by having `startevent' allocate a unique ID for each |
| 184 | process, and every other procevent initializing from that |
| 185 | startevent copying the ID. The data field `id' contains the ID so |
| 186 | that it can be compared by clients. |
| 187 | |
| 188 | An example of such a process might be a client connection, where a |
| 189 | `startevent' is emitted when a client connects, another subclass |
| 190 | of `procevent' emitted when the client runs a command, and a |
| 191 | `finishevent' emitted when the connection is closed. |
| 192 | """ |
| 193 | def __init__(self, id): |
| 194 | super(procevent, self).__init__() |
| 195 | if isinstance(id, procevent): |
| 196 | self.id = id.id |
| 197 | else: |
| 198 | self.id = id |
| 199 | |
| 200 | class startevent(procevent): |
| 201 | """A subclass of `procevent'. See its documentation for details.""" |
| 202 | def __init__(self): |
| 203 | super(startevent, self).__init__(getprocid()) |
| 204 | |
| 205 | class finishevent(procevent): |
| 206 | """A subclass of `procevent'. Intended to be emitted when a |
| 207 | process finishes and terminates. The `aborted' field can be used |
| 208 | to indicate whether the process completed successfully, if such a |
| 209 | distinction is meaningful. The `start' parameter should be the |
| 210 | `startevent' instance used when the process was initiated.""" |
| 211 | def __init__(self, start, aborted = False): |
| 212 | super(finishevent, self).__init__(start) |
| 213 | self.aborted = aborted |
| 214 | |
| 215 | sysres = staticdir() |
| 216 | itime = time.time() |
| 217 | sysres["realtime"] = simpleattr(func = lambda: time.time() - itime) |
| 218 | |
| 219 | sysinfo = staticdir() |
| 220 | sysinfo["pid"] = simpleattr(func = os.getpid) |
| 221 | sysinfo["hostname"] = simpleattr(func = socket.gethostname) |
| 222 | sysinfo["platform"] = valueattr(init = sys.platform) |