irongit

Reusable Django app for system monitoring. Built to make dev environment more like production environment. Combines celery with gunicorn and daphne.

155 lines4.2 KBPython
1import logging
2import signal
3import time
4from subprocess import DEVNULL
5from threading import Thread
6
7from psutil import Popen
8from watchdog.events import RegexMatchingEventHandler
9from watchdog.observers import Observer
10
11from smartwatch import settings
12
13
14def create_collectstatic():
15 return Popen(
16 ["python", "manage.py", "collectstatic", "--noinput"],
17 )
18
19
20def create_migrate():
21 return Popen(["python", "manage.py", "migrate"], stdout=DEVNULL, stderr=DEVNULL)
22
23
24def create_gunicorn():
25 return Popen(
26 [
27 "gunicorn",
28 f"--bind={settings.SMARTWATCH_GUNICORN_HOST}:{settings.SMARTWATCH_GUNICORN_PORT}",
29 f"--workers={settings.SMARTWATCH_GUNICORN_WORKERS}",
30 f"{settings.PROJECT_NAME}.wsgi:application",
31 f"--log-level={settings.SMARTWATCH_GUNICORN_LOGLEVEL}",
32 ]
33 )
34
35
36def create_daphne():
37 return Popen(
38 [
39 "daphne",
40 "-b",
41 "0.0.0.0",
42 "-p",
43 str(settings.SMARTWATCH_DAPHNE_PORT),
44 f"{settings.PROJECT_NAME}.asgi:application",
45 ]
46 )
47
48
49def create_celery():
50 return Popen(
51 [
52 "celery",
53 "-A",
54 settings.PROJECT_NAME,
55 "worker",
56 f"--loglevel={settings.SMARTWATCH_CELERY_LOGLEVEL}",
57 ]
58 )
59
60
61def create_install():
62 return Popen(
63 ["pip", "install", "-r", "requirements.txt"],
64 stdout=DEVNULL,
65 stderr=DEVNULL,
66 )
67
68
69def prep_server():
70 create_install().wait()
71 logging.info("Requirements installed")
72 if settings.SMARTWATCH_MIGRATE:
73 create_migrate().wait()
74 logging.info("Migrated database")
75 if settings.SMARTWATCH_COLLECT_STATIC:
76 create_collectstatic().wait()
77 logging.info("Collected static files")
78
79
80class ServerHandler(RegexMatchingEventHandler):
81 DEBOUNCE_SECONDS = settings.SMARTWATCH_DEBOUNCE_SECONDS
82
83 def __init__(self, *args, **kwargs):
84 regexes = [".*.py", ".*.html", ".*/static/.*", ".*requirements.txt"]
85 super().__init__(*args, **kwargs, regexes=regexes, ignore_directories=True)
86 self.last_modified = time.time()
87 self.changes_queue = ""
88 self.is_restarting = False
89 self.is_running = True
90 """Adds all the changed files into a string to be processed at once"""
91 prep_server()
92 self.gunicorn = create_gunicorn()
93 self.daphne = create_daphne()
94 self.celery = create_celery()
95
96 def on_any_event(self, event):
97 if event.event_type in ["modified", "created", "deleted", "moved"]:
98 self.changes_queue += str(event.src_path)
99 if not self.is_restarting:
100 Thread(target=self.restart).start()
101
102 def restart(self):
103 self.is_restarting = True
104 prep_server()
105 if ".py" or ".html" in self.changes_queue:
106 self.gunicorn.send_signal(signal.SIGINT)
107 self.daphne.send_signal(signal.SIGINT)
108 self.celery.send_signal(signal.SIGINT)
109 self.gunicorn.wait()
110 self.gunicorn = create_gunicorn()
111 self.daphne.wait()
112 self.daphne = create_daphne()
113 self.celery.wait()
114 self.celery = create_celery()
115 self.changes_queue = ""
116 self.is_restarting = False
117
118 def stop(self):
119 self.is_running = False
120 self.gunicorn.send_signal(signal.SIGINT)
121 self.daphne.send_signal(signal.SIGINT)
122 self.celery.send_signal(signal.SIGINT)
123
124 def join(self):
125 self.gunicorn.wait()
126 self.daphne.wait()
127 self.celery.wait()
128
129 def sleep(self, seconds):
130 start = time.time()
131 while self.is_running and time.time() - start < seconds:
132 continue
133
134
135def watch_server():
136 server_handler = ServerHandler()
137 observer = Observer()
138 observer.schedule(server_handler, ".", recursive=True)
139 observer.start()
140
141 def shutdown(*_):
142 logging.warning("Stopping Smartwatch server from SIGKILL...")
143 server_handler.stop()
144 observer.stop()
145 observer.join()
146
147 signal.signal(signal.SIGTERM, shutdown)
148
149 try:
150 while observer.is_alive():
151 observer.join(1)
152 except KeyboardInterrupt:
153 logging.warning("Stopping Smartwatch server...")
154 finally:
155 shutdown()