google_nest_sdm.diagnostics
Diagnostics for debugging.
1"""Diagnostics for debugging.""" 2 3from __future__ import annotations 4 5import time 6from collections import Counter 7from collections.abc import Mapping 8from contextlib import contextmanager 9from typing import Any, Generator, TypeVar, cast 10 11__all__ = [ 12 "get_diagnostics", 13] 14 15 16class Diagnostics: 17 """Information for the library.""" 18 19 def __init__(self) -> None: 20 """Initialize Diagnostics.""" 21 self._counter: Counter = Counter() 22 self._subkeys: dict[str, Diagnostics] = {} 23 24 def increment(self, key: str, count: int = 1) -> None: 25 """Increment a counter for the specified key/event.""" 26 self._counter.update(Counter({key: count})) 27 28 def elapsed(self, key_prefix: str, elapsed_ms: int = 1) -> None: 29 """Track a latency event for the specified key/event prefix.""" 30 self.increment(f"{key_prefix}_count", 1) 31 self.increment(f"{key_prefix}_sum", elapsed_ms) 32 33 def as_dict(self) -> Mapping[str, Any]: 34 """Return diagnostics as a debug dictionary.""" 35 data: dict[str, Any] = {k: self._counter[k] for k in self._counter} 36 for k, d in self._subkeys.items(): 37 v = d.as_dict() 38 if not v: 39 continue 40 data[k] = v 41 return data 42 43 def subkey(self, key: str) -> Diagnostics: 44 """Return sub-Diagnositics object with the specified subkey.""" 45 if key not in self._subkeys: 46 self._subkeys[key] = Diagnostics() 47 return self._subkeys[key] 48 49 @contextmanager 50 def timer(self, key_prefix: str) -> Generator[None, None, None]: 51 """A context manager that records the timing of operations as a diagnostic.""" 52 start = time.perf_counter() 53 try: 54 yield 55 finally: 56 end = time.perf_counter() 57 ms = int((end - start) * 1000) 58 self.elapsed(key_prefix, ms) 59 60 def reset(self) -> None: 61 """Clear all diagnostics, for testing.""" 62 self._counter = Counter() 63 for d in self._subkeys.values(): 64 d.reset() 65 66 67SUBSCRIBER_DIAGNOSTICS = Diagnostics() 68DEVICE_MANAGER_DIAGNOSTICS = Diagnostics() 69EVENT_DIAGNOSTICS = Diagnostics() 70EVENT_MEDIA_DIAGNOSTICS = Diagnostics() 71STREAMING_MANAGER_DIAGNOSTICS = Diagnostics() 72 73MAP = { 74 "subscriber": SUBSCRIBER_DIAGNOSTICS, 75 "device_manager": DEVICE_MANAGER_DIAGNOSTICS, 76 "event": EVENT_DIAGNOSTICS, 77 "event_media": EVENT_MEDIA_DIAGNOSTICS, 78 "streaming_manager": STREAMING_MANAGER_DIAGNOSTICS, 79} 80 81 82def reset() -> None: 83 """Clear all diagnostics, for testing.""" 84 for diagnostics in MAP.values(): 85 diagnostics.reset() 86 87 88def get_diagnostics() -> dict[str, Any]: 89 """Produce diagnostics information for the library.""" 90 return {k: v.as_dict() for (k, v) in MAP.items() if v.as_dict()} 91 92 93REDACT_KEYS = { 94 "name", 95 "custom_name", 96 "displayName", 97 "parent", 98 "assignee", 99 "subject", 100 "object", 101 "userId", 102 "resourceGroup", 103 "eventId", 104 "eventSessionId", 105 "eventThreadId", 106} 107REDACTED = "**REDACTED**" 108 109 110T = TypeVar("T") 111 112 113def redact_data(data: T) -> T | dict | list: 114 """Redact sensitive data in a dict.""" 115 if not isinstance(data, (Mapping, list)): 116 return data 117 118 if isinstance(data, list): 119 return cast(T, [redact_data(item) for item in data]) 120 121 redacted = {**data} 122 123 for key, value in redacted.items(): 124 if key in REDACT_KEYS: 125 redacted[key] = REDACTED 126 elif isinstance(value, dict): 127 redacted[key] = redact_data(value) 128 elif isinstance(value, list): 129 redacted[key] = [redact_data(item) for item in value] 130 131 return redacted
def
get_diagnostics() -> dict[str, typing.Any]:
89def get_diagnostics() -> dict[str, Any]: 90 """Produce diagnostics information for the library.""" 91 return {k: v.as_dict() for (k, v) in MAP.items() if v.as_dict()}
Produce diagnostics information for the library.