diff --git a/addon_examples/appearance_delay_tracker.py b/addon_examples/appearance_delay_tracker.py new file mode 100644 index 0000000..e17eba6 --- /dev/null +++ b/addon_examples/appearance_delay_tracker.py @@ -0,0 +1,94 @@ +""" +Try and diagnose very slow avatar appearance loads when the avatars first come on the scene + +I guess use LEAP or something to detect when things _actually_ declouded. +""" +from typing import * + +import dataclasses +import datetime as dt + +from hippolyzer.lib.base.datatypes import UUID +from hippolyzer.lib.base.message.message import Message +from hippolyzer.lib.base.objects import Object +from hippolyzer.lib.base.templates import PCode +from hippolyzer.lib.proxy.addon_utils import BaseAddon, GlobalProperty +from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow +from hippolyzer.lib.proxy.region import ProxiedRegion +from hippolyzer.lib.proxy.sessions import Session, SessionManager + + +@dataclasses.dataclass +class AvatarBakeRequest: + requested: dt.datetime + received: Optional[dt.datetime] = None + + +@dataclasses.dataclass +class AvatarAppearanceRecord: + object_received: dt.datetime + """When we learned about the agent as an object""" + appearance_received: Optional[dt.datetime] = None + """When AvatarAppearance was first received""" + bake_requests: Dict[str, AvatarBakeRequest] = dataclasses.field(default_factory=dict) + """Layer name -> request / response details""" + + +class AppearanceDelayTrackerAddon(BaseAddon): + # Should be able to access this in the REPL + # Normally we'd use a session property, but we may not have a proper session context for some requests + av_appearance_data: Dict[UUID, AvatarAppearanceRecord] = GlobalProperty(dict) + + def handle_object_updated(self, session: Session, region: ProxiedRegion, + obj: Object, updated_props: Set[str], msg: Optional[Message]): + if obj.PCode == PCode.AVATAR and obj.FullID not in self.av_appearance_data: + self.av_appearance_data[obj.FullID] = AvatarAppearanceRecord(object_received=dt.datetime.now()) + + def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message): + if message.name != "AvatarAppearance": + return + agent_id = message["Sender"]["ID"] + appearance_data = self.av_appearance_data.get(agent_id) + if not appearance_data: + print(f"Got appearance for {agent_id} without knowing about object?") + return + + if appearance_data.appearance_received: + return + appearance_data.appearance_received = dt.datetime.now() + + def handle_http_request(self, session_manager: SessionManager, flow: HippoHTTPFlow): + if not flow.cap_data: + return + if flow.cap_data.cap_name != "AppearanceService": + return + + agent_id = UUID(flow.request.url.split('/')[-3]) + slot_name = flow.request.url.split('/')[-2] + appearance_data = self.av_appearance_data.get(agent_id) + if not appearance_data: + print(f"Got AppearanceService req for {agent_id} without knowing about object?") + return + if slot_name in appearance_data.bake_requests: + # We already requested this slot before + return + appearance_data.bake_requests[slot_name] = AvatarBakeRequest(requested=dt.datetime.now()) + + def handle_http_response(self, session_manager: SessionManager, flow: HippoHTTPFlow): + if not flow.cap_data: + return + if flow.cap_data.cap_name != "AppearanceService": + return + + agent_id = UUID(flow.request.url.split('/')[-3]) + slot_name = flow.request.url.split('/')[-2] + appearance_data = self.av_appearance_data.get(agent_id) + if not appearance_data: + return + slot_details = appearance_data.bake_requests.get(slot_name) + if not slot_details: + return + slot_details.received = dt.datetime.now() + + +addons = [AppearanceDelayTrackerAddon()]