aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElena ``of Valhalla'' Grandi <valhalla@trueelena.org>2023-12-27 13:10:01 +0100
committerElena ``of Valhalla'' Grandi <valhalla@trueelena.org>2023-12-29 07:29:22 +0100
commite9c843a4eb817daf249167b79ba3c60237bb93e9 (patch)
treeea60527374dc05f333dd6122fc1517e68558b8bf
parent27bc8de7c7f8e29ba10324b56fef9fc4e80a9fd6 (diff)
Generate graphs when data is updated.
-rw-r--r--kerbana/settings.py26
-rw-r--r--rrd/models.py29
-rw-r--r--rrd/mqtt.py5
-rw-r--r--rrd/tests/test_graphs.py39
4 files changed, 95 insertions, 4 deletions
diff --git a/kerbana/settings.py b/kerbana/settings.py
index 5d65b47..383d86f 100644
--- a/kerbana/settings.py
+++ b/kerbana/settings.py
@@ -154,4 +154,30 @@ RRA:MAX:0.5:288:365
RRA:MIN:0.5:288:365
"""
+RRD_GRAPH_CONFIG = """
+--imgformat
+ PNG
+--width
+ 800
+--height
+ 200
+--start
+ -86400
+--end
+ -1
+--vertical-label
+ {ds_names[0]}
+--title
+ {title}
+--right-axis
+ 1:0
+--alt-autoscale
+DEF:{ds_names[0]}={ds_paths[0]}:{ds_names[0]}:AVERAGE
+VDEF:max={ds_names[0]},MAXIMUM
+VDEF:min={ds_names[0]},MINIMUM
+LINE2:{ds_names[0]}#0000FF
+LINE1:max#FF0000
+LINE1:min#FF0000
+"""
+
from .config import * # noqa
diff --git a/rrd/models.py b/rrd/models.py
index 8d995c8..1d1e5e9 100644
--- a/rrd/models.py
+++ b/rrd/models.py
@@ -80,6 +80,9 @@ class DataSource(ModelWithPerms):
except ValueError as e:
log.warning("Could not update ds: %s", e)
+ for graph in self.graph_set.all():
+ graph.update()
+
class Graph(ModelWithPerms):
title = models.CharField(max_length=64)
@@ -89,11 +92,35 @@ class Graph(ModelWithPerms):
recursive=True,
max_length=512,
)
- rrd_config = models.TextField()
+ rrd_config = models.TextField(
+ default=settings.RRD_GRAPH_CONFIG
+ )
def __str__(self):
return self.title
+ def update(self):
+ graph_path = os.path.join(settings.RRD_GRAPH_PATH, self.path)
+ os.makedirs(os.path.dirname(graph_path), exist_ok=True)
+ rrd_paths = []
+ rrd_topics = []
+ rrd_ds_names = []
+ for ds in self.data_sources.all():
+ rrd_paths.append(os.path.join(settings.RRD_DB_PATH, ds.path))
+ rrd_topics.append(ds.topic)
+ rrd_ds_names.append(ds.topic.split("/")[-1])
+ opts = self.rrd_config.format(
+ topics=rrd_topics,
+ ds_names=rrd_ds_names,
+ ds_paths=rrd_paths,
+ title=self.title,
+ ).strip().split('\n')
+ opts = [o.strip() for o in opts]
+ rrdtool.graph(
+ graph_path,
+ * opts
+ )
+
class Dashboard(ModelWithPerms):
title = models.CharField(max_length=64)
diff --git a/rrd/mqtt.py b/rrd/mqtt.py
index 09589c3..94bd772 100644
--- a/rrd/mqtt.py
+++ b/rrd/mqtt.py
@@ -66,10 +66,9 @@ class MQTTClient:
topic = msg.topic.removeprefix(settings.MQTT_TOPIC)
dss = models.DataSource.objects.filter(topic=topic)
if not dss:
- dss = [models.DataSource.objects.create(
+ dss = (models.DataSource.objects.create(
topic=topic,
path=slugify.slugify(topic) + ".rrd",
- )]
- dss[0].save()
+ ),)
for ds in dss:
ds.update(ts, msg.payload.decode())
diff --git a/rrd/tests/test_graphs.py b/rrd/tests/test_graphs.py
new file mode 100644
index 0000000..f2a4948
--- /dev/null
+++ b/rrd/tests/test_graphs.py
@@ -0,0 +1,39 @@
+import datetime
+import os
+
+from django.conf import settings
+from django.test import TestCase
+
+from .. import models
+
+
+class TestGraphs(TestCase):
+ def setUp(self):
+ self.rrd_file = "test.rrd"
+ try:
+ os.remove(os.path.join(settings.RRD_DB_PATH, self.rrd_file))
+ except FileNotFoundError:
+ pass
+
+ def tearDown(self):
+ try:
+ os.remove(os.path.join(settings.RRD_DB_PATH, self.rrd_file))
+ except FileNotFoundError:
+ pass
+
+ def test_generate_at_ds_update(self):
+ ds = models.DataSource.objects.create(
+ topic="test",
+ path=self.rrd_file,
+ )
+ graph = models.Graph.objects.create(
+ title="Test Graph",
+ path="test/test.png",
+ )
+ graph.data_sources.add(ds)
+ graph.save()
+ now = datetime.datetime.now()
+ ts = int(now.timestamp())
+ ds.update(ts, 10)
+ stat = os.stat(os.path.join(settings.RRD_GRAPH_PATH, "test/test.png"))
+ self.assertGreaterEqual(stat.st_mtime, now.timestamp())