Merge: More keep going
[nit.git] / src / metrics / metrics_base.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # Helpers for various statistics tools.
19 module metrics_base
20
21 import modelbuilder
22 import csv
23 import counter
24 import console
25
26 redef class ToolContext
27
28 # --all
29 var opt_all = new OptionBool("Compute all metrics", "--all")
30
31 # --mmodules
32 var opt_mmodules = new OptionBool("Compute metrics about mmodules", "--mmodules")
33 # --mclassses
34 var opt_mclasses = new OptionBool("Compute metrics about mclasses", "--mclasses")
35 # --mendel
36 var opt_mendel = new OptionBool("Compute mendel metrics", "--mendel")
37 # --inheritance
38 var opt_inheritance = new OptionBool("Compute metrics about inheritance usage", "--inheritance")
39 # --genericity
40 var opt_refinement = new OptionBool("Compute metrics about refinement usage", "--refinement")
41 # --self
42 var opt_self = new OptionBool("Compute metrics about the usage of explicit and implicit self", "--self")
43 # --ast
44 var opt_ast = new OptionBool("Compute metrics about the usage of nodes and identifiers in the AST", "--ast")
45 # --nullables
46 var opt_nullables = new OptionBool("Compute metrics on nullables send", "--nullables")
47 # --static-types
48 var opt_static_types = new OptionBool("Compute explicit static types metrics", "--static-types")
49 # --tables
50 var opt_tables = new OptionBool("Compute tables metrics", "--tables")
51 # --rta
52 var opt_rta = new OptionBool("Compute RTA metrics", "--rta")
53 # --generate-csv
54 var opt_csv = new OptionBool("Export metrics in CSV format", "--csv")
55 # --generate_hyperdoc
56 var opt_generate_hyperdoc = new OptionBool("Generate Hyperdoc", "--generate_hyperdoc")
57 # --poset
58 var opt_poset = new OptionBool("Complete metrics on posets", "--poset")
59 # --no-colors
60 var opt_nocolors = new OptionBool("Disable colors in console outputs", "--no-colors")
61 # --dir
62 var opt_dir = new OptionString("Directory where some statistics files are generated", "-d", "--dir")
63
64 # Output directory for metrics files.
65 var output_dir: String = "."
66
67 redef init
68 do
69 super
70 self.option_context.add_option(opt_all)
71 self.option_context.add_option(opt_mmodules)
72 self.option_context.add_option(opt_mclasses)
73 self.option_context.add_option(opt_mendel)
74 self.option_context.add_option(opt_inheritance)
75 self.option_context.add_option(opt_refinement)
76 self.option_context.add_option(opt_self)
77 self.option_context.add_option(opt_ast)
78 self.option_context.add_option(opt_nullables)
79 self.option_context.add_option(opt_static_types)
80 self.option_context.add_option(opt_tables)
81 self.option_context.add_option(opt_rta)
82 self.option_context.add_option(opt_csv)
83 self.option_context.add_option(opt_generate_hyperdoc)
84 self.option_context.add_option(opt_poset)
85 self.option_context.add_option(opt_dir)
86 self.option_context.add_option(opt_nocolors)
87 end
88
89 redef fun process_options(args)
90 do
91 super
92 var val = self.opt_dir.value
93 if val != null then
94 val = val.simplify_path
95 val.mkdir
96 self.output_dir = val
97 end
98 end
99
100 # Format and colorize a string heading of level 1 for console output.
101 #
102 # Default style is yellow and bold.
103 fun format_h1(str: String): String do
104 if opt_nocolors.value then return str
105 return str.yellow.bold
106 end
107
108 # Format and colorize a string heading of level 2 for console output.
109 #
110 # Default style is white and bold.
111 fun format_h2(str: String): String do
112 if opt_nocolors.value then return str
113 return str.bold
114 end
115
116 # Format and colorize a string heading of level 3 for console output.
117 #
118 # Default style is white and nobold.
119 fun format_h3(str: String): String do
120 if opt_nocolors.value then return str
121 return str
122 end
123
124 # Format and colorize a string heading of level 4 for console output.
125 #
126 # Default style is green.
127 fun format_h4(str: String): String do
128 if opt_nocolors.value then return str
129 return str.green
130 end
131
132 # Format and colorize a string heading of level 5 for console output.
133 #
134 # Default style is light gray.
135 fun format_p(str: String): String do
136 if opt_nocolors.value then return str
137 return str.light_gray
138 end
139
140 end
141
142 redef class MClass
143 # is the class imported from standard lib?
144 fun is_standard: Bool do
145 return self.intro_mmodule.mgroup.mproject.name == "standard"
146 end
147 end
148
149 redef class MModule
150 # is the module imported from standard lib?
151 fun is_standard: Bool do
152 return self.mgroup.mproject.name == "standard"
153 end
154 end
155
156 # A Metric is used to collect data about things
157 #
158 # The concept is reified here for a better organization and documentation
159 interface Metric
160
161 # Type of elements measured by this metric.
162 type ELM: Object
163
164 # Type of values used to measure elements.
165 type VAL: Object
166
167 # Type of data representation used to associate elements and values.
168 type RES: Map[ELM, VAL]
169
170 # The name of this metric (generally an acronym about the metric).
171 fun name: String is abstract
172
173 # A long and understandable description about what is measured by this metric.
174 fun desc: String is abstract
175
176 # Clear all results for this metric
177 fun clear is abstract
178
179 # Values for each element
180 fun values: RES is abstract
181
182 # Collect metric values on elements
183 fun collect(elements: Set[ELM]) is abstract
184
185 # The value calculated for the element
186 fun [](element: ELM): VAL do return values[element]
187
188 # Does the element have a value for this metric?
189 fun has_element(element: ELM): Bool do return values.has_key(element)
190
191 # The values average
192 fun avg: Float is abstract
193
194 # Pretty print the metric results in console
195 fun to_console(indent: Int, colors: Bool) do
196 if values.is_empty then
197 if colors then
198 print "{"\t" * indent}{name}: {desc} -- nothing".green
199 else
200 print "{"\t" * indent}{name}: {desc} -- nothing"
201 end
202 return
203 end
204
205 var max = self.max
206 var min = self.min
207 if colors then
208 print "{"\t" * indent}{name}: {desc}".green
209 print "{"\t" * indent} avg: {avg}".light_gray
210 print "{"\t" * indent} max: {max} ({self[max]})".light_gray
211 print "{"\t" * indent} min: {min} ({self[min]})".light_gray
212 print "{"\t" * indent} std: {std_dev}".light_gray
213 else
214 print "{"\t" * indent}{name}: {desc}"
215 print "{"\t" * indent} avg: {avg}"
216 print "{"\t" * indent} max: {max} ({self[max]})"
217 print "{"\t" * indent} min: {min} ({self[min]})"
218 print "{"\t" * indent} std: {std_dev}"
219 end
220 end
221
222 # The sum of all the values.
223 fun sum: VAL is abstract
224
225 # The values standard derivation
226 fun std_dev: Float is abstract
227
228 # The element with the highest value
229 fun max: ELM is abstract
230
231 # The element with the lowest value
232 fun min: ELM is abstract
233
234 # The value threshold above what elements are considered as 'interesting'
235 fun threshold: Float do return avg + std_dev
236
237 # The set of element above the threshold
238 fun above_threshold: Set[ELM] is abstract
239
240 # Sort the metric keys by values
241 fun sort: Array[ELM] do
242 return values.keys_sorted_by_values(default_reverse_comparator)
243 end
244 end
245
246 # A Metric that collects integer data
247 #
248 # Used to count things
249 class IntMetric
250 super Metric
251
252 redef type VAL: Int
253 redef type RES: Counter[ELM]
254
255 # `IntMetric` uses a Counter to store values in intern.
256 protected var values_cache = new Counter[ELM]
257
258 redef fun values do return values_cache
259
260 redef fun clear do values_cache.clear
261
262 redef fun sum do return values_cache.sum
263
264 redef fun max do
265 assert not values_cache.is_empty
266 return values_cache.max.as(not null)
267 end
268
269 redef fun min do
270 assert not values_cache.is_empty
271 return values_cache.min.as(not null)
272 end
273
274 # Values average
275 redef fun avg do return values_cache.avg
276
277 redef fun std_dev do return values_cache.std_dev
278
279 redef fun above_threshold do
280 var above = new HashSet[ELM]
281 var threshold = threshold
282 for element, value in values do
283 if value.to_f > threshold then above.add(element)
284 end
285 return above
286 end
287
288 redef fun to_console(indent, colors) do
289 super
290 if colors then
291 print "{"\t" * indent} sum: {sum}".light_gray
292 else
293 print "{"\t" * indent} sum: {sum}"
294 end
295 end
296 end
297
298 # A Metric that collects float datas
299 #
300 # Used sor summarization
301 class FloatMetric
302 super Metric
303
304 redef type VAL: Float
305
306 # `FloatMetric` uses a Map to store values in intern.
307 protected var values_cache = new HashMap[ELM, VAL]
308
309 redef fun values do return values_cache
310
311 redef fun clear do values_cache.clear
312
313
314 redef fun sum do
315 var sum = 0.0
316 for v in values.values do sum += v
317 return sum
318 end
319
320 redef fun max do
321 assert not values.is_empty
322 var max: nullable Float = null
323 var elem: nullable ELM = null
324 for e, v in values do
325 if max == null or v > max then
326 max = v
327 elem = e
328 end
329 end
330 return elem.as(not null)
331 end
332
333 redef fun min do
334 assert not values.is_empty
335 var min: nullable Float = null
336 var elem: nullable ELM = null
337 for e, v in values do
338 if min == null or v < min then
339 min = v
340 elem = e
341 end
342 end
343 return elem.as(not null)
344 end
345
346 redef fun avg do
347 if values.is_empty then return 0.0
348 return sum / values.length.to_f
349 end
350
351 redef fun std_dev do
352 var sum = 0.0
353 for value in values.values do
354 sum += (value - avg).pow(2.to_f)
355 end
356 return (sum / values.length.to_f).sqrt
357 end
358
359 redef fun above_threshold do
360 var above = new HashSet[ELM]
361 var threshold = threshold
362 for element, value in values do
363 if value > threshold then above.add(element)
364 end
365 return above
366 end
367
368 redef fun to_console(indent, colors) do
369 super
370 if colors then
371 print "{"\t" * indent} sum: {sum}".light_gray
372 else
373 print "{"\t" * indent} sum: {sum}"
374 end
375 end
376 end
377
378 # A MetricSet is a metric holder
379 #
380 # It purpose is to be extended with a metric collect service
381 class MetricSet
382
383 # Type of element measured by this `MetricSet`.
384 type ELM: Object
385
386 # Metrics to compute
387 var metrics: Set[Metric] = new HashSet[Metric]
388
389 # Add a metric to the set
390 fun register(metrics: Metric...) do for metric in metrics do self.metrics.add(metric)
391
392 # Clear all results for all metrics
393 fun clear do for metric in metrics do metric.clear
394
395 # Collect all metrics for this set of class
396 fun collect(elements: Set[ELM]) do
397 for metric in metrics do metric.collect(elements)
398 end
399
400 # Pretty print the resuls in console
401 fun to_console(indent: Int, colors: Bool) do
402 for metric in metrics do metric.to_console(indent, colors)
403 end
404
405 # Export the metric set in CSV format
406 fun to_csv: CsvDocument do
407 var csv = new CsvDocument
408
409 csv.format = new CsvFormat('"', ';', "\n")
410
411 # set csv headers
412 csv.header.add("entry")
413 for metric in metrics do csv.header.add(metric.name)
414
415 # collect all entries to merge metric results
416 var entries = new HashSet[ELM]
417 for metric in metrics do
418 for entry in metric.values.keys do entries.add(entry)
419 end
420
421 # collect results
422 for entry in entries do
423 var line = [entry.to_s]
424 for metric in metrics do
425 if metric.has_element(entry) then
426 line.add(metric[entry].to_s)
427 else
428 line.add("n/a")
429 end
430 end
431 csv.records.add(line)
432 end
433 return csv
434 end
435 end