Coverage for src/_griffe/stats.py: 41.82%
94 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
1# This module contains utilities to compute loading statistics,
2# like time spent visiting modules statically or dynamically.
4from __future__ import annotations
6from collections import defaultdict
7from pathlib import Path
8from typing import TYPE_CHECKING
10from _griffe.enumerations import Kind
12if TYPE_CHECKING:
13 from _griffe.loader import GriffeLoader
14 from _griffe.models import Alias, Object
17class Stats:
18 """Load statistics for a Griffe loader."""
20 def __init__(self, loader: GriffeLoader) -> None:
21 """Initialiwe the stats object.
23 Parameters:
24 loader: The loader to compute stats for.
25 """
26 self.loader = loader
27 """The loader to compute stats for."""
29 modules_by_extension = defaultdict(
30 int,
31 {
32 "": 0,
33 ".py": 0,
34 ".pyi": 0,
35 ".pyc": 0,
36 ".pyo": 0,
37 ".pyd": 0,
38 ".so": 0,
39 },
40 )
42 top_modules = loader.modules_collection.members.values()
44 self.by_kind = {
45 Kind.MODULE: 0,
46 Kind.CLASS: 0,
47 Kind.FUNCTION: 0,
48 Kind.ATTRIBUTE: 0,
49 }
50 """Number of objects by kind."""
52 self.packages = len(top_modules)
53 """Number of packages."""
55 self.modules_by_extension = modules_by_extension
56 """Number of modules by extension."""
58 self.lines = sum(len(lines) for lines in loader.lines_collection.values())
59 """Total number of lines."""
61 self.time_spent_visiting = 0
62 """Time spent visiting modules."""
64 self.time_spent_inspecting = 0
65 """Time spent inspecting modules."""
67 self.time_spent_serializing = 0
68 """Time spent serializing objects."""
70 for module in top_modules:
71 self._itercount(module)
73 def _itercount(self, root: Object | Alias) -> None:
74 if root.is_alias:
75 return
76 self.by_kind[root.kind] += 1
77 if root.is_module:
78 if isinstance(root.filepath, Path): 78 ↛ 80line 78 didn't jump to line 80 because the condition on line 78 was always true
79 self.modules_by_extension[root.filepath.suffix] += 1
80 elif root.filepath is None:
81 self.modules_by_extension[""] += 1
82 for member in root.members.values():
83 self._itercount(member)
85 def as_text(self) -> str:
86 """Format the statistics as text.
88 Returns:
89 Text stats.
90 """
91 lines = []
92 packages = self.packages
93 modules = self.by_kind[Kind.MODULE]
94 classes = self.by_kind[Kind.CLASS]
95 functions = self.by_kind[Kind.FUNCTION]
96 attributes = self.by_kind[Kind.ATTRIBUTE]
97 objects = sum((modules, classes, functions, attributes))
98 lines.append("Statistics")
99 lines.append("---------------------")
100 lines.append("Number of loaded objects")
101 lines.append(f" Modules: {modules}")
102 lines.append(f" Classes: {classes}")
103 lines.append(f" Functions: {functions}")
104 lines.append(f" Attributes: {attributes}")
105 lines.append(f" Total: {objects} across {packages} packages")
106 per_ext = self.modules_by_extension
107 builtin = per_ext[""]
108 regular = per_ext[".py"]
109 stubs = per_ext[".pyi"]
110 compiled = modules - builtin - regular - stubs
111 lines.append("")
112 lines.append(f"Total number of lines: {self.lines}")
113 lines.append("")
114 lines.append("Modules")
115 lines.append(f" Builtin: {builtin}")
116 lines.append(f" Compiled: {compiled}")
117 lines.append(f" Regular: {regular}")
118 lines.append(f" Stubs: {stubs}")
119 lines.append(" Per extension:")
120 for ext, number in sorted(per_ext.items()):
121 if ext:
122 lines.append(f" {ext}: {number}")
124 visit_time = self.time_spent_visiting / 1000
125 inspect_time = self.time_spent_inspecting / 1000
126 total_time = visit_time + inspect_time
127 visit_percent = visit_time / total_time * 100
128 inspect_percent = inspect_time / total_time * 100
130 force_inspection = self.loader.force_inspection
131 visited_modules = 0 if force_inspection else regular
132 try:
133 visit_time_per_module = visit_time / visited_modules
134 except ZeroDivisionError:
135 visit_time_per_module = 0
137 inspected_modules = builtin + compiled + (regular if force_inspection else 0)
138 try:
139 inspect_time_per_module = inspect_time / inspected_modules
140 except ZeroDivisionError:
141 inspect_time_per_module = 0
143 lines.append("")
144 lines.append(
145 f"Time spent visiting modules ({visited_modules}): "
146 f"{visit_time}ms, {visit_time_per_module:.02f}ms/module ({visit_percent:.02f}%)",
147 )
148 lines.append(
149 f"Time spent inspecting modules ({inspected_modules}): "
150 f"{inspect_time}ms, {inspect_time_per_module:.02f}ms/module ({inspect_percent:.02f}%)",
151 )
153 serialize_time = self.time_spent_serializing / 1000
154 serialize_time_per_module = serialize_time / modules
155 lines.append(f"Time spent serializing: {serialize_time}ms, {serialize_time_per_module:.02f}ms/module")
157 return "\n".join(lines)