فهرست منبع

Add diff timing instrumentation and fix CalDAV cache bypasses

diff() now records per-stage and total wall-clock timings, printed at
the end of every CLI diff run.

CalDAVBackend.fetch_range() was calling fetch_events() directly,
bypassing the Calendar-level cache. Route it through fetch_date_range()
so repeated fetches of the same range within a run are free. Also fixes
two other callers that bypassed the cache: busy/push_caldav() was
constructing a throwaway Calendar instance; ical_feeds/_fetch_caldav()
called fetch_events() on the shared instance. Both now use
fetch_date_range().

open_dates() in rules/_utils.py was awaiting closed and reservation
fetches sequentially; convert to asyncio.gather().
ZiB 4 ماه پیش
والد
کامیت
8eb50548dd
6فایلهای تغییر یافته به همراه44 افزوده شده و 32 حذف شده
  1. 8 0
      room_cal/actions/_cli.py
  2. 21 10
      room_cal/actions/diff.py
  3. 4 14
      room_cal/busy/__init__.py
  4. 4 4
      room_cal/ical_feeds/__init__.py
  5. 4 3
      room_cal/rules/_utils.py
  6. 3 1
      room_cal/store/_caldav.py

+ 8 - 0
room_cal/actions/_cli.py

@@ -287,6 +287,14 @@ async def _diff(start: date, end: date, args) -> None:
   if not any_diff:
   if not any_diff:
     print("  no differences")
     print("  no differences")
 
 
