Defer profiler imports until needed to reduce memory pressure (#70202)

This commit is contained in:
J. Nick Koston 2022-04-17 20:16:25 -10:00 committed by GitHub
parent c53aa50093
commit a9a5645255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 11 deletions

View File

@ -1,6 +1,5 @@
"""The profiler integration.""" """The profiler integration."""
import asyncio import asyncio
import cProfile
from datetime import timedelta from datetime import timedelta
import logging import logging
import reprlib import reprlib
@ -10,9 +9,6 @@ import time
import traceback import traceback
from typing import Any from typing import Any
from guppy import hpy
import objgraph
from pyprof2calltree import convert
import voluptuous as vol import voluptuous as vol
from homeassistant.components import persistent_notification 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)}" return f"Failed to serialize {type(obj)}"
def _dump_log_objects(call: ServiceCall) -> None: 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] obj_type = call.data[CONF_TYPE]
_LOGGER.critical( _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): 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) start_time = int(time.time() * 1000000)
persistent_notification.async_create( persistent_notification.async_create(
hass, hass,
@ -246,6 +252,11 @@ async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall):
async def _async_generate_memory_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) start_time = int(time.time() * 1000000)
persistent_notification.async_create( persistent_notification.async_create(
hass, hass,
@ -269,6 +280,11 @@ async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall)
def _write_profile(profiler, cprofile_path, callgrind_path): 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.create_stats()
profiler.dump_stats(cprofile_path) profiler.dump_stats(cprofile_path)
convert(profiler.getstats(), callgrind_path) convert(profiler.getstats(), callgrind_path)
@ -279,4 +295,9 @@ def _write_memory_profile(heap, heap_path):
def _log_objects(*_): 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)) _LOGGER.critical("Memory Growth: %s", objgraph.growth(limit=100))

View File

@ -39,9 +39,7 @@ async def test_basic_usage(hass, tmpdir):
last_filename = f"{test_dir}/{filename}" last_filename = f"{test_dir}/{filename}"
return last_filename return last_filename
with patch("homeassistant.components.profiler.cProfile.Profile"), patch.object( with patch("cProfile.Profile"), patch.object(hass.config, "path", _mock_path):
hass.config, "path", _mock_path
):
await hass.services.async_call(DOMAIN, SERVICE_START, {CONF_SECONDS: 0.000001}) await hass.services.async_call(DOMAIN, SERVICE_START, {CONF_SECONDS: 0.000001})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -70,9 +68,7 @@ async def test_memory_usage(hass, tmpdir):
last_filename = f"{test_dir}/{filename}" last_filename = f"{test_dir}/{filename}"
return last_filename return last_filename
with patch("homeassistant.components.profiler.hpy") as mock_hpy, patch.object( with patch("guppy.hpy") as mock_hpy, patch.object(hass.config, "path", _mock_path):
hass.config, "path", _mock_path
):
await hass.services.async_call(DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001}) await hass.services.async_call(DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001})
await hass.async_block_till_done() 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_START_LOG_OBJECTS)
assert hass.services.has_service(DOMAIN, SERVICE_STOP_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( await hass.services.async_call(
DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 10} DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 10}
) )