From a9a56452559dea93ea65662b7d32595c2d5219f1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Apr 2022 20:16:25 -1000 Subject: [PATCH] Defer profiler imports until needed to reduce memory pressure (#70202) --- homeassistant/components/profiler/__init__.py | 29 ++++++++++++++++--- tests/components/profiler/test_init.py | 10 ++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index 0f997dd41bd..978c19dc2ab 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -1,6 +1,5 @@ """The profiler integration.""" import asyncio -import cProfile from datetime import timedelta import logging import reprlib @@ -10,9 +9,6 @@ import time import traceback from typing import Any -from guppy import hpy -import objgraph -from pyprof2calltree import convert import voluptuous as vol from homeassistant.components import persistent_notification @@ -100,6 +96,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return f"Failed to serialize {type(obj)}" def _dump_log_objects(call: ServiceCall) -> None: + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import objgraph # pylint: disable=import-outside-toplevel + obj_type = call.data[CONF_TYPE] _LOGGER.critical( @@ -220,6 +221,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import cProfile # pylint: disable=import-outside-toplevel + start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, @@ -246,6 +252,11 @@ async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + from guppy import hpy # pylint: disable=import-outside-toplevel + start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, @@ -269,6 +280,11 @@ async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall) def _write_profile(profiler, cprofile_path, callgrind_path): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + from pyprof2calltree import convert # pylint: disable=import-outside-toplevel + profiler.create_stats() profiler.dump_stats(cprofile_path) convert(profiler.getstats(), callgrind_path) @@ -279,4 +295,9 @@ def _write_memory_profile(heap, heap_path): def _log_objects(*_): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import objgraph # pylint: disable=import-outside-toplevel + _LOGGER.critical("Memory Growth: %s", objgraph.growth(limit=100)) diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index e4a1f5a5f1b..7196ef16eaf 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -39,9 +39,7 @@ async def test_basic_usage(hass, tmpdir): last_filename = f"{test_dir}/{filename}" return last_filename - with patch("homeassistant.components.profiler.cProfile.Profile"), patch.object( - hass.config, "path", _mock_path - ): + with patch("cProfile.Profile"), patch.object(hass.config, "path", _mock_path): await hass.services.async_call(DOMAIN, SERVICE_START, {CONF_SECONDS: 0.000001}) await hass.async_block_till_done() @@ -70,9 +68,7 @@ async def test_memory_usage(hass, tmpdir): last_filename = f"{test_dir}/{filename}" return last_filename - with patch("homeassistant.components.profiler.hpy") as mock_hpy, patch.object( - hass.config, "path", _mock_path - ): + with patch("guppy.hpy") as mock_hpy, patch.object(hass.config, "path", _mock_path): await hass.services.async_call(DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001}) await hass.async_block_till_done() @@ -94,7 +90,7 @@ async def test_object_growth_logging(hass, caplog): assert hass.services.has_service(DOMAIN, SERVICE_START_LOG_OBJECTS) assert hass.services.has_service(DOMAIN, SERVICE_STOP_LOG_OBJECTS) - with patch("homeassistant.components.profiler.objgraph.growth"): + with patch("objgraph.growth"): await hass.services.async_call( DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 10} )