Browse Source

implemented feature #34: introduced view output format prometheus

Gregory Laplace 4 years ago
parent
commit
aed660117c
2 changed files with 162 additions and 2 deletions
  1. 136 2
      metrixpp/ext/std/tools/view.py
  2. 26 0
      metrixpp/mpp/promout.py

+ 136 - 2
metrixpp/ext/std/tools/view.py

@@ -11,6 +11,7 @@ import sys
 from metrixpp.mpp import api
 from metrixpp.mpp import api
 from metrixpp.mpp import utils
 from metrixpp.mpp import utils
 from metrixpp.mpp import cout
 from metrixpp.mpp import cout
+from metrixpp.mpp import promout
 
 
 DIGIT_COUNT = 8
 DIGIT_COUNT = 8
 
 
@@ -22,9 +23,9 @@ class Plugin(api.Plugin, api.IConfigurable, api.IRunable):
 
 
     def declare_configuration(self, parser):
     def declare_configuration(self, parser):
         self.parser = parser
         self.parser = parser
-        parser.add_option("--format", "--ft", default='txt', choices=['txt', 'xml', 'python'],
+        parser.add_option("--format", "--ft", default='txt', choices=['txt', 'xml', 'python', 'prometheus'],
                           help="Format of the output data. "
                           help="Format of the output data. "
-                          "Possible values are 'xml', 'txt' or 'python' [default: %default]")
+                          "Possible values are 'xml', 'txt', 'python' or 'prometheus' [default: %default]")
         parser.add_option("--nest-regions", "--nr", action="store_true", default=False,
         parser.add_option("--nest-regions", "--nr", action="store_true", default=False,
                           help="If the option is set (True), data for regions is exported in the form of a tree. "
                           help="If the option is set (True), data for regions is exported in the form of a tree. "
                           "Otherwise, all regions are exported in plain list. [default: %default]")
                           "Otherwise, all regions are exported in plain list. [default: %default]")
@@ -122,6 +123,8 @@ def export_to_str(out_format, paths, loader, loader_prev, nest_regions, dist_col
 
 
         if out_format == 'txt':
         if out_format == 'txt':
             cout_txt(data, loader)
             cout_txt(data, loader)
+        elif out_format == 'prometheus':
+            cout_prom(data, loader)
         elif out_format == 'xml':
         elif out_format == 'xml':
             result += utils.serialize_to_xml(data, root_name = "data", digitCount = DIGIT_COUNT) + "\n"
             result += utils.serialize_to_xml(data, root_name = "data", digitCount = DIGIT_COUNT) + "\n"
         elif out_format == 'python':
         elif out_format == 'python':
@@ -657,3 +660,134 @@ def cout_txt(data, loader):
                 "Directory content:",
                 "Directory content:",
                 details)
                 details)
     
     
+def cout_prom_regions(path, regions, indent = 0):
+    for region in regions:
+#         details = [
+#             ('Region name', region['info']['name']),
+#             ('Region type', region['info']['type']),
+#             ('Offsets', str(region['info']['offset_begin']) + "-" + str(region['info']['offset_end'])),
+#             ('Line numbers', str(region['info']['line_begin']) + "-" + str(region['info']['line_end'])),
+#             ('Modified', str(region['info']['modified']))
+#         ]
+        details = []
+        for namespace in sorted(list(region['data'].keys())):
+            diff_data = {}
+            if '__diff__' in list(region['data'][namespace].keys()):
+                diff_data = region['data'][namespace]['__diff__']
+            for field in sorted(list(region['data'][namespace].keys())):
+                diff_str = ""
+                if field == '__diff__':
+                    continue
+                if field in list(diff_data.keys()):
+                    diff_str = " [" + ("+" if diff_data[field] >= 0 else "") + str(diff_data[field]) + "]"
+                details.append((namespace + ":" + field, str(region['data'][namespace][field]) + diff_str))
+        promout.notify(path = path,
+                        region = region['info']['name'],
+                        metric = "",
+                        details = details)
+        if 'subregions' in list(region.keys()):
+            cout_txt_regions(path, region['subregions'], indent=indent+1)
+            
+def cout_prom(data, loader):
+    
+    details = []
+    for key in list(data['file-data'].keys()):
+        if key == 'regions':
+            cout_prom_regions(data['info']['path'], data['file-data'][key])
+        else:
+            namespace = key
+            diff_data = {}
+            if '__diff__' in list(data['file-data'][namespace].keys()):
+                diff_data = data['file-data'][namespace]['__diff__']
+            for field in sorted(list(data['file-data'][namespace].keys())):
+                diff_str = ""
+                if field == '__diff__':
+                    continue
+                if field in list(diff_data.keys()):
+                    diff_str = " [" + ("+" if diff_data[field] >= 0 else "") + str(diff_data[field]) + "]"
+                details.append((namespace + ":" + field, str(data['file-data'][namespace][field]) + diff_str))
+    if len(details) > 0:
+        promout.notify(data['info']['path'],
+                    0,
+                    promout.SEVERITY_INFO,
+                    "Metrics per file",
+                    details)
+
+    attr_map = {'total': 'total',
+                'avg': 'avg',
+                'min': 'min',
+                'max': 'max',
+    }
+    for namespace in sorted(list(data['aggregated-data'].keys())):
+        for field in sorted(list(data['aggregated-data'][namespace].keys())):
+            details = []
+            diff_data = {}
+            if '__diff__' in list(data['aggregated-data'][namespace][field].keys()):
+                diff_data = data['aggregated-data'][namespace][field]['__diff__']
+            for attr in ['avg', 'min', 'max', 'total']:
+                diff_str = ""
+                if attr in list(diff_data.keys()):
+                    if isinstance(diff_data[attr], float):
+                        diff_str = " [" + ("+" if diff_data[attr] >= 0 else "") + str(round(diff_data[attr], DIGIT_COUNT)) + "]"
+                    else:
+                        diff_str = " [" + ("+" if diff_data[attr] >= 0 else "") + str(diff_data[attr]) + "]"
+                if attr == 'avg' and data['aggregated-data'][namespace][field]['nonzero'] == True:
+                    diff_str += " (excluding zero metric values)"
+                if isinstance(data['aggregated-data'][namespace][field][attr], float):
+                    # round the data to reach same results on platforms with different precision
+                    details.append((attr_map[attr], str(round(data['aggregated-data'][namespace][field][attr], DIGIT_COUNT)) + diff_str))
+                else:
+                    details.append((attr_map[attr], str(data['aggregated-data'][namespace][field][attr]) + diff_str))
+
+            measured = data['aggregated-data'][namespace][field]['count']
+            if 'count' in list(diff_data.keys()):
+                diff_str = ' [{0:{1}}]'.format(diff_data['count'], '+' if diff_data['count'] >= 0 else '')
+            sup_diff_str = ""
+            if 'sup' in list(diff_data.keys()):
+                sup_diff_str = ' [{0:{1}}]'.format(diff_data['sup'], '+' if diff_data['sup'] >= 0 else '')
+            elem_name = 'regions'
+            if loader.get_namespace(namespace).are_regions_supported() == False:
+                elem_name = 'files'
+#             details.append(('Distribution',
+#                             '{0}{1} {2} in total (including {3}{4} suppressed)'.format(measured,
+#                                                                                    diff_str,
+#                                                                                    elem_name,
+#                                                                                    data['aggregated-data'][namespace][field]['sup'],
+#                                                                                    sup_diff_str)))
+#             details.append(('  Metric value', 'Ratio : R-sum : Number of ' + elem_name))
+#             count_str_len  = len(str(measured))
+#             sum_ratio = 0
+#             for bar in data['aggregated-data'][namespace][field]['distribution-bars']:
+#                 sum_ratio += bar['ratio']
+#                 diff_str = ""
+#                 if '__diff__' in list(bar.keys()):
+#                     if bar['__diff__'] >= 0:
+#                         diff_str = ' [+{0:<{1}}]'.format(bar['__diff__'], count_str_len)
+#                     else:
+#                         diff_str = ' [{0:<{1}}]'.format(bar['__diff__'], count_str_len+1)
+#                 if isinstance(bar['metric'], float):
+#                     metric_str = "{0:.4f}".format(bar['metric'])
+#                 else:
+#                     metric_str = str(bar['metric'])
+#                 
+#                 metric_str = (" " * (promout.DETAILS_OFFSET - len(metric_str) - 1)) + metric_str
+#                 count_str = str(bar['count'])
+#                 count_str = ((" " * (count_str_len - len(count_str))) + count_str + diff_str + "\t")
+#                 details.append((metric_str,
+#                                 "{0:.3f}".format(bar['ratio']) + " : " + "{0:.3f}".format(sum_ratio) +  " : " +
+#                                 count_str + ('|' * int(bar['ratio']*100))))
+            promout.notify(path = data['info']['path'],
+                    metric = namespace + "." + field,
+                    details = details)
+#     details = []
+#     for each in sorted(data['subdirs']):
+#         details.append(('Directory', each))
+#     for each in sorted(data['subfiles']):
+#         details.append(('File', each))
+#     if len(details) > 0: 
+#         promout.notify(data['info']['path'],
+#                 '', # no line number
+#                 promout.SEVERITY_INFO,
+#                 "Directory content:",
+#                 details)
+    

+ 26 - 0
metrixpp/mpp/promout.py

@@ -0,0 +1,26 @@
+#
+#    Metrix++, Copyright 2009-2019, Metrix++ Project
+#    Link: https://github.com/metrixplusplus/metrixplusplus
+#    
+#    This file is a part of Metrix++ Tool.
+#    
+
+import re
+
+SEVERITY_INFO = 0x01
+SEVERITY_WARNING = 0x02
+SEVERITY_ERROR = 0x03
+DETAILS_OFFSET = 15
+
+
+def notify(path, metric, details, region=""):
+    notification = ""
+
+    for each in details:
+        if str(each[1]) != 'None':
+            if region:
+                notification += ("{metric} {{file=\"{path}\", region=\"{region}\"}} {value}\n".format(metric=re.sub(r'^_', '', re.sub(r'[\.\:]', '_', metric + "." + str(each[0]))), value=str(each[1]), path=path, region=region))
+            else:
+                notification += ("{metric} {{file=\"{path}\"}} {value}\n".format(metric=re.sub(r'^_', '', re.sub(r'[\.\:]', '_', metric + "." + str(each[0]))), value=str(each[1]), path=path, region=region))
+        
+    print(notification)