2bb61cb98d50728f32305cbf40ffbb7b61292603
[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
36 # --inheritance
37 var opt_inheritance = new OptionBool("Compute metrics about inheritance usage", "--inheritance")
38 # --genericity
39 var opt_refinement = new OptionBool("Compute metrics about refinement usage", "--refinement")
40 # --self
41 var opt_self = new OptionBool("Compute metrics about the usage of explicit and implicit self", "--self")
42 # --ast
43 var opt_ast = new OptionBool("Compute metrics about the usage of nodes and identifiers in the AST", "--ast")
44 # --nullables
45 var opt_nullables = new OptionBool("Compute metrics on nullables send", "--nullables")
46 # --static-types
47 var opt_static_types = new OptionBool("Compute explicit static types metrics", "--static-types")
48 # --tables
49 var opt_tables = new OptionBool("Compute tables metrics", "--tables")
50 # --rta
51 var opt_rta = new OptionBool("Compute RTA metrics", "--rta")
52 # --generate-csv
53 var opt_generate_csv = new OptionBool("Generate CVS format metrics", "--generate-csv")
54 # --generate_hyperdoc
55 var opt_generate_hyperdoc = new OptionBool("Generate Hyperdoc", "--generate_hyperdoc")
56 # --poset
57 var opt_poset = new OptionBool("Complete metrics on posets", "--poset")
58
59 # --no-colors
60 var opt_nocolors = new OptionBool("Disable colors in console outputs", "--no-colors")
61
62
63 var opt_dir = new OptionString("Directory where some statistics files are generated", "-d", "--dir")
64 var output_dir: String = "."
65
66 redef init
67 do
68 super
69 self.option_context.add_option(opt_all)
70 self.option_context.add_option(opt_mmodules)
71 self.option_context.add_option(opt_mclasses)
72 self.option_context.add_option(opt_inheritance)
73 self.option_context.add_option(opt_refinement)
74 self.option_context.add_option(opt_self)
75 self.option_context.add_option(opt_ast)
76 self.option_context.add_option(opt_nullables)
77 self.option_context.add_option(opt_static_types)
78 self.option_context.add_option(opt_tables)
79 self.option_context.add_option(opt_rta)
80 self.option_context.add_option(opt_generate_csv)
81 self.option_context.add_option(opt_generate_hyperdoc)
82 self.option_context.add_option(opt_poset)
83 self.option_context.add_option(opt_dir)
84 self.option_context.add_option(opt_nocolors)
85 end
86
87 redef fun process_options
88 do
89 super
90 var val = self.opt_dir.value
91 if val != null then
92 val = val.simplify_path
93 val.mkdir
94 self.output_dir = val
95 end
96 end
97
98 # colorize heading 1 for console output
99 fun format_h1(str: String): String do
100 if opt_nocolors.value then return str
101 return str.yellow.bold
102 end
103
104 fun format_h2(str: String): String do
105 if opt_nocolors.value then return str
106 return str.bold
107 end
108
109 fun format_h3(str: String): String do
110 if opt_nocolors.value then return str
111 return str
112 end
113
114 fun format_h4(str: String): String do
115 if opt_nocolors.value then return str
116 return str.green
117 end
118
119 fun format_p(str: String): String do
120 if opt_nocolors.value then return str
121 return str.light_gray
122 end
123
124 end
125
126 redef class MClass
127 # is the class imported from standard lib?
128 fun is_standard: Bool do
129 return self.intro_mmodule.mgroup.mproject.name == "standard"
130 end
131 end
132
133 redef class MModule
134 # is the module imported from standard lib?
135 fun is_standard: Bool do
136 return self.mgroup.mproject.name == "standard"
137 end
138 end
139
140 # A Metric is used to collect data about things
141 #
142 # The concept is reified here for a better organization and documentation
143 interface Metric
144 type ELM: Object
145 type VAL: Object
146 type RES: Map[ELM, VAL]
147
148 fun name: String is abstract
149 fun desc: String is abstract
150
151 # Clear all results for this metric
152 fun clear is abstract
153
154 # Values for each element
155 fun values: RES is abstract
156
157 # Collect metric values on elements
158 fun collect(elements: Set[ELM]) is abstract
159
160 # The value calculated for the element
161 fun [](element: ELM): VAL do return values[element]
162
163 # Does the element have a value for this metric?
164 fun has_element(element: ELM): Bool do return values.has_key(element)
165
166 # The values average
167 fun avg: Float is abstract
168
169 # Pretty print the metric results in console
170 fun to_console(indent: Int, colors: Bool) do
171 var max = self.max
172 var min = self.min
173 if colors then
174 print "{"\t" * indent}{name}: {desc}".green
175 print "{"\t" * indent} avg: {avg}".light_gray
176 print "{"\t" * indent} max: {max} ({self[max]})".light_gray
177 print "{"\t" * indent} min: {min} ({self[min]})".light_gray
178 print "{"\t" * indent} std: {std_dev}".light_gray
179 else
180 print "{"\t" * indent}{name}: {desc}"
181 print "{"\t" * indent} avg: {avg}"
182 print "{"\t" * indent} max: {max} ({self[max]})"
183 print "{"\t" * indent} min: {min} ({self[min]})"
184 print "{"\t" * indent} std: {std_dev}"
185 end
186 end
187
188 # The values standard derivation
189 fun std_dev: Float is abstract
190 end
191
192 # A Metric that collects integer data
193 #
194 # Used to count things
195 class IntMetric
196 super Metric
197
198 redef type VAL: Int
199 redef type RES: Counter[ELM]
200
201 protected var values_cache = new Counter[ELM]
202 redef fun values do return values_cache
203
204 redef fun clear do values_cache.clear
205
206 # Return the couple with the highest value
207 fun max: Couple[ELM, Int] do
208 assert not values_cache.is_empty
209 var elem = values_cache.max.as(not null)
210 var value = values_cache[elem]
211 return new Couple[ELM, Int](elem, value)
212 end
213
214 # Return the couple with the lowest value
215 fun min: Couple[ELM, Int] do
216 assert not values_cache.is_empty
217 var elem = values_cache.min.as(not null)
218 var value = values_cache[elem]
219 return new Couple[ELM, Int](elem, value)
220 end
221
222 # Values average
223 redef fun avg: Float do return values_cache.avg
224
225 redef fun std_dev: Float do return values_cache.std_dev
226 end
227
228 # A Metric that collects float datas
229 #
230 # Used sor summarization
231 class FloatMetric
232 super Metric
233
234 redef type VAL: Float
235
236 protected var values_cache = new HashMap[ELM, VAL]
237 redef fun values do return values_cache
238
239 redef fun clear do values_cache.clear
240
241 # Return the couple with the highest value
242 fun max: Couple[ELM, Float] do
243 assert not values.is_empty
244 var max: nullable Float = null
245 var elem: nullable ELM = null
246 for e, v in values do
247 if max == null or v > max then
248 max = v
249 elem = e
250 end
251 end
252 return new Couple[ELM, Float](elem.as(not null), max.as(not null))
253 end
254
255 # Return the couple with the lowest value
256 fun min: Couple[ELM, Float] do
257 assert not values.is_empty
258 var min: nullable Float = null
259 var elem: nullable ELM = null
260 for e, v in values do
261 if min == null or v < min then
262 min = v
263 elem = e
264 end
265 end
266 return new Couple[ELM, Float](elem.as(not null), min.as(not null))
267 end
268
269 redef fun avg do
270 if values.is_empty then return 0.0
271 var sum = 0.0
272 for value in values.values do
273 sum += value
274 end
275 return sum / values.length.to_f
276 end
277
278 redef fun std_dev: Float do
279 var sum = 0.0
280 for value in values.values do
281 sum += (value - avg).pow(2.to_f)
282 end
283 return (sum / values.length.to_f).sqrt
284 end
285 end
286
287 # A MetricSet is a metric holder
288 #
289 # It purpose is to be extended with a metric collect service
290 class MetricSet
291 type ELM: Object
292
293 # Metrics to compute
294 var metrics: Set[Metric] = new HashSet[Metric]
295
296 # Add a metric to the set
297 fun register(metrics: Metric...) do for metric in metrics do self.metrics.add(metric)
298
299 # Clear all results for all metrics
300 fun clear do for metric in metrics do metric.clear
301
302 # Collect all metrics for this set of class
303 fun collect(elements: Set[ELM]) do
304 for metric in metrics do metric.collect(elements)
305 end
306
307 # Pretty print the resuls in console
308 fun to_console(indent: Int, colors: Bool) do
309 for metric in metrics do metric.to_console(indent, colors)
310 end
311
312 end