def test_not_one_constraint_something_matched_but_constraint_rejected_it( self): rule = Rule( [self.cause_a], self.effect, [ { 'clues_groups': [[0, 1], [1, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_NOT ) # yapf: disable effect_clues_dict = { 'effect': Clue((42, ), '42 dinners', 1420, self.line_source) } clues = { 'cause_a': [ Clue((13,), '13 carrots', 130, self.line_source), ], 'dummy': [ Clue((98,), '98 foo bar', 980, self.line_source), Clue((99,), '99 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert results assert len(results) == 1 assert results[0].lines == [] assert results[0].constraints_linkage == InvestigationResult.NOT
def test_not_one_constraint_something_matched_but_constraint_approves( self): rule = Rule( [self.cause_a], self.effect, [ { 'clues_groups': [[0, 1], [1, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_NOT ) # yapf: disable effect_clues_dict = { 'effect': Clue((42, ), '42 dinners', 1420, self.line_source) } clues = { 'cause_a': [ Clue((42,), '42 carrots', 420, self.line_source), ], 'dummy': [ Clue((98,), '98 foo bar', 980, self.line_source), Clue((99,), '99 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert not results
def test_one_matched_line_when_two_occurrences_requested(self): rule = Rule( [self.cause_a, self.cause_a], self.effect, [ { 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_AND ) # yapf: disable effect_clues_dict = { 'effect': Clue((42, ), '42 dinners', 1420, self.line_source) } clues = { # it's dictionary of the same type as clues dict collected in SearchManager 'cause_a': [ Clue((42,), '42 carrots', 420, self.line_source), ], 'dummy': [ Clue((98,), '98 foo bar', 980, self.line_source), Clue((99,), '99 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert effect_clues_dict['effect'].regex_parameters[0] == \ clues['cause_a'][0].regex_parameters[0] assert not results
def test_constraints_or_two_time_constraints(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Foo occurred', datetime(2000, 6, 14, second=15)), 'Foo occurred, 2000 06 14 00:00:15', 500, line_source) clues_lists = [ ([ Clue(('Bar occurred', datetime(2000, 6, 14, second=10)), 'Bar occurred, 2000 06 14 00:00:10', 250, line_source), ], 1) ] # yapf: disable constraints = [{ 'clues_groups': [[1, 2], [0, 2]], 'name': 'time_delta', 'params': { 'max_delta': 8.0 } }, { 'clues_groups': [[1, 2], [0, 2]], 'name': 'time_delta', 'params': { 'max_delta': 3.0 } }] causes = Verifier.constraints_or(clues_lists, effect, constraints, ConstraintManager()) assert len(causes) == 1 assert all(cause.constraints_linkage == InvestigationResult.OR for cause in causes) assert causes[0].lines == [ FrontInput.from_clue(Clue( ('Bar occurred', datetime(2000, 6, 14, second=10)), 'Bar occurred, 2000 06 14 00:00:10', 250, line_source)) ] # yapf: disable assert len(causes[0].constraints) == 1 assert causes[0].constraints[0] == constraints[0]
def test_constraints_and_basic(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Pear', 2), 'Pear, 2 times', 3000, line_source) clues_lists = [ ([ Clue(('Milk', 3), 'Milk, 3 times', 50, line_source), Clue(('Chocolate', 2), 'Chocolate, 2 times', 100, line_source), Clue(('Pear', 2), 'Pear, 2 times', 150, line_source) ], 1), ([ Clue(('Pear', 2), 'Pear, 2 times', 1050, line_source), Clue(('Milk', 1), 'Milk, 1 times', 1100, line_source) ], 1) ] # yapf: disable constraints = [{ 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} }, { 'clues_groups': [[0, 2], [1, 2], [2, 2]], 'name': 'identical', 'params': {} }] causes = Verifier.constraints_and(clues_lists, effect, constraints, ConstraintManager()) assert len(causes) == 1 assert all(cause.constraints_linkage == InvestigationResult.AND for cause in causes) assert causes[0].lines == [ FrontInput.from_clue(Clue( ('Pear', 2), 'Pear, 2 times', 150, line_source)), FrontInput.from_clue(Clue( ('Pear', 2), 'Pear, 2 times', 1050, line_source)) ] # yapf: disable
def test_constraints_or_without_constraints(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Pineapple', 44), 'Pineapple, 44 times', 3000, line_source) clues_lists = [ ([ Clue(('Banana', 2), 'Banana, 2 times', 1050, line_source), Clue(('Milk', 1), 'Milk, 2 times', 1100, line_source) ], 1), ([ Clue(('Chocolate', 2), 'Chocolate, 2 times', 100, line_source) ], 1) ] # yapf: disable constraints = [] causes = Verifier.constraints_or(clues_lists, effect, constraints, ConstraintManager()) assert len(causes) == 2 assert all(cause.constraints_linkage == InvestigationResult.OR for cause in causes) assert causes[0].lines == [ FrontInput.from_clue(Clue( ('Banana', 2), 'Banana, 2 times', 1050, line_source)), FrontInput.from_clue(Clue( ('Chocolate', 2), 'Chocolate, 2 times', 100, line_source)) ] # yapf: disable assert causes[1].lines == [ FrontInput.from_clue(Clue( ('Milk', 1), 'Milk, 2 times', 1100, line_source)), FrontInput.from_clue(Clue( ('Chocolate', 2), 'Chocolate, 2 times', 100, line_source)) ] # yapf: disable assert all(not cause.constraints for cause in causes)
def mocked_investigation_plan(): super_parser = RegexSuperParser('^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*', [1], {1: 'date'}) matcher = WildCardFilenameMatcher('localhost', 'node_1.log', 'default', super_parser) default_log_type = LogType('default', [matcher]) cause = RegexParser( 'cause', '2015-12-03 12:08:08 root cause', '^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) root cause$', [1], 'default', {1: 'date'} ) effect = RegexParser( 'effect', '2015-12-03 12:08:09 visible effect', '^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) visible effect$', [1], 'default', {1: 'date'} ) concatenated = ConcatenatedRegexParser([cause]) effect_time = datetime(2015, 12, 3, 12, 8, 9) search_range = { 'default': { 'date': { 'left_bound': datetime(2015, 12, 3, 12, 8, 8), 'right_bound': effect_time } } } default_investigation_step = InvestigationStep(concatenated, search_range) rule = Rule( [cause], effect, [ { 'clues_groups': [[1, 1], [0, 1]], 'name': 'time', 'params': {'max_delta': 1} } ], Rule.LINKAGE_AND ) # yapf: disable line_source = LineSource('localhost', 'node_1.log') effect_clues = {'effect': Clue((effect_time,), 'visible effect', 40, line_source)} return InvestigationPlan([rule], [(default_investigation_step, default_log_type)], effect_clues)
def setUpClass(cls): SettingsFactorySelector.WHYLOG_DIR = TestPaths.WHYLOG_DIR cls.config = SettingsFactorySelector.get_settings()['config'] cls.whylog_dir = SettingsFactorySelector._attach_whylog_dir(os.getcwd()) cause1_regex = '^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) cause1 transaction number: (\d+) Host: (\w)$' cause1_line = '2016-04-12 23:39:43 cause1 transaction number: 10101 Host: db_host' convertions = {1: 'date'} cls.cause1 = RegexParser("cause1", cause1_line, cause1_regex, [1], 'database', convertions) cause2_regex = '^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) cause2 moved resource id: (\d+) Host: (\w)$' cause2_line = '2016-04-12 23:40:43 cause2 moved resource id: 1234 Host: apache_host' convertions = {1: 'date'} cls.cause2 = RegexParser("cause2", cause2_line, cause2_regex, [1], 'apache', convertions) effect_regex = '^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) effect internal server error Host: (\w)$' effect_line = '2016-04-12 23:54:43 effect internal server error Host: apache_host' convertions = {1: 'date'} cls.effect = RegexParser("effect", effect_line, effect_regex, [1], 'apache', convertions) line_source = LineSource('localhost', 'node_1.log') cls.effect_time = datetime(2016, 4, 12, 23, 54, 43) effect_line = '2016-04-12 23:54:43 effect internal server error Host: apache_host' cls.effect_clues = { 'effect': Clue((cls.effect_time, 'apache_host'), effect_line, 40, line_source) } cls.earliest_date = datetime(1, 1, 1, 1, 1, 1) cls.ten_second_earlier = datetime(2016, 4, 12, 23, 54, 33) cls.one_hundred_second_earlier = datetime(2016, 4, 12, 23, 53, 3) cls.ten_second_later = datetime(2016, 4, 12, 23, 54, 53)
def test_constraints_check_same_cause_parser_as_effect(self): rule = Rule( [self.cause_a], self.cause_a, [ { 'clues_groups': [[0, 1], [1, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_AND ) # yapf: disable effect_clues_dict = { 'cause_a': Clue((42, ), '42 carrots', 1420, self.line_source) } clues = { # it's dictionary of the same type as clues dict collected in SearchManager 'cause_a': [ Clue((40,), '40 carrots', 400, self.line_source), Clue((42,), '42 carrots', 420, self.line_source), Clue((44,), '44 carrots', 440, self.line_source) ], 'dummy': [ Clue((98,), '98 foo bar', 980, self.line_source), Clue((99,), '99 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert len(results) == 1 assert results[0].lines == [ FrontInput.from_clue( Clue((42,), '42 carrots', 420, self.line_source)) ] # yapf: disable
def test_single_constraint_not_basic(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Dinner', 44), 'Dinner, 44 times', 440, line_source) clues_lists = [ ([ Clue(('Car', 40), 'Car, 40 times', 400, line_source), Clue(('Car', 1), 'Car, 1 times', 100, line_source), Clue(('Car', 15), 'Car, 15 times', 150, line_source) ], 1), ([ Clue(('Forest', 0), 'Forest, 0 times', 0, line_source), Clue(('Forest', 3), 'Forest, 3 times', 30, line_source), Clue(('Forest', 42), 'Forest, 42 times', 420, line_source) ], 1) ] # yapf: disable constraint = { 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} } causes = Verifier.single_constraint_not(clues_lists, effect, constraint, ConstraintManager()) assert len(causes) == 1 assert causes[0].constraints_linkage == InvestigationResult.NOT assert causes[0].constraints[0] == constraint
def _create_effect_clues(self, effect_params, front_input): effect_clues = {} for parser_name, params in six.iteritems(effect_params): parser = self._parsers[parser_name] clue = Clue( parser.convert_params( params ), front_input.line_content, front_input.offset, front_input.line_source ) # yapf: disable effect_clues[parser_name] = clue return effect_clues
def test_constraints_when_one_unmatched(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Banana', 2), 'Banana, 2 times', 3000, line_source) clues_lists = [ ([ ], 1), ([ Clue(('Banana', 2), 'Banana, 2 times', 1050, line_source), Clue(('Milk', 1), 'Milk, 1 times', 1100, line_source) ], 1) ] # yapf: disable constraints = [{ 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} }, { 'clues_groups': [[0, 2], [2, 2]], 'name': 'identical', 'params': {} }] # testing 'or' causes = Verifier.constraints_or(clues_lists, effect, constraints, ConstraintManager()) assert len(causes) == 1 assert all(cause.constraints_linkage == InvestigationResult.OR for cause in causes) assert causes[0].lines == [ FrontInput.from_clue(Clue( ('Banana', 2), 'Banana, 2 times', 1050, line_source)) ] # yapf: disable assert causes[0].constraints == [{ 'clues_groups': [[0, 2], [2, 2]], 'name': 'identical', 'params': {} }] # testing 'and' causes = Verifier.constraints_and(clues_lists, effect, constraints, ConstraintManager()) assert not causes
def test_constraints_or_verification_failed(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Pineapple', 44), 'Pineapple, 44 times', 3000, line_source) clues_lists = [ ([ Clue(('Milk', 3), 'Milk, 3 times', 50, line_source), Clue(('Chocolate', 2), 'Chocolate, 2 times', 100, line_source), Clue(('Banana', 1), 'Banana, 1 times', 150, line_source) ], 1), ([ Clue(('Banana', 2), 'Banana, 2 times', 1050, line_source), Clue(('Milk', 1), 'Milk, 2 times', 1100, line_source) ], 1) ] # yapf: disable constraints = [{ 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} }, { 'clues_groups': [[0, 2], [1, 2], [2, 2]], 'name': 'identical', 'params': {} }] causes = Verifier.constraints_or(clues_lists, effect, constraints, ConstraintManager()) assert not causes
def test_empty_clues_going_to_verify(self): rule = Rule( [self.cause_a, self.cause_a], self.effect, [ { 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_AND ) # yapf: disable effect_clues_dict = { 'effect': Clue((42, ), '42 dinners', 1420, self.line_source) } clues = { # it's dictionary of the same type as clues dict collected in SearchManager 'cause_a': [], 'dummy': [ Clue((98,), '98 foo bar', 980, self.line_source), Clue((99,), '99 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert not results
def test_constraints_and_verification_failed_when_or_succeeded(self): line_source = LineSource('localhost', 'node_0.log') effect = Clue(('Banana', 44), 'Banana, 44 times', 3000, line_source) clues_lists = [ ([ Clue(('Milk', 3), 'Milk, 3 times', 50, line_source), Clue(('Chocolate', 4), 'Chocolate, 4 times', 100, line_source), Clue(('Pear', 2), 'Pear, 2 times', 150, line_source) # <- should be found (parser 1) ], 1), ([ Clue(('Pineapple', 2), 'Pineapple, 2 times', 1050, line_source), # <- should be found (parser 2) Clue(('Milk', 1), 'Milk, 1 times', 1100, line_source) ], 1) ] # yapf: disable constraints = [{ 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} }, { 'clues_groups': [[1, 2], [2, 2]], 'name': 'identical', 'params': {} }] # testing 'and' causes = Verifier.constraints_and(clues_lists, effect, constraints, ConstraintManager()) assert not causes # testing 'or' causes = Verifier.constraints_or(clues_lists, effect, constraints, ConstraintManager()) assert len(causes) == 1 assert all(cause.constraints_linkage == InvestigationResult.OR for cause in causes) assert causes[0].lines == [ FrontInput.from_clue(Clue( ('Pear', 2), 'Pear, 2 times', 150, line_source)), FrontInput.from_clue(Clue( ('Pineapple', 2), 'Pineapple, 2 times', 1050, line_source)) ] # yapf: disable assert causes[0].constraints == [{ 'clues_groups': [[1, 2], [2, 2]], 'name': 'identical', 'params': {} }]
def test_unmatched_clues_comparison(self): unmatched_clue = Clue(None, None, None, None) assert unmatched_clue == Verifier.UNMATCHED
def test_constraints_check_basic(self): rule = Rule( [self.cause_a, self.cause_b], self.effect, [ { 'clues_groups': [[0, 1], [1, 1], [2, 1]], 'name': 'identical', 'params': {} } ], Rule.LINKAGE_AND ) # yapf: disable effect_clues_dict = { 'effect': Clue((42, ), '42 dinners', 1420, self.line_source) } clues = { # it's dictionary of the same type as clues dict collected in SearchManager 'cause_a': [ Clue((40,), '40 carrots', 400, self.line_source), Clue((42,), '42 carrots', 420, self.line_source), Clue((44,), '44 carrots', 440, self.line_source) ], 'cause_b': [ Clue((32,), '32 broccoli', 100, self.line_source), Clue((42,), '42 broccoli', 120, self.line_source), Clue((52,), '52 broccoli', 140, self.line_source) ], 'dummy': [ Clue((42,), '42 foo bar', 980, self.line_source), Clue((84,), '84 foo bar', 990, self.line_source) ] } # yapf: disable results = rule.constraints_check(clues, effect_clues_dict) assert len(results) == 1 assert results[0].lines == [ FrontInput.from_clue( Clue((42,), '42 carrots', 420, self.line_source)), FrontInput.from_clue( Clue((42,), '42 broccoli', 120, self.line_source)) ] # yapf: disable
class Verifier(object): UNMATCHED = Clue(None, None, None, None) @classmethod def _create_investigation_result(cls, clues_combination, constraints, linkage): """ basing on clues combination and constraints, returns appropriate InvestigationResult object which collects information about lines (FrontInput objects) instead of Clues """ return InvestigationResult( [FrontInput.from_clue(clue) for clue in clues_combination], constraints, linkage) @classmethod def _verify_constraint(cls, combination, effect, constraint, constraint_manager): """ checks if specified clues (which represents parsers: 1,2,.. for some rule) and effect (which represents parser 0 from this rule) satisfy one given constraint. returns True if so, or False otherwise """ constraint_verifier = constraint_manager[constraint] groups = [] for group_info in constraint['clues_groups']: parser_num, group_num = group_info if parser_num == 0: groups.append(effect.regex_parameters[group_num - 1]) else: if combination[parser_num - 1] == Verifier.UNMATCHED: return False groups.append(combination[parser_num - 1].regex_parameters[group_num - 1]) return constraint_verifier.verify(groups, constraint['params']) @classmethod def _clues_combinations(cls, clues_tuples, collected_subset=[]): """ recursive generator that returns all permutations according to schema: from first pair (list, number) of clues_tuples, produces permutations with size 'number' from 'list's elements and concatenates it with _clues_combinations invoked on the rest of clues_tuples. example: >>> xs = [([1, 2, 3], 2), ([4, 5], 1)] >>> for l in Verifier._clues_combinations(xs): >>> print l [1, 2, 4] [1, 2, 5] [1, 3, 4] [1, 3, 5] [2, 1, 4] [2, 1, 5] [2, 3, 4] [2, 3, 5] [3, 1, 4] [3, 1, 5] [3, 2, 4] [3, 2, 5] it always should be called with empty accumulator, that is collected_subset=[] """ if len(clues_tuples) != 0: first_list, repetitions_number = clues_tuples[0] for clues in itertools.permutations(first_list, repetitions_number): for subset in cls._clues_combinations( clues_tuples[1:], collected_subset + list(clues)): yield subset else: yield collected_subset @classmethod def _construct_proper_clues_lists(cls, original_clues_lists): clues_lists = [] for clues, occurrences in original_clues_lists: if clues: clues_lists.append((clues, occurrences)) else: clues_lists.append(([Verifier.UNMATCHED], occurrences)) return clues_lists @classmethod def _pack_results_for_constraint_or(cls, combination, constraints): return cls._create_investigation_result( (clue for clue in combination if not clue == Verifier.UNMATCHED), constraints, InvestigationResult.OR) @classmethod def constraints_and(cls, clues_lists, effect, constraints, constraint_manager): """ for each combination of clues (they are generated by _clues_combinations) checks if for all given constraints their requirements are satisfied and for each such combination produces InvestigationResult object. returns list of all produced InvestigationResults """ clues_lists = cls._construct_proper_clues_lists(clues_lists) causes = [] for combination in cls._clues_combinations(clues_lists): if all( cls._verify_constraint(combination, effect, constraint, constraint_manager) for constraint in constraints): causes.append( cls._create_investigation_result(combination, constraints, InvestigationResult.AND)) return causes @classmethod def constraints_or(cls, clues_lists, effect, constraints, constraint_manager): """ for each combination of clues (they are generated by _clues_combinations) checks if for any of given constraints their requirements are satisfied and for each such combination produces InvestigationResult object. returns list of all produced InvestigationResults """ if not constraints: # when there is lack of constraints, but there are existing clues combinations, # each of them should be returned return [ cls._pack_results_for_constraint_or(combination, constraints) for combination in cls._clues_combinations(clues_lists) ] causes = [] clues_lists = cls._construct_proper_clues_lists(clues_lists) for combination in cls._clues_combinations(clues_lists): verified_constraints = [ constraint for constraint in constraints if cls._verify_constraint(combination, effect, constraint, constraint_manager) ] # yapf: disable if verified_constraints: causes.append( cls._pack_results_for_constraint_or( combination, verified_constraints)) return causes @classmethod def constraints_not(cls, clues_lists, effect, constraints, constraint_manager): """ provide investigation if there is zero or one constraint, because only in such cases NOT linkage has sense """ if len(constraints) > 1: raise TooManyConstraintsToNegate() if constraints: if clues_lists: return cls.single_constraint_not(clues_lists, effect, constraints[0], constraint_manager) else: if clues_lists: # if all parsers found their matched logs, the NOT requirement isn't satisfied return [] return [ cls._create_investigation_result([], [], InvestigationResult.NOT) ] @classmethod def single_constraint_not(cls, clues_lists, effect, constraint, constraint_manager): """ for each combination of clues (they are generated by _clues_combinations) checks for given constraint if its requirements are not satisfied and if they are not, it produces InvestigationResult object. returns list of all produced InvestigationResults """ clues_lists = cls._construct_proper_clues_lists(clues_lists) for combination in cls._clues_combinations(clues_lists): if cls._verify_constraint(combination, effect, constraint, constraint_manager): return [] return [ cls._create_investigation_result([], [constraint], InvestigationResult.NOT) ]