# 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)