Example #1
0
    def reduce(self, tasks: Set[str],
               min_redundancy_confidence: float) -> Set[str]:
        failing_together = test_scheduling.get_failing_together_db()

        priorities = ["linux", "win", "mac", "android"]

        to_drop = set()
        to_analyze = sorted(tasks)
        while len(to_analyze) > 1:
            task1 = to_analyze.pop(0)

            for task2 in to_analyze:
                key = f"{task1}${task2}".encode("utf-8")
                if key not in failing_together:
                    continue

                support, confidence = struct.unpack("ff",
                                                    failing_together[key])
                if confidence < min_redundancy_confidence:
                    continue

                for priority in priorities:
                    if priority in task1:
                        to_drop.add(task2)
                        break
                    elif priority in task2:
                        to_drop.add(task1)
                        break

            to_analyze = [t for t in to_analyze if t not in to_drop]

        return tasks - to_drop
Example #2
0
def test_reduce():
    failing_together = test_scheduling.get_failing_together_db("label")
    failing_together[b"test-linux64/debug$test-windows10/debug"] = struct.pack(
        "ff", 0.1, 1.0)
    failing_together[b"test-linux64/debug$test-windows10/opt"] = struct.pack(
        "ff", 0.1, 1.0)
    failing_together[b"test-linux64/opt$test-windows10/opt"] = struct.pack(
        "ff", 0.1, 0.91)
    failing_together[b"test-linux64/debug$test-linux64/opt"] = struct.pack(
        "ff", 0.1, 1.0)
    failing_together[
        b"test-linux64-asan/debug$test-linux64/debug"] = struct.pack(
            "ff", 0.1, 1.0)
    test_scheduling.close_failing_together_db("label")

    model = TestLabelSelectModel()
    assert model.reduce({"test-linux64/debug", "test-windows10/debug"},
                        1.0) == {"test-linux64/debug"}
    assert model.reduce({"test-linux64/debug", "test-windows10/opt"},
                        1.0) == {"test-linux64/debug"}
    assert model.reduce({"test-linux64/opt", "test-windows10/opt"}, 1.0) == {
        "test-linux64/opt",
        "test-windows10/opt",
    }
    assert model.reduce({"test-linux64/opt", "test-windows10/opt"},
                        0.9) == {"test-linux64/opt"}
    assert model.reduce({"test-linux64/opt", "test-linux64/debug"},
                        1.0) == {"test-linux64/opt"}
    assert model.reduce({"test-linux64-asan/debug", "test-linux64/debug"},
                        1.0) == {"test-linux64/debug"}
Example #3
0
def test_all(g: Graph) -> None:
    tasks = [f"windows10/opt-{chr(i)}" for i in range(len(g.vs))]

    try:
        test_scheduling.close_failing_together_db("label")
    except AssertionError:
        pass
    test_scheduling.remove_failing_together_db("label")

    # TODO: Also add some couples that are *not* failing together.
    ft: Dict[str, Dict[str, Tuple[float, float]]] = {}

    for edge in g.es:
        task1 = tasks[edge.tuple[0]]
        task2 = tasks[edge.tuple[1]]
        assert task1 < task2
        if task1 not in ft:
            ft[task1] = {}
        ft[task1][task2] = (0.1, 1.0)

    failing_together = test_scheduling.get_failing_together_db("label", False)
    for t, ts in ft.items():
        failing_together[t.encode("ascii")] = pickle.dumps(ts)

    test_scheduling.close_failing_together_db("label")

    model = TestLabelSelectModel()
    result = model.reduce(tasks, 1.0)
    hypothesis.note(f"Result: {sorted(result)}")
    assert len(result) == len(g.components())
