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."""
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))

View file

@ -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}
)