test_rule_parser.py 9.3 KB

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