Example #4
0
def mock_schedule_tests_classify(monkeypatch):
    with open("known_tasks", "w") as f:
        f.write("prova")

    # Initialize a mock past failures DB.
    for granularity in ("label", "group"):
        past_failures_data = test_scheduling.get_past_failures(granularity)
        past_failures_data["push_num"] = 1
        past_failures_data["all_runnables"] = [
            f"test-{granularity}1",
            f"test-{granularity}2",
            "test-linux64/opt",
            "test-windows10/opt",
        ]
        past_failures_data.close()

    failing_together = test_scheduling.get_failing_together_db()
    failing_together[b"test-linux64/opt$test-windows10/opt"] = struct.pack(
        "ff", 0.1, 1.0)
    test_scheduling.close_failing_together_db()

    def do_mock(labels_to_choose, groups_to_choose):
        # Add a mock test selection model.
        def classify(self, items, probabilities=False):
            assert probabilities
            results = []
            for item in items:
                runnable_name = item["test_job"]["name"]
                if self.granularity == "label":
                    if runnable_name in labels_to_choose:
                        results.append([
                            1 - labels_to_choose[runnable_name],
                            labels_to_choose[runnable_name],
                        ])
                    else:
                        results.append([0.9, 0.1])
                elif self.granularity == "group":
                    if runnable_name in groups_to_choose:
                        results.append([
                            1 - groups_to_choose[runnable_name],
                            groups_to_choose[runnable_name],
                        ])
                    else:
                        results.append([0.9, 0.1])
            return np.array(results)

        class MockModelCache:
            def get(self, model_name):
                if "group" in model_name:
                    return bugbug.models.testselect.TestGroupSelectModel()
                else:
                    return bugbug.models.testselect.TestLabelSelectModel()

        monkeypatch.setattr(bugbug_http.models, "MODEL_CACHE",
                            MockModelCache())
        monkeypatch.setattr(bugbug.models.testselect.TestSelectModel,
                            "classify", classify)

    return do_mock
Example #5
0
    def reduce(self, tasks: Set[str],
               min_redundancy_confidence: float) -> Set[str]:
        failing_together = test_scheduling.get_failing_together_db(
            self.granularity)

        priorities1 = [
            "tsan",
            "android-hw",
            "linux1804-32",
            "asan",
            "mac",
            "windows7",
            "android-em",
            "windows10",
            "linux1804-64",
        ]
        priorities2 = ["debug", "opt"]

        to_drop = set()
        to_analyze = sorted(tasks)
        while len(to_analyze) > 1:
            task1 = to_analyze.pop(0)

            key = test_scheduling.failing_together_key(task1)

            try:
                failing_together_stats = pickle.loads(failing_together[key])
            except KeyError:
                continue

            for task2 in to_analyze:
                try:
                    support, confidence = failing_together_stats[task2]
                except KeyError:
                    continue

                if confidence < min_redundancy_confidence:
                    continue

                for priority in priorities1:
                    if priority in task1 and priority in task2:
                        for priority in priorities2:
                            if priority in task1:
                                to_drop.add(task1)
                                break
                            elif priority in task2:
                                to_drop.add(task2)
                                break
                        break
                    elif priority in task1:
                        to_drop.add(task1)
                        break
                    elif priority in task2:
                        to_drop.add(task2)
                        break

            to_analyze = [t for t in to_analyze if t not in to_drop]

        return tasks - to_drop
