File size: 5,323 Bytes
d631808
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# This module contains utilities to compute loading statistics,
# like time spent visiting modules statically or dynamically.

from __future__ import annotations

from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING

from _griffe.enumerations import Kind

if TYPE_CHECKING:
    from _griffe.loader import GriffeLoader
    from _griffe.models import Alias, Object


class Stats:
    """Load statistics for a Griffe loader."""

    def __init__(self, loader: GriffeLoader) -> None:
        """Initialiwe the stats object.

        Parameters:
            loader: The loader to compute stats for.
        """
        self.loader = loader
        """The loader to compute stats for."""

        modules_by_extension = defaultdict(
            int,
            {
                "": 0,
                ".py": 0,
                ".pyi": 0,
                ".pyc": 0,
                ".pyo": 0,
                ".pyd": 0,
                ".so": 0,
            },
        )

        top_modules = loader.modules_collection.members.values()

        self.by_kind = {
            Kind.MODULE: 0,
            Kind.CLASS: 0,
            Kind.FUNCTION: 0,
            Kind.ATTRIBUTE: 0,
        }
        """Number of objects by kind."""

        self.packages = len(top_modules)
        """Number of packages."""

        self.modules_by_extension = modules_by_extension
        """Number of modules by extension."""

        self.lines = sum(len(lines) for lines in loader.lines_collection.values())
        """Total number of lines."""

        self.time_spent_visiting = 0
        """Time spent visiting modules."""

        self.time_spent_inspecting = 0
        """Time spent inspecting modules."""

        self.time_spent_serializing = 0
        """Time spent serializing objects."""

        for module in top_modules:
            self._itercount(module)

    def _itercount(self, root: Object | Alias) -> None:
        if root.is_alias:
            return
        self.by_kind[root.kind] += 1
        if root.is_module:
            if isinstance(root.filepath, Path):
                self.modules_by_extension[root.filepath.suffix] += 1
            elif root.filepath is None:
                self.modules_by_extension[""] += 1
        for member in root.members.values():
            self._itercount(member)

    def as_text(self) -> str:
        """Format the statistics as text.

        Returns:
            Text stats.
        """
        lines = []
        packages = self.packages
        modules = self.by_kind[Kind.MODULE]
        classes = self.by_kind[Kind.CLASS]
        functions = self.by_kind[Kind.FUNCTION]
        attributes = self.by_kind[Kind.ATTRIBUTE]
        objects = sum((modules, classes, functions, attributes))
        lines.append("Statistics")
        lines.append("---------------------")
        lines.append("Number of loaded objects")
        lines.append(f"  Modules: {modules}")
        lines.append(f"  Classes: {classes}")
        lines.append(f"  Functions: {functions}")
        lines.append(f"  Attributes: {attributes}")
        lines.append(f"  Total: {objects} across {packages} packages")
        per_ext = self.modules_by_extension
        builtin = per_ext[""]
        regular = per_ext[".py"]
        stubs = per_ext[".pyi"]
        compiled = modules - builtin - regular - stubs
        lines.append("")
        lines.append(f"Total number of lines: {self.lines}")
        lines.append("")
        lines.append("Modules")
        lines.append(f"  Builtin: {builtin}")
        lines.append(f"  Compiled: {compiled}")
        lines.append(f"  Regular: {regular}")
        lines.append(f"  Stubs: {stubs}")
        lines.append("  Per extension:")
        for ext, number in sorted(per_ext.items()):
            if ext:
                lines.append(f"    {ext}: {number}")

        visit_time = self.time_spent_visiting / 1000
        inspect_time = self.time_spent_inspecting / 1000
        total_time = visit_time + inspect_time
        visit_percent = visit_time / total_time * 100
        inspect_percent = inspect_time / total_time * 100

        force_inspection = self.loader.force_inspection
        visited_modules = 0 if force_inspection else regular
        try:
            visit_time_per_module = visit_time / visited_modules
        except ZeroDivisionError:
            visit_time_per_module = 0

        inspected_modules = builtin + compiled + (regular if force_inspection else 0)
        try:
            inspect_time_per_module = inspect_time / inspected_modules
        except ZeroDivisionError:
            inspect_time_per_module = 0

        lines.append("")
        lines.append(
            f"Time spent visiting modules ({visited_modules}): "
            f"{visit_time}ms, {visit_time_per_module:.02f}ms/module ({visit_percent:.02f}%)",
        )
        lines.append(
            f"Time spent inspecting modules ({inspected_modules}): "
            f"{inspect_time}ms, {inspect_time_per_module:.02f}ms/module ({inspect_percent:.02f}%)",
        )

        serialize_time = self.time_spent_serializing / 1000
        serialize_time_per_module = serialize_time / modules
        lines.append(f"Time spent serializing: {serialize_time}ms, {serialize_time_per_module:.02f}ms/module")

        return "\n".join(lines)