diff options
Diffstat (limited to 'taskcluster/gecko_taskgraph/actions/side_by_side.py')
-rw-r--r-- | taskcluster/gecko_taskgraph/actions/side_by_side.py | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/taskcluster/gecko_taskgraph/actions/side_by_side.py b/taskcluster/gecko_taskgraph/actions/side_by_side.py new file mode 100644 index 0000000000..816e716fde --- /dev/null +++ b/taskcluster/gecko_taskgraph/actions/side_by_side.py @@ -0,0 +1,189 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import logging +import os +import sys +from functools import partial + +from taskgraph.util.taskcluster import get_artifact, get_task_definition + +from ..util.taskcluster import list_task_group_complete_tasks +from .registry import register_callback_action +from .util import create_tasks, fetch_graph_and_labels, get_decision_task_id, get_pushes + +logger = logging.getLogger(__name__) + + +def input_for_support_action(revision, base_revision, base_branch, task): + """Generate input for action to be scheduled. + + Define what label to schedule with 'label'. + If it is a test task that uses explicit manifests add that information. + """ + platform, test_name = task["metadata"]["name"].split("/opt-") + new_branch = os.environ.get("GECKO_HEAD_REPOSITORY", "/try").split("/")[-1] + symbol = task["extra"]["treeherder"]["symbol"] + input = { + "label": "perftest-linux-side-by-side", + "symbol": symbol, + "new_revision": revision, + "base_revision": base_revision, + "test_name": test_name, + "platform": platform, + "base_branch": base_branch, + "new_branch": new_branch, + } + + return input + + +def side_by_side_modifier(task, input): + if task.label != input["label"]: + return task + + # Make side-by-side job searchable by the platform, test name, and revisions + # it was triggered for + task.task["metadata"][ + "name" + ] = f"{input['platform']} {input['test_name']} {input['base_revision'][:12]} {input['new_revision'][:12]}" + # Use a job symbol to include the symbol of the job the side-by-side + # is running for + task.task["extra"]["treeherder"]["symbol"] += f"-{input['symbol']}" + + cmd = task.task["payload"]["command"] + task.task["payload"]["command"][1][-1] = cmd[1][-1].format(**input) + + return task + + +@register_callback_action( + title="Side by side", + name="side-by-side", + symbol="gen-sxs", + description=( + "Given a performance test pageload job generate a side-by-side comparison against" + "the pageload job from the revision at the input." + ), + order=200, + context=[{"test-type": "raptor"}], + schema={ + "type": "object", + "properties": { + "revision": { + "type": "string", + "default": "", + "description": "Revision of the push against the comparison is wanted.", + }, + "project": { + "type": "string", + "default": "autoland", + "description": "Revision of the push against the comparison is wanted.", + }, + }, + "additionalProperties": False, + }, +) +def side_by_side_action(parameters, graph_config, input, task_group_id, task_id): + """ + This action does a side-by-side comparison between current revision and + the revision entered manually or the latest revision that ran the + pageload job (via support action). + + To execute this action locally follow the documentation here: + https://firefox-source-docs.mozilla.org/taskcluster/actions.html#testing-the-action-locally + """ + task = get_task_definition(task_id) + decision_task_id, full_task_graph, label_to_taskid = fetch_graph_and_labels( + parameters, graph_config + ) + # TODO: find another way to detect side-by-side comparable jobs + # (potentially lookig at the visual metrics flag) + if not ( + "browsertime-tp6" in task["metadata"]["name"] + or "welcome" in task["metadata"]["name"] + ): + logger.exception( + f"Task {task['metadata']['name']} is not side-by-side comparable." + ) + return + + failed = False + input_for_action = {} + + if input.get("revision"): + # If base_revision was introduced manually, use that + input_for_action = input_for_support_action( + revision=parameters["head_rev"], + base_revision=input.get("revision"), + base_branch=input.get("project"), + task=task, + ) + else: + current_push_id = int(parameters["pushlog_id"]) - 1 + # Go decrementally through pushlog_id, get push data, decision task id, + # full task graph and everything needed to find which of the past revisions + # ran the pageload job to compare against + while int(parameters["pushlog_id"]) - current_push_id < 30: + pushes = get_pushes( + project=parameters["head_repository"], + end_id=current_push_id, + depth=1, + full_response=True, + ) + try: + # Get label-to-taskid.json artifact + the tasks triggered + # by the action tasks at a later time than the decision task + current_decision_task_id = get_decision_task_id( + parameters["project"], current_push_id + ) + current_task_group_id = get_task_definition(current_decision_task_id)[ + "taskGroupId" + ] + current_label_to_taskid = get_artifact( + current_decision_task_id, "public/label-to-taskid.json" + ) + current_full_label_to_taskid = current_label_to_taskid.copy() + action_task_triggered = list_task_group_complete_tasks( + current_task_group_id + ) + current_full_label_to_taskid.update(action_task_triggered) + if task["metadata"]["name"] in current_full_label_to_taskid.keys(): + input_for_action = input_for_support_action( + revision=parameters["head_rev"], + base_revision=pushes[str(current_push_id)]["changesets"][-1], + base_branch=input.get("project", parameters["project"]), + task=task, + ) + break + except Exception: + logger.warning( + f"Could not find decision task for push {current_push_id}" + ) + # The decision task may have failed, this is common enough that we + # don't want to report an error for it. + continue + current_push_id -= 1 + if not input_for_action: + raise Exception( + "Could not find a side-by-side comparable task within a depth of 30 revisions." + ) + + try: + create_tasks( + graph_config, + [input_for_action["label"]], + full_task_graph, + label_to_taskid, + parameters, + decision_task_id, + modifier=partial(side_by_side_modifier, input=input_for_action), + ) + except Exception as e: + logger.exception(f"Failed to trigger action: {e}.") + failed = True + + if failed: + sys.exit(1) |