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 | ||
57808152 FT |
20 | - sysinfo -- A directory containing the following objects pertaining |
21 | to the environment of the server process: | |
22 | ||
23 | - pid -- An attribute returning the PID of the server process. | |
24 | ||
57808152 FT |
25 | - hostname -- An attribute returning the hostname of the system. |
26 | ||
27 | - platform -- An attribute returning the Python build platform. | |
5463509c FT |
28 | """ |
29 | ||
901a5a6f | 30 | import os, sys, time, socket, threading |
7f97a47e | 31 | |
5463509c FT |
32 | __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj", |
33 | "staticdir", "event", "procevent", "startevent", | |
34 | "finishevent"] | |
35 | ||
7f97a47e | 36 | class attrinfo(object): |
5463509c | 37 | """The return value of the `attrinfo' method on `attr' objects as |
57808152 | 38 | described in L{pdm.srv.perf}. |
5463509c FT |
39 | |
40 | Currently contains a single data field, `desc', which should have | |
41 | a human-readable description of the purpose of the attribute. | |
42 | """ | |
7f97a47e FT |
43 | def __init__(self, desc = None): |
44 | self.desc = desc | |
45 | ||
46 | class perfobj(object): | |
47 | def __init__(self, *args, **kwargs): | |
48 | super(perfobj, self).__init__() | |
49 | ||
50 | def pdm_protocols(self): | |
51 | return [] | |
52 | ||
53 | class simpleattr(perfobj): | |
5463509c FT |
54 | """An implementation of the `attr' interface, which is initialized |
55 | with a function, and returns whatever that function returns when | |
56 | read. | |
57 | """ | |
7f97a47e FT |
58 | def __init__(self, func, info = None, *args, **kwargs): |
59 | super(simpleattr, self).__init__(*args, **kwargs) | |
60 | self.func = func | |
61 | if info is None: | |
62 | info = attrinfo() | |
63 | self.info = info | |
64 | ||
65 | def readattr(self): | |
66 | return self.func() | |
67 | ||
68 | def attrinfo(self): | |
69 | return self.info | |
70 | ||
71 | def pdm_protocols(self): | |
72 | return super(simpleattr, self).pdm_protocols() + ["attr"] | |
73 | ||
74 | class valueattr(perfobj): | |
5463509c FT |
75 | """An implementation of the `attr' interface, which is initialized |
76 | with a single value, and returns that value when read. Subsequent | |
77 | updates to the value are reflected in subsequent reads. | |
78 | """ | |
7f97a47e FT |
79 | def __init__(self, init, info = None, *args, **kwargs): |
80 | super(valueattr, self).__init__(*args, **kwargs) | |
81 | self.value = init | |
82 | if info is None: | |
83 | info = attrinfo() | |
84 | self.info = info | |
85 | ||
86 | def readattr(self): | |
87 | return self.value | |
88 | ||
89 | def attrinfo(self): | |
90 | return self.info | |
91 | ||
92 | def pdm_protocols(self): | |
93 | return super(valueattr, self).pdm_protocols() + ["attr"] | |
94 | ||
7f97a47e | 95 | class eventobj(perfobj): |
5463509c FT |
96 | """An implementation of the `event' interface. It keeps track of |
97 | subscribers, and will multiplex any event to all current | |
98 | subscribers when submitted with the `notify' method. | |
99 | """ | |
7f97a47e FT |
100 | def __init__(self, *args, **kwargs): |
101 | super(eventobj, self).__init__(*args, **kwargs) | |
102 | self.subscribers = set() | |
103 | ||
104 | def subscribe(self, cb): | |
105 | if cb in self.subscribers: | |
106 | raise ValueError("Already subscribed") | |
107 | self.subscribers.add(cb) | |
108 | ||
109 | def unsubscribe(self, cb): | |
110 | self.subscribers.remove(cb) | |
111 | ||
112 | def notify(self, event): | |
5463509c | 113 | """Notify all subscribers with the given event object.""" |
7f97a47e FT |
114 | for cb in self.subscribers: |
115 | try: | |
116 | cb(event) | |
117 | except: pass | |
118 | ||
119 | def pdm_protocols(self): | |
120 | return super(eventobj, self).pdm_protocols() + ["event"] | |
121 | ||
122 | class staticdir(perfobj): | |
5463509c FT |
123 | """An implementation of the `dir' interface. Put other PERF |
124 | objects in it using the normal dict assignment syntax, and it will | |
125 | return them to requesting clients. | |
126 | """ | |
7f97a47e FT |
127 | def __init__(self, *args, **kwargs): |
128 | super(staticdir, self).__init__(*args, **kwargs) | |
129 | self.map = {} | |
130 | ||
131 | def __setitem__(self, name, ob): | |
132 | self.map[name] = ob | |
133 | ||
134 | def __delitem__(self, name): | |
135 | del self.map[name] | |
136 | ||
137 | def __getitem__(self, name): | |
138 | return self.map[name] | |
139 | ||
140 | def get(self, name, default = None): | |
141 | return self.map.get(name, default) | |
142 | ||
143 | def listdir(self): | |
144 | return self.map.keys() | |
145 | ||
146 | def lookup(self, name): | |
147 | return self.map[name] | |
148 | ||
149 | def pdm_protocols(self): | |
150 | return super(staticdir, self).pdm_protocols() + ["dir"] | |
151 | ||
bcb622d6 | 152 | class event(object): |
5463509c FT |
153 | """This class should be subclassed by all event objects sent via |
154 | the `event' interface. Its main utility is that it keeps track of | |
155 | the time it was created, so that listening clients can precisely | |
156 | measure the time between event notifications. | |
157 | ||
158 | Subclasses should make sure to call the __init__ method if they | |
159 | override it. | |
160 | """ | |
bcb622d6 FT |
161 | def __init__(self): |
162 | self.time = time.time() | |
163 | ||
87b07b36 FT |
164 | idlock = threading.Lock() |
165 | procevid = 0 | |
709446f6 FT |
166 | |
167 | def getprocid(): | |
168 | global procevid | |
169 | idlock.acquire() | |
170 | try: | |
171 | ret = procevid | |
172 | procevid += 1 | |
173 | return ret | |
174 | finally: | |
175 | idlock.release() | |
176 | ||
177 | class procevent(event): | |
5463509c FT |
178 | """A subclass of the `event' class meant to group several events |
179 | related to the same process. Create a new process by creating (a | |
180 | subclass of) the `startevent' class, and subsequent events in the | |
181 | same process by passing that startevent as the `id' parameter. | |
182 | ||
183 | It works by having `startevent' allocate a unique ID for each | |
184 | process, and every other procevent initializing from that | |
185 | startevent copying the ID. The data field `id' contains the ID so | |
186 | that it can be compared by clients. | |
187 | ||
188 | An example of such a process might be a client connection, where a | |
189 | `startevent' is emitted when a client connects, another subclass | |
190 | of `procevent' emitted when the client runs a command, and a | |
191 | `finishevent' emitted when the connection is closed. | |
192 | """ | |
709446f6 FT |
193 | def __init__(self, id): |
194 | super(procevent, self).__init__() | |
195 | if isinstance(id, procevent): | |
196 | self.id = id.id | |
197 | else: | |
198 | self.id = id | |
199 | ||
200 | class startevent(procevent): | |
5463509c | 201 | """A subclass of `procevent'. See its documentation for details.""" |
87b07b36 | 202 | def __init__(self): |
709446f6 FT |
203 | super(startevent, self).__init__(getprocid()) |
204 | ||
205 | class finishevent(procevent): | |
5463509c FT |
206 | """A subclass of `procevent'. Intended to be emitted when a |
207 | process finishes and terminates. The `aborted' field can be used | |
208 | to indicate whether the process completed successfully, if such a | |
209 | distinction is meaningful. The `start' parameter should be the | |
210 | `startevent' instance used when the process was initiated.""" | |
0f1f2a1b | 211 | def __init__(self, start, aborted = False): |
709446f6 | 212 | super(finishevent, self).__init__(start) |
87b07b36 FT |
213 | self.aborted = aborted |
214 | ||
7f97a47e FT |
215 | sysres = staticdir() |
216 | itime = time.time() | |
7f97a47e | 217 | sysres["realtime"] = simpleattr(func = lambda: time.time() - itime) |
7f97a47e FT |
218 | |
219 | sysinfo = staticdir() | |
220 | sysinfo["pid"] = simpleattr(func = os.getpid) | |
7f97a47e FT |
221 | sysinfo["hostname"] = simpleattr(func = socket.gethostname) |
222 | sysinfo["platform"] = valueattr(init = sys.platform) |