Merge pull request #45 from craigerl/flask

Flask
This commit is contained in:
Walter A. Boring IV 2021-01-22 08:21:20 -05:00 committed by GitHub
commit f932c203d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 320 additions and 27 deletions

View File

@ -7,7 +7,7 @@ import re
import smtplib import smtplib
import time import time
from aprsd import messaging, threads from aprsd import messaging, stats, threads
import imapclient import imapclient
from validate_email import validate_email from validate_email import validate_email
@ -269,6 +269,7 @@ def send_email(to_addr, content):
[to_addr], [to_addr],
msg.as_string(), msg.as_string(),
) )
stats.APRSDStats().email_tx_inc()
except Exception as e: except Exception as e:
msg = getattr(e, "message", repr(e)) msg = getattr(e, "message", repr(e))
LOG.error("Sendmail Error!!!! '{}'", msg) LOG.error("Sendmail Error!!!! '{}'", msg)
@ -366,6 +367,7 @@ class APRSDEmailThread(threads.APRSDThread):
past = datetime.datetime.now() past = datetime.datetime.now()
while not self.thread_stop: while not self.thread_stop:
time.sleep(5) time.sleep(5)
stats.APRSDStats().email_thread_update()
# always sleep for 5 seconds and see if we need to check email # always sleep for 5 seconds and see if we need to check email
# This allows CTRL-C to stop the execution of this loop sooner # This allows CTRL-C to stop the execution of this loop sooner
# than check_email_delay time # than check_email_delay time

38
aprsd/flask.py Normal file
View File

@ -0,0 +1,38 @@
import json
import aprsd
from aprsd import messaging, stats
import flask
import flask_classful
class APRSDFlask(flask_classful.FlaskView):
config = None
def set_config(self, config):
self.config = config
def index(self):
return "Hello"
# return flask.render_template("index.html", message=msg)
def stats(self):
stats_obj = stats.APRSDStats()
track = messaging.MsgTrack()
result = {
"version": aprsd.__version__,
"uptime": stats_obj.uptime,
"size_tracker": len(track),
"stats": stats_obj.stats(),
}
return json.dumps(result)
def init_flask(config):
flask_app = flask.Flask("aprsd")
server = APRSDFlask()
server.set_config(config)
# flask_app.route('/', methods=['GET'])(server.index)
flask_app.route("/stats", methods=["GET"])(server.stats)
return flask_app

View File

