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 | ||
8 | See the documentation for pdm.srv.perf for a description of the | |
9 | various PERF interfaces. | |
10 | ||
11 | It contains two named PERF objects: | |
12 | ||
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 | |
18 | has been running). | |
19 | * cputime -- An attribute returning the amount of CPU time | |
20 | consumed by the server process (in both user and | |
21 | kernel mode). | |
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 | |
29 | server process. | |
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 | |
34 | system. | |
35 | * hostname -- An attribute returning the hostname of the system. | |
36 | * platform -- An attribute returning the Python build platform. | |
37 | """ | |
38 | ||
87b07b36 | 39 | import os, sys, resource, time, socket, threading |
7f97a47e | 40 | |
5463509c FT |
41 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
42 | "staticdir", "event", "procevent", "startevent", | |
43 | "finishevent"] | |
44 | ||
7f97a47e | 45 | class attrinfo(object): |
5463509c FT |
46 | """The return value of the `attrinfo' method on `attr' objects as |
47 | described in pdm.srv.perf. | |
48 | ||
49 | Currently contains a single data field, `desc', which should have | |
50 | a human-readable description of the purpose of the attribute. | |
51 | """ | |
7f97a47e FT |
52 | def __init__(self, desc = None): |
53 | self.desc = desc | |
54 | ||
55 | class perfobj(object): | |
56 | def __init__(self, *args, **kwargs): | |
ed115f48 | 57 | super().__init__() |
7f97a47e FT |
58 | |
59 | def pdm_protocols(self): | |
60 | return [] | |
61 | ||
62 | class simpleattr(perfobj): | |
5463509c FT |
63 | """An implementation of the `attr' interface, which is initialized |
64 | with a function, and returns whatever that function returns when | |
65 | read. | |
66 | """ | |
7f97a47e | 67 | def __init__(self, func, info = None, *args, **kwargs): |
ed115f48 | 68 | super().__init__(*args, **kwargs) |
7f97a47e FT |
69 | self.func = func |
70 | if info is None: | |
71 | info = attrinfo() | |
72 | self.info = info | |
73 | ||
74 | def readattr(self): | |
75 | return self.func() | |
76 | ||
77 | def attrinfo(self): | |
78 | return self.info | |
79 | ||
80 | def pdm_protocols(self): | |
ed115f48 | 81 | return super().pdm_protocols() + ["attr"] |
7f97a47e FT |
82 | |
83 | class valueattr(perfobj): | |
5463509c FT |
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. | |
87 | """ | |
7f97a47e | 88 | def __init__(self, init, info = None, *args, **kwargs): |
ed115f48 | 89 | super().__init__(*args, **kwargs) |
7f97a47e FT |
90 | self.value = init |
91 | if info is None: | |
92 | info = attrinfo() | |
93 | self.info = info | |
94 | ||
95 | def readattr(self): | |
96 | return self.value | |
97 | ||
98 | def attrinfo(self): | |
99 | return self.info | |
100 | ||
101 | def pdm_protocols(self): | |
ed115f48 | 102 | return super().pdm_protocols() + ["attr"] |
7f97a47e | 103 | |
7f97a47e | 104 | class eventobj(perfobj): |
5463509c FT |
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. | |
108 | """ | |
7f97a47e | 109 | def __init__(self, *args, **kwargs): |
ed115f48 | 110 | super().__init__(*args, **kwargs) |
7f97a47e FT |
111 | self.subscribers = set() |
112 | ||
113 | def subscribe(self, cb): | |
114 | if cb in self.subscribers: | |
115 | raise ValueError("Already subscribed") | |
116 | self.subscribers.add(cb) | |
117 | ||
118 | def unsubscribe(self, cb): | |
119 | self.subscribers.remove(cb) | |
120 | ||
121 | def notify(self, event): | |
5463509c | 122 | """Notify all subscribers with the given event object.""" |
7f97a47e FT |
123 | for cb in self.subscribers: |
124 | try: | |
125 | cb(event) | |
126 | except: pass | |
127 | ||
128 | def pdm_protocols(self): | |
ed115f48 | 129 | return super().pdm_protocols() + ["event"] |
7f97a47e FT |
130 | |
131 | class staticdir(perfobj): | |
5463509c FT |
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. | |
135 | """ | |
7f97a47e | 136 | def __init__(self, *args, **kwargs): |
ed115f48 | 137 | super().__init__(*args, **kwargs) |
7f97a47e FT |
138 | self.map = {} |
139 | ||
140 | def __setitem__(self, name, ob): | |
141 | self.map[name] = ob | |
142 | ||
143 | def __delitem__(self, name): | |
144 | del self.map[name] | |
145 | ||
146 | def __getitem__(self, name): | |
147 | return self.map[name] | |
148 | ||
149 | def get(self, name, default = None): | |
150 | return self.map.get(name, default) | |
151 | ||
152 | def listdir(self): | |
afd9f04c | 153 | return list(self.map.keys()) |
7f97a47e FT |
154 | |
155 | def lookup(self, name): | |
156 | return self.map[name] | |
157 | ||
158 | def pdm_protocols(self): | |
ed115f48 | 159 | return super().pdm_protocols() + ["dir"] |
7f97a47e | 160 | |
bcb622d6 | 161 | class event(object): |
5463509c FT |
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. | |
166 | ||
167 | Subclasses should make sure to call the __init__ method if they | |
168 | override it. | |
169 | """ | |
bcb622d6 FT |
170 | def __init__(self): |
171 | self.time = time.time() | |
172 | ||
87b07b36 FT |
173 | idlock = threading.Lock() |
174 | procevid = 0 | |
709446f6 FT |
175 | |
176 | def getprocid(): | |
177 | global procevid | |
178 | idlock.acquire() | |
179 | try: | |
180 | ret = procevid | |
181 | procevid += 1 | |
182 | return ret | |
183 | finally: | |
184 | idlock.release() | |
185 | ||
186 | class procevent(event): | |
5463509c FT |
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. | |
191 | ||
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. | |
196 | ||
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. | |
201 | """ | |
709446f6 | 202 | def __init__(self, id): |
ed115f48 | 203 | super().__init__() |
709446f6 FT |
204 | if isinstance(id, procevent): |
205 | self.id = id.id | |
206 | else: | |
207 | self.id = id | |
208 | ||
209 | class startevent(procevent): | |
5463509c | 210 | """A subclass of `procevent'. See its documentation for details.""" |
87b07b36 | 211 | def __init__(self): |
ed115f48 | 212 | super().__init__(getprocid()) |
709446f6 FT |
213 | |
214 | class finishevent(procevent): | |
5463509c FT |
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.""" | |
0f1f2a1b | 220 | def __init__(self, start, aborted = False): |
ed115f48 | 221 | super().__init__(start) |
87b07b36 FT |
222 | self.aborted = aborted |
223 | ||
7f97a47e FT |
224 | sysres = staticdir() |
225 | itime = time.time() | |
226 | ires = resource.getrusage(resource.RUSAGE_SELF) | |
227 | def ct(): | |
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)) | |
236 | ||
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) |