ba4856e85611caf178eac950b3e8618c17899c5a
[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
61 # --no-colors
62 var opt_nocolors = new OptionBool("Disable colors in console outputs", "--no-colors")
63
64
65 var opt_dir = new OptionString("Directory where some statistics files are generated", "-d", "--dir")
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 # colorize heading 1 for console output
102 fun format_h1(str: String): String do
103 if opt_nocolors.value then return str
104 return str.yellow.bold
105 end
106
107 fun format_h2(str: String): String do
108 if opt_nocolors.value then return str
109 return str.bold
110 end
111
112 fun format_h3(str: String): String do
113 if opt_nocolors.value then return str
114 return str
115 end
116
117 fun format_h4(str: String): String do
118 if opt_nocolors.value then return str
119 return str.green
120 end
121
122 fun format_p(str: String): String do
123 if opt_nocolors.value then return str
124 return str.light_gray
125 end
126
127 end
128
129 redef class MClass
130 # is the class imported from standard lib?
131 fun is_standard: Bool do
132 return self.intro_mmodule.mgroup.mproject.name == "standard"
133 end
134 end
135
136 redef class MModule
137 # is the module imported from standard lib?
138 fun is_standard: Bool do
139 return self.mgroup.mproject.name == "standard"
140 end
141 end
142
143 # A Metric is used to collect data about things
144 #
145 # The concept is reified here for a better organization and documentation
146 interface Metric
147 type ELM: Object
148 type VAL: Object
149 type RES: Map[ELM, VAL]
150
151 fun name: String is abstract
152 fun desc: String is abstract
153
154 # Clear all results for this metric
155 fun clear is abstract
156
157 # Values for each element
158 fun values: RES is abstract
159
160 # Collect metric values on elements
161 fun collect(elements: Set[ELM]) is abstract
162
163 # The value calculated for the element
164 fun [](element: ELM): VAL do return values[element]
165
166 # Does the element have a value for this metric?
167 fun has_element(element: ELM): Bool do return values.has_key(element)
168
169 # The values average
170 fun avg: Float is abstract
171
172 # Pretty print the metric results in console
173 fun to_console(indent: Int, colors: Bool) do
174 if values.is_empty then
175 if colors then
176 print "{"\t" * indent}{name}: {desc} -- nothing".green
177 else
178 print "{"\t" * indent}{name}: {desc} -- nothing"
179 end
180 return
181 end
182
183 var max = self.max
184 var min = self.min
185 if colors then
186 print "{"\t" * indent}{name}: {desc}".green
187 print "{"\t" * indent} avg: {avg}".light_gray
188 print "{"\t" * indent} max: {max} ({self[max]})".light_gray
189 print "{"\t" * indent} min: {min} ({self[min]})".light_gray
190 print "{"\t" * indent} std: {std_dev}".light_gray
191 else
192 print "{"\t" * indent}{name}: {desc}"
193 print "{"\t" * indent} avg: {avg}"
194 print "{"\t" * indent} max: {max} ({self[max]})"
195 print "{"\t" * indent} min: {min} ({self[min]})"
196 print "{"\t" * indent} std: {std_dev}"
197 end
198 end
199
200 # The sum of all the values.
201 fun sum: VAL is abstract
202
203 # The values standard derivation
204 fun std_dev: Float is abstract
205
206 # The element with the highest value
207 fun max: ELM is abstract
208
209 # The element with the lowest value
210 fun min: ELM is abstract
211
212 # The value threshold above what elements are considered as 'interesting'
213 fun threshold: Float do return avg + std_dev
214
215 # The set of element above the threshold
216 fun above_threshold: Set[ELM] is abstract
217
218 # Sort the metric keys by values
219 fun sort: Array[ELM] do
220 return values.keys_sorted_by_values(default_reverse_comparator)
221 end
222 end
223
224 # A Metric that collects integer data
225 #
226 # Used to count things
227 class IntMetric
228 super Metric
229
230 redef type VAL: Int
231 redef type RES: Counter[ELM]
232
233 protected var values_cache = new Counter[ELM]
234 redef fun values do return values_cache
235
236 redef fun clear do values_cache.clear
237
238 redef fun sum do return values_cache.sum
239
240 redef fun max do
241 assert not values_cache.is_empty
242 return values_cache.max.as(not null)
243 end
244
245 redef fun min do
246 assert not values_cache.is_empty
247 return values_cache.min.as(not null)
248 end
249
250 # Values average
251 redef fun avg do return values_cache.avg
252
253 redef fun std_dev do return values_cache.std_dev
254
255 redef fun above_threshold do
256 var above = new HashSet[ELM]
257 var threshold = threshold
258 for element, value in values do
259 if value.to_f > threshold then above.add(element)
260 end
261 return above
262 end
263
264 redef fun to_console(indent, colors) do
265 super
266 if colors then
267 print "{"\t" * indent} sum: {sum}".light_gray
268 else
269 print "{"\t" * indent} sum: {sum}"
270 end
271 end
272 end
273
274 # A Metric that collects float datas
275 #
276 # Used sor summarization
277 class FloatMetric
278 super Metric
279
280 redef type VAL: Float
281
282 protected var values_cache = new HashMap[ELM, VAL]
283 redef fun values do return values_cache
284
285 redef fun clear do values_cache.clear
286
287
288 redef fun sum do
289 var sum = 0.0
290 for v in values.values do sum += v
291 return sum
292 end
293
294 redef fun max do
295 assert not values.is_empty
296 var max: nullable Float = null
297 var elem: nullable ELM = null
298 for e, v in values do
299 if max == null or v > max then
300 max = v
301 elem = e
302 end
303 end
304 return elem.as(not null)
305 end
306
307 redef fun min do
308 assert not values.is_empty
309 var min: nullable Float = null
310 var elem: nullable ELM = null
311 for e, v in values do
312 if min == null or v < min then
313 min = v
314 elem = e
315 end
316 end
317 return elem.as(not null)
318 end
319
320 redef fun avg do
321 if values.is_empty then return 0.0
322 return sum / values.length.to_f
323 end
324
325 redef fun std_dev do
326 var sum = 0.0
327 for value in values.values do
328 sum += (value - avg).pow(2.to_f)
329 end
330 return (sum / values.length.to_f).sqrt
331 end
332
333 redef fun above_threshold do
334 var above = new HashSet[ELM]
335 var threshold = threshold
336 for element, value in values do
337 if value > threshold then above.add(element)
338 end
339 return above
340 end
341
342 redef fun to_console(indent, colors) do
343 super
344 if colors then
345 print "{"\t" * indent} sum: {sum}".light_gray
346 else
347 print "{"\t" * indent} sum: {sum}"
348 end
349 end
350 end
351
352 # A MetricSet is a metric holder
353 #
354 # It purpose is to be extended with a metric collect service
355 class MetricSet
356 type ELM: Object
357
358 # Metrics to compute
359 var metrics: Set[Metric] = new HashSet[Metric]
360
361 # Add a metric to the set
362 fun register(metrics: Metric...) do for metric in metrics do self.metrics.add(metric)
363
364 # Clear all results for all metrics
365 fun clear do for metric in metrics do metric.clear
366
367 # Collect all metrics for this set of class
368 fun collect(elements: Set[ELM]) do
369 for metric in metrics do metric.collect(elements)
370 end
371
372 # Pretty print the resuls in console
373 fun to_console(indent: Int, colors: Bool) do
374 for metric in metrics do metric.to_console(indent, colors)
375 end
376
377 # Export the metric set in CSV format
378 fun to_csv: CsvDocument do
379 var csv = new CsvDocument
380
381 csv.format = new CsvFormat('"', ';', "\n")
382
383 # set csv headers
384 csv.header.add("entry")
385 for metric in metrics do csv.header.add(metric.name)
386
387 # collect all entries to merge metric results
388 var entries = new HashSet[ELM]
389 for metric in metrics do
390 for entry in metric.values.keys do entries.add(entry)
391 end
392
393 # collect results
394 for entry in entries do
395 var line = [entry.to_s]
396 for metric in metrics do
397 if metric.has_element(entry) then
398 line.add(metric[entry].to_s)
399 else
400 line.add("n/a")
401 end
402 end
403 csv.records.add(line)
404 end
405 return csv
406 end
407 end