Bug 1414921 - Add --geckoProfile to capture profiles from talos tasks to |mach try fuzzy|, r?dustin draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 13 Dec 2017 16:16:09 -0500
changeset 778037 9a2bde7ff85c2334b6df321796c2e44d4c7fdda8
parent 778035 8a17d3fe18f612e6046fa3902a88f37cacec55cb
push id105374
push userahalberstadt@mozilla.com
push dateThu, 05 Apr 2018 18:58:55 +0000
reviewersdustin
bugs1414921
milestone61.0a1
Bug 1414921 - Add --geckoProfile to capture profiles from talos tasks to |mach try fuzzy|, r?dustin Enables |./mach try fuzzy --talos-profile|. This template only applies to talos tasks. It also provides --geckoProfile for consistency with |mach try syntax|, but I don't like this name so it's hidden from the help. The 'talos-profile.yml' template is also very specific (only applies to Talos tasks). Ideally I'd like a general 'command.yml' template that just appends arguments to the command for any arbitrary tasks. But then we'd need to invent an expression syntax in try_task_config.json so we could make sure it only applies to Talos. Then I thought rather than implement it for a specific template, we should have a general way of doing this which could apply to any and all of the templates. Needless to say, it's a rabbit hole and something that's best left to a follow-up so we don't delay this bug. MozReview-Commit-ID: GhllZ7sr0ar
taskcluster/taskgraph/templates/talos-profile.yml
taskcluster/taskgraph/test/test_morph.py
tools/tryselect/selectors/fuzzy.py
tools/tryselect/templates.py
tools/tryselect/test/test_templates.py
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/templates/talos-profile.yml
@@ -0,0 +1,36 @@
+---
+$if: input && task["extra"]
+then:
+    $if: task.extra["suite"]
+    then:
+        $if: task.extra.suite["name"] == "talos"
+        then:
+            task:
+                # We can't use mergeDeep as that will append the command below
+                # instead of overwriting the original command.
+                $merge:
+                    - $eval: task
+                    - payload:
+                          $merge:
+                              - $eval: task.payload
+                              - command:
+                                    $if: typeof(task.payload.command[0]) == 'array'
+                                    then:
+                                        # Command is an array of arrays. Assumes the command we want
+                                        # to append --geckoProfile to is the first one. Also assumes
+                                        # that we can't have a space delimited string here (which is
+                                        # the case for now at least).
+                                        - $flatten:
+                                              - $eval: task.payload.command[0]
+                                              - ["--geckoProfile"]
+                                    else:
+                                        $if: len(task.payload.command) == 1
+                                        then:
+                                            # Command is an array with a single space delimited string.
+                                            # This only happens on Windows.
+                                            - "${task.payload.command[0]} --geckoProfile"
+                                        else:
+                                            # Command is an array of strings.
+                                            $flatten:
+                                                - $eval: task.payload.command
+                                                - ["--geckoProfile"]
--- a/taskcluster/taskgraph/test/test_morph.py
+++ b/taskcluster/taskgraph/test/test_morph.py
@@ -108,16 +108,17 @@ TASKS = [
         }
     },
     {
         'kind': 'test',
         'label': 'b',
         'attributes': {},
         'task': {
             'extra': {
+                'suite': {'name': 'talos'},
                 'treeherder': {
                     'group': 'tc',
                     'symbol': 't'
                 }
             },
             'payload': {
                 'env': {
                     'FOO': 'BAR'
@@ -127,25 +128,23 @@ TASKS = [
                 'kind': 'test'
             }
         }
     },
 ]
 
 
 @pytest.fixture
-def taskgraph(make_taskgraph):
-    return make_taskgraph({
-        t['label']: Task(**t) for t in TASKS[:]
-    })
+def get_morphed(make_taskgraph):
+    def inner(try_task_config, tasks=None):
+        tasks = tasks or TASKS
+        taskgraph = make_taskgraph({
+            t['label']: Task(**t) for t in tasks[:]
+        })
 
-
-@pytest.fixture
-def get_morphed(taskgraph):
-    def inner(try_task_config):
         fn = morph.apply_jsone_templates(try_task_config)
         return fn(*taskgraph)[0]
     return inner
 
 
 def test_template_artifact(get_morphed):
     morphed = get_morphed({
         'templates': {
@@ -211,10 +210,39 @@ def test_template_rebuild(get_morphed):
     for t in tasks:
         if t.label == 'a':
             assert 'task_duplicates' not in t.attributes
         elif t.label == 'b':
             assert 'task_duplicates' in t.attributes
             assert t.attributes['task_duplicates'] == 4
 
 
+@pytest.mark.parametrize('command', (
+    ['foo --bar'],
+    ['foo', '--bar'],
+    [['foo']],
+    [['foo', '--bar']],
+))
+def test_template_talos_profile(get_morphed, command):
+    tasks = TASKS[:]
+    for t in tasks:
+        t['task']['payload']['command'] = command
+
+    morphed = get_morphed({
+        'templates': {
+            'talos-profile': True,
+        }
+    }, tasks)
+
+    for t in morphed.tasks.values():
+        command = t.task['payload']['command']
+        if isinstance(command[0], list):
+            command = command[0]
+        command = ' '.join(command)
+
+        if t.label == 'a':
+            assert not command.endswith('--geckoProfile')
+        elif t.label == 'b':
+            assert command.endswith('--geckoProfile')
+
+
 if __name__ == '__main__':
     main()
--- a/tools/tryselect/selectors/fuzzy.py
+++ b/tools/tryselect/selectors/fuzzy.py
@@ -86,17 +86,17 @@ class FuzzyParser(BaseTryParser):
           }],
         [['-u', '--update'],
          {'action': 'store_true',
           'default': False,
           'help': "Update fzf before running.",
           }],
     ]
     common_groups = ['push', 'task', 'preset']
-    templates = ['artifact', 'path', 'env', 'rebuild', 'chemspill-prio']
+    templates = ['artifact', 'path', 'env', 'rebuild', 'chemspill-prio', 'talos-profile']
 
 
 def run(cmd, cwd=None):
     is_win = platform.system() == 'Windows'
     return subprocess.call(cmd, cwd=cwd, shell=True if is_win else False)
 
 
 def run_fzf_install_script(fzf_path):
--- a/tools/tryselect/templates.py
+++ b/tools/tryselect/templates.py
@@ -7,17 +7,17 @@ Templates provide a way of modifying the
 tasks. They live under taskcluster/taskgraph/templates.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 import sys
 from abc import ABCMeta, abstractmethod
-from argparse import Action
+from argparse import Action, SUPPRESS
 
 import mozpack.path as mozpath
 from mozbuild.base import BuildEnvironmentNotFoundException, MozbuildObject
 
 here = os.path.abspath(os.path.dirname(__file__))
 build = MozbuildObject.from_environment(cwd=here)
 
 
@@ -139,15 +139,31 @@ class ChemspillPrio(Template):
 
     def context(self, chemspill_prio, **kwargs):
         if chemspill_prio:
             return {
                 'chemspill-prio': {}
             }
 
 
+class TalosProfile(Template):
+
+    def add_arguments(self, parser):
+        parser.add_argument('--talos-profile', dest='profile', action='store_true', default=False,
+                            help='Create and upload a gecko profile during talos tasks.')
+        # This is added for consistency with the 'syntax' selector
+        parser.add_argument('--geckoProfile', dest='profile', action='store_true', default=False,
+                            help=SUPPRESS)
+
+    def context(self, profile, **kwargs):
+        if not profile:
+            return
+        return {'talos-profile': profile}
+
+
 all_templates = {
     'artifact': Artifact,
     'path': Path,
     'env': Environment,
     'rebuild': Rebuild,
     'chemspill-prio': ChemspillPrio,
+    'talos-profile': TalosProfile,
 }
--- a/tools/tryselect/test/test_templates.py
+++ b/tools/tryselect/test/test_templates.py
@@ -14,16 +14,20 @@ from tryselect.templates import all_temp
 
 
 # templates have a list of tests of the form (input, expected)
 TEMPLATE_TESTS = {
     'artifact': [
         (['--no-artifact'], None),
         (['--artifact'], {'artifact': {'enabled': '1'}}),
     ],
+    'chemspill-prio': [
+        ([], None),
+        (['--chemspill-prio'], {'chemspill-prio': {}}),
+    ],
     'env': [
         ([], None),
         (['--env', 'foo=bar', '--env', 'num=10'], {'env': {'foo': 'bar', 'num': '10'}}),
     ],
     'path': [
         ([], None),
         (['dom/indexedDB'], {'env': {'MOZHARNESS_TEST_PATHS': 'dom/indexedDB'}}),
         (['dom/indexedDB', 'testing'],
@@ -31,16 +35,21 @@ TEMPLATE_TESTS = {
         (['invalid/path'], SystemExit),
     ],
     'rebuild': [
         ([], None),
         (['--rebuild', '10'], {'rebuild': 10}),
         (['--rebuild', '1'], SystemExit),
         (['--rebuild', '21'], SystemExit),
     ],
+    'talos-profile': [
+        ([], None),
+        (['--talos-profile'], {'talos-profile': True}),
+        (['--geckoProfile'], {'talos-profile': True}),
+    ],
 }
 
 
 def test_templates(template, args, expected):
     parser = ArgumentParser()
 
     t = all_templates[template]()
     t.add_arguments(parser)