Example #6
0
    def reduce(
        self,
        tasks: Collection[str],
        min_redundancy_confidence: float,
        assume_redundant: bool = False,
    ) -> Set[str]:
        failing_together = test_scheduling.get_failing_together_db(
            self.granularity, True
        )

        def load_failing_together(task: str) -> Dict[str, Tuple[float, float]]:
            key = test_scheduling.failing_together_key(task)
            return pickle.loads(failing_together[key])

        solver = pywraplp.Solver(
            "select_configs", pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING
        )

        task_vars = {task: solver.BoolVar(task) for task in tasks}

        equivalence_sets = self._generate_equivalence_sets(
            tasks, min_redundancy_confidence, load_failing_together, assume_redundant
        )

        # Create constraints to ensure at least one task from each set of equivalent
        # sets is selected.

        mutually_exclusive = True
        seen = set()
        for equivalence_set in equivalence_sets:
            if any(config in seen for config in equivalence_set):
                mutually_exclusive = False
                break

            seen |= equivalence_set

        for equivalence_set in equivalence_sets:
            sum_constraint = sum(task_vars[task] for task in equivalence_set)
            if mutually_exclusive:
                solver.Add(sum_constraint == 1)
            else:
                solver.Add(sum_constraint >= 1)

        # Choose the best set of tasks that satisfy the constraints with the lowest cost.
        solver.Minimize(
            sum(self._get_cost(task) * task_vars[task] for task in task_vars.keys())
        )

        if self._solve_optimization(solver):
            return {
                task
                for task, task_var in task_vars.items()
                if task_var.solution_value() == 1
            }
        else:
            return set(tasks)
Example #7
0
    def reduce(self, tasks: Set[str],
               min_redundancy_confidence: float) -> Set[str]:
        failing_together = test_scheduling.get_failing_together_db(
            self.granularity)

        priorities1 = [
            "tsan",
            "android-hw",
            "linux32",
            "asan",
            "mac",
            "windows7",
            "android-em",
            "windows10",
            "linux64",
        ]
        priorities2 = ["debug", "opt"]

        to_drop = set()
        to_analyze = sorted(tasks)
        while len(to_analyze) > 1:
            task1 = to_analyze.pop(0)

            for task2 in to_analyze:
                key = f"{task1}${task2}".encode("utf-8")
                if key not in failing_together:
                    continue

                support, confidence = struct.unpack("ff",
                                                    failing_together[key])
                if confidence < min_redundancy_confidence:
                    continue

                for priority in priorities1:
                    if priority in task1 and priority in task2:
                        for priority in priorities2:
                            if priority in task1:
                                to_drop.add(task1)
                                break
                            elif priority in task2:
                                to_drop.add(task2)
                                break
                        break
                    elif priority in task1:
                        to_drop.add(task1)
                        break
                    elif priority in task2:
                        to_drop.add(task2)
                        break

            to_analyze = [t for t in to_analyze if t not in to_drop]

        return tasks - to_drop
Example #8
0
def mock_get_config_specific_groups(
    monkeypatch: MonkeyPatch,
) -> None:
    with open("known_tasks", "w") as f:
        f.write("prova")

    # Initialize a mock past failures DB.
    past_failures_data = test_scheduling.get_past_failures("group", False)
    past_failures_data["push_num"] = 1
    past_failures_data["all_runnables"] = [
        "test-group1",
        "test-group2",
    ]
    past_failures_data.close()

    try:
        test_scheduling.close_failing_together_db("config_group")
    except AssertionError:
        pass
    failing_together = test_scheduling.get_failing_together_db("config_group", False)
    failing_together[b"$ALL_CONFIGS$"] = pickle.dumps(
        ["test-linux1804-64/opt-*", "test-windows10/debug-*", "test-windows10/opt-*"]
    )
    failing_together[b"$CONFIGS_BY_GROUP$"] = pickle.dumps(
        {
            "test-group1": {
                "test-linux1804-64/opt-*",
                "test-windows10/debug-*",
                "test-windows10/opt-*",
            },
            "test-group2": {
                "test-linux1804-64/opt-*",
                "test-windows10/debug-*",
                "test-windows10/opt-*",
            },
        }
    )
    failing_together[b"test-group1"] = pickle.dumps(
        {
            "test-linux1804-64/opt-*": {
                "test-windows10/debug-*": (1.0, 0.0),
                "test-windows10/opt-*": (1.0, 0.0),
            },
            "test-windows10/debug-*": {
                "test-windows10/opt-*": (1.0, 1.0),
            },
        }
    )
    test_scheduling.close_failing_together_db("config_group")

    monkeypatch.setattr(bugbug_http.models, "MODEL_CACHE", MockModelCache())
