diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index 99d074d95fe..0f997dd41bd 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -8,6 +8,7 @@ import sys import threading import time import traceback +from typing import Any from guppy import hpy import objgraph @@ -87,13 +88,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: persistent_notification.async_dismiss(hass, "profile_object_logging") domain_data.pop(LOG_INTERVAL_SUB)() + def _safe_repr(obj: Any) -> str: + """Get the repr of an object but keep going if there is an exception. + + We wrap repr to ensure if one object cannot be serialized, we can + still get the rest. + """ + try: + return repr(obj) + except Exception: # pylint: disable=broad-except + return f"Failed to serialize {type(obj)}" + def _dump_log_objects(call: ServiceCall) -> None: obj_type = call.data[CONF_TYPE] _LOGGER.critical( "%s objects in memory: %s", obj_type, - objgraph.by_type(obj_type), + [_safe_repr(obj) for obj in objgraph.by_type(obj_type)], ) persistent_notification.create( diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 37e48763131..4c88e6170c6 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -131,19 +131,31 @@ async def test_dump_log_object(hass, caplog): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + class DumpLogDummy: + def __init__(self, fail): + self.fail = fail + + def __repr__(self): + if self.fail: + raise Exception("failed") + return "" + + obj1 = DumpLogDummy(False) + obj2 = DumpLogDummy(True) + assert hass.services.has_service(DOMAIN, SERVICE_DUMP_LOG_OBJECTS) await hass.services.async_call( - DOMAIN, SERVICE_DUMP_LOG_OBJECTS, {CONF_TYPE: "MockConfigEntry"} + DOMAIN, SERVICE_DUMP_LOG_OBJECTS, {CONF_TYPE: "DumpLogDummy"} ) await hass.async_block_till_done() - assert "MockConfigEntry" in caplog.text + assert "" in caplog.text + assert "Failed to serialize" in caplog.text + del obj1 + del obj2 caplog.clear() - assert await hass.config_entries.async_unload(entry.entry_id) - await hass.async_block_till_done() - async def test_log_thread_frames(hass, caplog): """Test we can log thread frames."""