limit.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import logging
  20. import re
  21. import core.log
  22. import core.db.loader
  23. import core.db.post
  24. import core.db.utils
  25. import core.cout
  26. import core.warn
  27. import core.cmdparser
  28. import core.api
  29. class Tool(core.api.ITool):
  30. def run(self, tool_args):
  31. return main(tool_args)
  32. def main(tool_args):
  33. exit_code = 0
  34. log_plugin = core.log.Plugin()
  35. db_plugin = core.db.post.Plugin()
  36. warn_plugin = core.warn.Plugin()
  37. parser = core.cmdparser.MultiOptionParser(usage="Usage: %prog limit [options] -- [path 1] ... [path N]")
  38. log_plugin.declare_configuration(parser)
  39. db_plugin.declare_configuration(parser)
  40. warn_plugin.declare_configuration(parser)
  41. parser.add_option("--hotspots", "--hs", default=None, help="If not set (none), all exceeded limits are printed."
  42. " If set, exceeded limits are sorted (the worst is the first) and only first HOTSPOTS limits are printed."
  43. " [default: %default]", type=int)
  44. parser.add_option("--disable-suppressions", "--ds", action="store_true", default=False,
  45. help = "If not set (none), all suppressions are ignored"
  46. " and associated warnings are printed. [default: %default]")
  47. (options, args) = parser.parse_args(tool_args)
  48. log_plugin.configure(options)
  49. db_plugin.configure(options)
  50. warn_plugin.configure(options)
  51. hotspots = options.__dict__['hotspots']
  52. no_suppress = options.__dict__['disable_suppressions']
  53. loader_prev = core.db.loader.Loader()
  54. if db_plugin.dbfile_prev != None:
  55. loader_prev.open_database(db_plugin.dbfile_prev)
  56. loader = core.db.loader.Loader()
  57. loader.open_database(db_plugin.dbfile)
  58. warn_plugin.verify_namespaces(loader.iterate_namespace_names())
  59. for each in loader.iterate_namespace_names():
  60. warn_plugin.verify_fields(each, loader.get_namespace(each).iterate_field_names())
  61. # Check for versions consistency
  62. for each in loader.iterate_properties():
  63. if db_plugin.dbfile_prev != None:
  64. prev = loader_prev.get_property(each.name)
  65. if prev != each.value:
  66. logging.warn("Previous data has got different metadata:")
  67. logging.warn(" - identification of change trends can be not reliable")
  68. logging.warn(" - use 'info' tool to get more details")
  69. break
  70. paths = None
  71. if len(args) == 0:
  72. paths = [""]
  73. else:
  74. paths = args
  75. # Try to optimise iterative change scans
  76. modified_file_ids = None
  77. if warn_plugin.mode != warn_plugin.MODE_ALL:
  78. modified_file_ids = get_list_of_modified_files(loader, loader_prev)
  79. for path in paths:
  80. logging.info("Processing: " + re.sub(r'''[\\]''', "/", path))
  81. for limit in warn_plugin.iterate_limits():
  82. logging.info("Applying limit: " + str(limit))
  83. filters = [limit.filter]
  84. if modified_file_ids != None:
  85. filters.append(('file_id', 'IN', modified_file_ids))
  86. sort_by = None
  87. limit_by = None
  88. if hotspots != None:
  89. sort_by = limit.field
  90. if limit.type == "max":
  91. sort_by = "-" + sort_by
  92. limit_by = hotspots
  93. selected_data = loader.load_selected_data(limit.namespace,
  94. fields = [limit.field],
  95. path=path,
  96. filters = filters,
  97. sort_by=sort_by,
  98. limit_by=limit_by)
  99. if selected_data == None:
  100. logging.error("Specified path '" + path + "' is invalid (not found in the database records)")
  101. exit_code += 1
  102. continue
  103. for select_data in selected_data:
  104. is_modified = None
  105. diff = None
  106. file_data = loader.load_file_data(select_data.get_path())
  107. file_data_prev = loader_prev.load_file_data(select_data.get_path())
  108. if file_data_prev != None:
  109. if file_data.get_checksum() == file_data_prev.get_checksum():
  110. diff = 0
  111. is_modified = False
  112. else:
  113. matcher = core.db.utils.FileRegionsMatcher(file_data, file_data_prev)
  114. prev_id = matcher.get_prev_id(select_data.get_region().get_id())
  115. if matcher.is_matched(select_data.get_region().get_id()):
  116. if matcher.is_modified(select_data.get_region().get_id()):
  117. is_modified = True
  118. else:
  119. is_modified = False
  120. diff = core.db.loader.DiffData(select_data,
  121. file_data_prev.get_region(prev_id)).get_data(limit.namespace, limit.field)
  122. if (warn_plugin.is_mode_matched(limit.limit,
  123. select_data.get_data(limit.namespace, limit.field),
  124. diff,
  125. is_modified) == False):
  126. continue
  127. is_sup = is_metric_suppressed(limit.namespace, limit.field, loader, select_data)
  128. if is_sup == True and no_suppress == False:
  129. continue
  130. exit_code += 1
  131. region_cursor = 0
  132. region_name = ""
  133. if select_data.get_region() != None:
  134. region_cursor = select_data.get_region().cursor
  135. region_name = select_data.get_region().name
  136. report_limit_exceeded(select_data.get_path(),
  137. region_cursor,
  138. limit.namespace,
  139. limit.field,
  140. region_name,
  141. select_data.get_data(limit.namespace, limit.field),
  142. diff,
  143. limit.limit,
  144. is_modified,
  145. is_sup)
  146. return exit_code
  147. def get_list_of_modified_files(loader, loader_prev):
  148. logging.info("Identifying changed files...")
  149. old_files_map = {}
  150. for each in loader_prev.iterate_file_data():
  151. old_files_map[each.get_path()] = each.get_checksum()
  152. if len(old_files_map) == 0:
  153. return None
  154. modified_file_ids = []
  155. for each in loader.iterate_file_data():
  156. if len(modified_file_ids) > 1000: # If more than 1000 files changed, skip optimisation
  157. return None
  158. if (each.get_path() not in old_files_map.keys()) or old_files_map[each.get_path()] != each.get_checksum():
  159. modified_file_ids.append(str(each.get_id()))
  160. old_files_map = None
  161. if len(modified_file_ids) != 0:
  162. modified_file_ids = " , ".join(modified_file_ids)
  163. modified_file_ids = "(" + modified_file_ids + ")"
  164. return modified_file_ids
  165. return None
  166. def is_metric_suppressed(metric_namespace, metric_field, loader, select_data):
  167. data = loader.load_file_data(select_data.get_path())
  168. if select_data.get_region() != None:
  169. data = data.get_region(select_data.get_region().get_id())
  170. sup_data = data.get_data('std.suppress', 'list')
  171. if sup_data != None and sup_data.find('[' + metric_namespace + ':' + metric_field + ']') != -1:
  172. return True
  173. return False
  174. def report_limit_exceeded(path, cursor, namespace, field, region_name,
  175. stat_level, trend_value, stat_limit,
  176. is_modified, is_suppressed):
  177. message = "Metric '" + namespace + ":" + field + "' for region '" + region_name + "' exceeds the limit."
  178. details = [("Metric name", namespace + ":" + field),
  179. ("Region name", region_name),
  180. ("Metric value", stat_level),
  181. ("Modified", is_modified),
  182. ("Change trend", '{0:{1}}'.format(trend_value, '+' if trend_value else '')),
  183. ("Limit", stat_limit),
  184. ("Suppressed", is_suppressed)]
  185. core.cout.notify(path, cursor, core.cout.SEVERITY_WARNING, message, details)