diff --git a/aprsd/main.py b/aprsd/main.py index 1f5a4ebd..68d454b8 100644 --- a/aprsd/main.py +++ b/aprsd/main.py @@ -70,7 +70,7 @@ def main(): # First import all the possible commands for the CLI # The commands themselves live in the cmds directory from .cmds import ( # noqa - completion, dev, fetch_stats, healthcheck, list_plugins, listen, + completion, config, dev, fetch_stats, healthcheck, list_plugins, listen, send_message, server, webchat, ) cli(auto_envvar_prefix="APRSD") @@ -145,6 +145,8 @@ def get_namespaces(): if not sys.argv[1:]: raise SystemExit raise + LOG.warning(conf.namespace) + return generator.generate(conf) diff --git a/aprsd/packets/core.py b/aprsd/packets/core.py index cfede1fd..83cffa3a 100644 --- a/aprsd/packets/core.py +++ b/aprsd/packets/core.py @@ -108,6 +108,8 @@ class Packet(metaclass=abc.ABCMeta): # hash=False #) last_send_time: float = field(repr=False, default=0, compare=False, hash=False) + last_send_attempt: int = field(repr=False, default=0, compare=False, hash=False) + # Do we allow this packet to be saved to send later? allow_delay: bool = field(repr=False, default=True, compare=False, hash=False) path: List[str] = field(default_factory=list, compare=False, hash=False) diff --git a/aprsd/packets/packet_list.py b/aprsd/packets/packet_list.py index a6dc6f72..87f67d82 100644 --- a/aprsd/packets/packet_list.py +++ b/aprsd/packets/packet_list.py @@ -19,11 +19,12 @@ class PacketList(MutableMapping): lock = threading.Lock() _total_rx: int = 0 _total_tx: int = 0 + types = {} def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) - cls._maxlen = 1000 + cls._maxlen = 100 cls.d = OrderedDict() return cls._instance @@ -32,6 +33,10 @@ def rx(self, packet): """Add a packet that was received.""" self._total_rx += 1 self._add(packet) + ptype = packet.__class__.__name__ + if not ptype in self.types: + self.types[ptype] = {"tx": 0, "rx": 0} + self.types[ptype]["rx"] += 1 seen_list.SeenList().update_seen(packet) stats.APRSDStats().rx(packet) @@ -40,6 +45,10 @@ def tx(self, packet): """Add a packet that was received.""" self._total_tx += 1 self._add(packet) + ptype = packet.__class__.__name__ + if not ptype in self.types: + self.types[ptype] = {"tx": 0, "rx": 0} + self.types[ptype]["tx"] += 1 seen_list.SeenList().update_seen(packet) stats.APRSDStats().tx(packet) @@ -50,6 +59,10 @@ def add(self, packet): def _add(self, packet): self[packet.key] = packet + def copy(self): + return self.d.copy() + + @property def maxlen(self): return self._maxlen diff --git a/aprsd/packets/tracker.py b/aprsd/packets/tracker.py index b5123202..1246dc1b 100644 --- a/aprsd/packets/tracker.py +++ b/aprsd/packets/tracker.py @@ -65,6 +65,7 @@ def __len__(self): @wrapt.synchronized(lock) def add(self, packet): key = packet.msgNo + packet._last_send_attempt = 0 self.data[key] = packet self.total_tracked += 1 @@ -83,7 +84,7 @@ def restart(self): """Walk the list of messages and restart them if any.""" for key in self.data.keys(): pkt = self.data[key] - if pkt.last_send_attempt < pkt.retry_count: + if pkt._last_send_attempt < pkt.retry_count: tx.send(pkt) def _resend(self, packet): diff --git a/aprsd/web/admin/static/js/echarts.js b/aprsd/web/admin/static/js/echarts.js new file mode 100644 index 00000000..adeb5f6d --- /dev/null +++ b/aprsd/web/admin/static/js/echarts.js @@ -0,0 +1,403 @@ +var packet_list = {}; + +var tx_data = []; +var rx_data = []; + +var packet_types_data = {}; + +var mem_current = [] +var mem_peak = [] + + +function start_charts() { + console.log("start_charts() called"); + // Initialize the echarts instance based on the prepared dom + create_packets_chart(); + create_packets_types_chart(); + create_messages_chart(); + create_ack_chart(); + create_memory_chart(); +} + + +function create_packets_chart() { + // The packets totals TX/RX chart. + pkt_c_canvas = document.getElementById('packetsChart'); + packets_chart = echarts.init(pkt_c_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'APRS Packet totals' + }, + legend: {}, + tooltip : { + trigger: 'axis' + }, + toolbox: { + show : true, + feature : { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable : true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + // Display the chart using the configuration items and data just specified. + packets_chart.setOption(option); +} + + +function create_packets_types_chart() { + // The packets types chart + pkt_types_canvas = document.getElementById('packetTypesChart'); + packet_types_chart = echarts.init(pkt_types_canvas); + + // The series and data are built and updated on the fly + // as packets come in. + var option = { + title: { + text: 'Packet Types' + }, + legend: {}, + tooltip : { + trigger: 'axis' + }, + toolbox: { + show : true, + feature : { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable : true, + xAxis: { type: 'time' }, + yAxis: { }, + } + + packet_types_chart.setOption(option); +} + + +function create_messages_chart() { + msg_c_canvas = document.getElementById('messagesChart'); + message_chart = echarts.init(msg_c_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Message Packets' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + // Display the chart using the configuration items and data just specified. + message_chart.setOption(option); +} + +function create_ack_chart() { + ack_canvas = document.getElementById('acksChart'); + ack_chart = echarts.init(ack_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Ack Packets' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: false}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + ack_chart.setOption(option); +} + +function create_memory_chart() { + ack_canvas = document.getElementById('memChart'); + memory_chart = echarts.init(ack_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Memory Usage' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: false}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'current', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'current' // refer sensor 1 value + } + },{ + name: 'peak', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'peak' + } + }] + }; + + memory_chart.setOption(option); +} + + + + +function updatePacketData(chart, time, first, second) { + tx_data.push([time, first]); + rx_data.push([time, second]); + option = { + series: [ + { + name: 'tx', + data: tx_data, + }, + { + name: 'rx', + data: rx_data, + } + ] + } + chart.setOption(option); +} + +function updatePacketTypesData(time, typesdata) { + //The options series is created on the fly each time based on + //the packet types we have in the data + var series = [] + + for (const k in typesdata) { + tx = [time, typesdata[k]["tx"]] + rx = [time, typesdata[k]["rx"]] + + if (packet_types_data.hasOwnProperty(k)) { + packet_types_data[k]["tx"].push(tx) + packet_types_data[k]["rx"].push(rx) + } else { + packet_types_data[k] = {'tx': [tx], 'rx': [rx]} + } + } +} + +function updatePacketTypesChart() { + series = [] + for (const k in packet_types_data) { + entry = { + name: k+"tx", + data: packet_types_data[k]["tx"], + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: k+'tx' // refer sensor 1 value + } + } + series.push(entry) + entry = { + name: k+"rx", + data: packet_types_data[k]["rx"], + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: k+'rx' // refer sensor 1 value + } + } + series.push(entry) + } + + option = { + series: series + } + console.log(option) + packet_types_chart.setOption(option); +} + +function updateTypeChart(chart, key) { + //Generic function to update a packet type chart + if (! packet_types_data.hasOwnProperty(key)) { + return; + } + + if (! packet_types_data[key].hasOwnProperty('tx')) { + return; + } + var option = { + series: [{ + name: "tx", + data: packet_types_data[key]["tx"], + }, + { + name: "rx", + data: packet_types_data[key]["rx"] + }] + } + + chart.setOption(option); +} + +function updateMemChart(time, current, peak) { + mem_current.push([time, current]); + mem_peak.push([time, peak]); + option = { + series: [ + { + name: 'current', + data: mem_current, + }, + { + name: 'peak', + data: mem_peak, + } + ] + } + memory_chart.setOption(option); +} + +function updateMessagesChart() { + updateTypeChart(message_chart, "MessagePacket") +} + +function updateAcksChart() { + updateTypeChart(ack_chart, "AckPacket") +} + +function update_stats( data ) { + console.log(data); + our_callsign = data["stats"]["aprsd"]["callsign"]; + $("#version").text( data["stats"]["aprsd"]["version"] ); + $("#aprs_connection").html( data["aprs_connection"] ); + $("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] ); + const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json'); + $("#jsonstats").html(html_pretty); + + t = Date.parse(data["time"]); + ts = new Date(t); + updatePacketData(packets_chart, ts, data["stats"]["packets"]["sent"], data["stats"]["packets"]["received"]); + updatePacketTypesData(ts, data["stats"]["packets"]["types"]); + updatePacketTypesChart(); + updateMessagesChart(); + updateAcksChart(); + updateMemChart(ts, data["stats"]["aprsd"]["memory_current"], data["stats"]["aprsd"]["memory_peak"]); + //updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["received"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]); + //updateDualData(email_chart, short_time, data["stats"]["email"]["sent"], data["stats"]["email"]["recieved"]); + //updateDualData(memory_chart, short_time, data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]); +} diff --git a/aprsd/web/admin/templates/index.html b/aprsd/web/admin/templates/index.html index fe9bac1c..3bd95bb9 100644 --- a/aprsd/web/admin/templates/index.html +++ b/aprsd/web/admin/templates/index.html @@ -6,6 +6,7 @@ + @@ -15,7 +16,7 @@ - + @@ -83,6 +84,7 @@
{{ stats }}-
+
+
{{ stats|safe }}+
{{ stats|safe }}