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, time, socket, threading
50 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
51 "staticdir", "event", "procevent", "startevent",
54 class error(Exception):
56 class nosuchname(LookupError, error):
58 class nosuchproto(error):
61 class attrinfo(object):
62 """The return value of the `attrinfo' method on `attr' objects as
63 described in L{pdm.srv.perf}.
65 Currently contains a single data field, `desc', which should have
66 a human-readable description of the purpose of the attribute.
68 def __init__(self, desc = None):
71 class perfobj(object):
72 def __init__(self, *args, **kwargs):
75 def pdm_protocols(self):
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
83 def __init__(self, func, info = None, *args, **kwargs):
84 super().__init__(*args, **kwargs)
96 def pdm_protocols(self):
97 return super().pdm_protocols() + ["attr"]
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.
104 def __init__(self, init, info = None, *args, **kwargs):
105 super().__init__(*args, **kwargs)
117 def pdm_protocols(self):
118 return super().pdm_protocols() + ["attr"]
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.
125 def __init__(self, *args, **kwargs):
126 super().__init__(*args, **kwargs)
127 self.subscribers = set()
129 def subscribe(self, cb):
130 if cb in self.subscribers:
131 raise ValueError("Already subscribed")
132 self.subscribers.add(cb)
134 def unsubscribe(self, cb):
135 self.subscribers.remove(cb)
137 def notify(self, event):
138 """Notify all subscribers with the given event object."""
139 for cb in list(self.subscribers):
144 def pdm_protocols(self):
145 return super().pdm_protocols() + ["event"]
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.
152 def __init__(self, *args, **kwargs):
153 super().__init__(*args, **kwargs)
156 def __setitem__(self, name, ob):
159 def __delitem__(self, name):
162 def __getitem__(self, name):
163 return self.map[name]
165 def get(self, name, default = None):
166 return self.map.get(name, default)
169 return list(self.map.keys())
171 def lookup(self, name):
172 return self.map[name]
174 def pdm_protocols(self):
175 return super().pdm_protocols() + ["dir"]
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)
185 self.map.update(kwargs)
187 def __setitem__(self, name, func):
188 self.map[name] = func
190 def __delitem__(self, name):
193 def invoke(self, method, *args, **kwargs):
194 if method not in self.map:
195 raise AttributeError(method)
196 self.map[method](*args, **kwargs)
198 def pdm_protocols(self):
199 return super().pdm_protocols() + ["invoke"]
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.
207 Subclasses should make sure to call the __init__ method if they
211 self.time = time.time()
213 idlock = threading.Lock()
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.
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.
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.
242 def __init__(self, id):
244 if isinstance(id, procevent):
249 class startevent(procevent):
250 """A subclass of `procevent'. See its documentation for details."""
252 super().__init__(getprocid())
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
266 sysres["realtime"] = simpleattr(func = lambda: time.time() - itime)
272 ires = resource.getrusage(resource.RUSAGE_SELF)
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))
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)
289 mod = sys.modules.get(modname)
291 raise ValueError(modname)
293 importlib.reload(mod)
295 sysctl = simplefunc(exit=lambda status=0: os._exit(status),