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