- """Management for daemon processes
+ """Python Daemon Management -- Client functions
- This module provides some client support for the daemon management
- provided in the pdm.srv module.
+ This module implements the client part of the PDM protocols. The
+ primary objects of interest are the replclient and perfclient classes,
+ which implement support for their respective protocols. See their
+ documentation for details.
"""
import socket, pickle, struct, select, threading
select() method).
"""
self.sk = resolve(sk)
- self.buf = ""
+ self.buf = b""
line = self.readline()
- if line != "+PDM1":
+ if line != b"+PDM1":
raise protoerr("Illegal protocol signature")
if proto is not None:
self.select(proto)
def readline(self):
"""Read a single NL-terminated line and return it."""
while True:
- p = self.buf.find("\n")
+ p = self.buf.find(b"\n")
if p >= 0:
ret = self.buf[:p]
self.buf = self.buf[p + 1:]
return ret
ret = self.sk.recv(1024)
- if ret == "":
+ if ret == b"":
return None
self.buf += ret
def select(self, proto):
"""Negotiate the given subprotocol with the server"""
- if "\n" in proto:
+ if isinstance(proto, str):
+ proto = proto.encode("ascii")
+ if b"\n" in proto:
raise Exception("Illegal protocol specified: %r" % proto)
- self.sk.send(proto + "\n")
+ self.sk.send(proto + b"\n")
rep = self.readline()
- if len(rep) < 1 or rep[0] != "+":
+ if len(rep) < 1 or rep[0] != b"+"[0]:
raise protoerr("Error reply when selecting protocol %s: %s" % (proto, rep[1:]))
def __enter__(self):
"""
def __init__(self, sk):
"""Create a connected client as documented in the `client' class."""
- super(replclient, self).__init__(sk, "repl")
+ super().__init__(sk, "repl")
def run(self, code):
"""Run a single block of Python code on the server. Returns
code = ncode
while len(code) > 0 and code[-1] == "\n":
code = code[:-1]
- self.sk.send(code + "\n\n")
- buf = ""
+ self.sk.send((code + "\n\n").encode("utf-8"))
+ buf = b""
while True:
ln = self.readline()
- if ln[0] == " ":
- buf += ln[1:] + "\n"
- elif ln[0] == "+":
- return buf
- elif ln[0] == "-":
- raise protoerr("Error reply: %s" % ln[1:])
+ if ln[0] == b" "[0]:
+ buf += ln[1:] + b"\n"
+ elif ln[0] == b"+"[0]:
+ return buf.decode("utf-8")
+ elif ln[0] == b"-"[0]:
+ raise protoerr("Error reply: %s" % ln[1:].decode("utf-8"))
else:
raise protoerr("Illegal reply: %s" % ln)
"""
def __init__(self, sk):
"""Create a connected client as documented in the `client' class."""
- super(perfclient, self).__init__(sk, "perf")
+ super().__init__(sk, "perf")
self.nextid = 0
self.lock = threading.Lock()
self.proxies = {}
self.sk.send(buf)
def recvb(self, num):
- buf = ""
+ buf = b""
while len(buf) < num:
data = self.sk.recv(num - len(buf))
- if data == "":
+ if data == b"":
raise EOFError()
buf += data
return buf
of the slash. For instance, find("pdm.perf.sysres/cputime")
will return the built-in attribute for reading the CPU time
used by the server process.
+
+ The proxy objects returned by this function are cached and the
+ same object are returned the next time the same name is
+ requested, which means that they are kept live until the
+ client connection is closed.
"""
ret = self.names.get(name)
if ret is None:
+ """Python Daemon Management -- PERF utilities
+
+ This module serves two purposes: It has a few utility classes
+ for implementing PERF interfaces in common ways, and uses those
+ classes to implement some standard PERF objects that can be used by
+ PERF clients connecting to any PERF server.
+
+ See the documentation for pdm.srv.perf for a description of the
+ various PERF interfaces.
+
+ It contains two named PERF objects:
+
+ * sysres -- A directory containing the following objects pertaining
+ to the resource usage of the server process:
+ * realtime -- An attribute returning the amount of real time
+ since the PDM module was imported (which likely
+ coincides with the amount of time the server process
+ has been running).
+ * cputime -- An attribute returning the amount of CPU time
+ consumed by the server process (in both user and
+ kernel mode).
+ * utime -- An attribute returning the amount of CPU time the
+ server process has spent in user mode.
+ * stime -- An attribute returning the amount of CPU time the
+ server process has spent in kernel mode.
+ * maxrss -- An attribute returning the largest resident set size
+ the server process has used during its lifetime.
+ * rusage -- An attribute returning the current rusage of the
+ server process.
+ * sysinfo -- A directory containing the following objects pertaining
+ to the environment of the server process:
+ * pid -- An attribute returning the PID of the server process.
+ * uname -- An attribute returning the uname information of the
+ system.
+ * hostname -- An attribute returning the hostname of the system.
+ * platform -- An attribute returning the Python build platform.
+ """
+
import os, sys, resource, time, socket, threading
+ __all__ = ["attrinfo", "simpleattr", "valueattr", "eventobj",
+ "staticdir", "event", "procevent", "startevent",
+ "finishevent"]
+
class attrinfo(object):
+ """The return value of the `attrinfo' method on `attr' objects as
+ described in pdm.srv.perf.
+
+ Currently contains a single data field, `desc', which should have
+ a human-readable description of the purpose of the attribute.
+ """
def __init__(self, desc = None):
self.desc = desc
class perfobj(object):
def __init__(self, *args, **kwargs):
- super(perfobj, self).__init__()
+ super().__init__()
def pdm_protocols(self):
return []
class simpleattr(perfobj):
+ """An implementation of the `attr' interface, which is initialized
+ with a function, and returns whatever that function returns when
+ read.
+ """
def __init__(self, func, info = None, *args, **kwargs):
- super(simpleattr, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.func = func
if info is None:
info = attrinfo()
return self.info
def pdm_protocols(self):
- return super(simpleattr, self).pdm_protocols() + ["attr"]
+ return super().pdm_protocols() + ["attr"]
class valueattr(perfobj):
+ """An implementation of the `attr' interface, which is initialized
+ with a single value, and returns that value when read. Subsequent
+ updates to the value are reflected in subsequent reads.
+ """
def __init__(self, init, info = None, *args, **kwargs):
- super(valueattr, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.value = init
if info is None:
info = attrinfo()
return self.info
def pdm_protocols(self):
- return super(valueattr, self).pdm_protocols() + ["attr"]
+ return super().pdm_protocols() + ["attr"]
-
class eventobj(perfobj):
+ """An implementation of the `event' interface. It keeps track of
+ subscribers, and will multiplex any event to all current
+ subscribers when submitted with the `notify' method.
+ """
def __init__(self, *args, **kwargs):
- super(eventobj, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.subscribers = set()
def subscribe(self, cb):
self.subscribers.remove(cb)
def notify(self, event):
+ """Notify all subscribers with the given event object."""
for cb in self.subscribers:
try:
cb(event)
except: pass
def pdm_protocols(self):
- return super(eventobj, self).pdm_protocols() + ["event"]
+ return super().pdm_protocols() + ["event"]
class staticdir(perfobj):
+ """An implementation of the `dir' interface. Put other PERF
+ objects in it using the normal dict assignment syntax, and it will
+ return them to requesting clients.
+ """
def __init__(self, *args, **kwargs):
- super(staticdir, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.map = {}
def __setitem__(self, name, ob):
return self.map.get(name, default)
def listdir(self):
- return self.map.keys()
+ return list(self.map.keys())
def lookup(self, name):
return self.map[name]
def pdm_protocols(self):
- return super(staticdir, self).pdm_protocols() + ["dir"]
+ return super().pdm_protocols() + ["dir"]
class event(object):
+ """This class should be subclassed by all event objects sent via
+ the `event' interface. Its main utility is that it keeps track of
+ the time it was created, so that listening clients can precisely
+ measure the time between event notifications.
+
+ Subclasses should make sure to call the __init__ method if they
+ override it.
+ """
def __init__(self):
self.time = time.time()
idlock.release()
class procevent(event):
+ """A subclass of the `event' class meant to group several events
+ related to the same process. Create a new process by creating (a
+ subclass of) the `startevent' class, and subsequent events in the
+ same process by passing that startevent as the `id' parameter.
+
+ It works by having `startevent' allocate a unique ID for each
+ process, and every other procevent initializing from that
+ startevent copying the ID. The data field `id' contains the ID so
+ that it can be compared by clients.
+
+ An example of such a process might be a client connection, where a
+ `startevent' is emitted when a client connects, another subclass
+ of `procevent' emitted when the client runs a command, and a
+ `finishevent' emitted when the connection is closed.
+ """
def __init__(self, id):
- super(procevent, self).__init__()
+ super().__init__()
if isinstance(id, procevent):
self.id = id.id
else:
self.id = id
class startevent(procevent):
+ """A subclass of `procevent'. See its documentation for details."""
def __init__(self):
- super(startevent, self).__init__(getprocid())
+ super().__init__(getprocid())
class finishevent(procevent):
- def __init__(self, start, aborted):
+ """A subclass of `procevent'. Intended to be emitted when a
+ process finishes and terminates. The `aborted' field can be used
+ to indicate whether the process completed successfully, if such a
+ distinction is meaningful. The `start' parameter should be the
+ `startevent' instance used when the process was initiated."""
+ def __init__(self, start, aborted = False):
- super(finishevent, self).__init__(start)
+ super().__init__(start)
self.aborted = aborted
sysres = staticdir()