Show daily repo size changes as graph

This commit is contained in:
George Lacey 2021-05-09 15:58:53 +01:00
parent 5b9c7dfa31
commit 93da0603da
5 changed files with 148 additions and 37 deletions

View File

@ -1,7 +1,7 @@
from django.db import models
from datetime import datetime, timedelta
from ..utility.time import seconds_to_string
from ..utility.data import bytes_to_string
from ..utility.data import bytes_to_string, convert_bytes
from . import Label
@ -46,12 +46,15 @@ class Repo(models.Model):
def size(self):
if self.archive_set.all().exists():
cache = self.latest_archive().cache
return f"{bytes_to_string(cache.unique_csize)}"
return cache.unique_csize
else:
return "No archives stored"
return 0
def recent_errors(self):
days = 7
def size_string(self):
size = self.size()
return bytes_to_string(size)
def recent_errors(self, days: int = 7):
days_ago = (datetime.utcnow() - timedelta(days=days))
errors = self.label.errors.all().filter(time__gt=days_ago)
return errors
@ -71,21 +74,57 @@ class Repo(models.Model):
days.append(len(cday_archives) > 0)
return days
def get_archive_hours_dict(self):
if self.archive_set.all().exists():
return {"id": self.id,
def daily_dict(self, units, n_hours: int = 24):
archives = self.daily_archives(n_hours)
return {
"id": self.id,
"label": self.label.label,
"hours": self.get_archive_hours()}
else:
return {"id": self.id,
"label": self.label.label,
"hours": []}
"daily_size": list(reversed(self.series_csize(archives, units)))
}
def get_archive_hours(self):
hours = []
for hour in range(24):
chour = datetime.utcnow() - timedelta(hours=hour)
cday_archives = self.archive_set.all().filter(start__date=chour.date()).filter(start__hour=chour.hour)
hours.append(len(cday_archives) > 0)
hours = ''.join(['H' if hour is True else '-' for hour in hours])
return hours
@staticmethod
def series_times(archives):
return [archive.start if archive is not None else None for archive in archives]
@staticmethod
def series_csize(archives, units=None):
return [convert_bytes(archive.cache.unique_csize, units)[0]
if archive is not None else None for archive in archives]
@staticmethod
def series_csize_pretty(archives):
return [archive.cache.unique_csize if archive is not None else None for archive in archives]
@staticmethod
def series_success_string(archives):
return ''.join(['H' if archive is not None else '-' for archive in archives])
def hourly_archive_string(self):
return ''.join(['H' if archive is not None else '-' for archive in self.hourly_archives(24)])
def daily_archives(self, n_days: int = 24):
archives = []
for day in range(n_days):
current_date = (datetime.utcnow() - timedelta(days=day)).date()
archive_current_date = self.archive_set.all()\
.filter(start__date=current_date)\
.order_by('-start')
if len(archive_current_date) > 0:
archives.append(archive_current_date[0])
else:
archives.append(None)
return archives
def hourly_archives(self, n_hours: int = 24):
archives = []
for hour in range(n_hours):
current_hour = datetime.utcnow() - timedelta(hours=hour)
archives_hour = self.archive_set.all()\
.filter(start__date=current_hour.date())\
.filter(start__hour=current_hour.hour)\
.order_by('-start')
if len(archives_hour) > 0:
archives.append(archives_hour[0])
else:
archives.append(None)
return archives

View File

@ -1,7 +1,46 @@
window.addEventListener("DOMContentLoaded", function () {
const hour_json = JSON.parse(document.getElementById('hour_list').textContent);
hour_json.forEach(function (repo) {
console.log(repo.hours);
})
const repoDict = JSON.parse(document.getElementById('hour_list').textContent);
set_daily_graph(repoDict)
}, false);
function set_daily_graph(repoDict) {
const labels = repoDict.date_labels;
const y_units = repoDict.units
var datasets = []
repoDict.repos.forEach(function (repo) {
datasets.push({label: repo.label,
data: repo.daily_size,
fill: false,
tension: 0.1,
borderColor: 'rgb(75, 192, 192)'});
})
const data = {
labels: labels,
datasets: datasets
};
const config = {
type: 'line',
data,
options: {
scales: {
y: {
ticks: {
display: true,
grid: false,
callback: function(value, index, values) {
return value + " " + y_units;
}
}
}
}
}
};
var myChart = new Chart(
document.getElementById('backup_csize_hourly'),
config
);
}

View File

@ -31,11 +31,11 @@
</dl>
<dl class="row">
<dt class="col-sm-3">Hourly backups:</dt>
<dd class="col-sm-9">{{ repo.get_archive_hours }}</dd>
<dd class="col-sm-9">{{ repo.hourly_archive_string }}</dd>
</dl>
<dl class="row">
<dt class="col-sm-3">Size:</dt>
<dd class="col-sm-9">{{ repo.size }}</dd>
<dd class="col-sm-9">{{ repo.size_string }}</dd>
</dl>
{% if repo.recent_errors|length > 0 %}
<dl class="row">
@ -53,6 +53,9 @@
{% else %}
<p>No repos found.</p>
{% endif %}
<div class="col-md-4 rounded float-xl-left">
<canvas id="backup_csize_hourly" width="200" height="100"></canvas>
</div>
</div>
</body>
</html>

View File

@ -1,11 +1,24 @@
from math import floor, log
bytes_in_unit = 1024 # Kibibyte
units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "HiB")
# bytes_in_unit = 1000 # Kilobyte
# units = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "HB")
def bytes_to_string(bytes: int):
suffixes = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "HB")
if bytes == 0:
return f"0{suffixes[0]}"
def convert_bytes(c_bytes: int, unit: str = None) -> (float, str):
if c_bytes == 0:
return 0, units[0] if unit is None else unit
if unit is None:
index = int(floor(log(c_bytes, bytes_in_unit)))
else:
index = int(floor(log(bytes, 1024)))
s = round(bytes / pow(1024, index), 2)
return f"{s}{suffixes[index]}"
index = units.index(unit)
result = round(c_bytes / pow(bytes_in_unit, index), 2)
return result, units[index]
def bytes_to_string(c_bytes: int, unit: str = None) -> str:
n_bytes, unit = convert_bytes(c_bytes, unit)
return f"{n_bytes} {unit}"

View File

@ -5,19 +5,36 @@ from .models import Repo, Label, Archive, Cache, Error
from django.urls import reverse
from .forms import RepoForm, ArchiveForm, ErrorForm
from django.contrib.auth.decorators import permission_required
from .utility import data
from datetime import datetime, timedelta
def index(request):
repo_list = Repo.objects.all()
hour_list = [repo.get_archive_hours_dict() for repo in repo_list]
repo_dict = repo_daily_dict(repo_list, 24)
context = {
'repo_list': repo_list,
'hour_list': hour_list
'hour_list': repo_dict
}
return render(request, 'borg/index.html', context)
def repo_daily_dict(repo_list, n_days=14):
date_labels = list(reversed([(datetime.utcnow() - timedelta(days=day)).strftime("%d %b") for day in range(n_days)]))
max_repo_size = max(repo.latest_archive().cache.unique_csize for repo in repo_list)
_, max_unit = data.convert_bytes(max_repo_size)
repo_dicts = [repo.daily_dict(max_unit, n_days) for repo in repo_list]
return {
"date_labels": date_labels,
"repos": repo_dicts,
"units": max_unit
}
@permission_required("borg.add_repo")
def get_repo(request):
if request.method == 'POST':