Adds support for using client-side profilers from the server side.
This makes a few significant infrastructure changes in order the
support this:
  - it modifies autotest.py to allow "background" clients to be
    launched, where the server simply kicks off a client as a
    background process without bothering to monitor it
  - it adds a profiler_test test which is not a "real" test but is
    instead used to allow the server to start & stop the profilers
    in a reasonably controlled way

Currently, this still lacks any support for dealing with reboots; if
a test reboots the remote machine then the profilers will not be
restarted and the logs collected will only go as far as the first
reboot.

Risk: Medium
Visibility: You can use profilers from the server side (via
job.profilers.add and job.profilers.delete)

Signed-off-by: John Admanski <[email protected]>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@2514 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/autotest.py b/server/autotest.py
index 4629a73..c339d21 100644
--- a/server/autotest.py
+++ b/server/autotest.py
@@ -58,8 +58,8 @@
 
 
     @log.record
-    def install(self, host=None):
-        self._install(host=host)
+    def install(self, host=None, autodir=None):
+        self._install(host=host, autodir=autodir)
 
 
     def install_base(self, host=None, autodir=None):
@@ -181,7 +181,7 @@
 
 
     def run(self, control_file, results_dir = '.', host = None,
-            timeout=None, tag=None, parallel_flag=False):
+            timeout=None, tag=None, parallel_flag=False, background=False):
         """
         Run an autotest job on the remote machine.
 
@@ -194,6 +194,10 @@
                 tag: tag name for the client side instance of autotest
                 parallel_flag: flag set when multiple jobs are run at the
                           same time
+                background: indicates that the client should be launched as
+                            a background job; the code calling run will be
+                            responsible for monitoring the client and
+                            collecting the results
         Raises:
                 AutotestRunError: if there is a problem executing
                         the control file
@@ -204,7 +208,7 @@
         if tag:
             results_dir = os.path.join(results_dir, tag)
 
-        atrun = _Run(host, results_dir, tag, parallel_flag)
+        atrun = _Run(host, results_dir, tag, parallel_flag, background)
         self._do_run(control_file, results_dir, host, atrun, timeout)
 
 
@@ -270,9 +274,10 @@
         try:
             atrun.execute_control(timeout=timeout)
         finally:
-            collector = log_collector(host, atrun.tag, results_dir)
-            collector.collect_client_job_results()
-            self._process_client_state_file(host, atrun, results_dir)
+            if not atrun.background:
+                collector = log_collector(host, atrun.tag, results_dir)
+                collector.collect_client_job_results()
+                self._process_client_state_file(host, atrun, results_dir)
 
 
     def _create_state_file(self, job, state_dict):
@@ -349,12 +354,13 @@
     It is not intended to be used directly, rather control files
     should be run using the run method in Autotest.
     """
-    def __init__(self, host, results_dir, tag, parallel_flag):
+    def __init__(self, host, results_dir, tag, parallel_flag, background):
         self.host = host
         self.results_dir = results_dir
         self.env = host.env
         self.tag = tag
         self.parallel_flag = parallel_flag
+        self.background = background
         self.autodir = _get_autodir(self.host)
         control = os.path.join(self.autodir, 'control')
         if tag:
@@ -379,8 +385,9 @@
 
     def get_full_cmd(self, section):
         # build up the full command we want to run over the host
-        cmd = [os.path.join(self.autodir, 'bin/autotest_client'),
-               '-H autoserv']
+        cmd = [os.path.join(self.autodir, 'bin/autotest_client')]
+        if not self.background:
+            cmd.append('-H autoserv')
         if section > 0:
             cmd.append('-c')
         if self.tag:
@@ -388,6 +395,8 @@
         if self.host.job.use_external_logging():
             cmd.append('-l')
         cmd.append(self.remote_control_file)
+        if self.background:
+            cmd = ['nohup'] + cmd + ['>/dev/null 2>/dev/null &']
         return ' '.join(cmd)
 
 
@@ -417,7 +426,7 @@
 
         if result.exit_status == 1:
             raise error.AutotestRunError("client job was aborted")
-        if not result.stderr:
+        if not self.background and not result.stderr:
             raise error.AutotestRunError(
                 "execute_section: %s failed to return anything\n"
                 "stdout:%s\n" % (full_cmd, result.stdout))
@@ -464,6 +473,8 @@
                     section_timeout = None
                 last = self.execute_section(section, section_timeout,
                                             logger)
