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