]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | # This source code is licensed under both the GPLv2 (found in the | |
3 | # COPYING file in the root directory) and Apache 2.0 License | |
4 | # (found in the LICENSE.Apache file in the root directory). | |
5 | ||
6 | import os | |
7 | import unittest | |
1e59de90 | 8 | |
11fdf7f2 TL |
9 | from advisor.db_log_parser import DatabaseLogs, DataSource |
10 | from advisor.db_options_parser import DatabaseOptions | |
1e59de90 | 11 | from advisor.rule_parser import RulesSpec |
11fdf7f2 TL |
12 | |
13 | RuleToSuggestions = { | |
1e59de90 | 14 | "stall-too-many-memtables": ["inc-bg-flush", "inc-write-buffer"], |
11fdf7f2 | 15 | "stall-too-many-L0": [ |
1e59de90 TL |
16 | "inc-max-subcompactions", |
17 | "inc-max-bg-compactions", | |
18 | "inc-write-buffer-size", | |
19 | "dec-max-bytes-for-level-base", | |
20 | "inc-l0-slowdown-writes-trigger", | |
11fdf7f2 TL |
21 | ], |
22 | "stop-too-many-L0": [ | |
1e59de90 TL |
23 | "inc-max-bg-compactions", |
24 | "inc-write-buffer-size", | |
25 | "inc-l0-stop-writes-trigger", | |
11fdf7f2 TL |
26 | ], |
27 | "stall-too-many-compaction-bytes": [ | |
1e59de90 TL |
28 | "inc-max-bg-compactions", |
29 | "inc-write-buffer-size", | |
30 | "inc-hard-pending-compaction-bytes-limit", | |
31 | "inc-soft-pending-compaction-bytes-limit", | |
11fdf7f2 | 32 | ], |
1e59de90 | 33 | "level0-level1-ratio": ["l0-l1-ratio-health-check"], |
11fdf7f2 TL |
34 | } |
35 | ||
36 | ||
37 | class TestAllRulesTriggered(unittest.TestCase): | |
38 | def setUp(self): | |
39 | # load the Rules | |
40 | this_path = os.path.abspath(os.path.dirname(__file__)) | |
1e59de90 | 41 | ini_path = os.path.join(this_path, "input_files/triggered_rules.ini") |
11fdf7f2 TL |
42 | self.db_rules = RulesSpec(ini_path) |
43 | self.db_rules.load_rules_from_spec() | |
44 | self.db_rules.perform_section_checks() | |
45 | # load the data sources: LOG and OPTIONS | |
1e59de90 TL |
46 | log_path = os.path.join(this_path, "input_files/LOG-0") |
47 | options_path = os.path.join(this_path, "input_files/OPTIONS-000005") | |
11fdf7f2 TL |
48 | db_options_parser = DatabaseOptions(options_path) |
49 | self.column_families = db_options_parser.get_column_families() | |
50 | db_logs_parser = DatabaseLogs(log_path, self.column_families) | |
51 | self.data_sources = { | |
52 | DataSource.Type.DB_OPTIONS: [db_options_parser], | |
1e59de90 | 53 | DataSource.Type.LOG: [db_logs_parser], |
11fdf7f2 TL |
54 | } |
55 | ||
56 | def test_triggered_conditions(self): | |
57 | conditions_dict = self.db_rules.get_conditions_dict() | |
58 | rules_dict = self.db_rules.get_rules_dict() | |
59 | # Make sure none of the conditions is triggered beforehand | |
60 | for cond in conditions_dict.values(): | |
61 | self.assertFalse(cond.is_triggered(), repr(cond)) | |
62 | for rule in rules_dict.values(): | |
63 | self.assertFalse( | |
1e59de90 | 64 | rule.is_triggered(conditions_dict, self.column_families), repr(rule) |
11fdf7f2 TL |
65 | ) |
66 | ||
67 | # # Trigger the conditions as per the data sources. | |
68 | # trigger_conditions(, conditions_dict) | |
69 | ||
70 | # Get the set of rules that have been triggered | |
71 | triggered_rules = self.db_rules.get_triggered_rules( | |
72 | self.data_sources, self.column_families | |
73 | ) | |
74 | ||
75 | # Make sure each condition and rule is triggered | |
76 | for cond in conditions_dict.values(): | |
77 | if cond.get_data_source() is DataSource.Type.TIME_SERIES: | |
78 | continue | |
79 | self.assertTrue(cond.is_triggered(), repr(cond)) | |
80 | ||
81 | for rule in rules_dict.values(): | |
82 | self.assertIn(rule, triggered_rules) | |
83 | # Check the suggestions made by the triggered rules | |
84 | for sugg in rule.get_suggestions(): | |
85 | self.assertIn(sugg, RuleToSuggestions[rule.name]) | |
86 | ||
87 | for rule in triggered_rules: | |
88 | self.assertIn(rule, rules_dict.values()) | |
89 | for sugg in RuleToSuggestions[rule.name]: | |
90 | self.assertIn(sugg, rule.get_suggestions()) | |
91 | ||
92 | ||
93 | class TestConditionsConjunctions(unittest.TestCase): | |
94 | def setUp(self): | |
95 | # load the Rules | |
96 | this_path = os.path.abspath(os.path.dirname(__file__)) | |
1e59de90 | 97 | ini_path = os.path.join(this_path, "input_files/test_rules.ini") |
11fdf7f2 TL |
98 | self.db_rules = RulesSpec(ini_path) |
99 | self.db_rules.load_rules_from_spec() | |
100 | self.db_rules.perform_section_checks() | |
101 | # load the data sources: LOG and OPTIONS | |
1e59de90 TL |
102 | log_path = os.path.join(this_path, "input_files/LOG-1") |
103 | options_path = os.path.join(this_path, "input_files/OPTIONS-000005") | |
11fdf7f2 TL |
104 | db_options_parser = DatabaseOptions(options_path) |
105 | self.column_families = db_options_parser.get_column_families() | |
106 | db_logs_parser = DatabaseLogs(log_path, self.column_families) | |
107 | self.data_sources = { | |
108 | DataSource.Type.DB_OPTIONS: [db_options_parser], | |
1e59de90 | 109 | DataSource.Type.LOG: [db_logs_parser], |
11fdf7f2 TL |
110 | } |
111 | ||
112 | def test_condition_conjunctions(self): | |
113 | conditions_dict = self.db_rules.get_conditions_dict() | |
114 | rules_dict = self.db_rules.get_rules_dict() | |
115 | # Make sure none of the conditions is triggered beforehand | |
116 | for cond in conditions_dict.values(): | |
117 | self.assertFalse(cond.is_triggered(), repr(cond)) | |
118 | for rule in rules_dict.values(): | |
119 | self.assertFalse( | |
1e59de90 | 120 | rule.is_triggered(conditions_dict, self.column_families), repr(rule) |
11fdf7f2 TL |
121 | ) |
122 | ||
123 | # Trigger the conditions as per the data sources. | |
124 | self.db_rules.trigger_conditions(self.data_sources) | |
125 | ||
126 | # Check for the conditions | |
1e59de90 TL |
127 | conds_triggered = ["log-1-true", "log-2-true", "log-3-true"] |
128 | conds_not_triggered = ["log-4-false", "options-1-false"] | |
11fdf7f2 TL |
129 | for cond in conds_triggered: |
130 | self.assertTrue(conditions_dict[cond].is_triggered(), repr(cond)) | |
131 | for cond in conds_not_triggered: | |
132 | self.assertFalse(conditions_dict[cond].is_triggered(), repr(cond)) | |
133 | ||
134 | # Check for the rules | |
1e59de90 | 135 | rules_triggered = ["multiple-conds-true"] |
11fdf7f2 | 136 | rules_not_triggered = [ |
1e59de90 TL |
137 | "single-condition-false", |
138 | "multiple-conds-one-false", | |
139 | "multiple-conds-all-false", | |
11fdf7f2 TL |
140 | ] |
141 | for rule_name in rules_triggered: | |
142 | rule = rules_dict[rule_name] | |
143 | self.assertTrue( | |
1e59de90 | 144 | rule.is_triggered(conditions_dict, self.column_families), repr(rule) |
11fdf7f2 TL |
145 | ) |
146 | for rule_name in rules_not_triggered: | |
147 | rule = rules_dict[rule_name] | |
148 | self.assertFalse( | |
1e59de90 | 149 | rule.is_triggered(conditions_dict, self.column_families), repr(rule) |
11fdf7f2 TL |
150 | ) |
151 | ||
152 | ||
153 | class TestSanityChecker(unittest.TestCase): | |
154 | def setUp(self): | |
155 | this_path = os.path.abspath(os.path.dirname(__file__)) | |
1e59de90 | 156 | ini_path = os.path.join(this_path, "input_files/rules_err1.ini") |
11fdf7f2 TL |
157 | db_rules = RulesSpec(ini_path) |
158 | db_rules.load_rules_from_spec() | |
159 | self.rules_dict = db_rules.get_rules_dict() | |
160 | self.conditions_dict = db_rules.get_conditions_dict() | |
161 | self.suggestions_dict = db_rules.get_suggestions_dict() | |
162 | ||
163 | def test_rule_missing_suggestions(self): | |
1e59de90 | 164 | regex = ".*rule must have at least one suggestion.*" |
11fdf7f2 | 165 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 166 | self.rules_dict["missing-suggestions"].perform_checks() |
11fdf7f2 TL |
167 | |
168 | def test_rule_missing_conditions(self): | |
1e59de90 | 169 | regex = ".*rule must have at least one condition.*" |
11fdf7f2 | 170 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 171 | self.rules_dict["missing-conditions"].perform_checks() |
11fdf7f2 TL |
172 | |
173 | def test_condition_missing_regex(self): | |
1e59de90 | 174 | regex = ".*provide regex for log condition.*" |
11fdf7f2 | 175 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 176 | self.conditions_dict["missing-regex"].perform_checks() |
11fdf7f2 TL |
177 | |
178 | def test_condition_missing_options(self): | |
1e59de90 | 179 | regex = ".*options missing in condition.*" |
11fdf7f2 | 180 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 181 | self.conditions_dict["missing-options"].perform_checks() |
11fdf7f2 TL |
182 | |
183 | def test_condition_missing_expression(self): | |
1e59de90 | 184 | regex = ".*expression missing in condition.*" |
11fdf7f2 | 185 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 186 | self.conditions_dict["missing-expression"].perform_checks() |
11fdf7f2 TL |
187 | |
188 | def test_suggestion_missing_option(self): | |
1e59de90 | 189 | regex = ".*provide option or description.*" |
11fdf7f2 | 190 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 191 | self.suggestions_dict["missing-option"].perform_checks() |
11fdf7f2 TL |
192 | |
193 | def test_suggestion_missing_description(self): | |
1e59de90 | 194 | regex = ".*provide option or description.*" |
11fdf7f2 | 195 | with self.assertRaisesRegex(ValueError, regex): |
1e59de90 | 196 | self.suggestions_dict["missing-description"].perform_checks() |
11fdf7f2 TL |
197 | |
198 | ||
199 | class TestParsingErrors(unittest.TestCase): | |
200 | def setUp(self): | |
201 | self.this_path = os.path.abspath(os.path.dirname(__file__)) | |
202 | ||
203 | def test_condition_missing_source(self): | |
1e59de90 | 204 | ini_path = os.path.join(self.this_path, "input_files/rules_err2.ini") |
11fdf7f2 | 205 | db_rules = RulesSpec(ini_path) |
1e59de90 | 206 | regex = ".*provide source for condition.*" |
11fdf7f2 TL |
207 | with self.assertRaisesRegex(NotImplementedError, regex): |
208 | db_rules.load_rules_from_spec() | |
209 | ||
210 | def test_suggestion_missing_action(self): | |
1e59de90 | 211 | ini_path = os.path.join(self.this_path, "input_files/rules_err3.ini") |
11fdf7f2 | 212 | db_rules = RulesSpec(ini_path) |
1e59de90 | 213 | regex = ".*provide action for option.*" |
11fdf7f2 TL |
214 | with self.assertRaisesRegex(ValueError, regex): |
215 | db_rules.load_rules_from_spec() | |
216 | ||
217 | def test_section_no_name(self): | |
1e59de90 | 218 | ini_path = os.path.join(self.this_path, "input_files/rules_err4.ini") |
11fdf7f2 | 219 | db_rules = RulesSpec(ini_path) |
1e59de90 | 220 | regex = "Parsing error: needed section header:.*" |
11fdf7f2 TL |
221 | with self.assertRaisesRegex(ValueError, regex): |
222 | db_rules.load_rules_from_spec() | |
223 | ||
224 | ||
1e59de90 | 225 | if __name__ == "__main__": |
11fdf7f2 | 226 | unittest.main() |