7c0d561effaf32b8e33f2d9b8a55702af3ca441a
[pdm.git] / pdm / perf.py
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 L{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
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.
46 """
47
48 import os, sys, time, socket, threading
49
50 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
51            "staticdir", "event", "procevent", "startevent",
52            "finishevent"]
53
54 class error(Exception):
55     pass
56 class nosuchname(LookupError, error):
57     pass
58 class nosuchproto(error):
59     pass
60
61 class attrinfo(object):
62     """The return value of the `attrinfo' method on `attr' objects as
63     described in L{pdm.srv.perf}.
64
65     Currently contains a single data field, `desc', which should have
66     a human-readable description of the purpose of the attribute.
67     """
68     def __init__(self, desc = None):
69         self.desc = desc
70
71 class perfobj(object):
72     def __init__(self, *args, **kwargs):
73         super().__init__()
74     
75     def pdm_protocols(self):
76         return []
77
78 class simpleattr(perfobj):
79     """An implementation of the `attr' interface, which is initialized
80     with a function, and returns whatever that function returns when
81     read.
82     """
83     def __init__(self, func, info = None, *args, **kwargs):
84         super().__init__(*args, **kwargs)
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):
97         return super().pdm_protocols() + ["attr"]
98
99 class valueattr(perfobj):
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     """
104     def __init__(self, init, info = None, *args, **kwargs):
105         super().__init__(*args, **kwargs)
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):
118         return super().pdm_protocols() + ["attr"]
119
120 class eventobj(perfobj):
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     """
125     def __init__(self, *args, **kwargs):
126         super().__init__(*args, **kwargs)
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):
138         """Notify all subscribers with the given event object."""
139         for cb in list(self.subscribers):
140             try:
141                 cb(event)
142             except: pass
143
144     def pdm_protocols(self):
145         return super().pdm_protocols() + ["event"]
146
147 class staticdir(perfobj):
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     """
152     def __init__(self, *args, **kwargs):
153         super().__init__(*args, **kwargs)
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):
169         return list(self.map.keys())
170
171     def lookup(self, name):
172         return self.map[name]
173
174     def pdm_protocols(self):
175         return super().pdm_protocols() + ["dir"]
176
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):
183         super().__init__(*args)
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)
196         self.map[method](*args, **kwargs)
197
198     def pdm_protocols(self):
199         return super().pdm_protocols() + ["invoke"]
200
201 class event(object):
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     """
210     def __init__(self):
211         self.time = time.time()
212
213 idlock = threading.Lock()
214 procevid = 0
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):
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     """
242     def __init__(self, id):
243         super().__init__()
244         if isinstance(id, procevent):
245             self.id = id.id
246         else:
247             self.id = id
248
249 class startevent(procevent):
250     """A subclass of `procevent'. See its documentation for details."""
251     def __init__(self):
252         super().__init__(getprocid())
253
254 class finishevent(procevent):
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."""
260     def __init__(self, start, aborted = False):
261         super().__init__(start)
262         self.aborted = aborted
263
264 sysres = staticdir()
265 itime = time.time()
266 sysres["realtime"] = simpleattr(func = lambda: time.time() - itime)
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))
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)
287
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)