Doc review on newmodel.
[nit.git] / src / nitstats.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # A program that collects various data about nit programs and libraries
18 module nitstats
19 # Collected datas are :
20 # * number of modules
21 # * number of classes (interface, class, enum, extern, abstract)
22 # * number of class definitions and refinments
23 # * number of properties
24 # * number of used types and runtime classes
25
26 import modelbuilder
27 import exprbuilder
28 import runtime_type
29
30 # The job of this visitor is to resolve all types found
31 class ATypeCounterVisitor
32 super Visitor
33 var modelbuilder: ModelBuilder
34 var nclassdef: AClassdef
35
36 var typecount: HashMap[MType, Int]
37 var total: Int = 0
38
39 # Get a new visitor on a classef to add type count in `typecount'.
40 init(modelbuilder: ModelBuilder, nclassdef: AClassdef, typecount: HashMap[MType, Int])
41 do
42 self.modelbuilder = modelbuilder
43 self.nclassdef = nclassdef
44 self.typecount = typecount
45 end
46
47 redef fun visit(n)
48 do
49 if n isa AType then
50 var mtype = modelbuilder.resolve_mtype(self.nclassdef, n)
51 if mtype != null then
52 self.total += 1
53 if self.typecount.has_key(mtype) then
54 self.typecount[mtype] += 1
55 else
56 self.typecount[mtype] = 1
57 end
58 if not mtype.need_anchor then
59 var cldefs = mtype.collect_mclassdefs(nclassdef.mclassdef.mmodule)
60 end
61 end
62 end
63 n.visit_all(self)
64 end
65 end
66
67 class ANewVisitor
68 super Visitor
69 var modelbuilder: ModelBuilder
70 var nclassdef: AClassdef
71
72 var news: Set[MClass]
73
74 # Get a new visitor on a classef to add type count in `typecount'.
75 init(modelbuilder: ModelBuilder, nclassdef: AClassdef, news: Set[MClass])
76 do
77 self.modelbuilder = modelbuilder
78 self.nclassdef = nclassdef
79 self.news = news
80 end
81
82 redef fun visit(n)
83 do
84 if n isa ANewExpr then
85 var mtype = modelbuilder.resolve_mtype(self.nclassdef, n.n_type)
86 if mtype != null then
87 assert mtype isa MClassType
88 self.news.add(mtype.mclass)
89 end
90 end
91 n.visit_all(self)
92 end
93 end
94
95 # Visit all `nmodules' of a modelbuilder and compute statistics on the usage of explicit static types
96 fun count_ntypes(modelbuilder: ModelBuilder)
97 do
98 # Count each occurence of a specific static type
99 var typecount = new HashMap[MType, Int]
100 # Total number of explicit static types
101 var total = 0
102
103 # Visit all the source code to collect data
104 for nmodule in modelbuilder.nmodules do
105 for nclassdef in nmodule.n_classdefs do
106 var visitor = new ATypeCounterVisitor(modelbuilder, nclassdef, typecount)
107 visitor.enter_visit(nclassdef)
108 total += visitor.total
109 end
110 end
111
112 # Display data
113 print "--- Statistics of the explitic static types ---"
114 print "Total number of explicit static types: {total}"
115 if total == 0 then return
116
117 # types sorted by usage
118 var types = typecount.keys.to_a
119 types.sort !cmp a, b = typecount[a] <=> typecount[b]
120
121 # Display most used types (ie the last of `types')
122 print "Most used types: "
123 var min = 10
124 if types.length < min then min = types.length
125 for i in [0..min[ do
126 var t = types[types.length-i-1]
127 print " {t}: {typecount[t]}"
128 end
129
130 # Compute the distribution of type usage
131 print "Distribution of type usage:"
132 var count = 0
133 var sum = 0
134 var limit = 1
135 for t in types do
136 if typecount[t] > limit then
137 print " <={limit}: {count} ({div(count*100,types.length)}% of types; {div(sum*100,total)}% of usage)"
138 count = 0
139 sum = 0
140 while typecount[t] > limit do limit = limit * 2
141 end
142 count += 1
143 sum += typecount[t]
144 end
145 print " <={limit}: {count} ({div(count*100,types.length)}% of types; {div(sum*100,total)}% of usage)"
146 end
147
148 fun visit_news(modelbuilder: ModelBuilder, mainmodule: MModule)
149 do
150 print "--- Dead classes ---"
151 # Count each occurence of a specific static type
152 var news = new HashSet[MClass]
153
154 # Visit all the source code to collect data
155 for nmodule in modelbuilder.nmodules do
156 for nclassdef in nmodule.n_classdefs do
157 var visitor = new ANewVisitor(modelbuilder, nclassdef, news)
158 visitor.enter_visit(nclassdef)
159 end
160 end
161
162 for c in modelbuilder.model.mclasses do
163 if c.kind == concrete_kind and not news.has(c) then
164 print "{c.full_name}"
165 end
166 end
167
168 var hier = mainmodule.flatten_mclass_hierarchy
169 for c in hier do
170 if c.kind != abstract_kind and not (c.kind == concrete_kind and not news.has(c)) then continue
171
172 for sup in hier[c].greaters do
173 for cd in sup.mclassdefs do
174 for p in cd.intro_mproperties do
175 if p isa MAttribute then
176 continue label classes
177 end
178 end
179 end
180 end
181 print "no attributes: {c.full_name}"
182
183 end label classes
184 end
185
186 # Compute general statistics on a model
187 fun compute_statistics(model: Model)
188 do
189 print "--- Statistics of the model ---"
190 var nbmod = model.mmodules.length
191 print "Number of modules: {nbmod}"
192
193 print ""
194
195 var nbcla = model.mclasses.length
196 var nbcladef = model.mclassdef_hierarchy.length
197 print "Number of classes: {nbcla}"
198
199 # determine the distribution of:
200 # * class kinds (interface, abstract class, etc.)
201 # * refinex classes (vs. unrefined ones)
202 var kinds = new HashMap[MClassKind, Int]
203 var refined = 0
204 for c in model.mclasses do
205 if kinds.has_key(c.kind) then
206 kinds[c.kind] += 1
207 else
208 kinds[c.kind] = 1
209 end
210 if c.mclassdefs.length > 1 then
211 refined += 1
212 end
213 end
214 for k,v in kinds do
215 print " Number of {k} kind: {v} ({div(v*100,nbcla)}%)"
216 end
217
218
219 print ""
220
221 print "Nomber of class definitions: {nbcladef}"
222 print "Number of refined classes: {refined} ({div(refined*100,nbcla)}%)"
223 print "Average number of class refinments by classes: {div(nbcladef-nbcla,nbcla)}"
224 print "Average number of class refinments by refined classes: {div(nbcladef-nbcla,refined)}"
225
226 print ""
227
228 var nbprop = model.mproperties.length
229 print "Number of properties: {model.mproperties.length}"
230 end
231
232 # Compute class tables for the classes of the program main
233 fun compute_tables(main: MModule)
234 do
235 var model = main.model
236
237 var nc = 0 # Number of runtime classes
238 var nl = 0 # Number of usages of class definitions (a class definition can be used more than once)
239 var nhp = 0 # Number of usages of properties (a property can be used more than once)
240 var npas = 0 # Number of usages of properties without lookup (easy easy case, easier that CHA)
241
242 # Collect the full class hierarchy
243 var hier = main.flatten_mclass_hierarchy
244 for c in hier do
245 # Skip classes without direct instances
246 if c.kind == interface_kind or c.kind == abstract_kind then continue
247
248 nc += 1
249
250 # Now, we need to collect all properties defined/inherited/imported
251 # So, visit all definitions of all super-classes
252 for sup in hier[c].greaters do
253 for cd in sup.mclassdefs do
254 nl += 1
255
256 # Now, search properties introduced
257 for p in cd.intro_mproperties do
258
259 nhp += 1
260 # Select property definition
261 if p.mpropdefs.length == 1 then
262 npas += 1
263 else
264 var sels = p.lookup_definitions(main, c.mclassdefs.first.bound_mtype)
265 if sels.length > 1 then
266 print "conflict for {p.full_name} in class {c.full_name}: {sels.join(", ")}"
267 else if sels.is_empty then
268 print "ERROR: no property for {p.full_name} in class {c.full_name}!"
269 end
270 end
271 end
272 end
273 end
274 end
275
276 print "--- Construction of tables ---"
277 print "Number of runtime classes: {nc} (excluding interfaces and abstract classes)"
278 print "Average number of composing class definition by runtime class: {div(nl,nc)}"
279 print "Total size of tables (classes and instances): {nhp} (not including stuff like info for subtyping or call-next-method)"
280 print "Average size of table by runtime class: {div(nhp,nc)}"
281 print "Values never redefined: {npas} ({div(npas*100,nhp)}%)"
282 end
283
284 # Helper function to display n/d and handle division by 0
285 fun div(n: Int, d: Int): String
286 do
287 if d == 0 then return "na"
288 return ((100*n/d).to_f/100.0).to_precision(2)
289 end
290
291 # Create a dot file representing the module hierarchy of a model.
292 # Importation relation is represented with arrow
293 # Nesting relation is represented with nested boxes
294 fun generate_module_hierarchy(model: Model)
295 do
296 var buf = new Buffer
297 buf.append("digraph \{\n")
298 buf.append("node [shape=box];\n")
299 buf.append("rankdir=BT;\n")
300 for mmodule in model.mmodules do
301 if mmodule.direct_owner == null then
302 generate_module_hierarchy_for(mmodule, buf)
303 end
304 end
305 for mmodule in model.mmodules do
306 for s in mmodule.in_importation.direct_greaters do
307 buf.append("\"{mmodule}\" -> \"{s}\";\n")
308 end
309 end
310 buf.append("\}\n")
311 var f = new OFStream.open("module_hierarchy.dot")
312 f.write(buf.to_s)
313 f.close
314 end
315
316 # Helper function for `generate_module_hierarchy'.
317 # Create graphviz nodes for the module and recusrively for its nested modules
318 private fun generate_module_hierarchy_for(mmodule: MModule, buf: Buffer)
319 do
320 if mmodule.in_nesting.direct_greaters.is_empty then
321 buf.append("\"{mmodule.name}\";\n")
322 else
323 buf.append("subgraph \"cluster_{mmodule.name}\" \{label=\"\"\n")
324 buf.append("\"{mmodule.name}\";\n")
325 for s in mmodule.in_nesting.direct_greaters do
326 generate_module_hierarchy_for(s, buf)
327 end
328 buf.append("\}\n")
329 end
330 end
331
332 # Create a dot file representing the class hierarchy of a model.
333 fun generate_class_hierarchy(mmodule: MModule)
334 do
335 var buf = new Buffer
336 buf.append("digraph \{\n")
337 buf.append("node [shape=box];\n")
338 buf.append("rankdir=BT;\n")
339 var hierarchy = mmodule.flatten_mclass_hierarchy
340 for mclass in hierarchy do
341 buf.append("\"{mclass}\" [label=\"{mclass}\"];\n")
342 for s in hierarchy[mclass].direct_greaters do
343 buf.append("\"{mclass}\" -> \"{s}\";\n")
344 end
345 end
346 buf.append("\}\n")
347 var f = new OFStream.open("class_hierarchy.dot")
348 f.write(buf.to_s)
349 f.close
350 end
351
352 # Create a dot file representing the classdef hierarchy of a model.
353 # For a simple user of the model, the classdef hierarchy is not really usefull, it is more an internal thing.
354 fun generate_classdef_hierarchy(model: Model)
355 do
356 var buf = new Buffer
357 buf.append("digraph \{\n")
358 buf.append("node [shape=box];\n")
359 buf.append("rankdir=BT;\n")
360 for mmodule in model.mmodules do
361 for mclassdef in mmodule.mclassdefs do
362 buf.append("\"{mclassdef} {mclassdef.bound_mtype}\" [label=\"{mclassdef.mmodule}\\n{mclassdef.bound_mtype}\"];\n")
363 for s in mclassdef.in_hierarchy.direct_greaters do
364 buf.append("\"{mclassdef} {mclassdef.bound_mtype}\" -> \"{s} {s.bound_mtype}\";\n")
365 end
366 end
367 end
368 buf.append("\}\n")
369 var f = new OFStream.open("classdef_hierarchy.dot")
370 f.write(buf.to_s)
371 f.close
372 end
373
374 fun generate_model_hyperdoc(model: Model)
375 do
376 var buf = new Buffer
377 buf.append("<html>\n<body>\n")
378 buf.append("<h1>Model</h1>\n")
379
380 buf.append("<h2>Modules</h2>\n")
381 for mmodule in model.mmodules do
382 buf.append("<h3 id='module-{mmodule}'>{mmodule}</h3>\n")
383 buf.append("<dl>\n")
384 buf.append("<dt>direct owner</dt>\n")
385 var own = mmodule.direct_owner
386 if own != null then buf.append("<dd>{linkto(own)}</dd>\n")
387 buf.append("<dt>nested</dt>\n")
388 for x in mmodule.in_nesting.direct_greaters do
389 buf.append("<dd>{linkto(x)}</dd>\n")
390 end
391 buf.append("<dt>direct import</dt>\n")
392 for x in mmodule.in_importation.direct_greaters do
393 buf.append("<dd>{linkto(x)}</dd>\n")
394 end
395 buf.append("<dt>direct clients</dt>\n")
396 for x in mmodule.in_importation.direct_smallers do
397 buf.append("<dd>{linkto(x)}</dd>\n")
398 end
399 buf.append("<dt>introduced classes</dt>\n")
400 for x in mmodule.mclassdefs do
401 if not x.is_intro then continue
402 buf.append("<dd>{linkto(x.mclass)} by {linkto(x)}</dd>\n")
403 end
404 buf.append("<dt>refined classes</dt>\n")
405 for x in mmodule.mclassdefs do
406 if x.is_intro then continue
407 buf.append("<dd>{linkto(x.mclass)} by {linkto(x)}</dd>\n")
408 end
409 buf.append("</dl>\n")
410 end
411 buf.append("<h2>Classes</h2>\n")
412 for mclass in model.mclasses do
413 buf.append("<h3 id='class-{mclass}'>{mclass}</h3>\n")
414 buf.append("<dl>\n")
415 buf.append("<dt>module of introduction</dt>\n")
416 buf.append("<dd>{linkto(mclass.intro_mmodule)}</dd>\n")
417 buf.append("<dt>class definitions</dt>\n")
418 for x in mclass.mclassdefs do
419 buf.append("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
420 end
421 buf.append("</dl>\n")
422 end
423 buf.append("<h2>Class Definitions</h2>\n")
424 for mclass in model.mclasses do
425 for mclassdef in mclass.mclassdefs do
426 buf.append("<h3 id='classdef-{mclassdef}'>{mclassdef}</h3>\n")
427 buf.append("<dl>\n")
428 buf.append("<dt>module</dt>\n")
429 buf.append("<dd>{linkto(mclassdef.mmodule)}</dd>\n")
430 buf.append("<dt>class</dt>\n")
431 buf.append("<dd>{linkto(mclassdef.mclass)}</dd>\n")
432 buf.append("<dt>direct refinements</dt>\n")
433 for x in mclassdef.in_hierarchy.direct_greaters do
434 if x.mclass != mclass then continue
435 buf.append("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
436 end
437 buf.append("<dt>direct refinemees</dt>\n")
438 for x in mclassdef.in_hierarchy.direct_smallers do
439 if x.mclass != mclass then continue
440 buf.append("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
441 end
442 buf.append("<dt>direct superclasses</dt>\n")
443 for x in mclassdef.supertypes do
444 buf.append("<dd>{linkto(x.mclass)} by {x}</dd>\n")
445 end
446 buf.append("<dt>introduced properties</dt>\n")
447 for x in mclassdef.mpropdefs do
448 if not x.is_intro then continue
449 buf.append("<dd>{linkto(x.mproperty)} by {linkto(x)}</dd>\n")
450 end
451 buf.append("<dt>redefined properties</dt>\n")
452 for x in mclassdef.mpropdefs do
453 if x.is_intro then continue
454 buf.append("<dd>{linkto(x.mproperty)} by {linkto(x)}</dd>\n")
455 end
456 buf.append("</dl>\n")
457 end
458 end
459 buf.append("<h2>Properties</h2>\n")
460 for mprop in model.mproperties do
461 buf.append("<h3 id='property-{mprop}'>{mprop}</h3>\n")
462 buf.append("<dl>\n")
463 buf.append("<dt>module of introdcution</dt>\n")
464 buf.append("<dd>{linkto(mprop.intro_mclassdef.mmodule)}</dd>\n")
465 buf.append("<dt>class of introduction</dt>\n")
466 buf.append("<dd>{linkto(mprop.intro_mclassdef.mclass)}</dd>\n")
467 buf.append("<dt>class definition of introduction</dt>\n")
468 buf.append("<dd>{linkto(mprop.intro_mclassdef)}</dd>\n")
469 buf.append("<dt>property definitions</dt>\n")
470 for x in mprop.mpropdefs do
471 buf.append("<dd>{linkto(x)} in {linkto(x.mclassdef)}</dd>\n")
472 end
473 buf.append("</dl>\n")
474 end
475 buf.append("<h2>Property Definitions</h2>\n")
476 for mprop in model.mproperties do
477 for mpropdef in mprop.mpropdefs do
478 buf.append("<h3 id='propdef-{mpropdef}'>{mpropdef}</h3>\n")
479 buf.append("<dl>\n")
480 buf.append("<dt>module</dt>\n")
481 buf.append("<dd>{linkto(mpropdef.mclassdef.mmodule)}</dd>\n")
482 buf.append("<dt>class</dt>\n")
483 buf.append("<dd>{linkto(mpropdef.mclassdef.mclass)}</dd>\n")
484 buf.append("<dt>class definition</dt>\n")
485 buf.append("<dd>{linkto(mpropdef.mclassdef)}</dd>\n")
486 buf.append("<dt>super definitions</dt>\n")
487 for x in mpropdef.mproperty.lookup_super_definitions(mpropdef.mclassdef.mmodule, mpropdef.mclassdef.bound_mtype) do
488 buf.append("<dd>{linkto(x)} in {linkto(x.mclassdef)}</dd>\n")
489 end
490 end
491 end
492 buf.append("</body></html>\n")
493 var f = new OFStream.open("model.html")
494 f.write(buf.to_s)
495 f.close
496 end
497
498 fun linkto(o: Object): String
499 do
500 if o isa MModule then
501 return "<a href='#module-{o}'>{o}</a>"
502 else if o isa MClass then
503 return "<a href='#class-{o}'>{o}</a>"
504 else if o isa MClassDef then
505 return "<a href='#classdef-{o}'>{o}</a>"
506 else if o isa MProperty then
507 return "<a href='#property-{o}'>{o}</a>"
508 else if o isa MPropDef then
509 return "<a href='#propdef-{o}'>{o}</a>"
510 else
511 print "cannot linkto {o.class_name}"
512 abort
513 end
514 end
515
516 fun runtime_type(modelbuilder: ModelBuilder, mainmodule: MModule)
517 do
518 var analysis = modelbuilder.do_runtime_type(mainmodule)
519
520 print "--- Type Analysis ---"
521 print "Number of live runtime types (instantied resolved type): {analysis.live_types.length}"
522 print "Number of live polymorphic method: {analysis.polymorphic_methods.length}"
523 print "Number of live method definitions: {analysis.live_methoddefs.length}"
524 print "Number of live runtime method definitions (with customization): {analysis.runtime_methods.length}"
525 print "Number of live runtime cast types (ie used in as and isa): {analysis.live_cast_types.length}"
526
527 for mprop in modelbuilder.model.mproperties do
528 if not mprop isa MMethod then continue
529 if analysis.polymorphic_methods.has(mprop) then continue
530 for methoddef in mprop.mpropdefs do
531 if analysis.live_methoddefs.has(methoddef) then continue label l
532 end
533 #print " {mprop.full_name} is dead"
534 end label l
535 end
536
537 # Create a tool context to handle options and paths
538 var toolcontext = new ToolContext
539 # We do not add other options, so process them now!
540 toolcontext.process_options
541
542 # We need a model to collect stufs
543 var model = new Model
544 # An a model builder to parse files
545 var modelbuilder = new ModelBuilder(model, toolcontext)
546
547 # Here we load an process all modules passed on the command line
548 var mmodules = modelbuilder.parse_and_build(toolcontext.option_context.rest)
549
550 if mmodules.length == 0 then return
551
552 var mainmodule: MModule
553 if mmodules.length == 1 then
554 mainmodule = mmodules.first
555 else
556 # We need a main module, so we build it by importing all modules
557 mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
558 mainmodule.set_imported_mmodules(mmodules)
559 end
560
561 # Now, we just have to play with the model!
562 print "*** STATS ***"
563
564 print ""
565 compute_statistics(model)
566
567 print ""
568 #visit_news(modelbuilder, mainmodule)
569
570 print ""
571 count_ntypes(modelbuilder)
572
573 generate_module_hierarchy(model)
574 generate_classdef_hierarchy(model)
575 generate_class_hierarchy(mainmodule)
576 generate_model_hyperdoc(model)
577
578 print ""
579 compute_tables(mainmodule)
580
581 print ""
582 modelbuilder.full_propdef_semantic_analysis
583 runtime_type(modelbuilder, mainmodule)