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 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:
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
19 * cputime -- An attribute returning the amount of CPU time
20 consumed by the server process (in both user and
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
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
35 * hostname -- An attribute returning the hostname of the system.
36 * platform -- An attribute returning the Python build platform.
39 import os, sys, resource, time, socket, threading
41 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
42 "staticdir", "event", "procevent", "startevent",
45 class attrinfo(object):
46 """The return value of the `attrinfo' method on `attr' objects as
47 described in pdm.srv.perf.
49 Currently contains a single data field, `desc', which should have
50 a human-readable description of the purpose of the attribute.
52 def __init__(self, desc = None):
55 class perfobj(object):
56 def __init__(self, *args, **kwargs):
59 def pdm_protocols(self):
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
67 def __init__(self, func, info = None, *args, **kwargs):
68 super().__init__(*args, **kwargs)
80 def pdm_protocols(self):
81 return super().pdm_protocols() + ["attr"]
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.
88 def __init__(self, init, info = None, *args, **kwargs):
89 super().__init__(*args, **kwargs)
101 def pdm_protocols(self):
102 return super().pdm_protocols() + ["attr"]
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.
109 def __init__(self, *args, **kwargs):
110 super().__init__(*args, **kwargs)
111 self.subscribers = set()
113 def subscribe(self, cb):
114 if cb in self.subscribers:
115 raise ValueError("Already subscribed")
116 self.subscribers.add(cb)
118 def unsubscribe(self, cb):
119 self.subscribers.remove(cb)
121 def notify(self, event):
122 """Notify all subscribers with the given event object."""
123 for cb in self.subscribers:
128 def pdm_protocols(self):
129 return super().pdm_protocols() + ["event"]
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.
136 def __init__(self, *args, **kwargs):
137 super().__init__(*args, **kwargs)
140 def __setitem__(self, name, ob):
143 def __delitem__(self, name):
146 def __getitem__(self, name):
147 return self.map[name]
149 def get(self, name, default = None):
150 return self.map.get(name, default)
153 return list(self.map.keys())
155 def lookup(self, name):
156 return self.map[name]
158 def pdm_protocols(self):
159 return super().pdm_protocols() + ["dir"]
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.
167 Subclasses should make sure to call the __init__ method if they
171 self.time = time.time()
173 idlock = threading.Lock()
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.
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.
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.
202 def __init__(self, id):
204 if isinstance(id, procevent):
209 class startevent(procevent):
210 """A subclass of `procevent'. See its documentation for details."""
212 super().__init__(getprocid())
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().__init__(start)
222 self.aborted = aborted
226 ires = resource.getrusage(resource.RUSAGE_SELF)
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))
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)