Example #9
0
def test_reduce():
    failing_together = test_scheduling.get_failing_together_db("label")
    failing_together[b"test-linux1804-64/debug"] = pickle.dumps(
        {
            "test-windows10/debug": (0.1, 1.0),
            "test-windows10/opt": (0.1, 1.0),
            "test-linux1804-64/opt": (0.1, 1.0),
        }
    )
    failing_together[b"test-linux1804-64/opt"] = pickle.dumps(
        {"test-windows10/opt": (0.1, 0.91),}
    )
    failing_together[b"test-linux1804-64-asan/debug"] = pickle.dumps(
        {"test-linux1804-64/debug": (0.1, 1.0),}
    )
    test_scheduling.close_failing_together_db("label")

    model = TestLabelSelectModel()
    assert model.reduce({"test-linux1804-64/debug", "test-windows10/debug"}, 1.0) == {
        "test-linux1804-64/debug"
    }
    assert model.reduce({"test-linux1804-64/debug", "test-windows10/opt"}, 1.0) == {
        "test-linux1804-64/debug"
    }
    assert model.reduce({"test-linux1804-64/opt", "test-windows10/opt"}, 1.0) == {
        "test-linux1804-64/opt",
        "test-windows10/opt",
    }
    assert model.reduce({"test-linux1804-64/opt", "test-windows10/opt"}, 0.9) == {
        "test-linux1804-64/opt"
    }
    assert model.reduce({"test-linux1804-64/opt", "test-linux1804-64/debug"}, 1.0) == {
        "test-linux1804-64/opt"
    }
    assert model.reduce(
        {"test-linux1804-64-asan/debug", "test-linux1804-64/debug"}, 1.0
    ) == {"test-linux1804-64/debug"}

    # Test case where the second task is not present in the failing together stats of the first.
    assert model.reduce(
        {"test-linux1804-64-asan/debug", "test-windows10/opt"}, 1.0
    ) == {"test-linux1804-64-asan/debug", "test-windows10/opt"}

    # Test case where a task is not present at all in the failing together DB.
    assert model.reduce({"test-linux1804-64-qr/debug", "test-windows10/opt"}, 1.0) == {
        "test-linux1804-64-qr/debug",
        "test-windows10/opt",
    }
Example #10
0
    def _get_equivalence_sets(self, min_redundancy_confidence: float):
        try:
            with open(
                f"equivalence_sets_{min_redundancy_confidence}.pickle", "rb"
            ) as f:
                return pickle.load(f)
        except FileNotFoundError:
            past_failures_data = test_scheduling.get_past_failures(
                self.granularity, True
            )
            all_runnables = past_failures_data["all_runnables"]

            equivalence_sets = {}
            failing_together = test_scheduling.get_failing_together_db(
                "config_group", True
            )
            all_configs = pickle.loads(failing_together[b"$ALL_CONFIGS$"])
            configs_by_group = pickle.loads(failing_together[b"$CONFIGS_BY_GROUP$"])
            for group in all_runnables:
                key = test_scheduling.failing_together_key(group)
                try:
                    failing_together_stats = pickle.loads(failing_together[key])
                except KeyError:
                    failing_together_stats = {}

                def load_failing_together(
                    config: str,
                ) -> Dict[str, Tuple[float, float]]:
                    return failing_together_stats[config]

                configs = (
                    configs_by_group[group]
                    if group in configs_by_group
                    else all_configs
                )

                equivalence_sets[group] = self._generate_equivalence_sets(
                    configs, min_redundancy_confidence, load_failing_together, True
                )

            with open(
                f"equivalence_sets_{min_redundancy_confidence}.pickle", "wb"
            ) as f:
                pickle.dump(equivalence_sets, f)

            return equivalence_sets
