async def check_overdue_and_dead_threads(self): if self.AD.sched.realtime is True and self.AD.thread_duration_warning_threshold != 0: for thread_id in self.threads: if self.threads[thread_id]["thread"].is_alive() is not True: self.logger.critical("Thread %s has died", thread_id) self.logger.critical("Pinned apps were: %s", await self.get_pinned_apps(thread_id)) self.logger.critical("Thread will be restarted") id = thread_id.split("-")[1] await self.add_thread(silent=False, pinthread=False, id=id) if await self.get_state("_threading", "admin", "thread.{}".format(thread_id)) != "idle": start = utils.str_to_dt( await self.get_state( "_threading", "admin", "thread.{}".format(thread_id), attribute="time_called", ) ) dur = (await self.AD.sched.get_now() - start).total_seconds() if ( dur >= self.AD.thread_duration_warning_threshold and dur % self.AD.thread_duration_warning_threshold == 0 ): self.logger.warning( "Excessive time spent in callback: %s - %s", await self.get_state( "_threading", "admin", "thread.{}".format(thread_id), attribute="callback", ), dur, )
async def dump_threads(self): self.diag.info("--------------------------------------------------") self.diag.info("Threads") self.diag.info("--------------------------------------------------") current_busy = await self.get_state("_threading", "admin", "sensor.threads_current_busy") max_busy = await self.get_state("_threading", "admin", "sensor.threads_max_busy") max_busy_time = utils.str_to_dt(await self.get_state("_threading", "admin", "sensor.threads_max_busy_time")) last_action_time = await self.get_state("_threading", "admin", "sensor.threads_last_action_time") self.diag.info("Currently busy threads: %s", current_busy) self.diag.info("Most used threads: %s at %s", max_busy, max_busy_time) self.diag.info("Last activity: %s", last_action_time) self.diag.info("Total Q Entries: %s", self.total_q_size()) self.diag.info("--------------------------------------------------") for thread in sorted(self.threads, key=self.natural_keys): t = await self.get_state("_threading", "admin", "thread.{}".format(thread), attribute="all") # print("thread.{}".format(thread), t) self.diag.info( "%s - qsize: %s | current callback: %s | since %s, | alive: %s, | pinned apps: %s", thread, t["attributes"]["q"], t["state"], t["attributes"]["time_called"], t["attributes"]["is_alive"], await self.get_pinned_apps(thread), ) self.diag.info("--------------------------------------------------")
def as_datetime(args, key): if key in args: if isinstance(args[key], str): return utils.str_to_dt(args(key)).replace(microsecond=0) elif isinstance(args[key], datetime.datetime): return self.AD.tz.localize( args[key]).replace(microsecond=0) else: raise ValueError(f"Invalid type for {key}")
async def update_thread_info(self, thread_id, callback, app, type, uuid): self.logger.debug("Update thread info: %s", thread_id) if self.AD.log_thread_actions: if callback == "idle": self.diag.info( "%s done", thread_id) else: self.diag.info( "%s calling %s callback %s", thread_id, type, callback) now = await self.AD.sched.get_now() if callback == "idle": start = utils.str_to_dt(await self.get_state("_threading", "admin", "thread.{}".format(thread_id), attribute="time_called")) if self.AD.sched.realtime is True and (now - start).total_seconds() >= self.AD.thread_duration_warning_threshold: self.logger.warning("callback %s has now completed", await self.get_state("_threading", "admin", "thread.{}".format(thread_id))) await self.add_to_state("_threading", "admin", "sensor.threads_current_busy", -1) await self.add_to_attr("_threading", "admin", "app.{}".format(app), "callbacks", 1) await self.add_to_attr("_threading", "admin", "{}_callback.{}".format(type, uuid), "executed", 1) await self.add_to_state("_threading", "admin", "sensor.callbacks_total_executed", 1) self.current_callbacks_executed += 1 else: await self.add_to_state("_threading", "admin", "sensor.threads_current_busy", 1) self.current_callbacks_fired += 1 current_busy = await self.get_state("_threading", "admin", "sensor.threads_current_busy") max_busy = await self.get_state("_threading", "admin", "sensor.threads_max_busy") if current_busy > max_busy: await self.set_state("_threading", "admin", "sensor.threads_max_busy" , state=current_busy) await self.set_state("_threading", "admin", "sensor.threads_max_busy_time", state=utils.dt_to_str((await self.AD.sched.get_now()).replace(microsecond=0), self.AD.tz)) await self.set_state("_threading", "admin", "sensor.threads_last_action_time", state=utils.dt_to_str((await self.AD.sched.get_now()).replace(microsecond=0), self.AD.tz)) # Update thread info await self.set_state("_threading", "admin", "thread.{}".format(thread_id), q=self.threads[thread_id]["queue"].qsize(), state=callback, time_called=utils.dt_to_str(now.replace(microsecond=0), self.AD.tz), is_alive = self.threads[thread_id]["thread"].is_alive(), pinned_apps=await self.get_pinned_apps(thread_id) ) await self.set_state("_threading", "admin", "app.{}".format(app), state=callback)
async def update_thread_info(self, thread_id, callback, app, type, uuid, silent): self.logger.debug("Update thread info: %s", thread_id) if silent is True: return if self.AD.log_thread_actions: if callback == "idle": self.diag.info("%s done", thread_id) else: self.diag.info("%s calling %s callback %s", thread_id, type, callback) appinfo = self.AD.app_management.get_app_info(app) if appinfo is None: # app possibly terminated return appentity = "{}.{}".format(appinfo["type"], app) now = await self.AD.sched.get_now() if callback == "idle": start = utils.str_to_dt( await self.get_state("_threading", "admin", "thread.{}".format(thread_id), attribute="time_called",) ) if ( self.AD.sched.realtime is True and (now - start).total_seconds() >= self.AD.thread_duration_warning_threshold ): self.logger.warning( "callback %s has now completed", await self.get_state("_threading", "admin", "thread.{}".format(thread_id)), ) await self.add_to_state("_threading", "admin", "sensor.threads_current_busy", -1) await self.add_to_attr("_threading", "admin", appentity, "totalcallbacks", 1) await self.add_to_attr("_threading", "admin", appentity, "instancecallbacks", 1) await self.add_to_attr( "_threading", "admin", "{}_callback.{}".format(type, uuid), "executed", 1, ) await self.add_to_state("_threading", "admin", "sensor.callbacks_total_executed", 1) self.current_callbacks_executed += 1 else: await self.add_to_state("_threading", "admin", "sensor.threads_current_busy", 1) self.current_callbacks_fired += 1 current_busy = await self.get_state("_threading", "admin", "sensor.threads_current_busy") max_busy = await self.get_state("_threading", "admin", "sensor.threads_max_busy") if current_busy > max_busy: await self.set_state("_threading", "admin", "sensor.threads_max_busy", state=current_busy) await self.set_state( "_threading", "admin", "sensor.threads_max_busy_time", state=utils.dt_to_str((await self.AD.sched.get_now()).replace(microsecond=0), self.AD.tz), ) await self.set_state( "_threading", "admin", "sensor.threads_last_action_time", state=utils.dt_to_str((await self.AD.sched.get_now()).replace(microsecond=0), self.AD.tz), ) # Update thread info if thread_id == "async": await self.set_state( "_threading", "admin", "thread.{}".format(thread_id), q=0, state=callback, time_called=utils.dt_to_str(now.replace(microsecond=0), self.AD.tz), is_alive=True, pinned_apps=[], ) else: await self.set_state( "_threading", "admin", "thread.{}".format(thread_id), q=self.threads[thread_id]["queue"].qsize(), state=callback, time_called=utils.dt_to_str(now.replace(microsecond=0), self.AD.tz), is_alive=self.threads[thread_id]["thread"].is_alive(), pinned_apps=await self.get_pinned_apps(thread_id), ) await self.set_state("_threading", "admin", appentity, state=callback)
async def get_history_api(self, **kwargs): if "entity_id" in kwargs and kwargs["entity_id"] != "": filter_entity_id = "?filter_entity_id={}".format( kwargs["entity_id"]) else: filter_entity_id = "" start_time = "" end_time = "" if "days" in kwargs: days = kwargs["days"] if days - 1 < 0: days = 1 else: days = 1 if "start_time" in kwargs: if isinstance(kwargs["start_time"], str): start_time = utils.str_to_dt( kwargs["start_time"]).replace(microsecond=0) elif isinstance(kwargs["start_time"], datetime.datetime): start_time = self.AD.tz.localize( kwargs["start_time"]).replace(microsecond=0) else: raise ValueError("Invalid type for start time") if "end_time" in kwargs: if isinstance(kwargs["end_time"], str): end_time = utils.str_to_dt( kwargs["end_time"]).replace(microsecond=0) elif isinstance(kwargs["end_time"], datetime.datetime): end_time = self.AD.tz.localize( kwargs["end_time"]).replace(microsecond=0) else: raise ValueError("Invalid type for end time") # if both are declared, it can't process entity_id if start_time != "" and end_time != "": filter_entity_id = "" # if starttime is not declared and entity_id is declared, and days specified elif (filter_entity_id != "" and start_time == "") and "days" in kwargs: start_time = (await self.AD.sched.get_now()).replace( microsecond=0) - datetime.timedelta(days=days) # if starttime is declared and entity_id is not declared, and days specified elif filter_entity_id == "" and start_time != "" and end_time == "" and "days" in kwargs: end_time = start_time + datetime.timedelta(days=days) # if endtime is declared and entity_id is not declared, and days specified elif filter_entity_id == "" and end_time != "" and start_time == "" and "days" in kwargs: start_time = end_time - datetime.timedelta(days=days) if start_time != "": timestamp = "/{}".format( utils.dt_to_str(start_time.replace(microsecond=0), self.AD.tz)) if filter_entity_id != "": # if entity_id is specified, end_time cannot be used end_time = "" if end_time != "": end_time = "?end_time={}".format( quote( utils.dt_to_str(end_time.replace(microsecond=0), self.AD.tz))) # if no start_time is specified, other parameters are invalid else: timestamp = "" end_time = "" return "{}/api/history/period{}{}{}".format(self.config["ha_url"], timestamp, filter_entity_id, end_time)
async def call_plugin_service(self, namespace, domain, service, data): self.logger.debug( "call_plugin_service() namespace=%s domain=%s service=%s data=%s", namespace, domain, service, data) # # If data is a string just assume it's an entity_id # if isinstance(data, str): data = {"entity_id": data} config = (await self.AD.plugins.get_plugin_object(namespace)).config if "token" in config: headers = {'Authorization': "Bearer {}".format(config["token"])} elif "ha_key" in config: headers = {'x-ha-access': config["ha_key"]} else: headers = {} if domain == "database": if "entity_id" in data and data["entity_id"] != "": filter_entity_id = "?filter_entity_id={}".format( data["entity_id"]) else: filter_entity_id = "" start_time = "" end_time = "" if "days" in data: days = data["days"] if days - 1 < 0: days = 1 else: days = 1 if "start_time" in data: if isinstance(data["start_time"], str): start_time = utils.str_to_dt( data["start_time"]).replace(microsecond=0) elif isinstance(data["start_time"], datetime.datetime): start_time = self.AD.tz.localize( data["start_time"]).replace(microsecond=0) else: raise ValueError("Invalid type for start time") if "end_time" in data: if isinstance(data["end_time"], str): end_time = utils.str_to_dt( data["end_time"]).replace(microsecond=0) elif isinstance(data["end_time"], datetime.datetime): end_time = self.AD.tz.localize( data["end_time"]).replace(microsecond=0) else: raise ValueError("Invalid type for end time") #if both are declared, it can't process entity_id if start_time != "" and end_time != "": filter_entity_id = "" #if starttime is not declared and entity_id is declared, and days specified elif (filter_entity_id != "" and start_time == "") and "days" in data: start_time = (await self.AD.sched.get_now()).replace( microsecond=0) - datetime.timedelta(days=days) #if starttime is declared and entity_id is not declared, and days specified elif filter_entity_id == "" and start_time != "" and end_time == "" and "days" in data: end_time = start_time + datetime.timedelta(days=days) #if endtime is declared and entity_id is not declared, and days specified elif filter_entity_id == "" and end_time != "" and start_time == "" and "days" in data: start_time = end_time - datetime.timedelta(days=days) if start_time != "": timestamp = "/{}".format( utils.dt_to_str(start_time.replace(microsecond=0), self.AD.tz)) if filter_entity_id != "": #if entity_id is specified, end_time cannot be used end_time = "" if end_time != "": end_time = "?end_time={}".format( quote( utils.dt_to_str(end_time.replace(microsecond=0), self.AD.tz))) # if no start_time is specified, other parameters are invalid else: timestamp = "" end_time = "" api_url = "{}/api/history/period{}{}{}".format( config["ha_url"], timestamp, filter_entity_id, end_time) elif domain == "template": api_url = "{}/api/template".format(config["ha_url"]) else: api_url = "{}/api/services/{}/{}".format(config["ha_url"], domain, service) try: if domain == "database": r = await self.session.get(api_url, headers=headers, verify_ssl=self.cert_verify) else: r = await self.session.post(api_url, headers=headers, json=data, verify_ssl=self.cert_verify) if r.status == 200 or r.status == 201: if domain == "template": result = await r.text() else: result = await r.json() else: self.logger.warning( "Error calling Home Assistant service %s/%s/%s", namespace, domain, service) txt = await r.text() self.logger.warning("Code: %s, error: %s", r.status, txt) result = None return result except (asyncio.TimeoutError, asyncio.CancelledError): self.logger.warning("Timeout in call_service(%s/%s/%s, %s)", namespace, domain, service, data) except aiohttp.client_exceptions.ServerDisconnectedError: self.logger.warning( "HASS Disconnected unexpectedly during call_service()") except: self.logger.warning('-' * 60) self.logger.warning( "Unexpected error during call_plugin_service()") self.logger.warning("Service: %s.%s.%s Arguments: %s", namespace, domain, service, data) self.logger.warning('-' * 60) self.logger.warning(traceback.format_exc()) self.logger.warning('-' * 60) return None