@ -32,7 +32,7 @@ import time
# local imports here # local imports here
import aprsd import aprsd
from aprsd import client, email, messaging, plugin, threads, utils from aprsd import client, email, flask, messaging, plugin, stats, threads, utils
import aprslib import aprslib
from aprslib.exceptions import LoginError from aprslib.exceptions import LoginError
import click import click
@ -157,7 +157,9 @@ def signal_handler(sig, frame):
) )
threads.APRSDThreadList().stop_all() threads.APRSDThreadList().stop_all()
server_event.set() server_event.set()
time.sleep(1) LOG.info("EXITING STATS")
LOG.info(stats.APRSDStats())
# time.sleep(1)
signal.signal(signal.SIGTERM, sys.exit(0)) signal.signal(signal.SIGTERM, sys.exit(0))
@ -384,19 +386,12 @@ def send_message(
default=False, default=False,
help="Flush out all old aged messages on disk.", help="Flush out all old aged messages on disk.",
) )
@click.option(
"--stats-server",
is_flag=True,
default=False,
help="Run a stats web server on port 5001?",
)
def server( def server(
loglevel, loglevel,
quiet, quiet,
disable_validation, disable_validation,
config_file, config_file,
flush, flush,
stats_server,
): ):
"""Start the aprsd server process.""" """Start the aprsd server process."""
global event global event
@ -416,6 +411,7 @@ def server(
setup_logging(config, loglevel, quiet) setup_logging(config, loglevel, quiet)
LOG.info("APRSD Started version: {}".format(aprsd.__version__)) LOG.info("APRSD Started version: {}".format(aprsd.__version__))
stats.APRSDStats(config)
email_enabled = config["aprsd"]["email"].get("enabled", False) email_enabled = config["aprsd"]["email"].get("enabled", False)
@ -463,18 +459,25 @@ def server(
messaging.MsgTrack().restart() messaging.MsgTrack().restart()
cntr = 0 keepalive = threads.KeepAliveThread()
while not server_event.is_set(): keepalive.start()
# to keep the log noise down
if cntr % 12 == 0: try:
tracker = messaging.MsgTrack() web_enabled = utils.check_config_option(config, ["aprsd", "web", "enabled"])
LOG.debug("KeepAlive Tracker({}): {}".format(len(tracker), str(tracker))) except Exception:
cntr += 1 web_enabled = False
time.sleep(10)
if web_enabled:
app = flask.init_flask(config)
app.run(
host=config["aprsd"]["web"]["host"],
port=config["aprsd"]["web"]["port"],
)
# If there are items in the msgTracker, then save them # If there are items in the msgTracker, then save them
tracker = messaging.MsgTrack() tracker = messaging.MsgTrack()
tracker.save() tracker.save()
LOG.info(stats.APRSDStats())
LOG.info("APRSD Exiting.") LOG.info("APRSD Exiting.")

View File

@ -9,7 +9,7 @@ import re
import threading import threading
import time import time
from aprsd import client, threads, utils from aprsd import client, stats, threads, utils
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -49,7 +49,7 @@ class MsgTrack:
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance.track = {} cls._instance.track = {}
cls._start_time = datetime.datetime.now() cls._instance._start_time = datetime.datetime.now()
cls._instance.lock = threading.Lock() cls._instance.lock = threading.Lock()
return cls._instance return cls._instance
@ -57,6 +57,7 @@ class MsgTrack:
with self.lock: with self.lock:
key = int(msg.id) key = int(msg.id)
self.track[key] = msg self.track[key] = msg
stats.APRSDStats().msgs_tracked_inc()
self.total_messages_tracked += 1 self.total_messages_tracked += 1
def get(self, id): def get(self, id):
@ -251,6 +252,7 @@ class RawMessage(Message):
fromcall=self.fromcall, fromcall=self.fromcall,
) )
cl.sendall(repr(self)) cl.sendall(repr(self))
stats.APRSDStats().msgs_sent_inc()
class TextMessage(Message): class TextMessage(Message):
@ -267,7 +269,7 @@ class TextMessage(Message):
def __repr__(self): def __repr__(self):
"""Build raw string to send over the air.""" """Build raw string to send over the air."""
return "{}>APRS::{}:{}{{{}\n".format( return "{}>APZ100::{}:{}{{{}\n".format(
self.fromcall, self.fromcall,
self.tocall.ljust(9), self.tocall.ljust(9),
self._filter_for_send(), self._filter_for_send(),
@ -315,6 +317,7 @@ class TextMessage(Message):
fromcall=self.fromcall, fromcall=self.fromcall,
) )
cl.sendall(repr(self)) cl.sendall(repr(self))
stats.APRSDStats().msgs_tx_inc()
class SendMessageThread(threads.APRSDThread): class SendMessageThread(threads.APRSDThread):
@ -374,6 +377,7 @@ class SendMessageThread(threads.APRSDThread):
msg_num=msg.id, msg_num=msg.id,
) )
cl.sendall(repr(msg)) cl.sendall(repr(msg))
stats.APRSDStats().msgs_tx_inc()
msg.last_send_time = datetime.datetime.now() msg.last_send_time = datetime.datetime.now()
msg.last_send_attempt += 1 msg.last_send_attempt += 1
@ -389,7 +393,7 @@ class AckMessage(Message):
super().__init__(fromcall, tocall, msg_id=msg_id) super().__init__(fromcall, tocall, msg_id=msg_id)
def __repr__(self): def __repr__(self):
return "{}>APRS::{}:ack{}\n".format( return "{}>APZ100::{}:ack{}\n".format(
self.fromcall, self.fromcall,
self.tocall.ljust(9), self.tocall.ljust(9),
self.id, self.id,
@ -411,6 +415,7 @@ class AckMessage(Message):
retry_number=i, retry_number=i,
) )
cl.sendall(repr(self)) cl.sendall(repr(self))
stats.APRSDStats().ack_tx_inc()
# aprs duplicate detection is 30 secs? # aprs duplicate detection is 30 secs?
# (21 only sends first, 28 skips middle) # (21 only sends first, 28 skips middle)
time.sleep(31) time.sleep(31)
@ -478,6 +483,7 @@ class SendAckThread(threads.APRSDThread):
retry_number=self.ack.last_send_attempt, retry_number=self.ack.last_send_attempt,
) )
cl.sendall(repr(self.ack)) cl.sendall(repr(self.ack))
stats.APRSDStats().ack_tx_inc()
self.ack.last_send_attempt += 1 self.ack.last_send_attempt += 1
self.ack.last_send_time = datetime.datetime.now() self.ack.last_send_time = datetime.datetime.now()
time.sleep(5) time.sleep(5)

View File

@ -48,7 +48,7 @@ class LocationPlugin(plugin.APRSDPluginBase):
lat = aprs_data["entries"][0]["lat"] lat = aprs_data["entries"][0]["lat"]
lon = aprs_data["entries"][0]["lng"] lon = aprs_data["entries"][0]["lng"]
try: # altitude not always provided try: # altitude not always provided
alt = aprs_data["entries"][0]["altitude"] alt = float(aprs_data["entries"][0]["altitude"])
except Exception: except Exception:
alt = 0 alt = 0
altfeet = int(alt * 3.28084) altfeet = int(alt * 3.28084)