Example #11
0
def test_reduce3(failing_together: LMDBDict) -> None:
    test_scheduling.remove_failing_together_db("label")
    failing_together = test_scheduling.get_failing_together_db("label")
    failing_together[b"windows10/opt-a"] = pickle.dumps({
        "windows10/opt-b": (0.1, 1.0),
        "windows10/opt-c": (0.1, 0.3),
        "windows10/opt-d": (0.1, 1.0),
    })
    failing_together[b"windows10/opt-b"] = pickle.dumps({
        "windows10/opt-c": (0.1, 1.0),
        "windows10/opt-d": (0.1, 0.3),
    })
    failing_together[b"windows10/opt-c"] = pickle.dumps({
        "windows10/opt-d": (0.1, 1.0),
    })

    model = TestLabelSelectModel()
    result = model.reduce(
        {
            "windows10/opt-a", "windows10/opt-b", "windows10/opt-c",
            "windows10/opt-d"
        },
        1.0,
    )
    assert (result == {
        "windows10/opt-a",
        "windows10/opt-c",
    } or result == {
        "windows10/opt-d",
        "windows10/opt-c",
    } or result == {
        "windows10/opt-b",
        "windows10/opt-c",
    } or result == {
        "windows10/opt-b",
        "windows10/opt-d",
    })
Example #12
0
def failing_together_config_group() -> Iterator[LMDBDict]:
    yield test_scheduling.get_failing_together_db("config_group", False)
    test_scheduling.close_failing_together_db("config_group")
Example #13
0
def failing_together() -> Iterator[LMDBDict]:
    yield test_scheduling.get_failing_together_db("label", False)
    test_scheduling.close_failing_together_db("label")
Example #14
0
def mock_schedule_tests_classify(
    monkeypatch: MonkeyPatch,
) -> Callable[[dict[str, float], dict[str, float]], None]:
    with open("known_tasks", "w") as f:
        f.write("prova")

    # Initialize a mock past failures DB.
    for granularity in ("label", "group"):
        past_failures_data = test_scheduling.get_past_failures(granularity, False)
        past_failures_data["push_num"] = 1
        past_failures_data["all_runnables"] = [
            "test-linux1804-64-opt-label1",
            "test-linux1804-64-opt-label2",
            "test-group1",
            "test-group2",
            "test-linux1804-64/opt",
            "test-windows10/opt",
        ]
        past_failures_data.close()

    try:
        test_scheduling.close_failing_together_db("label")
    except AssertionError:
        pass
    failing_together = test_scheduling.get_failing_together_db("label", False)
    failing_together[b"test-linux1804-64/opt"] = pickle.dumps(
        {
            "test-windows10/opt": (0.1, 1.0),
        }
    )
    test_scheduling.close_failing_together_db("label")

    try:
        test_scheduling.close_failing_together_db("config_group")
    except AssertionError:
        pass
    failing_together = test_scheduling.get_failing_together_db("config_group", False)
    failing_together[b"$ALL_CONFIGS$"] = pickle.dumps(
        ["test-linux1804-64/opt", "test-windows10/debug", "test-windows10/opt"]
    )
    failing_together[b"$CONFIGS_BY_GROUP$"] = pickle.dumps(
        {
            "test-group1": {
                "test-linux1804-64/opt",
                "test-windows10/debug",
                "test-windows10/opt",
            },
            "test-group2": {
                "test-linux1804-64/opt",
                "test-windows10/debug",
                "test-windows10/opt",
            },
        }
    )
    failing_together[b"test-group1"] = pickle.dumps(
        {
            "test-linux1804-64/opt": {
                "test-windows10/debug": (1.0, 0.0),
                "test-windows10/opt": (1.0, 1.0),
            },
            "test-windows10/debug": {
                "test-windows10/opt": (1.0, 0.0),
            },
        }
    )
    test_scheduling.close_failing_together_db("config_group")

    try:
        test_scheduling.close_touched_together_db()
    except AssertionError:
        pass
    test_scheduling.get_touched_together_db(False)
    test_scheduling.close_touched_together_db()

    def do_mock(labels_to_choose, groups_to_choose):
        # Add a mock test selection model.
        def classify(self, items, probabilities=False):
            assert probabilities
            results = []
            for item in items:
                runnable_name = item["test_job"]["name"]
                if self.granularity == "label":
                    if runnable_name in labels_to_choose:
                        results.append(
                            [
                                1 - labels_to_choose[runnable_name],
                                labels_to_choose[runnable_name],
                            ]
                        )
                    else:
                        results.append([0.9, 0.1])
                elif self.granularity == "group":
                    if runnable_name in groups_to_choose:
                        results.append(
                            [
                                1 - groups_to_choose[runnable_name],
                                groups_to_choose[runnable_name],
                            ]
                        )
                    else:
                        results.append([0.9, 0.1])
            return np.array(results)

        monkeypatch.setattr(bugbug_http.models, "MODEL_CACHE", MockModelCache())
        monkeypatch.setattr(
            bugbug.models.testselect.TestSelectModel, "classify", classify
        )

    return do_mock
