test_rule_parser.py 9.1 KB

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