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 | ||
87b07b36 | 48 | import os, sys, resource, time, socket, threading |
7f97a47e | 49 | |
5463509c FT |
50 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
51 | "staticdir", "event", "procevent", "startevent", | |
52 | "finishevent"] | |
53 | ||
7f97a47e | 54 | class 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 | ||
64 | class perfobj(object): | |
65 | def __init__(self, *args, **kwargs): | |
ed115f48 | 66 | super().__init__() |
7f97a47e FT |
67 | |
68 | def pdm_protocols(self): | |
69 | return [] | |
70 | ||
71 | class 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 | |
92 | class 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 | 113 | class 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.""" |
7f97a47e FT |
132 | for cb in self.subscribers: |
133 | try: | |
134 | cb(event) | |
135 | except: pass | |
136 | ||
137 | def pdm_protocols(self): | |
ed115f48 | 138 | return super().pdm_protocols() + ["event"] |
7f97a47e FT |
139 | |
140 | class 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 | |
bcb622d6 | 170 | class event(object): |
5463509c FT |
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. | |
175 | ||
176 | Subclasses should make sure to call the __init__ method if they | |
177 | override it. | |
178 | """ | |
bcb622d6 FT |
179 | def __init__(self): |
180 | self.time = time.time() | |
181 | ||
87b07b36 FT |
182 | idlock = threading.Lock() |
183 | procevid = 0 | |
709446f6 FT |
184 | |
185 | def getprocid(): | |
186 | global procevid | |
187 | idlock.acquire() | |
188 | try: | |
189 | ret = procevid | |
190 | procevid += 1 | |
191 | return ret | |
192 | finally: | |
193 | idlock.release() | |
194 | ||
195 | class procevent(event): | |
5463509c FT |
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. | |
200 | ||
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. | |
205 | ||
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. | |
210 | """ | |
709446f6 | 211 | def __init__(self, id): |
ed115f48 | 212 | super().__init__() |
709446f6 FT |
213 | if isinstance(id, procevent): |
214 | self.id = id.id | |
215 | else: | |
216 | self.id = id | |
217 | ||
218 | class startevent(procevent): | |
5463509c | 219 | """A subclass of `procevent'. See its documentation for details.""" |
87b07b36 | 220 | def __init__(self): |
ed115f48 | 221 | super().__init__(getprocid()) |
709446f6 FT |
222 | |
223 | class finishevent(procevent): | |
5463509c FT |
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.""" | |
0f1f2a1b | 229 | def __init__(self, start, aborted = False): |
ed115f48 | 230 | super().__init__(start) |
87b07b36 FT |
231 | self.aborted = aborted |
232 | ||
7f97a47e FT |
233 | sysres = staticdir() |
234 | itime = time.time() | |
235 | ires = resource.getrusage(resource.RUSAGE_SELF) | |
236 | def ct(): | |
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)) | |
245 | ||
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) |