Commit | Line | Data |
---|---|---|
5463509c FT |
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 | ||
57808152 | 8 | See the documentation for L{pdm.srv.perf} for a description of the |
5463509c FT |
9 | various PERF interfaces. |
10 | ||
11 | It 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 | 48 | import os, sys, time, socket, threading |
7f97a47e | 49 | |
5463509c FT |
50 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
51 | "staticdir", "event", "procevent", "startevent", | |
52 | "finishevent"] | |
53 | ||
e7b433ee FT |
54 | class error(Exception): |
55 | pass | |
56 | class nosuchname(LookupError, error): | |
57 | pass | |
58 | class nosuchproto(error): | |
59 | pass | |
60 | ||
7f97a47e | 61 | class attrinfo(object): |
5463509c | 62 | """The return value of the `attrinfo' method on `attr' objects as |
57808152 | 63 | described in L{pdm.srv.perf}. |
5463509c FT |
64 | |
65 | Currently contains a single data field, `desc', which should have | |
66 | a human-readable description of the purpose of the attribute. | |
67 | """ | |
7f97a47e FT |
68 | def __init__(self, desc = None): |
69 | self.desc = desc | |
70 | ||
71 | class perfobj(object): | |
72 | def __init__(self, *args, **kwargs): | |
ed115f48 | 73 | super().__init__() |
7f97a47e FT |
74 | |
75 | def pdm_protocols(self): | |
76 | return [] | |
77 | ||
78 | class simpleattr(perfobj): | |
5463509c FT |
79 | """An implementation of the `attr' interface, which is initialized |
80 | with a function, and returns whatever that function returns when | |
81 | read. | |
82 | """ | |
7f97a47e | 83 | def __init__(self, func, info = None, *args, **kwargs): |
ed115f48 | 84 | super().__init__(*args, **kwargs) |
7f97a47e FT |
85 | self.func = func |
86 | if info is None: | |
87 | info = attrinfo() | |
88 | self.info = info | |
89 | ||
90 | def readattr(self): | |
91 | return self.func() | |
92 | ||
93 | def attrinfo(self): | |
94 | return self.info | |
95 | ||
96 | def pdm_protocols(self): | |
ed115f48 | 97 | return super().pdm_protocols() + ["attr"] |
7f97a47e FT |
98 | |
99 | class valueattr(perfobj): | |
5463509c FT |
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. | |
103 | """ | |
7f97a47e | 104 | def __init__(self, init, info = None, *args, **kwargs): |
ed115f48 | 105 | super().__init__(*args, **kwargs) |
7f97a47e FT |
106 | self.value = init |
107 | if info is None: | |
108 | info = attrinfo() | |
109 | self.info = info | |
110 | ||
111 | def readattr(self): | |
112 | return self.value | |
113 | ||
114 | def attrinfo(self): | |
115 | return self.info | |
116 | ||
117 | def pdm_protocols(self): | |
ed115f48 | 118 | return super().pdm_protocols() + ["attr"] |
7f97a47e | 119 | |
7f97a47e | 120 | class eventobj(perfobj): |
5463509c FT |
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. | |
124 | """ | |
7f97a47e | 125 | def __init__(self, *args, **kwargs): |
ed115f48 | 126 | super().__init__(*args, **kwargs) |
7f97a47e FT |
127 | self.subscribers = set() |
128 | ||
129 | def subscribe(self, cb): | |
130 | if cb in self.subscribers: | |
131 | raise ValueError("Already subscribed") | |
132 | self.subscribers.add(cb) | |
133 | ||
134 | def unsubscribe(self, cb): | |
135 | self.subscribers.remove(cb) | |
136 | ||
137 | def notify(self, event): | |
5463509c | 138 | """Notify all subscribers with the given event object.""" |
51c86362 | 139 | for cb in list(self.subscribers): |
7f97a47e FT |
140 | try: |
141 | cb(event) | |
142 | except: pass | |
143 | ||
144 | def pdm_protocols(self): | |
ed115f48 | 145 | return super().pdm_protocols() + ["event"] |
7f97a47e FT |
146 | |
147 | class staticdir(perfobj): | |
5463509c FT |
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. | |
151 | """ | |
7f97a47e | 152 | def __init__(self, *args, **kwargs): |
ed115f48 | 153 | super().__init__(*args, **kwargs) |
7f97a47e FT |
154 | self.map = {} |
155 | ||
156 | def __setitem__(self, name, ob): | |
157 | self.map[name] = ob | |
158 | ||
159 | def __delitem__(self, name): | |
160 | del self.map[name] | |
161 | ||
162 | def __getitem__(self, name): | |
163 | return self.map[name] | |
164 | ||
165 | def get(self, name, default = None): | |
166 | return self.map.get(name, default) | |
167 | ||
168 | def listdir(self): | |
afd9f04c | 169 | return list(self.map.keys()) |
7f97a47e FT |
170 | |
171 | def lookup(self, name): | |
172 | return self.map[name] | |
173 | ||
174 | def pdm_protocols(self): | |
ed115f48 | 175 | return super().pdm_protocols() + ["dir"] |
7f97a47e | 176 | |
0e2552cf FT |
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): | |
86b3a0ac | 183 | super().__init__(*args) |
0e2552cf FT |
184 | self.map = {} |
185 | self.map.update(kwargs) | |
186 | ||
187 | def __setitem__(self, name, func): | |
188 | self.map[name] = func | |
189 | ||
190 | def __delitem__(self, name): | |
191 | del self.map[name] | |
192 | ||
193 | def invoke(self, method, *args, **kwargs): | |
194 | if method not in self.map: | |
195 | raise AttributeError(method) | |
342058b0 | 196 | return self.map[method](*args, **kwargs) |
0e2552cf FT |
197 | |
198 | def pdm_protocols(self): | |
86b3a0ac | 199 | return super().pdm_protocols() + ["invoke"] |
0e2552cf | 200 | |
bcb622d6 | 201 | class event(object): |
5463509c FT |
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. | |
206 | ||
207 | Subclasses should make sure to call the __init__ method if they | |
208 | override it. | |
209 | """ | |
bcb622d6 FT |
210 | def __init__(self): |
211 | self.time = time.time() | |
212 | ||
87b07b36 FT |
213 | idlock = threading.Lock() |
214 | procevid = 0 | |
709446f6 FT |
215 | |
216 | def getprocid(): | |
217 | global procevid | |
218 | idlock.acquire() | |
219 | try: | |
220 | ret = procevid | |
221 | procevid += 1 | |
222 | return ret | |
223 | finally: | |
224 | idlock.release() | |
225 | ||
226 | class procevent(event): | |
5463509c FT |
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. | |
231 | ||
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. | |
236 | ||
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. | |
241 | """ | |
709446f6 | 242 | def __init__(self, id): |
ed115f48 | 243 | super().__init__() |
709446f6 FT |
244 | if isinstance(id, procevent): |
245 | self.id = id.id | |
246 | else: | |
247 | self.id = id | |
248 | ||
249 | class startevent(procevent): | |
5463509c | 250 | """A subclass of `procevent'. See its documentation for details.""" |
87b07b36 | 251 | def __init__(self): |
ed115f48 | 252 | super().__init__(getprocid()) |
709446f6 FT |
253 | |
254 | class finishevent(procevent): | |
5463509c FT |
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.""" | |
0f1f2a1b | 260 | def __init__(self, start, aborted = False): |
ed115f48 | 261 | super().__init__(start) |
87b07b36 FT |
262 | self.aborted = aborted |
263 | ||
7f97a47e FT |
264 | sysres = staticdir() |
265 | itime = time.time() | |
7f97a47e | 266 | sysres["realtime"] = simpleattr(func = lambda: time.time() - itime) |
796cc8b9 FT |
267 | try: |
268 | import resource | |
269 | except ImportError: | |
270 | pass | |
271 | else: | |
272 | ires = resource.getrusage(resource.RUSAGE_SELF) | |
273 | def ct(): | |
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)) | |
7f97a47e FT |
281 | |
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) | |
0e2552cf | 287 | |
6e858b0f FT |
288 | def reload(modname): |
289 | mod = sys.modules.get(modname) | |
290 | if mod is None: | |
291 | raise ValueError(modname) | |
292 | import importlib | |
293 | importlib.reload(mod) | |
294 | ||
295 | sysctl = simplefunc(exit=lambda status=0: os._exit(status), | |
296 | reload=reload) |