mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
1 line
No EOL
15 KiB
JavaScript
1 line
No EOL
15 KiB
JavaScript
"use strict";(globalThis.webpackChunkdocs_split_developer=globalThis.webpackChunkdocs_split_developer||[]).push([[3624],{6496:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"performance","title":"Performance Optimization","description":"Guidelines for maintaining and improving integration performance.","source":"@site/docs/performance.md","sourceDirName":".","slug":"/performance","permalink":"/hass.tibber_prices/developer/performance","draft":false,"unlisted":false,"editUrl":"https://github.com/jpawlowski/hass.tibber_prices/tree/main/docs/developer/docs/performance.md","tags":[],"version":"current","lastUpdatedAt":1764985026000,"frontMatter":{},"sidebar":"tutorialSidebar","previous":{"title":"Refactoring Guide","permalink":"/hass.tibber_prices/developer/refactoring-guide"},"next":{"title":"Contributing Guide","permalink":"/hass.tibber_prices/developer/contributing"}}');var a=r(4848),s=r(8453);const t={},l="Performance Optimization",c={},o=[{value:"Performance Goals",id:"performance-goals",level:2},{value:"Profiling",id:"profiling",level:2},{value:"Timing Decorator",id:"timing-decorator",level:3},{value:"Memory Profiling",id:"memory-profiling",level:3},{value:"Async Profiling",id:"async-profiling",level:3},{value:"Optimization Patterns",id:"optimization-patterns",level:2},{value:"Caching",id:"caching",level:3},{value:"Lazy Loading",id:"lazy-loading",level:3},{value:"Bulk Operations",id:"bulk-operations",level:3},{value:"Async Best Practices",id:"async-best-practices",level:3},{value:"Memory Management",id:"memory-management",level:2},{value:"Avoid Memory Leaks",id:"avoid-memory-leaks",level:3},{value:"Efficient Data Structures",id:"efficient-data-structures",level:3},{value:"Coordinator Optimization",id:"coordinator-optimization",level:2},{value:"Minimize API Calls",id:"minimize-api-calls",level:3},{value:"Smart Updates",id:"smart-updates",level:3},{value:"Database Impact",id:"database-impact",level:2},{value:"State Class Selection",id:"state-class-selection",level:3},{value:"Attribute Size",id:"attribute-size",level:3},{value:"Testing Performance",id:"testing-performance",level:2},{value:"Benchmark Tests",id:"benchmark-tests",level:3},{value:"Load Testing",id:"load-testing",level:3},{value:"Monitoring in Production",id:"monitoring-in-production",level:2},{value:"Log Performance Metrics",id:"log-performance-metrics",level:3},{value:"Memory Tracking",id:"memory-tracking",level:3}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"performance-optimization",children:"Performance Optimization"})}),"\n",(0,a.jsx)(n.p,{children:"Guidelines for maintaining and improving integration performance."}),"\n",(0,a.jsx)(n.h2,{id:"performance-goals",children:"Performance Goals"}),"\n",(0,a.jsx)(n.p,{children:"Target metrics:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Coordinator update"}),": <500ms (typical: 200-300ms)"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Sensor update"}),": <10ms per sensor"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Period calculation"}),": <100ms (typical: 20-50ms)"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Memory footprint"}),": <10MB per home"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"API calls"}),": <100 per day per home"]}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"profiling",children:"Profiling"}),"\n",(0,a.jsx)(n.h3,{id:"timing-decorator",children:"Timing Decorator"}),"\n",(0,a.jsx)(n.p,{children:"Use for performance-critical functions:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'import time\nimport functools\n\ndef timing(func):\n @functools.wraps(func)\n def wrapper(*args, **kwargs):\n start = time.perf_counter()\n result = func(*args, **kwargs)\n duration = time.perf_counter() - start\n _LOGGER.debug("%s took %.3fms", func.__name__, duration * 1000)\n return result\n return wrapper\n\n@timing\ndef expensive_calculation():\n # Your code here\n'})}),"\n",(0,a.jsx)(n.h3,{id:"memory-profiling",children:"Memory Profiling"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'import tracemalloc\n\ntracemalloc.start()\n# Run your code\ncurrent, peak = tracemalloc.get_traced_memory()\n_LOGGER.info("Memory: current=%.2fMB peak=%.2fMB",\n current / 1024**2, peak / 1024**2)\ntracemalloc.stop()\n'})}),"\n",(0,a.jsx)(n.h3,{id:"async-profiling",children:"Async Profiling"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"# Install aioprof\nuv pip install aioprof\n\n# Run with profiling\npython -m aioprof homeassistant -c config\n"})}),"\n",(0,a.jsx)(n.h2,{id:"optimization-patterns",children:"Optimization Patterns"}),"\n",(0,a.jsx)(n.h3,{id:"caching",children:"Caching"}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"1. Persistent Cache"})," (API data):"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# Already implemented in coordinator/cache.py\nstore = Store(hass, STORAGE_VERSION, STORAGE_KEY)\ndata = await store.async_load()\n"})}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"2. Translation Cache"})," (in-memory):"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'# Already implemented in const.py\n_TRANSLATION_CACHE: dict[str, dict] = {}\n\ndef get_translation(path: str, language: str) -> dict:\n cache_key = f"{path}_{language}"\n if cache_key not in _TRANSLATION_CACHE:\n _TRANSLATION_CACHE[cache_key] = load_translation(path, language)\n return _TRANSLATION_CACHE[cache_key]\n'})}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"3. Config Cache"})," (invalidated on options change):"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"class DataTransformer:\n def __init__(self):\n self._config_cache: dict | None = None\n\n def get_config(self) -> dict:\n if self._config_cache is None:\n self._config_cache = self._build_config()\n return self._config_cache\n\n def invalidate_config_cache(self):\n self._config_cache = None\n"})}),"\n",(0,a.jsx)(n.h3,{id:"lazy-loading",children:"Lazy Loading"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Load data only when needed:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'@property\ndef extra_state_attributes(self) -> dict | None:\n """Return attributes."""\n # Calculate only when accessed\n if self.entity_description.key == "complex_sensor":\n return self._calculate_complex_attributes()\n return None\n'})}),"\n",(0,a.jsx)(n.h3,{id:"bulk-operations",children:"Bulk Operations"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Process multiple items at once:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# \u274c Slow - loop with individual operations\nfor interval in intervals:\n enriched = enrich_single_interval(interval)\n results.append(enriched)\n\n# \u2705 Fast - bulk processing\nresults = enrich_intervals_bulk(intervals)\n"})}),"\n",(0,a.jsx)(n.h3,{id:"async-best-practices",children:"Async Best Practices"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"1. Concurrent API calls:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# \u274c Sequential (slow)\nuser_data = await fetch_user_data()\nprice_data = await fetch_price_data()\n\n# \u2705 Concurrent (fast)\nuser_data, price_data = await asyncio.gather(\n fetch_user_data(),\n fetch_price_data()\n)\n"})}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"2. Don't block event loop:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# \u274c Blocking\nresult = heavy_computation() # Blocks for seconds\n\n# \u2705 Non-blocking\nresult = await hass.async_add_executor_job(heavy_computation)\n"})}),"\n",(0,a.jsx)(n.h2,{id:"memory-management",children:"Memory Management"}),"\n",(0,a.jsx)(n.h3,{id:"avoid-memory-leaks",children:"Avoid Memory Leaks"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"1. Clear references:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'class Coordinator:\n async def async_shutdown(self):\n """Clean up resources."""\n self._listeners.clear()\n self._data = None\n self._cache = None\n'})}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"2. Use weak references for callbacks:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"import weakref\n\nclass Manager:\n def __init__(self):\n self._callbacks: list[weakref.ref] = []\n\n def register(self, callback):\n self._callbacks.append(weakref.ref(callback))\n"})}),"\n",(0,a.jsx)(n.h3,{id:"efficient-data-structures",children:"Efficient Data Structures"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Use appropriate types:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# \u274c List for lookups (O(n))\nif timestamp in timestamp_list:\n ...\n\n# \u2705 Set for lookups (O(1))\nif timestamp in timestamp_set:\n ...\n\n# \u274c List comprehension with filter\nresults = [x for x in items if condition(x)]\n\n# \u2705 Generator for large datasets\nresults = (x for x in items if condition(x))\n"})}),"\n",(0,a.jsx)(n.h2,{id:"coordinator-optimization",children:"Coordinator Optimization"}),"\n",(0,a.jsx)(n.h3,{id:"minimize-api-calls",children:"Minimize API Calls"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Already implemented:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Cache valid until midnight"}),"\n",(0,a.jsx)(n.li,{children:"User data cached for 24h"}),"\n",(0,a.jsx)(n.li,{children:"Only poll when tomorrow data expected"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Monitor API usage:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'_LOGGER.debug("API call: %s (cache_age=%s)",\n endpoint, cache_age)\n'})}),"\n",(0,a.jsx)(n.h3,{id:"smart-updates",children:"Smart Updates"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Only update when needed:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'async def _async_update_data(self) -> dict:\n """Fetch data from API."""\n if self._is_cache_valid():\n _LOGGER.debug("Using cached data")\n return self.data\n\n # Fetch new data\n return await self._fetch_data()\n'})}),"\n",(0,a.jsx)(n.h2,{id:"database-impact",children:"Database Impact"}),"\n",(0,a.jsx)(n.h3,{id:"state-class-selection",children:"State Class Selection"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Affects long-term statistics storage:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:"# \u274c MEASUREMENT for prices (stores every change)\nstate_class=SensorStateClass.MEASUREMENT # ~35K records/year\n\n# \u2705 None for prices (no long-term stats)\nstate_class=None # Only current state\n\n# \u2705 TOTAL for counters only\nstate_class=SensorStateClass.TOTAL # For cumulative values\n"})}),"\n",(0,a.jsx)(n.h3,{id:"attribute-size",children:"Attribute Size"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Keep attributes minimal:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'# \u274c Large nested structures (KB per update)\nattributes = {\n "all_intervals": [...], # 384 intervals\n "full_history": [...], # Days of data\n}\n\n# \u2705 Essential data only (bytes per update)\nattributes = {\n "timestamp": "...",\n "rating_level": "...",\n "next_interval": "...",\n}\n'})}),"\n",(0,a.jsx)(n.h2,{id:"testing-performance",children:"Testing Performance"}),"\n",(0,a.jsx)(n.h3,{id:"benchmark-tests",children:"Benchmark Tests"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'import pytest\nimport time\n\n@pytest.mark.benchmark\ndef test_period_calculation_performance(coordinator):\n """Period calculation should complete in <100ms."""\n start = time.perf_counter()\n\n periods = calculate_periods(coordinator.data)\n\n duration = time.perf_counter() - start\n assert duration < 0.1, f"Too slow: {duration:.3f}s"\n'})}),"\n",(0,a.jsx)(n.h3,{id:"load-testing",children:"Load Testing"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'@pytest.mark.integration\nasync def test_multiple_homes_performance(hass):\n """Test with 10 homes."""\n coordinators = []\n for i in range(10):\n coordinator = create_coordinator(hass, home_id=f"home_{i}")\n await coordinator.async_refresh()\n coordinators.append(coordinator)\n\n # Verify memory usage\n # Verify update times\n'})}),"\n",(0,a.jsx)(n.h2,{id:"monitoring-in-production",children:"Monitoring in Production"}),"\n",(0,a.jsx)(n.h3,{id:"log-performance-metrics",children:"Log Performance Metrics"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'@timing\nasync def _async_update_data(self) -> dict:\n """Fetch data with timing."""\n result = await self._fetch_data()\n _LOGGER.info("Update completed in %.2fs", timing_duration)\n return result\n'})}),"\n",(0,a.jsx)(n.h3,{id:"memory-tracking",children:"Memory Tracking"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",children:'import psutil\nimport os\n\nprocess = psutil.Process(os.getpid())\nmemory_mb = process.memory_info().rss / 1024**2\n_LOGGER.debug("Current memory usage: %.2f MB", memory_mb)\n'})}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsxs)(n.p,{children:["\ud83d\udca1 ",(0,a.jsx)(n.strong,{children:"Related:"})]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"/hass.tibber_prices/developer/caching-strategy",children:"Caching Strategy"})," - Cache layers"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"/hass.tibber_prices/developer/architecture",children:"Architecture"})," - System design"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"/hass.tibber_prices/developer/debugging",children:"Debugging"})," - Profiling tools"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}}}]); |