X-Git-Url: http://nitlanguage.org diff --git a/src/metrics/metrics_base.nit b/src/metrics/metrics_base.nit index c1fcb51..959041b 100644 --- a/src/metrics/metrics_base.nit +++ b/src/metrics/metrics_base.nit @@ -18,7 +18,7 @@ # Helpers for various statistics tools. module metrics_base -import model_utils +import modelbuilder import csv import counter import console @@ -32,7 +32,8 @@ redef class ToolContext var opt_mmodules = new OptionBool("Compute metrics about mmodules", "--mmodules") # --mclassses var opt_mclasses = new OptionBool("Compute metrics about mclasses", "--mclasses") - + # --mendel + var opt_mendel = new OptionBool("Compute mendel metrics", "--mendel") # --inheritance var opt_inheritance = new OptionBool("Compute metrics about inheritance usage", "--inheritance") # --genericity @@ -49,18 +50,20 @@ redef class ToolContext var opt_tables = new OptionBool("Compute tables metrics", "--tables") # --rta var opt_rta = new OptionBool("Compute RTA metrics", "--rta") + # --readme + var opt_readme = new OptionBool("Compute ReadMe metrics", "--readme") # --generate-csv - var opt_generate_csv = new OptionBool("Generate CVS format metrics", "--generate-csv") + var opt_csv = new OptionBool("Also export metrics in CSV format", "--csv") # --generate_hyperdoc var opt_generate_hyperdoc = new OptionBool("Generate Hyperdoc", "--generate_hyperdoc") # --poset var opt_poset = new OptionBool("Complete metrics on posets", "--poset") - # --no-colors var opt_nocolors = new OptionBool("Disable colors in console outputs", "--no-colors") - - + # --dir var opt_dir = new OptionString("Directory where some statistics files are generated", "-d", "--dir") + + # Output directory for metrics files. var output_dir: String = "." redef init @@ -69,6 +72,7 @@ redef class ToolContext self.option_context.add_option(opt_all) self.option_context.add_option(opt_mmodules) self.option_context.add_option(opt_mclasses) + self.option_context.add_option(opt_mendel) self.option_context.add_option(opt_inheritance) self.option_context.add_option(opt_refinement) self.option_context.add_option(opt_self) @@ -77,14 +81,15 @@ redef class ToolContext self.option_context.add_option(opt_static_types) self.option_context.add_option(opt_tables) self.option_context.add_option(opt_rta) - self.option_context.add_option(opt_generate_csv) + self.option_context.add_option(opt_readme) + self.option_context.add_option(opt_csv) self.option_context.add_option(opt_generate_hyperdoc) self.option_context.add_option(opt_poset) self.option_context.add_option(opt_dir) self.option_context.add_option(opt_nocolors) end - redef fun process_options + redef fun process_options(args) do super var val = self.opt_dir.value @@ -95,27 +100,41 @@ redef class ToolContext end end - # colorize heading 1 for console output + # Format and colorize a string heading of level 1 for console output. + # + # Default style is yellow and bold. fun format_h1(str: String): String do if opt_nocolors.value then return str return str.yellow.bold end + # Format and colorize a string heading of level 2 for console output. + # + # Default style is white and bold. fun format_h2(str: String): String do if opt_nocolors.value then return str return str.bold end + # Format and colorize a string heading of level 3 for console output. + # + # Default style is white and nobold. fun format_h3(str: String): String do if opt_nocolors.value then return str return str end + # Format and colorize a string heading of level 4 for console output. + # + # Default style is green. fun format_h4(str: String): String do if opt_nocolors.value then return str return str.green end + # Format and colorize a string heading of level 5 for console output. + # + # Default style is light gray. fun format_p(str: String): String do if opt_nocolors.value then return str return str.light_gray @@ -123,29 +142,24 @@ redef class ToolContext end -redef class MClass - # is the class imported from standard lib? - fun is_standard: Bool do - return self.intro_mmodule.mgroup.mproject.name == "standard" - end -end - -redef class MModule - # is the module imported from standard lib? - fun is_standard: Bool do - return self.mgroup.mproject.name == "standard" - end -end - # A Metric is used to collect data about things # # The concept is reified here for a better organization and documentation interface Metric + + # Type of elements measured by this metric. type ELM: Object + + # Type of values used to measure elements. type VAL: Object + + # Type of data representation used to associate elements and values. type RES: Map[ELM, VAL] + # The name of this metric (generally an acronym about the metric). fun name: String is abstract + + # A long and understandable description about what is measured by this metric. fun desc: String is abstract # Clear all results for this metric @@ -155,7 +169,7 @@ interface Metric fun values: RES is abstract # Collect metric values on elements - fun collect(elements: Set[ELM]) is abstract + fun collect(elements: Collection[ELM]) is abstract # The value calculated for the element fun [](element: ELM): VAL do return values[element] @@ -165,6 +179,57 @@ interface Metric # The values average fun avg: Float is abstract + + # Pretty print the metric results in console + fun to_console(indent: Int, colors: Bool) do + if values.is_empty then + if colors then + print "{"\t" * indent}{name}: {desc} -- nothing".green + else + print "{"\t" * indent}{name}: {desc} -- nothing" + end + return + end + + var max = self.max + var min = self.min + if colors then + print "{"\t" * indent}{name}: {desc}".green + print "{"\t" * indent} avg: {avg}".light_gray + print "{"\t" * indent} max: {max} ({self[max]})".light_gray + print "{"\t" * indent} min: {min} ({self[min]})".light_gray + print "{"\t" * indent} std: {std_dev}".light_gray + else + print "{"\t" * indent}{name}: {desc}" + print "{"\t" * indent} avg: {avg}" + print "{"\t" * indent} max: {max} ({self[max]})" + print "{"\t" * indent} min: {min} ({self[min]})" + print "{"\t" * indent} std: {std_dev}" + end + end + + # The sum of all the values. + fun sum: VAL is abstract + + # The values standard derivation + fun std_dev: Float is abstract + + # The element with the highest value + fun max: ELM is abstract + + # The element with the lowest value + fun min: ELM is abstract + + # The value threshold above what elements are considered as 'interesting' + fun threshold: Float do return avg + std_dev + + # The set of element above the threshold + fun above_threshold: Set[ELM] is abstract + + # Sort the metric keys by values + fun sort: Array[ELM] do + return values.keys_sorted_by_values(default_reverse_comparator) + end end # A Metric that collects integer data @@ -173,32 +238,50 @@ end class IntMetric super Metric - redef type VAL: Int + redef type VAL: Int is fixed redef type RES: Counter[ELM] + # `IntMetric` uses a Counter to store values in intern. protected var values_cache = new Counter[ELM] + redef fun values do return values_cache redef fun clear do values_cache.clear - # Return the couple with the highest value - fun max: Couple[ELM, Int] do + redef fun sum do return values_cache.sum + + redef fun max do assert not values_cache.is_empty - var elem = values_cache.max.as(not null) - var value = values_cache[elem] - return new Couple[ELM, Int](elem, value) + return values_cache.max.as(not null) end - # Return the couple with the lowest value - fun min: Couple[ELM, Int] do + redef fun min do assert not values_cache.is_empty - var elem = values_cache.min.as(not null) - var value = values_cache[elem] - return new Couple[ELM, Int](elem, value) + return values_cache.min.as(not null) end # Values average - redef fun avg: Float do return values_cache.avg + redef fun avg do return values_cache.avg + + redef fun std_dev do return values_cache.std_dev + + redef fun above_threshold do + var above = new HashSet[ELM] + var threshold = threshold + for element, value in values do + if value.to_f > threshold then above.add(element) + end + return above + end + + redef fun to_console(indent, colors) do + super + if colors then + print "{"\t" * indent} sum: {sum}".light_gray + else + print "{"\t" * indent} sum: {sum}" + end + end end # A Metric that collects float datas @@ -209,13 +292,24 @@ class FloatMetric redef type VAL: Float + # `FloatMetric` uses a Map to store values in intern. protected var values_cache = new HashMap[ELM, VAL] + redef fun values do return values_cache redef fun clear do values_cache.clear - # Return the couple with the highest value - fun max: Couple[ELM, Float] do + + redef fun sum do + var sum = 0.0 + for v in values.values do + if v.is_nan then continue + sum += v + end + return sum + end + + redef fun max do assert not values.is_empty var max: nullable Float = null var elem: nullable ELM = null @@ -225,11 +319,10 @@ class FloatMetric elem = e end end - return new Couple[ELM, Float](elem.as(not null), max.as(not null)) + return elem.as(not null) end - # Return the couple with the lowest value - fun min: Couple[ELM, Float] do + redef fun min do assert not values.is_empty var min: nullable Float = null var elem: nullable ELM = null @@ -239,16 +332,39 @@ class FloatMetric elem = e end end - return new Couple[ELM, Float](elem.as(not null), min.as(not null)) + return elem.as(not null) end redef fun avg do if values.is_empty then return 0.0 + return sum / values.length.to_f + end + + redef fun std_dev do var sum = 0.0 for value in values.values do - sum += value + if value.is_nan then continue + sum += (value - avg).pow(2.to_f) + end + return (sum / values.length.to_f).sqrt + end + + redef fun above_threshold do + var above = new HashSet[ELM] + var threshold = threshold + for element, value in values do + if value > threshold then above.add(element) + end + return above + end + + redef fun to_console(indent, colors) do + super + if colors then + print "{"\t" * indent} sum: {sum}".light_gray + else + print "{"\t" * indent} sum: {sum}" end - return sum / values.length.to_f end end @@ -256,14 +372,56 @@ end # # It purpose is to be extended with a metric collect service class MetricSet - type METRIC: Metric + + # Type of element measured by this `MetricSet`. + type ELM: Object # Metrics to compute - var metrics: Map[String, METRIC] = new HashMap[String, METRIC] + var metrics: Set[Metric] = new HashSet[Metric] # Add a metric to the set - fun register(metrics: METRIC...) do for metric in metrics do self.metrics[metric.name] = metric + fun register(metrics: Metric...) do for metric in metrics do self.metrics.add(metric) # Clear all results for all metrics - fun clear do for metric in metrics.values do metric.clear + fun clear do for metric in metrics do metric.clear + + # Collect all metrics for this set of class + fun collect(elements: Set[ELM]) do + for metric in metrics do metric.collect(elements) + end + + # Pretty print the resuls in console + fun to_console(indent: Int, colors: Bool) do + for metric in metrics do metric.to_console(indent, colors) + end + + # Export the metric set in CSV format + fun to_csv: CsvDocument do + var csv = new CsvDocument + csv.separator = ';' + + # set csv headers + csv.header.add("entry") + for metric in metrics do csv.header.add(metric.name) + + # collect all entries to merge metric results + var entries = new HashSet[ELM] + for metric in metrics do + for entry in metric.values.keys do entries.add(entry) + end + + # collect results + for entry in entries do + var line = [entry.to_s] + for metric in metrics do + if metric.has_element(entry) then + line.add(metric[entry].to_s) + else + line.add("n/a") + end + end + csv.records.add(line) + end + return csv + end end