Example #15
0
    def select_configs(
            self, groups: Iterable[str],
            min_redundancy_confidence: float) -> Dict[str, List[str]]:
        failing_together = test_scheduling.get_failing_together_db(
            "config_group")

        all_configs = pickle.loads(failing_together[b"$ALL_CONFIGS$"])
        config_costs = {
            config: self._get_cost(config)
            for config in all_configs
        }

        solver = pywraplp.Solver("select_configs",
                                 pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)

        config_group_vars = {(config, group):
                             solver.BoolVar(f"{group}@{config}")
                             for group in groups for config in all_configs}

        for group in groups:
            key = test_scheduling.failing_together_key(group)
            try:
                failing_together_stats = pickle.loads(failing_together[key])
            except KeyError:
                failing_together_stats = {}

            def load_failing_together(
                    config: str) -> Dict[str, Tuple[float, float]]:
                return failing_together_stats[config]

            equivalence_sets = self._generate_equivalence_sets(
                all_configs, min_redundancy_confidence, load_failing_together,
                True)

            # Create constraints to ensure at least one task from each set of equivalent
            # groups is selected.

            mutually_exclusive = True
            seen = set()
            for equivalence_set in equivalence_sets:
                if any(config in seen for config in equivalence_set):
                    mutually_exclusive = False
                    break

                seen |= equivalence_set

            for equivalence_set in equivalence_sets:
                sum_constraint = sum(config_group_vars[(config, group)]
                                     for config in equivalence_set)
                if mutually_exclusive:
                    solver.Add(sum_constraint == 1)
                else:
                    solver.Add(sum_constraint >= 1)

        # Choose the best set of tasks that satisfy the constraints with the lowest cost.
        solver.Minimize(
            sum(config_costs[config] * config_group_vars[(config, group)]
                for config, group in config_group_vars.keys()))

        self._solve_optimization(solver)

        configs_by_group: Dict[str, List[str]] = {}
        for group in groups:
            configs_by_group[group] = []

        for (config, group), config_group_var in config_group_vars.items():
            if config_group_var.solution_value() == 1:
                configs_by_group[group].append(config)

        return configs_by_group
