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