1 """Python Daemon Management -- PERF utilities
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.
8 See the documentation for L{pdm.srv.perf} for a description of the
9 various PERF interfaces.
11 It contains two named PERF objects:
13 - sysres -- A directory containing the following objects pertaining
14 to the resource usage of the server process:
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).
20 - cputime -- An attribute returning the amount of CPU time
21 consumed by the server process (in both user and kernel mode).
23 - utime -- An attribute returning the amount of CPU time the
24 server process has spent in user mode.
26 - stime -- An attribute returning the amount of CPU time the
27 server process has spent in kernel mode.
29 - maxrss -- An attribute returning the largest resident set size
30 the server process has used during its lifetime.
32 - rusage -- An attribute returning the current rusage of the
35 - sysinfo -- A directory containing the following objects pertaining
36 to the environment of the server process:
38 - pid -- An attribute returning the PID of the server process.
40 - uname -- An attribute returning the uname information of the
43 - hostname -- An attribute returning the hostname of the system.
45 - platform -- An attribute returning the Python build platform.
48 import os, sys, resource, time, socket, threading
50 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
51 "staticdir", "event", "procevent", "startevent",
54 class attrinfo(object):
55 """The return value of the `attrinfo' method on `attr' objects as
56 described in L{pdm.srv.perf}.
58 Currently contains a single data field, `desc', which should have
59 a human-readable description of the purpose of the attribute.
61 def __init__(self, desc = None):
64 class perfobj(object):
65 def __init__(self, *args, **kwargs):
68 def pdm_protocols(self):
71 class simpleattr(perfobj):
72 """An implementation of the `attr' interface, which is initialized
73 with a function, and returns whatever that function returns when
76 def __init__(self, func, info = None, *args, **kwargs):
77 super().__init__(*args, **kwargs)
89 def pdm_protocols(self):
90 return super().pdm_protocols() + ["attr"]
92 class valueattr(perfobj):
93 """An implementation of the `attr' interface, which is initialized
94 with a single value, and returns that value when read. Subsequent
95 updates to the value are reflected in subsequent reads.
97 def __init__(self, init, info = None, *args, **kwargs):
98 super().__init__(*args, **kwargs)
110 def pdm_protocols(self):
111 return super().pdm_protocols() + ["attr"]
113 class eventobj(perfobj):
114 """An implementation of the `event' interface. It keeps track of
115 subscribers, and will multiplex any event to all current
116 subscribers when submitted with the `notify' method.
118 def __init__(self, *args, **kwargs):
119 super().__init__(*args, **kwargs)
120 self.subscribers = set()
122 def subscribe(self, cb):
123 if cb in self.subscribers:
124 raise ValueError("Already subscribed")
125 self.subscribers.add(cb)
127 def unsubscribe(self, cb):
128 self.subscribers.remove(cb)
130 def notify(self, event):
131 """Notify all subscribers with the given event object."""
132 for cb in self.subscribers:
137 def pdm_protocols(self):
138 return super().pdm_protocols() + ["event"]
140 class staticdir(perfobj):
141 """An implementation of the `dir' interface. Put other PERF
142 objects in it using the normal dict assignment syntax, and it will
143 return them to requesting clients.
145 def __init__(self, *args, **kwargs):
146 super().__init__(*args, **kwargs)
149 def __setitem__(self, name, ob):
152 def __delitem__(self, name):
155 def __getitem__(self, name):
156 return self.map[name]
158 def get(self, name, default = None):
159 return self.map.get(name, default)
162 return list(self.map.keys())
164 def lookup(self, name):
165 return self.map[name]
167 def pdm_protocols(self):
168 return super().pdm_protocols() + ["dir"]
171 """This class should be subclassed by all event objects sent via
172 the `event' interface. Its main utility is that it keeps track of
173 the time it was created, so that listening clients can precisely
174 measure the time between event notifications.
176 Subclasses should make sure to call the __init__ method if they
180 self.time = time.time()
182 idlock = threading.Lock()
195 class procevent(event):
196 """A subclass of the `event' class meant to group several events
197 related to the same process. Create a new process by creating (a
198 subclass of) the `startevent' class, and subsequent events in the
199 same process by passing that startevent as the `id' parameter.
201 It works by having `startevent' allocate a unique ID for each
202 process, and every other procevent initializing from that
203 startevent copying the ID. The data field `id' contains the ID so
204 that it can be compared by clients.
206 An example of such a process might be a client connection, where a
207 `startevent' is emitted when a client connects, another subclass
208 of `procevent' emitted when the client runs a command, and a
209 `finishevent' emitted when the connection is closed.
211 def __init__(self, id):
213 if isinstance(id, procevent):
218 class startevent(procevent):
219 """A subclass of `procevent'. See its documentation for details."""
221 super().__init__(getprocid())
223 class finishevent(procevent):
224 """A subclass of `procevent'. Intended to be emitted when a
225 process finishes and terminates. The `aborted' field can be used
226 to indicate whether the process completed successfully, if such a
227 distinction is meaningful. The `start' parameter should be the
228 `startevent' instance used when the process was initiated."""
229 def __init__(self, start, aborted = False):
230 super().__init__(start)
231 self.aborted = aborted
235 ires = resource.getrusage(resource.RUSAGE_SELF)
237 ru = resource.getrusage(resource.RUSAGE_SELF)
238 return (ru.ru_utime - ires.ru_utime) + (ru.ru_stime - ires.ru_stime)
239 sysres["realtime"] = simpleattr(func = lambda: time.time() - itime)
240 sysres["cputime"] = simpleattr(func = ct)
241 sysres["utime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_utime - ires.ru_utime)
242 sysres["stime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_stime - ires.ru_stime)
243 sysres["maxrss"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
244 sysres["rusage"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF))
246 sysinfo = staticdir()
247 sysinfo["pid"] = simpleattr(func = os.getpid)
248 sysinfo["uname"] = simpleattr(func = os.uname)
249 sysinfo["hostname"] = simpleattr(func = socket.gethostname)
250 sysinfo["platform"] = valueattr(init = sys.platform)