Example #16
0
    def select_configs(
        self, groups: Collection[str], min_redundancy_confidence: float
    ) -> Dict[str, List[str]]:
        failing_together = test_scheduling.get_failing_together_db("config_group", True)

        all_configs = pickle.loads(failing_together[b"$ALL_CONFIGS$"])
        all_configs_by_group = pickle.loads(failing_together[b"$CONFIGS_BY_GROUP$"])
        config_costs = {config: self._get_cost(config) for config in all_configs}

        solver = pywraplp.Solver(
            "select_configs", pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING
        )

        config_group_vars = {
            (config, group): solver.BoolVar(f"{group}@{config}")
            for group in groups
            for config in (
                all_configs_by_group[group]
                if group in all_configs_by_group
                else all_configs
            )
        }

        equivalence_sets = self._get_equivalence_sets(min_redundancy_confidence)

        for group in groups:
            # Create constraints to ensure at least one task from each set of equivalent
            # groups is selected.

            mutually_exclusive = True
            seen = set()
            for equivalence_set in equivalence_sets[group]:
                if any(config in seen for config in equivalence_set):
                    mutually_exclusive = False
                    break

                seen |= equivalence_set

            for equivalence_set in equivalence_sets[group]:
                sum_constraint = sum(
                    config_group_vars[(config, group)] for config in equivalence_set
                )
                if mutually_exclusive:
                    solver.Add(sum_constraint == 1)
                else:
                    solver.Add(sum_constraint >= 1)

        # Choose the best set of tasks that satisfy the constraints with the lowest cost.
        solver.Minimize(
            sum(
                config_costs[config] * config_group_vars[(config, group)]
                for config, group in config_group_vars.keys()
            )
        )

        configs_by_group: Dict[str, List[str]] = {}
        for group in groups:
            configs_by_group[group] = []

        if self._solve_optimization(solver):
            for (config, group), config_group_var in config_group_vars.items():
                if config_group_var.solution_value() == 1:
                    configs_by_group[group].append(config)
        else:
            least_cost_config = min(config_costs, key=lambda c: config_costs[c])
            for group in groups:
                configs_by_group[group].append(least_cost_config)

        return configs_by_group
        def generate_failing_together_probabilities(push_data):
            # TODO: we should consider the probabilities of `task1 failure -> task2 failure` and
            # `task2 failure -> task1 failure` separately, as they could be different.

            count_runs = collections.Counter()
            count_single_failures = collections.Counter()
            count_both_failures = collections.Counter()

            for revisions, tasks, likely_regressions, candidate_regressions in tqdm(
                push_data
            ):
                failures = set(likely_regressions + candidate_regressions)
                all_tasks = list(set(tasks) | failures)

                for task1, task2 in itertools.combinations(sorted(all_tasks), 2):
                    count_runs[(task1, task2)] += 1

                    if task1 in failures:
                        if task2 in failures:
                            count_both_failures[(task1, task2)] += 1
                        else:
                            count_single_failures[(task1, task2)] += 1
                    elif task2 in failures:
                        count_single_failures[(task1, task2)] += 1

            stats = {}

            skipped = 0

            for couple, run_count in count_runs.most_common():
                failure_count = count_both_failures[couple]
                support = failure_count / run_count

                if support < 1 / 700:
                    skipped += 1
                    continue

                if failure_count != 0:
                    confidence = failure_count / (
                        count_single_failures[couple] + failure_count
                    )
                else:
                    confidence = 0.0

                stats[couple] = (support, confidence)

            logger.info(f"{skipped} couples skipped because their support was too low")

            logger.info("Redundancies with the highest support and confidence:")
            for couple, (support, confidence) in sorted(
                stats.items(), key=lambda k: (-k[1][1], -k[1][0])
            )[:7]:
                failure_count = count_both_failures[couple]
                run_count = count_runs[couple]
                logger.info(
                    f"{couple[0]} - {couple[1]} redundancy confidence {confidence}, support {support} ({failure_count} over {run_count})."
                )

            logger.info("Redundancies with the highest confidence and lowest support:")
            for couple, (support, confidence) in sorted(
                stats.items(), key=lambda k: (-k[1][1], k[1][0])
            )[:7]:
                failure_count = count_both_failures[couple]
                run_count = count_runs[couple]
                logger.info(
                    f"{couple[0]} - {couple[1]} redundancy confidence {confidence}, support {support} ({failure_count} over {run_count})."
                )

            failing_together = test_scheduling.get_failing_together_db()
            count_redundancies = collections.Counter()
            for couple, (support, confidence) in stats.items():
                if confidence == 1.0:
                    count_redundancies["==100%"] += 1
                if confidence > 0.9:
                    count_redundancies[">=90%"] += 1
                if confidence > 0.8:
                    count_redundancies[">=80%"] += 1
                if confidence > 0.7:
                    count_redundancies[">=70%"] += 1

                if confidence < 0.7:
                    continue

                failing_together[
                    f"{couple[0]}${couple[1]}".encode("utf-8")
                ] = struct.pack("ff", support, confidence)

            for percentage, count in count_redundancies.most_common():
                logger.info(f"{count} with {percentage} confidence")

            test_scheduling.close_failing_together_db()
Example #18
0
    def select_configs(
        self,
        groups: Collection[str],
        min_redundancy_confidence: float,
        max_configurations: int = 3,
    ) -> dict[str, list[str]]:
        failing_together = test_scheduling.get_failing_together_db("config_group", True)

        all_configs = pickle.loads(failing_together[b"$ALL_CONFIGS$"])
        all_configs_by_group = pickle.loads(failing_together[b"$CONFIGS_BY_GROUP$"])
        config_costs = {config: self._get_cost(config) for config in all_configs}

        solver = pywraplp.Solver(
            "select_configs", pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING
        )

        config_vars = {config: solver.BoolVar(config) for config in all_configs}
        config_group_vars = {
            (config, group): solver.BoolVar(f"{group}@{config}")
            for group in groups
            for config in (
                all_configs_by_group[group]
                if group in all_configs_by_group
                else all_configs
            )
        }

        equivalence_sets = self._get_equivalence_sets(min_redundancy_confidence)

        for group in groups:
            # Create constraints to ensure at least one task from each set of equivalent
            # groups is selected.

            mutually_exclusive = True
            seen = set()
            for equivalence_set in equivalence_sets[group]:
                if any(config in seen for config in equivalence_set):
                    mutually_exclusive = False
                    break

                seen |= equivalence_set

            set_variables = [
                solver.BoolVar(f"{group}_{j}")
                for j in range(len(equivalence_sets[group]))
            ]

            for j, equivalence_set in enumerate(equivalence_sets[group]):
                set_variable = set_variables[j]

                sum_constraint = sum(
                    config_group_vars[(config, group)] for config in equivalence_set
                )
                if mutually_exclusive:
                    solver.Add(sum_constraint == set_variable)
                else:
                    solver.Add(sum_constraint >= set_variable)

            # Cap to max_configurations equivalence sets.
            solver.Add(
                sum(set_variables)
                >= (
                    max_configurations
                    if len(set_variables) >= max_configurations
                    else len(set_variables)
                )
            )

        for config in all_configs:
            solver.Add(
                sum(
                    config_group_var
                    for (c, g), config_group_var in config_group_vars.items()
                    if config == c
                )
                <= config_vars[config] * len(groups)
            )

        # Choose the best set of tasks that satisfy the constraints with the lowest cost.
        # The cost is calculated as a sum of the following:
        # - a fixed cost to use a config (since selecting a config has overhead, it is
        #   wasteful to select a config only to run a single group);
        # - a cost for each selected group.
        # This way, for example, if we have a group that must run on a costly config and a
        # group that can run either on the costly one or on a cheaper one, they'd both run
        # on the costly one (since we have to pay its setup cost anyway).
        solver.Minimize(
            sum(10 * config_costs[c] * config_vars[c] for c in config_vars.keys())
            + sum(
                config_costs[config] * config_group_vars[(config, group)]
                for config, group in config_group_vars.keys()
            )
        )

        configs_by_group: dict[str, list[str]] = {}
        for group in groups:
            configs_by_group[group] = []

        if self._solve_optimization(solver):
            for (config, group), config_group_var in config_group_vars.items():
                if config_group_var.solution_value() == 1:
                    configs_by_group[group].append(config)
        else:
            least_cost_config = min(config_costs, key=lambda c: config_costs[c])
            for group in groups:
                configs_by_group[group].append(least_cost_config)

        return configs_by_group