Removed a few things not supported by Jython.
[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  - 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
25     - hostname -- An attribute returning the hostname of the system.
26
27     - platform -- An attribute returning the Python build platform.
28 """
29
30 import os, sys, time, socket, threading
31
32 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
33            "staticdir", "event", "procevent", "startevent",
34            "finishevent"]
35
36 class attrinfo(object):
37     """The return value of the `attrinfo' method on `attr' objects as
38     described in L{pdm.srv.perf}.
39
40     Currently contains a single data field, `desc', which should have
41     a human-readable description of the purpose of the attribute.
42     """
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):
54     """An implementation of the `attr' interface, which is initialized
55     with a function, and returns whatever that function returns when
56     read.
57     """
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):
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     """
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
95 class eventobj(perfobj):
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     """
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):
113         """Notify all subscribers with the given event object."""
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):
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     """
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
152 class event(object):
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     """
161     def __init__(self):
162         self.time = time.time()
163
164 idlock = threading.Lock()
165 procevid = 0
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):
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     """
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):
201     """A subclass of `procevent'. See its documentation for details."""
202     def __init__(self):
203         super(startevent, self).__init__(getprocid())
204
205 class finishevent(procevent):
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."""
211     def __init__(self, start, aborted = False):
212         super(finishevent, self).__init__(start)
213         self.aborted = aborted
214
215 sysres = staticdir()
216 itime = time.time()
217 sysres["realtime"] = simpleattr(func = lambda: time.time() - itime)
218
219 sysinfo = staticdir()
220 sysinfo["pid"] = simpleattr(func = os.getpid)
221 sysinfo["hostname"] = simpleattr(func = socket.gethostname)
222 sysinfo["platform"] = valueattr(init = sys.platform)