import logging import os import django.contrib.auth.models as amodels import rrdtool from django.conf import settings from django.db import models log = logging.getLogger(__name__) class ModelWithPerms(models.Model): users_read = models.ManyToManyField( amodels.User, related_name="%(class)s_read" ) users_write = models.ManyToManyField( amodels.User, related_name="%(class)s_write" ) groups_read = models.ManyToManyField( amodels.Group, related_name="%(class)s_read" ) groups_write = models.ManyToManyField( amodels.Group, related_name="%(class)s_write" ) class Meta: abstract = True class DataSource(ModelWithPerms): # an mqtt topic can be as long as 65,535 bytes when UTF-8 encoded, # which is probably too much for a sensible db topic = models.CharField(max_length=512) path = models.FilePathField( path=settings.RRD_DB_PATH.as_posix(), recursive=True, max_length=512, ) rrd_config = models.TextField( default=settings.RRD_DS_CONFIG ) active = models.BooleanField( default=True, ) def __str__(self): return self.topic @property def lastupdate(self): try: last = rrdtool.lastupdate(os.path.join( settings.RRD_DB_PATH, self.path )) except rrdtool.OperationalError as e: log.warning("Failure reading from ds: %s", e) return (None, None) else: return last["date"], last["ds"][self.ds_name] @property def ds_name(self): return self.topic.split("/")[-1] def update(self, ts, value): rrd_path = os.path.join(settings.RRD_DB_PATH, self.path) if not os.path.isfile(rrd_path): rrdtool.create( os.path.join(settings.RRD_DB_PATH, self.path), "--no-overwrite", self.rrd_config.format( ds_name=self.ds_name ).strip().split('\n'), ) try: rrdtool.update( os.path.join(settings.RRD_DB_PATH, self.path), str(ts) + ":" + str(value) ) 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) data_sources = models.ManyToManyField(DataSource) path = models.FilePathField( path=settings.RRD_GRAPH_PATH.as_posix(), recursive=True, max_length=512, ) 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.ds_name) 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) graphs = models.ManyToManyField(Graph) data_sources = models.ManyToManyField(DataSource) template = models.TextField() def __str__(self): return self.title