+                if self.background:
+                    return
                 section += 1
                 if re.match(r'^END .*\t----\t----\t.*$', last):
                     print "Client complete"
diff --git a/server/autotest_unittest.py b/server/autotest_unittest.py
index 619106d..14e5a14 100644
--- a/server/autotest_unittest.py
+++ b/server/autotest_unittest.py
@@ -132,7 +132,7 @@
         self.base_autotest.install.expect_call(self.host)
         self.host.wait_up.expect_call(timeout=30)
         os.path.abspath.expect_call('.').and_return('.')
-        run_obj = autotest._Run.expect_new(self.host, '.', None, False)
+        run_obj = autotest._Run.expect_new(self.host, '.', None, False, False)
         tag = None
         run_obj.manual_control_file = os.path.join('autodir',
                                                    'control.%s' % tag)
@@ -142,6 +142,7 @@
         run_obj.autodir = 'autodir'
         run_obj.verify_machine.expect_call()
         run_obj.verify_machine.expect_call()
+        run_obj.background = False
         debug = os.path.join('.', 'debug')
         os.makedirs.expect_call(debug)
         delete_file_list = [run_obj.remote_control_file,
diff --git a/server/profiler.py b/server/profiler.py
index a23c655..63b2e4d 100644
--- a/server/profiler.py
+++ b/server/profiler.py
@@ -1,7 +1,17 @@
-import itertools
+import os, itertools
 from autotest_lib.server import autotest
 
 
+PROFILER_TMPDIR = "/tmp/profilers"
+
+
+# control file template for running a job that uses profiler 'name'
+run_profiler_control = """\
+job.profilers.add(%s)
+job.run_test("profiler_test")
+job.profilers.delete(%r)
+"""
+
 
 def get_unpassable_types(arg):
     """ Given an argument, returns a set of types contained in arg that are
@@ -36,6 +46,13 @@
         raise TypeError(msg)
 
 
+def encode_args(profiler, args, dargs):
+    parts = [repr(profiler)]
+    parts += [repr(arg) for arg in dargs]
+    parts += ["%s=%r" % darg for darg in dargs.iteritems()]
+    return ", ".join(parts)
+
+
 class profiler_proxy(object):
     """ This is a server-side class that acts as a proxy to a real client-side
     profiler class."""
@@ -48,11 +65,13 @@
 
     def _install(self):
         """ Install autotest on any current job hosts. """
-        current_job_hosts = self.job.hosts
+        current_job_hosts = set(host for host in self.job.hosts
+                                if not host.get_autodir() or
+                                host.get_autodir().startswith(PROFILER_TMPDIR))
         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")
+            tmp_dir = host.get_tmp_dir(parent=PROFILER_TMPDIR)
             at = autotest.Autotest(host)
             at.install(autodir=tmp_dir)
             self.installed_hosts[host] = at
@@ -61,24 +80,50 @@
             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)
+        self.args, self.dargs = args, dargs
+
+
+    def setup(self, *args, **dargs):
+        assert self.args == args and self.dargs == dargs
+        # the actual setup happens lazily at start()
+
+
+    def _signal_clients(self, command):
+        """ Signal to each client that it should execute profilers.command
+        by writing a byte into AUTODIR/profilers.command. """
+        for host in self.installed_hosts.iterkeys():
+            autodir = host.get_autodir()
+            path = os.path.join(autodir, "profiler.%s" % command)
+            host.run("echo A > %s" % path)
 
 
     def start(self, test):
-        pass
+        self._install()
+        encoded_args = encode_args(self.name, self.args, self.dargs)
+        control_script = run_profiler_control % (encoded_args, self.name)
+        for at in self.installed_hosts.itervalues():
+            at.run(control_script, background=True)
+        self._signal_clients("start")
 
 
     def stop(self, test):
-        pass
+        self._signal_clients("stop")
 
 
     def report(self, test):
-        pass
+        self._signal_clients("report")
+        # pull back all the results
+        for host in self.installed_hosts.iterkeys():
+            results_dir = os.path.join(host.get_autodir(), "results",
+                                       "default", "profiler_test",
+                                       "profiling") + "/"
+            local_dir = os.path.join(test.profdir, host.hostname)
+            if not os.path.exists(local_dir):
+                os.makedirs(local_dir)
+            try:
+                host.get_file(results_dir, local_dir)
+            except error.AutoservRunError:
+                pass # no files to pull back, nothing we can do