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