161
aprsd/stats.py Normal file
View File

@ -0,0 +1,161 @@
import datetime
import logging
import threading
LOG = logging.getLogger("APRSD")
class APRSDStats:
_instance = None
lock = None
config = None
start_time = None
_msgs_tracked = 0
_msgs_tx = 0
_msgs_rx = 0
_msgs_mice_rx = 0
_ack_tx = 0
_ack_rx = 0
_email_thread_last_time = None
_email_tx = 0
_email_rx = 0
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
# any initializetion here
cls._instance.lock = threading.Lock()
cls._instance.start_time = datetime.datetime.now()
return cls._instance
def __init__(self, config=None):
if config:
self.config = config
@property
def uptime(self):
with self.lock:
return str(datetime.datetime.now() - self.start_time)
@property
def msgs_tx(self):
with self.lock:
return self._msgs_tx
def msgs_tx_inc(self):
with self.lock:
self._msgs_tx += 1
@property
def msgs_rx(self):
with self.lock:
return self._msgs_rx
def msgs_rx_inc(self):
with self.lock:
self._msgs_rx += 1
@property
def msgs_mice_rx(self):
with self.lock:
return self._msgs_mice_rx
def msgs_mice_inc(self):
with self.lock:
self._msgs_mice_rx += 1
@property
def ack_tx(self):
with self.lock:
return self._ack_tx
def ack_tx_inc(self):
with self.lock:
self._ack_tx += 1
@property
def ack_rx(self):
with self.lock:
return self._ack_rx
def ack_rx_inc(self):
with self.lock:
self._ack_rx += 1
@property
def msgs_tracked(self):
with self.lock:
return self._msgs_tracked
def msgs_tracked_inc(self):
with self.lock:
self._msgs_tracked += 1
@property
def email_tx(self):
with self.lock:
return self._email_tx
def email_tx_inc(self):
with self.lock:
self._email_tx += 1
@property
def email_rx(self):
with self.lock:
return self._email_rx
def email_rx_inc(self):
with self.lock:
self._email_rx += 1
@property
def email_thread_time(self):
with self.lock:
return self._email_thread_last_time
def email_thread_update(self):
with self.lock:
self._email_thread_last_time = datetime.datetime.now()
def stats(self):
now = datetime.datetime.now()
stats = {
"messages": {
"tracked": self.msgs_tracked,
"sent": self.msgs_tx,
"recieved": self.msgs_rx,
"ack_sent": self.ack_tx,
"ack_recieved": self.ack_rx,
"mic-e recieved": self.msgs_mice_rx,
},
"email": {
"sent": self._email_tx,
"recieved": self._email_rx,
"thread_last_update": str(now - self._email_thread_last_time),
},
}
LOG.debug("STATS {}".format(stats))
return stats
def __str__(self):
return (
"Msgs TX:{} RX:{} ACK: TX:{} RX:{} "
"Email TX:{} RX:{} LastLoop:{} "
"Uptime: {}".format(
self._msgs_tx,
self._msgs_rx,
self._ack_tx,
self._ack_rx,
self._email_tx,
self._email_rx,
self._email_thread_last_time,
self.uptime,
)
)

View File

@ -0,0 +1,4 @@
<html>
<body><h1>{{ message }}</h1></body>
</html>

View File

@ -1,10 +1,11 @@
import abc import abc
import datetime
import logging import logging
import queue import queue
import threading import threading
import time import time
from aprsd import client, messaging, plugin from aprsd import client, messaging, plugin, stats
import aprslib import aprslib
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -63,6 +64,37 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
LOG.debug("Exiting") LOG.debug("Exiting")
class KeepAliveThread(APRSDThread):
cntr = 0
def __init__(self):
super().__init__("KeepAlive")
def loop(self):
if self.cntr % 6 == 0:
tracker = messaging.MsgTrack()
stats_obj = stats.APRSDStats()
now = datetime.datetime.now()
last_email = stats.APRSDStats().email_thread_time
if last_email:
email_thread_time = str(now - last_email)
else:
email_thread_time = "N/A"
LOG.debug(
"Tracker({}) EmailThread: {} "
" Msgs: TX:{} RX:{}".format(
len(tracker),
email_thread_time,
stats_obj.msgs_tx,
stats_obj.msgs_rx,
),
)
self.cntr += 1
time.sleep(10)
return True
class APRSDRXThread(APRSDThread): class APRSDRXThread(APRSDThread):
def __init__(self, msg_queues, config): def __init__(self, msg_queues, config):
super().__init__("RX_MSG") super().__init__("RX_MSG")
@ -118,11 +150,13 @@ class APRSDRXThread(APRSDThread):
) )
tracker = messaging.MsgTrack() tracker = messaging.MsgTrack()
tracker.remove(ack_num) tracker.remove(ack_num)
stats.APRSDStats().ack_rx_inc()
return return
def process_mic_e_packet(self, packet): def process_mic_e_packet(self, packet):
LOG.info("Mic-E Packet detected. Currenlty unsupported.") LOG.info("Mic-E Packet detected. Currenlty unsupported.")
messaging.log_packet(packet) messaging.log_packet(packet)
stats.APRSDStats().msgs_mice_inc()
return return
def process_message_packet(self, packet): def process_message_packet(self, packet):
@ -196,6 +230,7 @@ class APRSDRXThread(APRSDThread):
try: try:
LOG.info("Got message: {}".format(packet)) LOG.info("Got message: {}".format(packet))
stats.APRSDStats().msgs_rx_inc()
msg = packet.get("message_text", None) msg = packet.get("message_text", None)
msg_format = packet.get("format", None) msg_format = packet.get("format", None)

