#!/usr/bin/env python3 from datetime import datetime import gc import linecache import os import sys from threading import Thread import time import traceback import tracemalloc from typing import Optional import constants import renderer import renderer import renderer_catalog import chooser import logging import trigger_catalog import utils def filter_news_during_dinnertime(page: str) -> bool: now = datetime.now() is_dinnertime = now.hour >= 17 and now.hour <= 20 return not is_dinnertime or not ( "cnn" in page or "news" in page or "mynorthwest" in page or "seattle" in page or "stranger" in page or "twitter" in page or "wsj" in page ) def thread_janitor() -> None: tracemalloc.start() tracemalloc_target = 0.0 gc_target = 0.0 gc.enable() while True: now = time.time() if now > tracemalloc_target: tracemalloc_target = now + 30.0 snapshot = tracemalloc.take_snapshot() snapshot = snapshot.filter_traces(( tracemalloc.Filter(False, ""), tracemalloc.Filter(False, ""), )) key_type = 'lineno' limit = 10 top_stats = snapshot.statistics(key_type) print("janitor: Top %s lines" % limit) for index, stat in enumerate(top_stats[:limit], 1): frame = stat.traceback[0] # replace "/path/to/module/file.py" with "module/file.py" filename = os.sep.join(frame.filename.split(os.sep)[-2:]) print("janitor: #%s: %s:%s: %.1f KiB" % (index, filename, frame.lineno, stat.size / 1024)) line = linecache.getline(frame.filename, frame.lineno).strip() if line: print('janitor: %s' % line) other = top_stats[limit:] if other: size = sum(stat.size for stat in other) print("janitor: %s other: %.1f KiB" % (len(other), size / 1024)) total = sum(stat.size for stat in top_stats) print("janitor: Total allocated size: %.1f KiB" % (total / 1024)) if now > gc_target: print("janitor: Running gc operation") gc_target = now + 60.0 gc.collect() time.sleep(10.0) def thread_change_current() -> None: page_chooser = chooser.weighted_random_chooser_with_triggers( trigger_catalog.get_triggers(), [filter_news_during_dinnertime] ) swap_page_target = 0.0 last_page = "" while True: now = time.time() (page, triggered) = page_chooser.choose_next_page() if triggered: print("chooser[%s] - WE ARE TRIGGERED." % utils.timestamp()) if page != last_page: print( "chooser[%s] - EMERGENCY PAGE %s LOAD NEEDED" % (utils.timestamp(), page) ) try: f = open(os.path.join(constants.pages_dir, "current.shtml"), "w") emit_wrapped(f, page) f.close() except: print("chooser[%s] - page does not exist?!" % (utils.timestamp())) continue last_page = page swap_page_target = now + constants.refresh_period_sec # Also notify XMLHTTP clients that they need to refresh now. path = os.path.join(constants.pages_dir, "reload_immediately.html") f = open(path, "w") f.write("Reload, suckers!") f.close() # Fix this hack... maybe read the webserver logs and see if it # actually was picked up? time.sleep(0.750) os.remove(path) elif now >= swap_page_target: if page == last_page: print( ( "chooser[%s] - nominal choice got the same page..." % (utils.timestamp()) ) ) continue print("chooser[%s] - nominal choice of %s" % (utils.timestamp(), page)) try: f = open(os.path.join(constants.pages_dir, "current.shtml"), "w") emit_wrapped(f, page) f.close() except: print("chooser[%s] - page does not exist?!" % (utils.timestamp())) continue last_page = page swap_page_target = now + constants.refresh_period_sec time.sleep(1) def emit_wrapped(f, filename) -> None: def pick_background_color() -> str: now = datetime.now() if now.hour <= 6 or now.hour >= 21: return "E6B8B8" elif now.hour == 7 or now.hour == 20: return "EECDCD" else: return "FFFFFF" def get_refresh_period() -> float: now = datetime.now() if now.hour < 7: return constants.refresh_period_night_sec * 1000 else: return constants.refresh_period_sec * 1000 age = utils.describe_age_of_file_briefly(f"pages/{filename}") bgcolor = pick_background_color() f.write( """ Kitchen Kiosk
 
 

%s @ %s ago.

""" % ( bgcolor, get_refresh_period(), constants.hostname, bgcolor, filename, filename, age, ) ) def thread_invoke_renderers() -> None: while True: print(f"renderer[{utils.timestamp()}]: invoking all renderers in catalog...") for r in renderer_catalog.get_renderers(): now = time.time() try: r.render() except Exception as e: traceback.print_exc() print( f"renderer[{utils.timestamp()}] unknown exception in {r.get_name()}, swallowing it." ) delta = time.time() - now if delta > 1.0: print( f"renderer[{utils.timestamp()}]: Warning: {r.get_name()}'s rendering took {delta:5.2f}s." ) print( f"renderer[{utils.timestamp()}]: thread having a little break for {constants.render_period_sec}s..." ) time.sleep(constants.render_period_sec) if __name__ == "__main__": logging.basicConfig() changer_thread: Optional[Thread] = None renderer_thread: Optional[Thread] = None janitor_thread: Optional[Thread] = None while True: if changer_thread is None or not changer_thread.is_alive(): print( f"MAIN[{utils.timestamp()}] - (Re?)initializing chooser thread... (wtf?!)" ) changer_thread = Thread(target=thread_change_current, args=()) changer_thread.start() if renderer_thread is None or not renderer_thread.is_alive(): print( f"MAIN[{utils.timestamp()}] - (Re?)initializing render thread... (wtf?!)" ) renderer_thread = Thread(target=thread_invoke_renderers, args=()) renderer_thread.start() if janitor_thread is None or not janitor_thread.is_alive(): print( f"MAIN[{utils.timestamp()}] - (Re?)initializing janitor thread... (wtf?!)" ) janitor_thread = Thread(target=thread_janitor, args=()) janitor_thread.start() time.sleep(60)