Skip to content

Commit

Permalink
Add profiler service to investigate ssl object leaks
Browse files Browse the repository at this point in the history
related issue aio-libs/aiohttp#7252

related PR #93013
  • Loading branch information
bdraco committed May 13, 2023
1 parent 18a7c59 commit bc2d3e9
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 15 deletions.
15 changes: 0 additions & 15 deletions homeassistant/components/profiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from contextlib import suppress
from datetime import timedelta
from functools import _lru_cache_wrapper
import json
import logging
import reprlib
import ssl
Expand All @@ -22,10 +21,8 @@
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_TYPE
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.json import ExtendedJSONEncoder
from homeassistant.helpers.service import async_register_admin_service

from .const import DOMAIN
Expand Down Expand Up @@ -88,9 +85,6 @@ async def async_setup_entry( # noqa: C901
domain_data = hass.data[DOMAIN] = {}

async def _async_run_profile(call: ServiceCall) -> None:
_LOGGER.warning(
"Modules: %s", json.dumps(sys.modules, indent=2, cls=ExtendedJSONEncoder)
)
async with lock:
await _async_generate_profile(hass, call)

Expand Down Expand Up @@ -218,15 +212,6 @@ def _lru_stats(call: ServiceCall) -> None:
maybe_lru.get_stats(),
)

_LOGGER.critical(
"Cache stats for LRU template_states: %s",
template.CACHED_TEMPLATE_LRU.get_stats(), # type: ignore[attr-defined]
)
_LOGGER.critical(
"Cache stats for LRU template_states_no_collect: %s",
template.CACHED_TEMPLATE_NO_COLLECT_LRU.get_stats(), # type: ignore[attr-defined]
)

for lru in objgraph.by_type(_SQLALCHEMY_LRU_OBJECT):
if (data := getattr(lru, "_data", None)) and isinstance(data, dict):
for key, value in dict(data).items():
Expand Down
54 changes: 54 additions & 0 deletions tests/components/profiler/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CONF_SECONDS,
SERVICE_DUMP_LOG_OBJECTS,
SERVICE_LOG_EVENT_LOOP_SCHEDULED,
SERVICE_LOG_SSL,
SERVICE_LOG_THREAD_FRAMES,
SERVICE_LRU_STATS,
SERVICE_MEMORY,
Expand Down Expand Up @@ -387,3 +388,56 @@ def __repr__(self):
await hass.services.async_call(
DOMAIN, SERVICE_STOP_LOG_OBJECT_SOURCES, {}, blocking=True
)


async def test_log_ssl(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None:
"""Test logging ssl objects."""

entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)

assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

class SSLProtocol:
def __init__(self):
"""Mock an SSLProtocol."""
self._transport = None

class SSLObject:
def __init__(self):
"""Mock an SSLObject."""
self._transport = None

def getpeercert(self, binary_form=False):
"""Mock getpeercert."""
return {"subject": (("commonName", "test"),)}

def server_hostname(self):
"""Mock server_hostname."""
return "test"

class _SSLProtocolTransport:
def __init__(self):
"""Mock an _SSLProtocolTransport."""

ssl_protocol = SSLProtocol()
ssl_object = SSLObject()
ssl_protocol_transport = _SSLProtocolTransport()
assert hass.services.has_service(DOMAIN, SERVICE_LOG_SSL)

def _mock_by_type(type_):
if type_ == "SSLProtocol":
return [ssl_protocol]
if type_ == "SSLObject":
return [ssl_object]
if type_ == "_SSLProtocolTransport":
return [ssl_protocol_transport]
raise ValueError("Unknown type")

with patch("objgraph.by_type", side_effect=_mock_by_type):
await hass.services.async_call(DOMAIN, SERVICE_LOG_SSL, blocking=True)

assert "SSLProtocol" in caplog.text
assert "SSLObject" in caplog.text
assert "_SSLProtocolTransport" in caplog.text

0 comments on commit bc2d3e9

Please sign in to comment.