| 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 | - cputime -- An attribute returning the amount of CPU time |
| 21 | consumed by the server process (in both user and kernel mode). |
| 22 | |
| 23 | - utime -- An attribute returning the amount of CPU time the |
| 24 | server process has spent in user mode. |
| 25 | |
| 26 | - stime -- An attribute returning the amount of CPU time the |
| 27 | server process has spent in kernel mode. |
| 28 | |
| 29 | - maxrss -- An attribute returning the largest resident set size |
| 30 | the server process has used during its lifetime. |
| 31 | |
| 32 | - rusage -- An attribute returning the current rusage of the |
| 33 | server process. |
| 34 | |
| 35 | - sysinfo -- A directory containing the following objects pertaining |
| 36 | to the environment of the server process: |
| 37 | |
| 38 | - pid -- An attribute returning the PID of the server process. |
| 39 | |
| 40 | - uname -- An attribute returning the uname information of the |
| 41 | system. |
| 42 | |
| 43 | - hostname -- An attribute returning the hostname of the system. |
| 44 | |
| 45 | - platform -- An attribute returning the Python build platform. |
| 46 | """ |
| 47 | |
| 48 | import os, sys, time, socket, threading |
| 49 | |
| 50 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
| 51 | "staticdir", "event", "procevent", "startevent", |
| 52 | "finishevent"] |
| 53 | |
| 54 | class error(Exception): |
| 55 | pass |
| 56 | class nosuchname(LookupError, error): |
| 57 | pass |
| 58 | class nosuchproto(error): |
| 59 | pass |
| 60 | |
| 61 | class attrinfo(object): |
| 62 | """The return value of the `attrinfo' method on `attr' objects as |
| 63 | described in L{pdm.srv.perf}. |
| 64 | |
| 65 | Currently contains a single data field, `desc', which should have |
| 66 | a human-readable description of the purpose of the attribute. |
| 67 | """ |
| 68 | def __init__(self, desc = None): |
| 69 | self.desc = desc |
| 70 | |
| 71 | class perfobj(object): |
| 72 | def __init__(self, *args, **kwargs): |
| 73 | super().__init__() |
| 74 | |
| 75 | def pdm_protocols(self): |
| 76 | return [] |
| 77 | |
| 78 | class simpleattr(perfobj): |
| 79 | """An implementation of the `attr' interface, which is initialized |
| 80 | with a function, and returns whatever that function returns when |
| 81 | read. |
| 82 | """ |
| 83 | def __init__(self, func, info = None, *args, **kwargs): |
| 84 | super().__init__(*args, **kwargs) |
| 85 | self.func = func |
| 86 | if info is None: |
| 87 | info = attrinfo() |
| 88 | self.info = info |
| 89 | |
| 90 | def readattr(self): |
| 91 | return self.func() |
| 92 | |
| 93 | def attrinfo(self): |
| 94 | return self.info |
| 95 | |
| 96 | def pdm_protocols(self): |
| 97 | return super().pdm_protocols() + ["attr"] |
| 98 | |
| 99 | class valueattr(perfobj): |
| 100 | """An implementation of the `attr' interface, which is initialized |
| 101 | with a single value, and returns that value when read. Subsequent |
| 102 | updates to the value are reflected in subsequent reads. |
| 103 | """ |
| 104 | def __init__(self, init, info = None, *args, **kwargs): |
| 105 | super().__init__(*args, **kwargs) |
| 106 | self.value = init |
| 107 | if info is None: |
| 108 | info = attrinfo() |
| 109 | self.info = info |
| 110 | |
| 111 | def readattr(self): |
| 112 | return self.value |
| 113 | |
| 114 | def attrinfo(self): |
| 115 | return self.info |
| 116 | |
| 117 | def pdm_protocols(self): |
| 118 | return super().pdm_protocols() + ["attr"] |
| 119 | |
| 120 | class eventobj(perfobj): |
| 121 | """An implementation of the `event' interface. It keeps track of |
| 122 | subscribers, and will multiplex any event to all current |
| 123 | subscribers when submitted with the `notify' method. |
| 124 | """ |
| 125 | def __init__(self, *args, **kwargs): |
| 126 | super().__init__(*args, **kwargs) |
| 127 | self.subscribers = set() |
| 128 | |
| 129 | def subscribe(self, cb): |
| 130 | if cb in self.subscribers: |
| 131 | raise ValueError("Already subscribed") |
| 132 | self.subscribers.add(cb) |
| 133 | |
| 134 | def unsubscribe(self, cb): |
| 135 | self.subscribers.remove(cb) |
| 136 | |
| 137 | def notify(self, event): |
| 138 | """Notify all subscribers with the given event object.""" |
| 139 | for cb in list(self.subscribers): |
| 140 | try: |
| 141 | cb(event) |
| 142 | except: pass |
| 143 | |
| 144 | def pdm_protocols(self): |
| 145 | return super().pdm_protocols() + ["event"] |
| 146 | |
| 147 | class staticdir(perfobj): |
| 148 | """An implementation of the `dir' interface. Put other PERF |
| 149 | objects in it using the normal dict assignment syntax, and it will |
| 150 | return them to requesting clients. |
| 151 | """ |
| 152 | def __init__(self, *args, **kwargs): |
| 153 | super().__init__(*args, **kwargs) |
| 154 | self.map = {} |
| 155 | |
| 156 | def __setitem__(self, name, ob): |
| 157 | self.map[name] = ob |
| 158 | |
| 159 | def __delitem__(self, name): |
| 160 | del self.map[name] |
| 161 | |
| 162 | def __getitem__(self, name): |
| 163 | return self.map[name] |
| 164 | |
| 165 | def get(self, name, default = None): |
| 166 | return self.map.get(name, default) |
| 167 | |
| 168 | def listdir(self): |
| 169 | return list(self.map.keys()) |
| 170 | |
| 171 | def lookup(self, name): |
| 172 | return self.map[name] |
| 173 | |
| 174 | def pdm_protocols(self): |
| 175 | return super().pdm_protocols() + ["dir"] |
| 176 | |
| 177 | class simplefunc(perfobj): |
| 178 | """An implementation of the `invoke' interface. Put callables in |
| 179 | it using the normal dict assignment syntax, and it will call them |
| 180 | when invoked with the corresponding method name. Additionally, it |
| 181 | updates itself with any keyword-arguments it is initialized with.""" |
| 182 | def __init__(self, *args, **kwargs): |
| 183 | super().__init__(*args) |
| 184 | self.map = {} |
| 185 | self.map.update(kwargs) |
| 186 | |
| 187 | def __setitem__(self, name, func): |
| 188 | self.map[name] = func |
| 189 | |
| 190 | def __delitem__(self, name): |
| 191 | del self.map[name] |
| 192 | |
| 193 | def invoke(self, method, *args, **kwargs): |
| 194 | if method not in self.map: |
| 195 | raise AttributeError(method) |
| 196 | self.map[method](*args, **kwargs) |
| 197 | |
| 198 | def pdm_protocols(self): |
| 199 | return super().pdm_protocols() + ["invoke"] |
| 200 | |
| 201 | class event(object): |
| 202 | """This class should be subclassed by all event objects sent via |
| 203 | the `event' interface. Its main utility is that it keeps track of |
| 204 | the time it was created, so that listening clients can precisely |
| 205 | measure the time between event notifications. |
| 206 | |
| 207 | Subclasses should make sure to call the __init__ method if they |
| 208 | override it. |
| 209 | """ |
| 210 | def __init__(self): |
| 211 | self.time = time.time() |
| 212 | |
| 213 | idlock = threading.Lock() |
| 214 | procevid = 0 |
| 215 | |
| 216 | def getprocid(): |
| 217 | global procevid |
| 218 | idlock.acquire() |
| 219 | try: |
| 220 | ret = procevid |
| 221 | procevid += 1 |
| 222 | return ret |
| 223 | finally: |
| 224 | idlock.release() |
| 225 | |
| 226 | class procevent(event): |
| 227 | """A subclass of the `event' class meant to group several events |
| 228 | related to the same process. Create a new process by creating (a |
| 229 | subclass of) the `startevent' class, and subsequent events in the |
| 230 | same process by passing that startevent as the `id' parameter. |
| 231 | |
| 232 | It works by having `startevent' allocate a unique ID for each |
| 233 | process, and every other procevent initializing from that |
| 234 | startevent copying the ID. The data field `id' contains the ID so |
| 235 | that it can be compared by clients. |
| 236 | |
| 237 | An example of such a process might be a client connection, where a |
| 238 | `startevent' is emitted when a client connects, another subclass |
| 239 | of `procevent' emitted when the client runs a command, and a |
| 240 | `finishevent' emitted when the connection is closed. |
| 241 | """ |
| 242 | def __init__(self, id): |
| 243 | super().__init__() |
| 244 | if isinstance(id, procevent): |
| 245 | self.id = id.id |
| 246 | else: |
| 247 | self.id = id |
| 248 | |
| 249 | class startevent(procevent): |
| 250 | """A subclass of `procevent'. See its documentation for details.""" |
| 251 | def __init__(self): |
| 252 | super().__init__(getprocid()) |
| 253 | |
| 254 | class finishevent(procevent): |
| 255 | """A subclass of `procevent'. Intended to be emitted when a |
| 256 | process finishes and terminates. The `aborted' field can be used |
| 257 | to indicate whether the process completed successfully, if such a |
| 258 | distinction is meaningful. The `start' parameter should be the |
| 259 | `startevent' instance used when the process was initiated.""" |
| 260 | def __init__(self, start, aborted = False): |
| 261 | super().__init__(start) |
| 262 | self.aborted = aborted |
| 263 | |
| 264 | sysres = staticdir() |
| 265 | itime = time.time() |
| 266 | sysres["realtime"] = simpleattr(func = lambda: time.time() - itime) |
| 267 | try: |
| 268 | import resource |
| 269 | except ImportError: |
| 270 | pass |
| 271 | else: |
| 272 | ires = resource.getrusage(resource.RUSAGE_SELF) |
| 273 | def ct(): |
| 274 | ru = resource.getrusage(resource.RUSAGE_SELF) |
| 275 | return (ru.ru_utime - ires.ru_utime) + (ru.ru_stime - ires.ru_stime) |
| 276 | sysres["cputime"] = simpleattr(func = ct) |
| 277 | sysres["utime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_utime - ires.ru_utime) |
| 278 | sysres["stime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_stime - ires.ru_stime) |
| 279 | sysres["maxrss"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) |
| 280 | sysres["rusage"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF)) |
| 281 | |
| 282 | sysinfo = staticdir() |
| 283 | sysinfo["pid"] = simpleattr(func = os.getpid) |
| 284 | sysinfo["uname"] = simpleattr(func = os.uname) |
| 285 | sysinfo["hostname"] = simpleattr(func = socket.gethostname) |
| 286 | sysinfo["platform"] = valueattr(init = sys.platform) |
| 287 | |
| 288 | def reload(modname): |
| 289 | mod = sys.modules.get(modname) |
| 290 | if mod is None: |
| 291 | raise ValueError(modname) |
| 292 | import importlib |
| 293 | importlib.reload(mod) |
| 294 | |
| 295 | sysctl = simplefunc(exit=lambda status=0: os._exit(status), |
| 296 | reload=reload) |