+  timings = result.get('_timings', {})
+  if timings:
+    total = timings.pop('_total', None)
+    header = f"  Timings  (wall: {total:.1f}s)" if total is not None else "  Timings"
+    print(f"\n{header}")
+    for k, v in sorted(timings.items(), key=lambda x: -x[1]):
+      print(f"    {k:<22} {v:.2f}s")
+
 
 
 _ACTIONS: dict[str, tuple] = {
 _ACTIONS: dict[str, tuple] = {
   'fetch-reservations':   (_fetch_reservations,   'Fetch reservations from all services and write to CalDAV'),
   'fetch-reservations':   (_fetch_reservations,   'Fetch reservations from all services and write to CalDAV'),

+ 21 - 10
room_cal/actions/diff.py

@@ -1,4 +1,5 @@
 import asyncio
 import asyncio
+import time
 from datetime import date, timedelta
 from datetime import date, timedelta
 from typing import TYPE_CHECKING
 from typing import TYPE_CHECKING
 
 
@@ -23,6 +24,13 @@ ACTION_SPEC = ('diff', [])
 _PRICE_PREFIX = 'Price: $'
 _PRICE_PREFIX = 'Price: $'
 
 
 
 
+async def _timed(key: str, coro, timings: dict):
+  t0 = time.perf_counter()
+  result = await coro
+  timings[key] = round(time.perf_counter() - t0, 2)
+  return result
+
+
 async def diff(
 async def diff(
   start_date: date,
   start_date: date,
   end_date: date,
   end_date: date,
@@ -46,19 +54,22 @@ async def diff(
   from room_cal.service_api import ServiceAPI as _ServiceAPI
   from room_cal.service_api import ServiceAPI as _ServiceAPI
   if api is None:
   if api is None:
     api = _ServiceAPI()
     api = _ServiceAPI()
+  timings: dict[str, float] = {}
+  t0 = time.perf_counter()
   results = await asyncio.gather(
   results = await asyncio.gather(
-    _diff_prices(start_date, end_date, api),
-    _diff_closed(start_date, end_date, api),
-    _diff_reservations(start_date, end_date, api),
-    _diff_user_cal_closed(start_date, end_date),
-    _diff_ical_feeds_closed(start_date, end_date),
-    _diff_rule_discounts(start_date, end_date),
-    _diff_rule_closed(start_date, end_date),
-    _diff_rule_prices(start_date, end_date),
-    _diff_orphans(start_date, end_date),
+    _timed('prices',            _diff_prices(start_date, end_date, api),  timings),
+    _timed('closed',            _diff_closed(start_date, end_date, api),  timings),
+    _timed('reservations',      _diff_reservations(start_date, end_date, api), timings),
+    _timed('user_cal_closed',   _diff_user_cal_closed(start_date, end_date),   timings),
+    _timed('ical_feeds_closed', _diff_ical_feeds_closed(start_date, end_date), timings),
+    _timed('rule_discounts',    _diff_rule_discounts(start_date, end_date),    timings),
+    _timed('rule_closed',       _diff_rule_closed(start_date, end_date),       timings),
+    _timed('rule_prices',       _diff_rule_prices(start_date, end_date),       timings),
+    _timed('orphans',           _diff_orphans(start_date, end_date),           timings),
   )
   )
+  timings['_total'] = round(time.perf_counter() - t0, 2)
   keys = ['prices', 'closed', 'reservations', 'user_cal_closed', 'ical_feeds_closed', 'rule_discounts', 'rule_closed', 'rule_prices', 'orphans']
   keys = ['prices', 'closed', 'reservations', 'user_cal_closed', 'ical_feeds_closed', 'rule_discounts', 'rule_closed', 'rule_prices', 'orphans']
-  return dict(zip(keys, results))
+  return {**dict(zip(keys, results)), '_timings': timings}
 
 
 
 
 # ── prices ────────────────────────────────────────────────────────────────────
 # ── prices ────────────────────────────────────────────────────────────────────

+ 4 - 14
room_cal/busy/__init__.py

@@ -1,6 +1,6 @@
 import asyncio
 import asyncio
 import hashlib
 import hashlib
-from datetime import date, datetime, timedelta
+from datetime import date, timedelta
 
 
 from room_cal.closed import Closed
 from room_cal.closed import Closed
 from room_cal.reservations import Reservation
 from room_cal.reservations import Reservation
@@ -72,20 +72,10 @@ async def push_caldav(busy_dates: list[date], start_date: date, end_date: date)
 
 
   Returns (added, removed) date lists.
   Returns (added, removed) date lists.
   """
   """
-  from room_cal.store._caldav import Calendar
-
-  cfg = get_config()
-  cal_cfg = cfg.store.caldav
-  cal = Calendar(
-    _get_calendar_name(),
-    url=cal_cfg.url,
-    username=cal_cfg.username,
-    password=cal_cfg.password,
-  )
+  from room_cal.store import get_calendar
 
 
-  start = datetime(start_date.year, start_date.month, start_date.day)
-  end = datetime(end_date.year, end_date.month, end_date.day) + timedelta(days=1)
-  existing = await cal.fetch_events(start, end)
+  cal = get_calendar(_get_calendar_name())
+  existing = await cal.fetch_date_range(start_date, end_date)
 
 
   existing_by_date = {
   existing_by_date = {
     e['start'].date(): e
     e['start'].date(): e

+ 4 - 4
room_cal/ical_feeds/__init__.py

@@ -62,12 +62,12 @@ async def _fetch_caldav(entry: CalendarEntry) -> list[EventRecord]:
   from room_cal.store import get_calendar
   from room_cal.store import get_calendar
 
 
   calendar_name = entry.caldav
   calendar_name = entry.caldav
-  today = datetime.today()
-  start = today - timedelta(days=entry.past_days if entry.past_days is not None else _DEFAULT_PAST_DAYS)
-  end = today + timedelta(days=entry.future_days if entry.future_days is not None else _DEFAULT_FUTURE_DAYS)
+  today = datetime.today().date()
+  start_date = today - timedelta(days=entry.past_days if entry.past_days is not None else _DEFAULT_PAST_DAYS)
+  end_date = today + timedelta(days=entry.future_days if entry.future_days is not None else _DEFAULT_FUTURE_DAYS)
 
 
   cal = get_calendar(calendar_name)
   cal = get_calendar(calendar_name)
-  raw_events = await cal.fetch_events(start, end)
+  raw_events = await cal.fetch_date_range(start_date, end_date)
   return [
   return [
     EventRecord(
     EventRecord(
       uid=e['uid'],
       uid=e['uid'],

+ 4 - 3
room_cal/rules/_utils.py

@@ -1,3 +1,4 @@
+import asyncio
 from datetime import date, timedelta
 from datetime import date, timedelta
 
 
 from room_cal.closed import Closed
 from room_cal.closed import Closed
@@ -13,9 +14,9 @@ async def open_dates(start_date: date, end_date: date) -> set[date]:
   """
   """
   closed_cal = Closed()
   closed_cal = Closed()
   res_cal = Reservation()
   res_cal = Reservation()
-  closed_events, res_events = (
-    await closed_cal.list(start_date, end_date),
-    await res_cal.list(start_date, end_date),
+  closed_events, res_events = await asyncio.gather(
+    closed_cal.list(start_date, end_date),
+    res_cal.list(start_date, end_date),
   )
   )
 
 
   def _rule_sourced(e) -> bool:
   def _rule_sourced(e) -> bool:

+ 3 - 1
room_cal/store/_caldav.py

@@ -243,7 +243,9 @@ class CalDAVBackend:
     self._cal._cached_url = None
     self._cal._cached_url = None
 
 
   async def fetch_range(self, start: datetime, end: datetime) -> list[EventRecord]:
   async def fetch_range(self, start: datetime, end: datetime) -> list[EventRecord]:
-    events = await self._cal.fetch_events(start, end)
+    start_date = start.date()
+    end_date = (end - timedelta(days=1)).date()
+    events = await self._cal.fetch_date_range(start_date, end_date)
     return [_dict_to_record(e) for e in events]
     return [_dict_to_record(e) for e in events]
 
 
   async def create(self, record: EventRecord) -> EventRecord:
   async def create(self, record: EventRecord) -> EventRecord: