def _ddmin(self, dag, carryover_inputs, precompute_cache=None, recursion_level=0, label_prefix=(), total_inputs_pruned=0): ''' carryover_inputs is the variable "r" from the paper. ''' # Hack: superclass calls _ddmin with an integer, which doesn't match our # API. Translate that to an empty sequence. (we also don't use precompute_cache) if type(carryover_inputs) == int: carryover_inputs = [] local_label = lambda i: "%s/%d" % ("l" if i == 0 else "r", recursion_level) subset_label = lambda label: ".".join( map(str, label_prefix + (label, ))) print_subset = lambda label, s: subset_label(label) + ": " + " ".join( map(lambda e: e.label, s)) # Base case. Note that atomic_inputs are grouped-together failure/recovery # pairs, or normal inputs otherwise. if len(dag.atomic_input_events) == 1: self.log("Base case %s" % str(dag.input_events)) return (dag, total_inputs_pruned) (left, right) = split_list(dag.atomic_input_events, 2) self.log("Subsets:\n" + "\n".join( print_subset(local_label(i), s) for i, s in enumerate([left, right]))) # This is: [dag.input_subset(left), dag.input_subset(right)] left_right_dag = [] for i, subsequence in enumerate([left, right]): label = local_label(i) prefix = label_prefix + (label, ) new_dag = dag.atomic_input_subset(subsequence) self.log("Current subset: %s" % print_subset(label, new_dag.atomic_input_events)) left_right_dag.append(new_dag) # We test on subsequence U carryover_inputs test_dag = new_dag.insert_atomic_inputs(carryover_inputs) self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(test_dag, i, label) if violation: self.log("Violation found in %dth half. Recursing" % i) total_inputs_pruned += len(dag.input_events) - len( new_dag.input_events) self.mcs_log_tracker.maybe_dump_intermediate_mcs( new_dag, "", self) return self._ddmin(new_dag, carryover_inputs, recursion_level=recursion_level + 1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log("Interference") (left_dag, right_dag) = left_right_dag self.log("Recursing on left half") prefix = label_prefix + ("il/%d" % recursion_level, ) (left_result, total_inputs_pruned) = self._ddmin( left_dag, right_dag.insert_atomic_inputs( carryover_inputs).atomic_input_events, recursion_level=recursion_level + 1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log("Recursing on right half") prefix = label_prefix + ("ir/%d" % recursion_level, ) (right_result, total_inputs_pruned) = self._ddmin( right_dag, left_dag.insert_atomic_inputs( carryover_inputs).atomic_input_events, recursion_level=recursion_level + 1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) return (left_result.insert_atomic_inputs( right_result.atomic_input_events), total_inputs_pruned)
def _ddmin(self, dag, split_ways, precompute_cache=None, label_prefix=(), total_inputs_pruned=0): # This is the delta-debugging algorithm from: # http://www.st.cs.uni-saarland.de/papers/tse2002/tse2002.pdf, # Section 3.2 # TODO(cs): we could do much better if we leverage domain knowledge (e.g., # start by pruning all LinkFailures, or splitting by nodes rather than # time) if split_ways > len(dag.input_events): self.log("Done") return (dag, total_inputs_pruned) local_label = lambda i, inv=False: "%s%d/%d" % ("~" if inv else "", i, split_ways) subset_label = lambda label: ".".join( map(str, label_prefix + (label, ))) print_subset = lambda label, s: subset_label(label) + ": " + " ".join( map(lambda e: e.label, s)) subsets = split_list(dag.input_events, split_ways) self.log("Subsets:\n" + "\n".join( print_subset(local_label(i), s) for i, s in enumerate(subsets))) for i, subset in enumerate(subsets): label = local_label(i) new_dag = dag.input_subset(subset) input_sequence = tuple(new_dag.input_events) self.log("Current subset: %s" % print_subset(label, input_sequence)) if precompute_cache.already_done(input_sequence): self.log("Already computed. Skipping") continue precompute_cache.update(input_sequence) if input_sequence == (): self.log( "Subset after pruning dependencies was empty. Skipping") continue self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(new_dag, i, label) if violation: self.log_violation( "Subset %s reproduced violation. Subselecting." % subset_label(label)) self.mcs_log_tracker.maybe_dump_intermediate_mcs( new_dag, subset_label(label), self) total_inputs_pruned += len(dag.input_events) - len( new_dag.input_events) return self._ddmin(new_dag, 2, precompute_cache=precompute_cache, label_prefix=label_prefix + (label, ), total_inputs_pruned=total_inputs_pruned) self.log_no_violation( "No subsets with violations. Checking complements") for i, subset in enumerate(subsets): label = local_label(i, True) prefix = label_prefix + (label, ) new_dag = dag.input_complement(subset) input_sequence = tuple(new_dag.input_events) self.log("Current complement: %s" % print_subset(label, input_sequence)) if precompute_cache.already_done(input_sequence): self.log("Already computed. Skipping") continue precompute_cache.update(input_sequence) if input_sequence == (): self.log( "Subset %s after pruning dependencies was empty. Skipping", subset_label(label)) continue self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(new_dag, i, label) if violation: self.log_violation( "Subset %s reproduced violation. Subselecting." % subset_label(label)) self.mcs_log_tracker.maybe_dump_intermediate_mcs( new_dag, subset_label(label), self) total_inputs_pruned += len(dag.input_events) - len( new_dag.input_events) return self._ddmin(new_dag, max(split_ways - 1, 2), precompute_cache=precompute_cache, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log_no_violation("No complements with violations.") if split_ways < len(dag.input_events): self.log("Increasing granularity.") return self._ddmin(dag, min(len(dag.input_events), split_ways * 2), precompute_cache=precompute_cache, label_prefix=label_prefix, total_inputs_pruned=total_inputs_pruned) return (dag, total_inputs_pruned)
def _ddmin(self, dag, split_ways, precompute_cache=None, label_prefix=(), total_inputs_pruned=0): # This is the delta-debugging algorithm from: # http://www.st.cs.uni-saarland.de/papers/tse2002/tse2002.pdf, # Section 3.2 # TODO(cs): we could do much better if we leverage domain knowledge (e.g., # start by pruning all LinkFailures, or splitting by nodes rather than # time) if split_ways > len(dag.input_events): self.log("Done") return (dag, total_inputs_pruned) local_label = lambda i, inv=False: "%s%d/%d" % ("~" if inv else "", i, split_ways) subset_label = lambda label: ".".join(map(str, label_prefix + ( label, ))) print_subset = lambda label, s: subset_label(label) + ": "+" ".join(map(lambda e: e.label, s)) subsets = split_list(dag.input_events, split_ways) self.log("Subsets:\n"+"\n".join(print_subset(local_label(i), s) for i, s in enumerate(subsets))) for i, subset in enumerate(subsets): label = local_label(i) new_dag = dag.input_subset(subset) input_sequence = tuple(new_dag.input_events) self.log("Current subset: %s" % print_subset(label, input_sequence)) if precompute_cache.already_done(input_sequence): self.log("Already computed. Skipping") continue precompute_cache.update(input_sequence) if input_sequence == (): self.log("Subset after pruning dependencies was empty. Skipping") continue self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(new_dag, i, label) if violation: self.log_violation("Subset %s reproduced violation. Subselecting." % subset_label(label)) self.mcs_log_tracker.maybe_dump_intermediate_mcs(new_dag, subset_label(label), self) total_inputs_pruned += len(dag.input_events) - len(new_dag.input_events) return self._ddmin(new_dag, 2, precompute_cache=precompute_cache, label_prefix = label_prefix + (label, ), total_inputs_pruned=total_inputs_pruned) self.log_no_violation("No subsets with violations. Checking complements") for i, subset in enumerate(subsets): label = local_label(i, True) prefix = label_prefix + (label, ) new_dag = dag.input_complement(subset) input_sequence = tuple(new_dag.input_events) self.log("Current complement: %s" % print_subset(label, input_sequence)) if precompute_cache.already_done(input_sequence): self.log("Already computed. Skipping") continue precompute_cache.update(input_sequence) if input_sequence == (): self.log("Subset %s after pruning dependencies was empty. Skipping", subset_label(label)) continue self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(new_dag, i, label) if violation: self.log_violation("Subset %s reproduced violation. Subselecting." % subset_label(label)) self.mcs_log_tracker.maybe_dump_intermediate_mcs(new_dag, subset_label(label), self) total_inputs_pruned += len(dag.input_events) - len(new_dag.input_events) return self._ddmin(new_dag, max(split_ways - 1, 2), precompute_cache=precompute_cache, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log_no_violation("No complements with violations.") if split_ways < len(dag.input_events): self.log("Increasing granularity.") return self._ddmin(dag, min(len(dag.input_events), split_ways*2), precompute_cache=precompute_cache, label_prefix=label_prefix, total_inputs_pruned=total_inputs_pruned) return (dag, total_inputs_pruned)
def _ddmin(self, dag, carryover_inputs, precompute_cache=None, recursion_level=0, label_prefix=(), total_inputs_pruned=0): ''' carryover_inputs is the variable "r" from the paper. ''' # Hack: superclass calls _ddmin with an integer, which doesn't match our # API. Translate that to an empty sequence. (we also don't use precompute_cache) if type(carryover_inputs) == int: carryover_inputs = [] local_label = lambda i: "%s/%d" % ("l" if i == 0 else "r", recursion_level) subset_label = lambda label: ".".join(map(str, label_prefix + ( label, ))) print_subset = lambda label, s: subset_label(label) + ": "+" ".join(map(lambda e: e.label, s)) # Base case. Note that atomic_inputs are grouped-together failure/recovery # pairs, or normal inputs otherwise. if len(dag.atomic_input_events) == 1: self.log("Base case %s" % str(dag.input_events)) return (dag, total_inputs_pruned) (left, right) = split_list(dag.atomic_input_events, 2) self.log("Subsets:\n"+"\n".join(print_subset(local_label(i), s) for i, s in enumerate([left,right]))) # This is: [dag.input_subset(left), dag.input_subset(right)] left_right_dag = [] for i, subsequence in enumerate([left, right]): label = local_label(i) prefix = label_prefix + (label, ) new_dag = dag.atomic_input_subset(subsequence) self.log("Current subset: %s" % print_subset(label, new_dag.atomic_input_events)) left_right_dag.append(new_dag) # We test on subsequence U carryover_inputs test_dag = new_dag.insert_atomic_inputs(carryover_inputs) self._track_iteration_size(total_inputs_pruned) violation = self._check_violation(test_dag, i, label) if violation: self.log("Violation found in %dth half. Recursing" % i) total_inputs_pruned += len(dag.input_events) - len(new_dag.input_events) self.mcs_log_tracker.maybe_dump_intermediate_mcs(new_dag, "", self) return self._ddmin(new_dag, carryover_inputs, recursion_level=recursion_level+1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log("Interference") (left_dag, right_dag) = left_right_dag self.log("Recursing on left half") prefix = label_prefix + ("il/%d" % recursion_level,) (left_result, total_inputs_pruned) = self._ddmin(left_dag, right_dag.insert_atomic_inputs(carryover_inputs).atomic_input_events, recursion_level=recursion_level+1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) self.log("Recursing on right half") prefix = label_prefix + ("ir/%d" % recursion_level,) (right_result, total_inputs_pruned) = self._ddmin(right_dag, left_dag.insert_atomic_inputs(carryover_inputs).atomic_input_events, recursion_level=recursion_level+1, label_prefix=prefix, total_inputs_pruned=total_inputs_pruned) return (left_result.insert_atomic_inputs(right_result.atomic_input_events), total_inputs_pruned)
def _ddmin(self, split_ways, precomputed_subsets=None, iteration=0): ''' - iteration is the # of times we've replayed (not the number of times we've invoked _ddmin)''' # This is the delta-debugging algorithm from: # http://www.st.cs.uni-saarland.de/papers/tse2002/tse2002.pdf, # Section 3.2 # TODO(cs): we could do much better if we leverage domain knowledge (e.g., # start by pruning all LinkFailures) if split_ways > len(self.dag.input_events): self._track_iteration_size(iteration + 1, split_ways) self.log("Done") return if precomputed_subsets is None: precomputed_subsets = set() self.log("Checking %d subsets" % split_ways) subsets = split_list(self.dag.input_events, split_ways) self.log("Subsets: %s" % str(subsets)) for i, subset in enumerate(subsets): new_dag = self.dag.input_subset(subset) input_sequence = tuple(new_dag.input_events) self.log("Current subset: %s" % str(input_sequence)) if input_sequence in precomputed_subsets: self.log("Already computed. Skipping") continue precomputed_subsets.add(input_sequence) if input_sequence == (): self.log("Subset after pruning dependencies was empty. Skipping") continue iteration += 1 violation = self._check_violation(new_dag, i, iteration, split_ways) if violation: self.dag = new_dag return self._ddmin(2, precomputed_subsets=precomputed_subsets, iteration=iteration) self.log("No subsets with violations. Checking complements") for i, subset in enumerate(subsets): new_dag = self.dag.input_complement(subset) input_sequence = tuple(new_dag.input_events) self.log("Current complement: %s" % str(input_sequence)) if input_sequence in precomputed_subsets: self.log("Already computed. Skipping") continue precomputed_subsets.add(input_sequence) if input_sequence == (): self.log("Subset after pruning dependencies was empty. Skipping") continue iteration += 1 violation = self._check_violation(new_dag, i, iteration, split_ways) if violation: self.dag = new_dag return self._ddmin(max(split_ways - 1, 2), precomputed_subsets=precomputed_subsets, iteration=iteration) self.log("No complements with violations.") if split_ways < len(self.dag.input_events): self.log("Increasing granularity.") return self._ddmin(min(len(self.dag.input_events), split_ways*2), precomputed_subsets=precomputed_subsets, iteration=iteration) self._track_iteration_size(iteration + 1, split_ways)