aprsd 3.4.3__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aprsd/cli_helper.py +12 -5
- aprsd/client/aprsis.py +68 -17
- aprsd/client/base.py +60 -12
- aprsd/client/drivers/aprsis.py +11 -5
- aprsd/client/drivers/fake.py +15 -20
- aprsd/client/factory.py +6 -3
- aprsd/client/fake.py +5 -4
- aprsd/client/kiss.py +43 -7
- aprsd/client/stats.py +2 -22
- aprsd/cmds/completion.py +7 -4
- aprsd/cmds/dev.py +39 -43
- aprsd/cmds/fetch_stats.py +221 -69
- aprsd/cmds/healthcheck.py +7 -5
- aprsd/cmds/list_plugins.py +140 -134
- aprsd/cmds/listen.py +102 -11
- aprsd/cmds/server.py +71 -37
- aprsd/conf/__init__.py +1 -2
- aprsd/conf/client.py +3 -4
- aprsd/conf/common.py +29 -92
- aprsd/conf/log.py +2 -4
- aprsd/conf/opts.py +5 -4
- aprsd/conf/plugin_common.py +11 -121
- aprsd/exception.py +2 -0
- aprsd/log/log.py +7 -46
- aprsd/main.py +19 -9
- aprsd/packets/__init__.py +14 -4
- aprsd/packets/core.py +82 -91
- aprsd/packets/log.py +8 -8
- aprsd/packets/packet_list.py +18 -25
- aprsd/plugin.py +33 -15
- aprsd/plugins/fortune.py +2 -2
- aprsd/plugins/notify.py +1 -4
- aprsd/plugins/weather.py +10 -8
- aprsd/stats/__init__.py +0 -2
- aprsd/stats/collector.py +11 -3
- aprsd/threads/__init__.py +3 -2
- aprsd/threads/aprsd.py +57 -10
- aprsd/threads/{keep_alive.py → keepalive.py} +14 -37
- aprsd/threads/registry.py +3 -3
- aprsd/threads/rx.py +48 -17
- aprsd/threads/stats.py +2 -2
- aprsd/threads/tx.py +34 -10
- aprsd/utils/__init__.py +62 -15
- aprsd/utils/counter.py +15 -12
- aprsd/utils/json.py +9 -4
- aprsd/utils/keepalive_collector.py +55 -0
- aprsd/utils/trace.py +4 -4
- aprsd-4.0.0.dist-info/AUTHORS +1 -0
- aprsd-4.0.0.dist-info/METADATA +293 -0
- aprsd-4.0.0.dist-info/RECORD +74 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/WHEEL +1 -1
- aprsd/cmds/webchat.py +0 -674
- aprsd/conf/plugin_email.py +0 -105
- aprsd/plugins/email.py +0 -709
- aprsd/plugins/location.py +0 -179
- aprsd/threads/log_monitor.py +0 -121
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +0 -84
- aprsd/web/admin/static/css/prism.css +0 -4
- aprsd/web/admin/static/css/tabs.css +0 -35
- aprsd/web/admin/static/images/Untitled.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/admin/static/js/charts.js +0 -235
- aprsd/web/admin/static/js/echarts.js +0 -465
- aprsd/web/admin/static/js/logs.js +0 -26
- aprsd/web/admin/static/js/main.js +0 -231
- aprsd/web/admin/static/js/prism.js +0 -12
- aprsd/web/admin/static/js/send-message.js +0 -114
- aprsd/web/admin/static/js/tabs.js +0 -28
- aprsd/web/admin/templates/index.html +0 -196
- aprsd/web/chat/static/css/chat.css +0 -115
- aprsd/web/chat/static/css/index.css +0 -66
- aprsd/web/chat/static/css/style.css.map +0 -1
- aprsd/web/chat/static/css/tabs.css +0 -41
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
- aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
- aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
- aprsd/web/chat/static/images/Untitled.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/chat/static/images/globe.svg +0 -3
- aprsd/web/chat/static/js/gps.js +0 -84
- aprsd/web/chat/static/js/main.js +0 -45
- aprsd/web/chat/static/js/send-message.js +0 -585
- aprsd/web/chat/static/js/tabs.js +0 -28
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
- aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
- aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
- aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
- aprsd/web/chat/templates/index.html +0 -139
- aprsd/wsgi.py +0 -315
- aprsd-3.4.3.dist-info/AUTHORS +0 -13
- aprsd-3.4.3.dist-info/METADATA +0 -793
- aprsd-3.4.3.dist-info/RECORD +0 -133
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/LICENSE +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/entry_points.txt +0 -0
- {aprsd-3.4.3.dist-info → aprsd-4.0.0.dist-info}/top_level.txt +0 -0
aprsd/packets/packet_list.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
from collections import OrderedDict
|
2
1
|
import logging
|
2
|
+
from collections import OrderedDict
|
3
3
|
|
4
4
|
from oslo_config import cfg
|
5
5
|
|
6
6
|
from aprsd.packets import core
|
7
7
|
from aprsd.utils import objectstore
|
8
8
|
|
9
|
-
|
10
9
|
CONF = cfg.CONF
|
11
10
|
LOG = logging.getLogger("APRSD")
|
12
11
|
|
13
12
|
|
14
13
|
class PacketList(objectstore.ObjectStoreMixin):
|
15
14
|
"""Class to keep track of the packets we tx/rx."""
|
15
|
+
|
16
16
|
_instance = None
|
17
17
|
_total_rx: int = 0
|
18
18
|
_total_tx: int = 0
|
@@ -37,9 +37,11 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
37
37
|
self._total_rx += 1
|
38
38
|
self._add(packet)
|
39
39
|
ptype = packet.__class__.__name__
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
type_stats = self.data["types"].setdefault(
|
41
|
+
ptype,
|
42
|
+
{"tx": 0, "rx": 0},
|
43
|
+
)
|
44
|
+
type_stats["rx"] += 1
|
43
45
|
|
44
46
|
def tx(self, packet: type[core.Packet]):
|
45
47
|
"""Add a packet that was received."""
|
@@ -47,9 +49,11 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
47
49
|
self._total_tx += 1
|
48
50
|
self._add(packet)
|
49
51
|
ptype = packet.__class__.__name__
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
type_stats = self.data["types"].setdefault(
|
53
|
+
ptype,
|
54
|
+
{"tx": 0, "rx": 0},
|
55
|
+
)
|
56
|
+
type_stats["tx"] += 1
|
53
57
|
|
54
58
|
def add(self, packet):
|
55
59
|
with self.lock:
|
@@ -81,28 +85,17 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
81
85
|
return self._total_tx
|
82
86
|
|
83
87
|
def stats(self, serializable=False) -> dict:
|
84
|
-
# limit the number of packets to return to 50
|
85
88
|
with self.lock:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
self.data.get("packets", OrderedDict()).items(),
|
90
|
-
),
|
91
|
-
),
|
92
|
-
)
|
93
|
-
pkts = []
|
94
|
-
count = 1
|
95
|
-
for packet in tmp:
|
96
|
-
pkts.append(tmp[packet])
|
97
|
-
count += 1
|
98
|
-
if count > CONF.packet_list_stats_maxlen:
|
99
|
-
break
|
89
|
+
# Get last N packets directly using list slicing
|
90
|
+
packets_list = list(self.data.get("packets", {}).values())
|
91
|
+
pkts = packets_list[-CONF.packet_list_stats_maxlen :][::-1]
|
100
92
|
|
101
93
|
stats = {
|
102
|
-
"total_tracked": self._total_rx
|
94
|
+
"total_tracked": self._total_rx
|
95
|
+
+ self._total_tx, # Fixed typo: was rx + rx
|
103
96
|
"rx": self._total_rx,
|
104
97
|
"tx": self._total_tx,
|
105
|
-
"types": self.data.get("types", []
|
98
|
+
"types": self.data.get("types", {}), # Changed default from [] to {}
|
106
99
|
"packet_count": len(self.data.get("packets", [])),
|
107
100
|
"maxlen": self.maxlen,
|
108
101
|
"packets": pkts,
|
aprsd/plugin.py
CHANGED
@@ -8,14 +8,13 @@ import re
|
|
8
8
|
import textwrap
|
9
9
|
import threading
|
10
10
|
|
11
|
-
from oslo_config import cfg
|
12
11
|
import pluggy
|
12
|
+
from oslo_config import cfg
|
13
13
|
|
14
14
|
import aprsd
|
15
15
|
from aprsd import client, packets, threads
|
16
16
|
from aprsd.packets import watch_list
|
17
17
|
|
18
|
-
|
19
18
|
# setup the global logger
|
20
19
|
CONF = cfg.CONF
|
21
20
|
LOG = logging.getLogger("APRSD")
|
@@ -166,7 +165,8 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
166
165
|
except Exception as ex:
|
167
166
|
LOG.error(
|
168
167
|
"Plugin {} failed to process packet {}".format(
|
169
|
-
self.__class__,
|
168
|
+
self.__class__,
|
169
|
+
ex,
|
170
170
|
),
|
171
171
|
)
|
172
172
|
if result:
|
@@ -214,7 +214,9 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
214
214
|
return result
|
215
215
|
|
216
216
|
if not isinstance(packet, packets.MessagePacket):
|
217
|
-
LOG.warning(
|
217
|
+
LOG.warning(
|
218
|
+
f"{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring"
|
219
|
+
)
|
218
220
|
return packets.NULL_MESSAGE
|
219
221
|
|
220
222
|
result = None
|
@@ -236,7 +238,8 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
236
238
|
except Exception as ex:
|
237
239
|
LOG.error(
|
238
240
|
"Plugin {} failed to process packet {}".format(
|
239
|
-
self.__class__,
|
241
|
+
self.__class__,
|
242
|
+
ex,
|
240
243
|
),
|
241
244
|
)
|
242
245
|
LOG.exception(ex)
|
@@ -286,7 +289,8 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
|
|
286
289
|
reply = None
|
287
290
|
for p in pm.get_plugins():
|
288
291
|
if (
|
289
|
-
p.enabled
|
292
|
+
p.enabled
|
293
|
+
and isinstance(p, APRSDRegexCommandPluginBase)
|
290
294
|
and p.command_name.lower() == command_name
|
291
295
|
):
|
292
296
|
reply = p.help()
|
@@ -345,6 +349,7 @@ class PluginManager:
|
|
345
349
|
|
346
350
|
def stats(self, serializable=False) -> dict:
|
347
351
|
"""Collect and return stats for all plugins."""
|
352
|
+
|
348
353
|
def full_name_with_qualname(obj):
|
349
354
|
return "{}.{}".format(
|
350
355
|
obj.__class__.__module__,
|
@@ -354,7 +359,6 @@ class PluginManager:
|
|
354
359
|
plugin_stats = {}
|
355
360
|
plugins = self.get_plugins()
|
356
361
|
if plugins:
|
357
|
-
|
358
362
|
for p in plugins:
|
359
363
|
plugin_stats[full_name_with_qualname(p)] = {
|
360
364
|
"enabled": p.enabled,
|
@@ -439,7 +443,9 @@ class PluginManager:
|
|
439
443
|
)
|
440
444
|
self._watchlist_pm.register(plugin_obj)
|
441
445
|
else:
|
442
|
-
LOG.warning(
|
446
|
+
LOG.warning(
|
447
|
+
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
448
|
+
)
|
443
449
|
elif isinstance(plugin_obj, APRSDRegexCommandPluginBase):
|
444
450
|
if plugin_obj.enabled:
|
445
451
|
LOG.info(
|
@@ -451,7 +457,9 @@ class PluginManager:
|
|
451
457
|
)
|
452
458
|
self._pluggy_pm.register(plugin_obj)
|
453
459
|
else:
|
454
|
-
LOG.warning(
|
460
|
+
LOG.warning(
|
461
|
+
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
462
|
+
)
|
455
463
|
elif isinstance(plugin_obj, APRSDPluginBase):
|
456
464
|
if plugin_obj.enabled:
|
457
465
|
LOG.info(
|
@@ -462,7 +470,9 @@ class PluginManager:
|
|
462
470
|
)
|
463
471
|
self._pluggy_pm.register(plugin_obj)
|
464
472
|
else:
|
465
|
-
LOG.warning(
|
473
|
+
LOG.warning(
|
474
|
+
f"Plugin {plugin_obj.__class__.__name__} is disabled"
|
475
|
+
)
|
466
476
|
except Exception as ex:
|
467
477
|
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
468
478
|
LOG.exception(ex)
|
@@ -470,9 +480,13 @@ class PluginManager:
|
|
470
480
|
def reload_plugins(self):
|
471
481
|
with self.lock:
|
472
482
|
del self._pluggy_pm
|
473
|
-
self.setup_plugins()
|
483
|
+
self.setup_plugins(load_help_plugin=CONF.load_help_plugin)
|
474
484
|
|
475
|
-
def setup_plugins(
|
485
|
+
def setup_plugins(
|
486
|
+
self,
|
487
|
+
load_help_plugin=True,
|
488
|
+
plugin_list=[],
|
489
|
+
):
|
476
490
|
"""Create the plugin manager and register plugins."""
|
477
491
|
|
478
492
|
LOG.info("Loading APRSD Plugins")
|
@@ -481,9 +495,13 @@ class PluginManager:
|
|
481
495
|
_help = HelpPlugin()
|
482
496
|
self._pluggy_pm.register(_help)
|
483
497
|
|
484
|
-
|
485
|
-
|
486
|
-
|
498
|
+
# if plugins_list is passed in, only load
|
499
|
+
# those plugins.
|
500
|
+
if plugin_list:
|
501
|
+
for plugin_name in plugin_list:
|
502
|
+
self._load_plugin(plugin_name)
|
503
|
+
elif CONF.enabled_plugins:
|
504
|
+
for p_name in CONF.enabled_plugins:
|
487
505
|
self._load_plugin(p_name)
|
488
506
|
else:
|
489
507
|
# Enabled plugins isn't set, so we default to loading all of
|
aprsd/plugins/fortune.py
CHANGED
@@ -8,7 +8,7 @@ from aprsd.utils import trace
|
|
8
8
|
|
9
9
|
LOG = logging.getLogger("APRSD")
|
10
10
|
|
11
|
-
DEFAULT_FORTUNE_PATH =
|
11
|
+
DEFAULT_FORTUNE_PATH = "/usr/games/fortune"
|
12
12
|
|
13
13
|
|
14
14
|
class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
|
@@ -45,7 +45,7 @@ class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
|
|
45
45
|
command,
|
46
46
|
shell=True,
|
47
47
|
timeout=3,
|
48
|
-
|
48
|
+
text=True,
|
49
49
|
)
|
50
50
|
output = (
|
51
51
|
output.replace("\r", "")
|
aprsd/plugins/notify.py
CHANGED
@@ -4,7 +4,6 @@ from oslo_config import cfg
|
|
4
4
|
|
5
5
|
from aprsd import packets, plugin
|
6
6
|
|
7
|
-
|
8
7
|
CONF = cfg.CONF
|
9
8
|
LOG = logging.getLogger("APRSD")
|
10
9
|
|
@@ -43,9 +42,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
|
|
43
42
|
pkt = packets.MessagePacket(
|
44
43
|
from_call=CONF.callsign,
|
45
44
|
to_call=notify_callsign,
|
46
|
-
message_text=(
|
47
|
-
f"{fromcall} was just seen by type:'{packet_type}'"
|
48
|
-
),
|
45
|
+
message_text=(f"{fromcall} was just seen by type:'{packet_type}'"),
|
49
46
|
allow_delay=False,
|
50
47
|
)
|
51
48
|
pkt.allow_delay = False
|
aprsd/plugins/weather.py
CHANGED
@@ -2,13 +2,12 @@ import json
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
|
5
|
-
from oslo_config import cfg
|
6
5
|
import requests
|
6
|
+
from oslo_config import cfg
|
7
7
|
|
8
8
|
from aprsd import plugin, plugin_utils
|
9
9
|
from aprsd.utils import trace
|
10
10
|
|
11
|
-
|
12
11
|
CONF = cfg.CONF
|
13
12
|
LOG = logging.getLogger("APRSD")
|
14
13
|
|
@@ -205,8 +204,9 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
205
204
|
|
206
205
|
def help(self):
|
207
206
|
_help = [
|
208
|
-
"openweathermap: Send {} to get weather "
|
209
|
-
|
207
|
+
"openweathermap: Send {} to get weather " "from your location".format(
|
208
|
+
self.command_regex
|
209
|
+
),
|
210
210
|
"openweathermap: Send {} <callsign> to get "
|
211
211
|
"weather from <callsign>".format(self.command_regex),
|
212
212
|
]
|
@@ -327,10 +327,12 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
327
327
|
|
328
328
|
def help(self):
|
329
329
|
_help = [
|
330
|
-
"avwxweather: Send {} to get weather "
|
331
|
-
|
332
|
-
|
333
|
-
"weather from <callsign>".format(
|
330
|
+
"avwxweather: Send {} to get weather " "from your location".format(
|
331
|
+
self.command_regex
|
332
|
+
),
|
333
|
+
"avwxweather: Send {} <callsign> to get " "weather from <callsign>".format(
|
334
|
+
self.command_regex
|
335
|
+
),
|
334
336
|
]
|
335
337
|
return _help
|
336
338
|
|
aprsd/stats/__init__.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from aprsd import plugin
|
2
2
|
from aprsd.client import stats as client_stats
|
3
3
|
from aprsd.packets import packet_list, seen_list, tracker, watch_list
|
4
|
-
from aprsd.plugins import email
|
5
4
|
from aprsd.stats import app, collector
|
6
5
|
from aprsd.threads import aprsd
|
7
6
|
|
@@ -15,6 +14,5 @@ stats_collector.register_producer(watch_list.WatchList)
|
|
15
14
|
stats_collector.register_producer(tracker.PacketTrack)
|
16
15
|
stats_collector.register_producer(plugin.PluginManager)
|
17
16
|
stats_collector.register_producer(aprsd.APRSDThreadList)
|
18
|
-
stats_collector.register_producer(email.EmailStats)
|
19
17
|
stats_collector.register_producer(client_stats.APRSClientStats)
|
20
18
|
stats_collector.register_producer(seen_list.SeenList)
|
aprsd/stats/collector.py
CHANGED
@@ -3,14 +3,14 @@ from typing import Callable, Protocol, runtime_checkable
|
|
3
3
|
|
4
4
|
from aprsd.utils import singleton
|
5
5
|
|
6
|
-
|
7
6
|
LOG = logging.getLogger("APRSD")
|
8
7
|
|
9
8
|
|
10
9
|
@runtime_checkable
|
11
10
|
class StatsProducer(Protocol):
|
12
11
|
"""The StatsProducer protocol is used to define the interface for collecting stats."""
|
13
|
-
|
12
|
+
|
13
|
+
def stats(self, serializable=False) -> dict:
|
14
14
|
"""provide stats in a dictionary format."""
|
15
15
|
...
|
16
16
|
|
@@ -18,6 +18,7 @@ class StatsProducer(Protocol):
|
|
18
18
|
@singleton
|
19
19
|
class Collector:
|
20
20
|
"""The Collector class is used to collect stats from multiple StatsProducer instances."""
|
21
|
+
|
21
22
|
def __init__(self):
|
22
23
|
self.producers: list[Callable] = []
|
23
24
|
|
@@ -26,7 +27,9 @@ class Collector:
|
|
26
27
|
for name in self.producers:
|
27
28
|
cls = name()
|
28
29
|
try:
|
29
|
-
stats[cls.__class__.__name__] = cls.stats(
|
30
|
+
stats[cls.__class__.__name__] = cls.stats(
|
31
|
+
serializable=serializable
|
32
|
+
).copy()
|
30
33
|
except Exception as e:
|
31
34
|
LOG.error(f"Error in producer {name} (stats): {e}")
|
32
35
|
return stats
|
@@ -35,3 +38,8 @@ class Collector:
|
|
35
38
|
if not isinstance(producer_name, StatsProducer):
|
36
39
|
raise TypeError(f"Producer {producer_name} is not a StatsProducer")
|
37
40
|
self.producers.append(producer_name)
|
41
|
+
|
42
|
+
def unregister_producer(self, producer_name: Callable):
|
43
|
+
if not isinstance(producer_name, StatsProducer):
|
44
|
+
raise TypeError(f"Producer {producer_name} is not a StatsProducer")
|
45
|
+
self.producers.remove(producer_name)
|
aprsd/threads/__init__.py
CHANGED
@@ -4,8 +4,9 @@ import queue
|
|
4
4
|
# aprsd.threads
|
5
5
|
from .aprsd import APRSDThread, APRSDThreadList # noqa: F401
|
6
6
|
from .rx import ( # noqa: F401
|
7
|
-
APRSDDupeRXThread,
|
7
|
+
APRSDDupeRXThread,
|
8
|
+
APRSDProcessPacketThread,
|
9
|
+
APRSDRXThread,
|
8
10
|
)
|
9
11
|
|
10
|
-
|
11
12
|
packet_queue = queue.Queue(maxsize=20)
|
aprsd/threads/aprsd.py
CHANGED
@@ -2,11 +2,11 @@ import abc
|
|
2
2
|
import datetime
|
3
3
|
import logging
|
4
4
|
import threading
|
5
|
+
import time
|
5
6
|
from typing import List
|
6
7
|
|
7
8
|
import wrapt
|
8
9
|
|
9
|
-
|
10
10
|
LOG = logging.getLogger("APRSD")
|
11
11
|
|
12
12
|
|
@@ -14,6 +14,8 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
14
14
|
"""Base class for all threads in APRSD."""
|
15
15
|
|
16
16
|
loop_count = 1
|
17
|
+
_pause = False
|
18
|
+
thread_stop = False
|
17
19
|
|
18
20
|
def __init__(self, name):
|
19
21
|
super().__init__(name=name)
|
@@ -22,11 +24,22 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
22
24
|
self._last_loop = datetime.datetime.now()
|
23
25
|
|
24
26
|
def _should_quit(self):
|
25
|
-
"""
|
27
|
+
"""see if we have a quit message from the global queue."""
|
26
28
|
if self.thread_stop:
|
27
29
|
return True
|
28
30
|
|
31
|
+
def pause(self):
|
32
|
+
"""Logically pause the processing of the main loop."""
|
33
|
+
LOG.debug(f"Pausing thread '{self.name}' loop_count {self.loop_count}")
|
34
|
+
self._pause = True
|
35
|
+
|
36
|
+
def unpause(self):
|
37
|
+
"""Logically resume processing of the main loop."""
|
38
|
+
LOG.debug(f"Resuming thread '{self.name}' loop_count {self.loop_count}")
|
39
|
+
self._pause = False
|
40
|
+
|
29
41
|
def stop(self):
|
42
|
+
LOG.debug(f"Stopping thread '{self.name}'")
|
30
43
|
self.thread_stop = True
|
31
44
|
|
32
45
|
@abc.abstractmethod
|
@@ -37,7 +50,9 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
37
50
|
"""Add code to subclass to do any cleanup"""
|
38
51
|
|
39
52
|
def __str__(self):
|
40
|
-
out =
|
53
|
+
out = (
|
54
|
+
f"Thread <{self.__class__.__name__}({self.name}) Alive? {self.is_alive()}>"
|
55
|
+
)
|
41
56
|
return out
|
42
57
|
|
43
58
|
def loop_age(self):
|
@@ -47,11 +62,14 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|
47
62
|
def run(self):
|
48
63
|
LOG.debug("Starting")
|
49
64
|
while not self._should_quit():
|
50
|
-
self.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
self.
|
65
|
+
if self._pause:
|
66
|
+
time.sleep(1)
|
67
|
+
else:
|
68
|
+
self.loop_count += 1
|
69
|
+
can_loop = self.loop()
|
70
|
+
self._last_loop = datetime.datetime.now()
|
71
|
+
if not can_loop:
|
72
|
+
self.stop()
|
55
73
|
self._cleanup()
|
56
74
|
APRSDThreadList().remove(self)
|
57
75
|
LOG.debug("Exiting")
|
@@ -71,6 +89,13 @@ class APRSDThreadList:
|
|
71
89
|
cls.threads_list = []
|
72
90
|
return cls._instance
|
73
91
|
|
92
|
+
def __contains__(self, name):
|
93
|
+
"""See if we have a thread in our list"""
|
94
|
+
for t in self.threads_list:
|
95
|
+
if t.name == name:
|
96
|
+
return True
|
97
|
+
return False
|
98
|
+
|
74
99
|
def stats(self, serializable=False) -> dict:
|
75
100
|
stats = {}
|
76
101
|
for th in self.threads_list:
|
@@ -100,9 +125,27 @@ class APRSDThreadList:
|
|
100
125
|
for th in self.threads_list:
|
101
126
|
LOG.info(f"Stopping Thread {th.name}")
|
102
127
|
if hasattr(th, "packet"):
|
103
|
-
LOG.info(
|
128
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
104
129
|
th.stop()
|
105
130
|
|
131
|
+
@wrapt.synchronized
|
132
|
+
def pause_all(self):
|
133
|
+
"""Iterate over all threads and pause them."""
|
134
|
+
for th in self.threads_list:
|
135
|
+
LOG.info(f"Pausing Thread {th.name}")
|
136
|
+
if hasattr(th, "packet"):
|
137
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
138
|
+
th.pause()
|
139
|
+
|
140
|
+
@wrapt.synchronized
|
141
|
+
def unpause_all(self):
|
142
|
+
"""Iterate over all threads and resume them."""
|
143
|
+
for th in self.threads_list:
|
144
|
+
LOG.info(f"Resuming Thread {th.name}")
|
145
|
+
if hasattr(th, "packet"):
|
146
|
+
LOG.info(f"{th.name} packet {th.packet}")
|
147
|
+
th.unpause()
|
148
|
+
|
106
149
|
@wrapt.synchronized(lock)
|
107
150
|
def info(self):
|
108
151
|
"""Go through all the threads and collect info about each."""
|
@@ -111,7 +154,11 @@ class APRSDThreadList:
|
|
111
154
|
alive = thread.is_alive()
|
112
155
|
age = thread.loop_age()
|
113
156
|
key = thread.__class__.__name__
|
114
|
-
info[key] = {
|
157
|
+
info[key] = {
|
158
|
+
"alive": True if alive else False,
|
159
|
+
"age": age,
|
160
|
+
"name": thread.name,
|
161
|
+
}
|
115
162
|
return info
|
116
163
|
|
117
164
|
@wrapt.synchronized(lock)
|
@@ -7,11 +7,10 @@ from loguru import logger
|
|
7
7
|
from oslo_config import cfg
|
8
8
|
|
9
9
|
from aprsd import packets, utils
|
10
|
-
from aprsd.client import client_factory
|
11
10
|
from aprsd.log import log as aprsd_log
|
12
11
|
from aprsd.stats import collector
|
13
12
|
from aprsd.threads import APRSDThread, APRSDThreadList
|
14
|
-
|
13
|
+
from aprsd.utils import keepalive_collector
|
15
14
|
|
16
15
|
CONF = cfg.CONF
|
17
16
|
LOG = logging.getLogger("APRSD")
|
@@ -35,18 +34,14 @@ class KeepAliveThread(APRSDThread):
|
|
35
34
|
thread_list = APRSDThreadList()
|
36
35
|
now = datetime.datetime.now()
|
37
36
|
|
38
|
-
if
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
else:
|
43
|
-
email_thread_time = "N/A"
|
44
|
-
else:
|
45
|
-
email_thread_time = "N/A"
|
46
|
-
|
47
|
-
if "APRSClientStats" in stats_json and stats_json["APRSClientStats"].get("transport") == "aprsis":
|
37
|
+
if (
|
38
|
+
"APRSClientStats" in stats_json
|
39
|
+
and stats_json["APRSClientStats"].get("transport") == "aprsis"
|
40
|
+
):
|
48
41
|
if stats_json["APRSClientStats"].get("server_keepalive"):
|
49
|
-
last_msg_time = utils.strfdelta(
|
42
|
+
last_msg_time = utils.strfdelta(
|
43
|
+
now - stats_json["APRSClientStats"]["server_keepalive"]
|
44
|
+
)
|
50
45
|
else:
|
51
46
|
last_msg_time = "N/A"
|
52
47
|
else:
|
@@ -63,7 +58,7 @@ class KeepAliveThread(APRSDThread):
|
|
63
58
|
|
64
59
|
keepalive = (
|
65
60
|
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
|
66
|
-
"Last:{}
|
61
|
+
"Last:{} - RAM Current:{} Peak:{} Threads:{} LoggingQueue:{}"
|
67
62
|
).format(
|
68
63
|
stats_json["APRSDStats"]["callsign"],
|
69
64
|
stats_json["APRSDStats"]["uptime"],
|
@@ -73,7 +68,6 @@ class KeepAliveThread(APRSDThread):
|
|
73
68
|
tx_msg,
|
74
69
|
rx_msg,
|
75
70
|
last_msg_time,
|
76
|
-
email_thread_time,
|
77
71
|
stats_json["APRSDStats"]["memory_current_str"],
|
78
72
|
stats_json["APRSDStats"]["memory_peak_str"],
|
79
73
|
len(thread_list),
|
@@ -96,28 +90,11 @@ class KeepAliveThread(APRSDThread):
|
|
96
90
|
LOGU.opt(colors=True).info(thread_msg)
|
97
91
|
# LOG.info(f"{key: <15} Alive? {str(alive): <5} {str(age): <20}")
|
98
92
|
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# The keepalive thread starts before the client has a chance
|
105
|
-
# to make it's connection the first time.
|
106
|
-
if not cl.is_alive() and self.cntr > 0:
|
107
|
-
LOG.error(f"{cl.__class__.__name__} is not alive!!! Resetting")
|
108
|
-
client_factory.create().reset()
|
109
|
-
# else:
|
110
|
-
# # See if we should reset the aprs-is client
|
111
|
-
# # Due to losing a keepalive from them
|
112
|
-
# delta_dict = utils.parse_delta_str(last_msg_time)
|
113
|
-
# delta = datetime.timedelta(**delta_dict)
|
114
|
-
#
|
115
|
-
# if delta > self.max_delta:
|
116
|
-
# # We haven't gotten a keepalive from aprs-is in a while
|
117
|
-
# # reset the connection.a
|
118
|
-
# if not client.KISSClient.is_enabled():
|
119
|
-
# LOG.warning(f"Resetting connection to APRS-IS {delta}")
|
120
|
-
# client.factory.create().reset()
|
93
|
+
# Go through the registered keepalive collectors
|
94
|
+
# and check them as well as call log.
|
95
|
+
collect = keepalive_collector.KeepAliveCollector()
|
96
|
+
collect.check()
|
97
|
+
collect.log()
|
121
98
|
|
122
99
|
# Check version every day
|
123
100
|
delta = now - self.checker_time
|
aprsd/threads/registry.py
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
import logging
|
2
2
|
import time
|
3
3
|
|
4
|
-
from oslo_config import cfg
|
5
4
|
import requests
|
5
|
+
from oslo_config import cfg
|
6
6
|
|
7
7
|
import aprsd
|
8
8
|
from aprsd import threads as aprsd_threads
|
9
9
|
|
10
|
-
|
11
10
|
CONF = cfg.CONF
|
12
11
|
LOG = logging.getLogger("APRSD")
|
13
12
|
|
14
13
|
|
15
14
|
class APRSRegistryThread(aprsd_threads.APRSDThread):
|
16
15
|
"""This sends service information to the configured APRS Registry."""
|
16
|
+
|
17
17
|
_loop_cnt: int = 1
|
18
18
|
|
19
19
|
def __init__(self):
|
@@ -41,7 +41,7 @@ class APRSRegistryThread(aprsd_threads.APRSDThread):
|
|
41
41
|
"description": CONF.aprs_registry.description,
|
42
42
|
"service_website": CONF.aprs_registry.service_website,
|
43
43
|
"software": f"APRSD version {aprsd.__version__} "
|
44
|
-
|
44
|
+
"https://github.com/craigerl/aprsd",
|
45
45
|
}
|
46
46
|
try:
|
47
47
|
requests.post(
|