Add guppy3 memory profile to Profiler integration (#42435)

* add guppy memory profile to profiler integration

* add output path to notification

* create new service for memory profile

* address review comments
This commit is contained in:
Jason Hunter 2020-10-28 21:05:16 -04:00 committed by GitHub
parent 3fb091c4ea
commit 2a795c0397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 9 deletions

View File

@ -3,6 +3,7 @@ import asyncio
import cProfile
import time
from guppy import hpy
from pyprof2calltree import convert
import voluptuous as vol
@ -14,6 +15,7 @@ from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
SERVICE_START = "start"
SERVICE_MEMORY = "memory"
CONF_SECONDS = "seconds"
@ -31,6 +33,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
async with lock:
await _async_generate_profile(hass, call)
async def _async_run_memory_profile(call: ServiceCall):
async with lock:
await _async_generate_memory_profile(hass, call)
async_register_admin_service(
hass,
DOMAIN,
@ -41,6 +47,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
),
)
async_register_admin_service(
hass,
DOMAIN,
SERVICE_MEMORY,
_async_run_memory_profile,
schema=vol.Schema(
{vol.Optional(CONF_SECONDS, default=60.0): vol.Coerce(float)}
),
)
return True
@ -53,7 +69,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall):
start_time = int(time.time() * 1000000)
hass.components.persistent_notification.async_create(
"The profile started. This notification will be updated when it is complete.",
"The profile has started. This notification will be updated when it is complete.",
title="Profile Started",
notification_id=f"profiler_{start_time}",
)
@ -74,7 +90,32 @@ async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall):
)
async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall):
start_time = int(time.time() * 1000000)
hass.components.persistent_notification.async_create(
"The memory profile has started. This notification will be updated when it is complete.",
title="Profile Started",
notification_id=f"memory_profiler_{start_time}",
)
heap_profiler = hpy()
heap_profiler.setref()
await asyncio.sleep(float(call.data[CONF_SECONDS]))
heap = heap_profiler.heap()
heap_path = hass.config.path(f"heap_profile.{start_time}.hpy")
await hass.async_add_executor_job(_write_memory_profile, heap, heap_path)
hass.components.persistent_notification.async_create(
f"Wrote heapy memory profile to {heap_path}",
title="Profile Complete",
notification_id=f"memory_profiler_{start_time}",
)
def _write_profile(profiler, cprofile_path, callgrind_path):
profiler.create_stats()
profiler.dump_stats(cprofile_path)
convert(profiler.getstats(), callgrind_path)
def _write_memory_profile(heap, heap_path):
heap.byrcs.dump(heap_path)

View File

@ -2,12 +2,8 @@
"domain": "profiler",
"name": "Profiler",
"documentation": "https://www.home-assistant.io/integrations/profiler",
"requirements": [
"pyprof2calltree==1.4.5"
],
"codeowners": [
"@bdraco"
],
"requirements": ["pyprof2calltree==1.4.5", "guppy3==3.1.0"],
"codeowners": ["@bdraco"],
"quality_scale": "internal",
"config_flow": true
}
}

View File

@ -4,3 +4,9 @@ start:
seconds:
description: The number of seconds to run the profiler.
example: 60.0
memory:
description: Start the Memory Profiler
fields:
seconds:
description: The number of seconds to run the memory profiler.
example: 60.0

View File

@ -716,6 +716,9 @@ growattServer==0.1.1
# homeassistant.components.gstreamer
gstreamer-player==1.1.2
# homeassistant.components.profiler
guppy3==3.1.0
# homeassistant.components.ffmpeg
ha-ffmpeg==2.0

View File

@ -360,6 +360,9 @@ greeclimate==0.9.0
# homeassistant.components.griddy
griddypower==0.1.0
# homeassistant.components.profiler
guppy3==3.1.0
# homeassistant.components.ffmpeg
ha-ffmpeg==2.0

View File

@ -2,7 +2,11 @@
import os
from homeassistant import setup
from homeassistant.components.profiler import CONF_SECONDS, SERVICE_START
from homeassistant.components.profiler import (
CONF_SECONDS,
SERVICE_MEMORY,
SERVICE_START,
)
from homeassistant.components.profiler.const import DOMAIN
from tests.async_mock import patch
@ -39,3 +43,35 @@ async def test_basic_usage(hass, tmpdir):
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
async def test_memory_usage(hass, tmpdir):
"""Test we can setup and the service is registered."""
test_dir = tmpdir.mkdir("profiles")
await setup.async_setup_component(hass, "persistent_notification", {})
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()
assert hass.services.has_service(DOMAIN, SERVICE_MEMORY)
last_filename = None
def _mock_path(filename):
nonlocal last_filename
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
):
await hass.services.async_call(DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001})
await hass.async_block_till_done()
mock_hpy.assert_called_once()
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()