Handle subscribed callbacks unregistering themselves.
[pdm.git] / pdm / perf.py
CommitLineData
5463509c
FT
1"""Python Daemon Management -- PERF utilities
2
3This module serves two purposes: It has a few utility classes
4for implementing PERF interfaces in common ways, and uses those
5classes to implement some standard PERF objects that can be used by
6PERF clients connecting to any PERF server.
7
57808152 8See the documentation for L{pdm.srv.perf} for a description of the
5463509c
FT
9various PERF interfaces.
10
11It contains two named PERF objects:
12
57808152
FT
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.
5463509c
FT
46"""
47
796cc8b9 48import os, sys, time, socket, threading
7f97a47e 49
5463509c
FT
50__all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
51 "staticdir", "event", "procevent", "startevent",
52 "finishevent"]
53
7f97a47e 54class attrinfo(object):
5463509c 55 """The return value of the `attrinfo' method on `attr' objects as
57808152 56 described in L{pdm.srv.perf}.
5463509c
FT
57
58 Currently contains a single data field, `desc', which should have
59 a human-readable description of the purpose of the attribute.
60 """
7f97a47e
FT
61 def __init__(self, desc = None):
62 self.desc = desc
63
64class perfobj(object):
65 def __init__(self, *args, **kwargs):
ed115f48 66 super().__init__()
7f97a47e
FT
67
68 def pdm_protocols(self):
69 return []
70
71class simpleattr(perfobj):
5463509c
FT
72 """An implementation of the `attr' interface, which is initialized
73 with a function, and returns whatever that function returns when
74 read.
75 """
7f97a47e 76 def __init__(self, func, info = None, *args, **kwargs):
ed115f48 77 super().__init__(*args, **kwargs)
7f97a47e
FT
78 self.func = func
79 if info is None:
80 info = attrinfo()
81 self.info = info
82
83 def readattr(self):
84 return self.func()
85
86 def attrinfo(self):
87 return self.info
88
89 def pdm_protocols(self):
ed115f48 90 return super().pdm_protocols() + ["attr"]
7f97a47e
FT
91
92class valueattr(perfobj):
5463509c
FT
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.
96 """
7f97a47e 97 def __init__(self, init, info = None, *args, **kwargs):
ed115f48 98 super().__init__(*args, **kwargs)
7f97a47e
FT
99 self.value = init
100 if info is None:
101 info = attrinfo()
102 self.info = info
103
104 def readattr(self):
105 return self.value
106
107 def attrinfo(self):
108 return self.info
109
110 def pdm_protocols(self):
ed115f48 111 return super().pdm_protocols() + ["attr"]
7f97a47e 112
7f97a47e 113class eventobj(perfobj):
5463509c
FT
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.
117 """
7f97a47e 118 def __init__(self, *args, **kwargs):
ed115f48 119 super().__init__(*args, **kwargs)
7f97a47e
FT
120 self.subscribers = set()
121
122 def subscribe(self, cb):
123 if cb in self.subscribers:
124 raise ValueError("Already subscribed")
125 self.subscribers.add(cb)
126
127 def unsubscribe(self, cb):
128 self.subscribers.remove(cb)
129
130 def notify(self, event):
5463509c 131 """Notify all subscribers with the given event object."""
51c86362 132 for cb in list(self.subscribers):
7f97a47e
FT
133 try:
134 cb(event)
135 except: pass
136
137 def pdm_protocols(self):
ed115f48 138 return super().pdm_protocols() + ["event"]
7f97a47e
FT
139
140class staticdir(perfobj):
5463509c
FT
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.
144 """
7f97a47e 145 def __init__(self, *args, **kwargs):
ed115f48 146 super().__init__(*args, **kwargs)
7f97a47e
FT
147 self.map = {}
148
149 def __setitem__(self, name, ob):
150 self.map[name] = ob
151
152 def __delitem__(self, name):
153 del self.map[name]
154
155 def __getitem__(self, name):
156 return self.map[name]
157
158 def get(self, name, default = None):
159 return self.map.get(name, default)
160
161 def listdir(self):
afd9f04c 162 return list(self.map.keys())
7f97a47e
FT
163
164 def lookup(self, name):
165 return self.map[name]
166
167 def pdm_protocols(self):
ed115f48 168 return super().pdm_protocols() + ["dir"]
7f97a47e 169
0e2552cf
FT
170class simplefunc(perfobj):
171 """An implementation of the `invoke' interface. Put callables in
172 it using the normal dict assignment syntax, and it will call them
173 when invoked with the corresponding method name. Additionally, it
174 updates itself with any keyword-arguments it is initialized with."""
175 def __init__(self, *args, **kwargs):
86b3a0ac 176 super().__init__(*args)
0e2552cf
FT
177 self.map = {}
178 self.map.update(kwargs)
179
180 def __setitem__(self, name, func):
181 self.map[name] = func
182
183 def __delitem__(self, name):
184 del self.map[name]
185
186 def invoke(self, method, *args, **kwargs):
187 if method not in self.map:
188 raise AttributeError(method)
189 self.map[method](*args, **kwargs)
190
191 def pdm_protocols(self):
86b3a0ac 192 return super().pdm_protocols() + ["invoke"]
0e2552cf 193
bcb622d6 194class event(object):
5463509c
FT
195 """This class should be subclassed by all event objects sent via
196 the `event' interface. Its main utility is that it keeps track of
197 the time it was created, so that listening clients can precisely
198 measure the time between event notifications.
199
200 Subclasses should make sure to call the __init__ method if they
201 override it.
202 """
bcb622d6
FT
203 def __init__(self):
204 self.time = time.time()
205
87b07b36
FT
206idlock = threading.Lock()
207procevid = 0
709446f6
FT
208
209def getprocid():
210 global procevid
211 idlock.acquire()
212 try:
213 ret = procevid
214 procevid += 1
215 return ret
216 finally:
217 idlock.release()
218
219class procevent(event):
5463509c
FT
220 """A subclass of the `event' class meant to group several events
221 related to the same process. Create a new process by creating (a
222 subclass of) the `startevent' class, and subsequent events in the
223 same process by passing that startevent as the `id' parameter.
224
225 It works by having `startevent' allocate a unique ID for each
226 process, and every other procevent initializing from that
227 startevent copying the ID. The data field `id' contains the ID so
228 that it can be compared by clients.
229
230 An example of such a process might be a client connection, where a
231 `startevent' is emitted when a client connects, another subclass
232 of `procevent' emitted when the client runs a command, and a
233 `finishevent' emitted when the connection is closed.
234 """
709446f6 235 def __init__(self, id):
ed115f48 236 super().__init__()
709446f6
FT
237 if isinstance(id, procevent):
238 self.id = id.id
239 else:
240 self.id = id
241
242class startevent(procevent):
5463509c 243 """A subclass of `procevent'. See its documentation for details."""
87b07b36 244 def __init__(self):
ed115f48 245 super().__init__(getprocid())
709446f6
FT
246
247class finishevent(procevent):
5463509c
FT
248 """A subclass of `procevent'. Intended to be emitted when a
249 process finishes and terminates. The `aborted' field can be used
250 to indicate whether the process completed successfully, if such a
251 distinction is meaningful. The `start' parameter should be the
252 `startevent' instance used when the process was initiated."""
0f1f2a1b 253 def __init__(self, start, aborted = False):
ed115f48 254 super().__init__(start)
87b07b36
FT
255 self.aborted = aborted
256
7f97a47e
FT
257sysres = staticdir()
258itime = time.time()
7f97a47e 259sysres["realtime"] = simpleattr(func = lambda: time.time() - itime)
796cc8b9
FT
260try:
261 import resource
262except ImportError:
263 pass
264else:
265 ires = resource.getrusage(resource.RUSAGE_SELF)
266 def ct():
267 ru = resource.getrusage(resource.RUSAGE_SELF)
268 return (ru.ru_utime - ires.ru_utime) + (ru.ru_stime - ires.ru_stime)
269 sysres["cputime"] = simpleattr(func = ct)
270 sysres["utime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_utime - ires.ru_utime)
271 sysres["stime"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_stime - ires.ru_stime)
272 sysres["maxrss"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
273 sysres["rusage"] = simpleattr(func = lambda: resource.getrusage(resource.RUSAGE_SELF))
7f97a47e
FT
274
275sysinfo = staticdir()
276sysinfo["pid"] = simpleattr(func = os.getpid)
277sysinfo["uname"] = simpleattr(func = os.uname)
278sysinfo["hostname"] = simpleattr(func = socket.gethostname)
279sysinfo["platform"] = valueattr(init = sys.platform)
0e2552cf
FT
280
281sysctl = simplefunc(exit=lambda status=0: os._exit(status))