Merge branch 'master' into python3
[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 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
39 import os, sys, resource, time, socket, threading
40
41 __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
42            "staticdir", "event", "procevent", "startevent",
43            "finishevent"]
44
45 class attrinfo(object):
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     """
52     def __init__(self, desc = None):
53         self.desc = desc
54
55 class perfobj(object):
56     def __init__(self, *args, **kwargs):
57         super().__init__()
58     
59     def pdm_protocols(self):
60         return []
61
62 class simpleattr(perfobj):
63     """An implementation of the `attr' interface, which is initialized
64     with a function, and returns whatever that function returns when
65     read.
66     """
67     def __init__(self, func, info = None, *args, **kwargs):
68         super().__init__(*args, **kwargs)
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):
81         return super().pdm_protocols() + ["attr"]
82
83 class valueattr(perfobj):
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     """
88     def __init__(self, init, info = None, *args, **kwargs):
89         super().__init__(*args, **kwargs)
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):
102         return super().pdm_protocols() + ["attr"]
103
104 class eventobj(perfobj):
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     """
109     def __init__(self, *args, **kwargs):
110         super().__init__(*args, **kwargs)
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):
122         """Notify all subscribers with the given event object."""
123         for cb in self.subscribers:
124             try:
125                 cb(event)
126             except: pass
127
128     def pdm_protocols(self):
129         return super().pdm_protocols() + ["event"]
130
131 class staticdir(perfobj):
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     """
136     def __init__(self, *args, **kwargs):
137         super().__init__(*args, **kwargs)
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):
153         return list(self.map.keys())
154
155     def lookup(self, name):
156         return self.map[name]
157
158     def pdm_protocols(self):
159         return super().pdm_protocols() + ["dir"]
160
161 class event(object):
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     """
170     def __init__(self):
171         self.time = time.time()
172
173 idlock = threading.Lock()
174 procevid = 0
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):
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     """
202     def __init__(self, id):
203         super().__init__()
204         if isinstance(id, procevent):
205             self.id = id.id
206         else:
207             self.id = id
208
209 class startevent(procevent):
210     """A subclass of `procevent'. See its documentation for details."""
211     def __init__(self):
212         super().__init__(getprocid())
213
214 class finishevent(procevent):
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."""
220     def __init__(self, start, aborted = False):
221         super().__init__(start)
222         self.aborted = aborted
223
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)