Coverage for src/griffe/_internal/stats.py: 41.07%
96 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-11 13:44 +0200
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-11 13:44 +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._internal.enumerations import Kind
12if TYPE_CHECKING:
13 from griffe._internal.loader import GriffeLoader
14 from griffe._internal.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 Kind.TYPE_ALIAS: 0,
50 }
51 """Number of objects by kind."""
53 self.packages = len(top_modules)
54 """Number of packages."""
56 self.modules_by_extension = modules_by_extension
57 """Number of modules by extension."""
59 self.lines = sum(len(lines) for lines in loader.lines_collection.values())
60 """Total number of lines."""
62 self.time_spent_visiting = 0
63 """Time spent visiting modules."""
65 self.time_spent_inspecting = 0
66 """Time spent inspecting modules."""
68 self.time_spent_serializing = 0
69 """Time spent serializing objects."""
71 for module in top_modules:
72 self._itercount(module)
74 def _itercount(self, root: Object | Alias) -> None:
75 if root.is_alias:
76 return
77 self.by_kind[root.kind] += 1
78 if root.is_module:
79 if isinstance(root.filepath, Path): 79 ↛ 81line 79 didn't jump to line 81 because the condition on line 79 was always true
80 self.modules_by_extension[root.filepath.suffix] += 1
81 elif root.filepath is None:
82 self.modules_by_extension[""] += 1
83 for member in root.members.values():
84 self._itercount(member)
86 def as_text(self) -> str:
87 """Format the statistics as text.
89 Returns:
90 Text stats.
91 """
92 lines = []
93 packages = self.packages
94 modules = self.by_kind[Kind.MODULE]
95 classes = self.by_kind[Kind.CLASS]
96 functions = self.by_kind[Kind.FUNCTION]
97 attributes = self.by_kind[Kind.ATTRIBUTE]
98 type_aliases = self.by_kind[Kind.TYPE_ALIAS]
99 objects = sum((modules, classes, functions, attributes, type_aliases))
100 lines.append("Statistics")
101 lines.append("---------------------")
102 lines.append("Number of loaded objects")
103 lines.append(f" Modules: {modules}")
104 lines.append(f" Classes: {classes}")
105 lines.append(f" Functions: {functions}")
106 lines.append(f" Attributes: {attributes}")
107 lines.append(f" Type aliases: {type_aliases}")
108 lines.append(f" Total: {objects} across {packages} packages")
109 per_ext = self.modules_by_extension
110 builtin = per_ext[""]
111 regular = per_ext[".py"]
112 stubs = per_ext[".pyi"]
113 compiled = modules - builtin - regular - stubs
114 lines.append("")
115 lines.append(f"Total number of lines: {self.lines}")
116 lines.append("")
117 lines.append("Modules")
118 lines.append(f" Builtin: {builtin}")
119 lines.append(f" Compiled: {compiled}")
120 lines.append(f" Regular: {regular}")
121 lines.append(f" Stubs: {stubs}")
122 lines.append(" Per extension:")
123 for ext, number in sorted(per_ext.items()):
124 if ext:
125 lines.append(f" {ext}: {number}")
127 visit_time = self.time_spent_visiting / 1000
128 inspect_time = self.time_spent_inspecting / 1000
129 total_time = visit_time + inspect_time
130 visit_percent = visit_time / total_time * 100
131 inspect_percent = inspect_time / total_time * 100
133 force_inspection = self.loader.force_inspection
134 visited_modules = 0 if force_inspection else regular
135 try:
136 visit_time_per_module = visit_time / visited_modules
137 except ZeroDivisionError:
138 visit_time_per_module = 0
140 inspected_modules = builtin + compiled + (regular if force_inspection else 0)
141 try:
142 inspect_time_per_module = inspect_time / inspected_modules
143 except ZeroDivisionError:
144 inspect_time_per_module = 0
146 lines.append("")
147 lines.append(
148 f"Time spent visiting modules ({visited_modules}): "
149 f"{visit_time}ms, {visit_time_per_module:.02f}ms/module ({visit_percent:.02f}%)",
150 )
151 lines.append(
152 f"Time spent inspecting modules ({inspected_modules}): "
153 f"{inspect_time}ms, {inspect_time_per_module:.02f}ms/module ({inspect_percent:.02f}%)",
154 )
156 serialize_time = self.time_spent_serializing / 1000
157 serialize_time_per_module = serialize_time / modules
158 lines.append(f"Time spent serializing: {serialize_time}ms, {serialize_time_per_module:.02f}ms/module")
160 return "\n".join(lines)