View File

@ -25,6 +25,11 @@ DEFAULT_CONFIG_DICT = {
"plugin_dir": "~/.config/aprsd/plugins", "plugin_dir": "~/.config/aprsd/plugins",
"enabled_plugins": plugin.CORE_PLUGINS, "enabled_plugins": plugin.CORE_PLUGINS,
"units": "imperial", "units": "imperial",
"web": {
"enabled": True,
"host": "0.0.0.0",
"port": 8001,
},
"email": { "email": {
"enabled": True, "enabled": True,
"shortcuts": { "shortcuts": {

View File

@ -23,6 +23,14 @@ aprsd.client module
aprsd.dev module aprsd.dev module
---------------- ----------------
.. automodule:: aprsd.dev
:members:
:undoc-members:
:show-inheritance:
aprsd.dev module
----------------
.. automodule:: aprsd.dev .. automodule:: aprsd.dev
:members: :members:
:undoc-members: :undoc-members:
@ -44,6 +52,14 @@ aprsd.fake\_aprs module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
aprsd.flask module
------------------
.. automodule:: aprsd.flask
:members:
:undoc-members:
:show-inheritance:
aprsd.fuzzyclock module aprsd.fuzzyclock module
----------------------- -----------------------
@ -79,6 +95,14 @@ aprsd.plugin module
aprsd.plugin\_utils module aprsd.plugin\_utils module
-------------------------- --------------------------
.. automodule:: aprsd.plugin_utils
:members:
:undoc-members:
:show-inheritance:
aprsd.plugin\_utils module
--------------------------
.. automodule:: aprsd.plugin_utils .. automodule:: aprsd.plugin_utils
:members: :members:
:undoc-members: :undoc-members:

View File

@ -12,3 +12,5 @@ py3-validate-email
pre-commit pre-commit
pytz pytz
opencage opencage
flask
flask_classful

View File

@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile # This file is autogenerated by pip-compile
# To update, run: # To update, run:
# #
# pip-compile # pip-compile requirements.in
# #
appdirs==1.4.4 appdirs==1.4.4
# via virtualenv # via virtualenv
@ -24,6 +24,7 @@ click==7.1.2
# via # via
# -r requirements.in # -r requirements.in
# click-completion # click-completion
# flask
cryptography==3.3.1 cryptography==3.3.1
# via pyopenssl # via pyopenssl
distlib==0.3.1 distlib==0.3.1
@ -34,6 +35,12 @@ filelock==3.0.12
# via # via
# py3-validate-email # py3-validate-email
# virtualenv # virtualenv
flask-classful==0.14.2
# via -r requirements.in
flask==1.1.2
# via
# -r requirements.in
# flask-classful
identify==1.5.13 identify==1.5.13
# via pre-commit # via pre-commit
idna==2.10 idna==2.10
@ -42,8 +49,12 @@ idna==2.10
# requests # requests
imapclient==2.2.0 imapclient==2.2.0
# via -r requirements.in # via -r requirements.in
itsdangerous==1.1.0
# via flask
jinja2==2.11.2 jinja2==2.11.2
# via click-completion # via
# click-completion
# flask
markupsafe==1.1.1 markupsafe==1.1.1
# via jinja2 # via jinja2
nodeenv==1.5.0 nodeenv==1.5.0
@ -64,7 +75,7 @@ pyopenssl==20.0.1
# via opencage # via opencage
pytz==2020.5 pytz==2020.5
# via -r requirements.in # via -r requirements.in
pyyaml==5.3.1 pyyaml==5.4.1
# via # via
# -r requirements.in # -r requirements.in
# pre-commit # pre-commit
@ -91,3 +102,5 @@ urllib3==1.26.2
# via requests # via requests
virtualenv==20.4.0 virtualenv==20.4.0
# via pre-commit # via pre-commit
werkzeug==1.0.1
# via flask