This depends on Martin's "run autoserv without a results dir" patch
to be applied cleanly.


A rather large refactoring and stubbing change working towards
client-side profilers from the server side. This makes a few major
changes:
  - refactors almost all of client/bin/profilers.py into a common lib
    profiler_manager class; except for the code for actually loading
    a profiler object, all of that code is already completely generic
  - add a server-side profiler_proxy class that will act as a proxy
    on the server for using a client-side profiler, this doesn't
    actually do anything useful right now but it basically just a
    stub for later changes
  - add a server-side profiler_manager implementation that creates a
    profiler_proxy class instead of actually loading a real profiler

The intended changes still in the pipeline that will build on this are:
  - add code to the profiler_proxy for actually making sure the
    profiler is set up and installed on the remote host(s)
  - add a mechanism for client-side profilers to deal with reboots

Risk: Medium
Visibility: Adds a bunch of stubs that don't actually do anything yet
but will do things soon.

Signed-off-by: John Admanski <jadmanski@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@2447 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/profiler.py b/server/profiler.py
new file mode 100644
index 0000000..a23c655
--- /dev/null
+++ b/server/profiler.py
@@ -0,0 +1,84 @@
+import itertools
+from autotest_lib.server import autotest
+
+
+
+def get_unpassable_types(arg):
+    """ Given an argument, returns a set of types contained in arg that are
+    unpassable. If arg is an atomic type (e.g. int) it either returns an
+    empty set (if the type is passable) or a singleton of the type (if the
+    type is not passable). """
+    if isinstance(arg, (basestring, int, long)):
+        return set()
+    elif isinstance(arg, (list, tuple, set, frozenset, dict)):
+        if isinstance(arg, dict):
+            # keys and values must both be passable
+            parts = itertools.chain(arg.iterkeys(), arg.itervalues())
+        else:
+            # for all other containers we just iterate
+            parts = iter(arg)
+        types = set()
+        for part in parts:
+            types |= get_unpassable_types(arg)
+        return types
+    else:
+        return set([type(arg)])
+
+
+def validate_args(args):
+    """ Validates arguments. Lists and dictionaries are valid argument types,
+    so you can pass *args and **dargs in directly, rather than having to
+    iterate over them yourself. """
+    unpassable_types = get_unpassable_types(args)
+    if unpassable_types:
+        msg = "arguments of type '%s' cannot be passed to remote profilers"
+        msg %= ", ".join(t.__name__ for t in unpassable_types)
+        raise TypeError(msg)
+
+
+class profiler_proxy(object):
+    """ This is a server-side class that acts as a proxy to a real client-side
+    profiler class."""
+
+    def __init__(self, job, profiler_name):
+        self.job = job
+        self.name = profiler_name
+        self.installed_hosts = {}
+
+
+    def _install(self):
+        """ Install autotest on any current job hosts. """
+        current_job_hosts = self.job.hosts
+        current_profiler_hosts = set(self.installed_hosts.keys())
+        # install autotest on any new hosts in job.hosts
+        for host in current_job_hosts - current_profiler_hosts:
+            tmp_dir = host.get_tmp_dir(parent="/tmp/profilers")
+            at = autotest.Autotest(host)
+            at.install(autodir=tmp_dir)
+            self.installed_hosts[host] = at
+        # drop any installs from hosts no longer in job.hosts
+        for host in current_profiler_hosts - current_job_hosts:
+            del self.installed_hosts[host]
+
+
+    def setup(self, *args, **dargs):
+        validate_args(args)
+        validate_args(dargs)
+        self._install()
+
+
+    def initialize(self, *args, **dargs):
+        validate_args(args)
+        validate_args(dargs)
+
+
+    def start(self, test):
+        pass
+
+
+    def stop(self, test):
+        pass
+
+
+    def report(self, test):
+        pass