From 0ebd15b6fc56a2588a0ad28cc45117c9af75c548 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 12 Mar 2019 13:53:36 +0300 Subject: [PATCH] recovery traffic statistics --- abonapp/templates/abonapp/peoples.html | 37 +++---- abonapp/views.py | 6 +- djing/settings.py | 2 +- djing/urls.py | 2 +- locale/ru/LC_MESSAGES/django.po | 3 + templates/base.html | 9 ++ traf_stat/__init__.py | 0 traf_stat/admin.py | 3 + traf_stat/apps.py | 5 + traf_stat/fields.py | 50 +++++++++ traf_stat/migrations/0001_initial.py | 31 ++++++ traf_stat/migrations/__init__.py | 0 traf_stat/models.py | 129 ++++++++++++++++++++++ traf_stat/templates/statistics/index.html | 45 ++++++++ traf_stat/tests.py | 3 + traf_stat/urls.py | 9 ++ traf_stat/views.py | 9 ++ 17 files changed, 319 insertions(+), 24 deletions(-) create mode 100644 traf_stat/__init__.py create mode 100644 traf_stat/admin.py create mode 100644 traf_stat/apps.py create mode 100644 traf_stat/fields.py create mode 100644 traf_stat/migrations/0001_initial.py create mode 100644 traf_stat/migrations/__init__.py create mode 100644 traf_stat/models.py create mode 100644 traf_stat/templates/statistics/index.html create mode 100644 traf_stat/tests.py create mode 100644 traf_stat/urls.py create mode 100644 traf_stat/views.py diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html index 0ecc717..605d336 100644 --- a/abonapp/templates/abonapp/peoples.html +++ b/abonapp/templates/abonapp/peoples.html @@ -24,14 +24,14 @@ -{# #} + -{# #} + {% endif %} -{# #} + -{# #} + diff --git a/abonapp/views.py b/abonapp/views.py index f0075b2..4b34273 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -52,11 +52,11 @@ class PeoplesListView(LoginRequiredMixin, OnlyAdminsMixin, if street_id > 0: peoples_list = peoples_list.filter(street=street_id) peoples_list = peoples_list.select_related( - 'group', 'street', 'current_tariff' + 'group', 'street', 'current_tariff__tariff', 'statcache' ).only( - 'group', 'street', 'fio', + 'group', 'street', 'fio', 'birth_day', 'street', 'house', 'telephone', 'ballance', 'markers', - 'username', 'is_active', 'current_tariff' + 'username', 'is_active', 'current_tariff', 'ip_address' ) ordering = self.get_ordering() if ordering and isinstance(ordering, str): diff --git a/djing/settings.py b/djing/settings.py index 349de4c..f838284 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -50,7 +50,7 @@ INSTALLED_APPS = [ 'searchapp', 'devapp', 'mapapp', - 'statistics', + 'traf_stat', 'taskapp', 'clientsideapp', 'messenger', diff --git a/djing/urls.py b/djing/urls.py index b409ea5..d224e65 100644 --- a/djing/urls.py +++ b/djing/urls.py @@ -11,7 +11,7 @@ urlpatterns = [ path('search/', include('searchapp.urls', namespace='searchapp')), path('dev/', include('devapp.urls', namespace='devapp')), path('map/', include('mapapp.urls', namespace='mapapp')), - # path('statistic/', include('statistics.urls', namespace='statistics')), + path('statistic/', include('traf_stat.urls', namespace='traf_stat')), path('tasks/', include('taskapp.urls', namespace='taskapp')), path('client/', include('clientsideapp.urls', namespace='client_side')), path('msg/', include('msg_app.urls', namespace='msg_app')), diff --git a/locale/ru/LC_MESSAGES/django.po b/locale/ru/LC_MESSAGES/django.po index 43fb3cd..f9ea9d0 100644 --- a/locale/ru/LC_MESSAGES/django.po +++ b/locale/ru/LC_MESSAGES/django.po @@ -125,3 +125,6 @@ msgstr "На сервере произошла ошибка. Пожалуйст msgid "Are you sure about them?" msgstr "Вы уверены в этом?" + +msgid "Traffic" +msgstr "Траффик" diff --git a/templates/base.html b/templates/base.html index 4ee2130..1c7b619 100644 --- a/templates/base.html +++ b/templates/base.html @@ -97,6 +97,15 @@ {% endif %} + {% if perms.traf_stat.statcache_view %} + {% url 'traf_stat:home' as stathome %} + + + {% trans 'Traffic' %} + + + {% endif %} + {% url 'devapp:group_list' as devapp_groups %} diff --git a/traf_stat/__init__.py b/traf_stat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/traf_stat/admin.py b/traf_stat/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/traf_stat/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/traf_stat/apps.py b/traf_stat/apps.py new file mode 100644 index 0000000..9c1b437 --- /dev/null +++ b/traf_stat/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TrafStatConfig(AppConfig): + name = 'traf_stat' diff --git a/traf_stat/fields.py b/traf_stat/fields.py new file mode 100644 index 0000000..6a6f78a --- /dev/null +++ b/traf_stat/fields.py @@ -0,0 +1,50 @@ +# +# Get from https://github.com/Niklas9/django-unixdatetimefield +# +import datetime +import time + +import django.db.models as models + + +class UnixDateTimeField(models.DateTimeField): + + # TODO(niklas9): + # * should we take care of transforming between time zones in any way here ? + # * get default datetime format from settings ? + DEFAULT_DATETIME_FMT = '%Y-%m-%d %H:%M:%S' + TZ_CONST = '+' + # TODO(niklas9): + # * metaclass below just for Django < 1.9, fix a if stmt for it? + #__metaclass__ = models.SubfieldBase + description = "Unix timestamp integer to datetime object" + + def get_internal_type(self): + return 'PositiveIntegerField' + + def to_python(self, val): + if val is None or isinstance(val, datetime.datetime): + return val + if isinstance(val, datetime.date): + return datetime.datetime(val.year, val.month, val.day) + elif isinstance(val, str): + # TODO(niklas9): + # * not addressing time zone support as todo above for now + if self.TZ_CONST in val: + val = val.split(self.TZ_CONST)[0] + return datetime.datetime.strptime(val, self.DEFAULT_DATETIME_FMT) + else: + return datetime.datetime.fromtimestamp(float(val)) + + def get_db_prep_value(self, val, *args, **kwargs): + if val is None: + if self.default == models.fields.NOT_PROVIDED: return None + return self.default + return int(time.mktime(val.timetuple())) + + def value_to_string(self, obj): + val = self._get_val_from_obj(obj) + return self.to_python(val).strftime(self.DEFAULT_DATETIME_FMT) + + def from_db_value(self, val, *args, **kwargs): + return self.to_python(val) diff --git a/traf_stat/migrations/0001_initial.py b/traf_stat/migrations/0001_initial.py new file mode 100644 index 0000000..50183b4 --- /dev/null +++ b/traf_stat/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 2.1.1 on 2019-03-06 18:07 + +from django.db import migrations, models +import django.db.models.deletion +import traf_stat.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('abonapp', '0001_squashed_0008_auto_20181115_1206'), + ] + + operations = [ + migrations.CreateModel( + name='StatCache', + fields=[ + ('last_time', traf_stat.fields.UnixDateTimeField()), + ('abon', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='abonapp.Abon')), + ('octets', models.PositiveIntegerField(default=0)), + ('packets', models.PositiveIntegerField(default=0)), + ], + options={ + 'db_table': 'flowcache', + 'ordering': ('-last_time',), + }, + ), + migrations.RunSQL(sql=(r'ALTER TABLE flowcache ENGINE=MEMORY;',)) + ] diff --git a/traf_stat/migrations/__init__.py b/traf_stat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/traf_stat/models.py b/traf_stat/models.py new file mode 100644 index 0000000..c2dbc0d --- /dev/null +++ b/traf_stat/models.py @@ -0,0 +1,129 @@ +from datetime import datetime, timedelta, date, time +import math + +from django.db import models, connection, ProgrammingError +from django.utils.timezone import now +from .fields import UnixDateTimeField + + +def get_dates(): + tables = connection.introspection.table_names() + tables = (t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')) + return tuple(datetime.strptime(t, '%d%m%Y').date() for t in tables) + + +class StatManager(models.Manager): + def chart(self, user, count_of_parts=12, want_date=date.today()): + def byte_to_mbit(x): + return ((x / 60) * 8) / 2 ** 20 + + def split_list(lst, chunk_count): + chunk_size = len(lst) // chunk_count + if chunk_size == 0: + chunk_size = 1 + return tuple(lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)) + + def avarage(elements): + return sum(elements) / len(elements) + + try: + charts_data = self.filter(abon=user) + charts_times = tuple(cd.cur_time.timestamp() * 1000 for cd in charts_data) + charts_octets = tuple(cd.octets for cd in charts_data) + if len(charts_octets) > 0 and len(charts_octets) == len(charts_times): + charts_octets = split_list(charts_octets, count_of_parts) + charts_octets = (byte_to_mbit(avarage(c)) for c in charts_octets) + + charts_times = split_list(charts_times, count_of_parts) + charts_times = tuple(avarage(t) for t in charts_times) + + charts_data = zip(charts_times, charts_octets) + charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data] + midnight = datetime.combine(want_date, time.min) + charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1)) + charts_data.append("{x:new Date(%d),y:0}" % (int((midnight + timedelta(days=1)).timestamp()) * 1000)) + return charts_data + else: + return + except ProgrammingError as e: + if "flowstat" in str(e): + return + + +class StatElem(models.Model): + cur_time = UnixDateTimeField(primary_key=True) + abon = models.ForeignKey('abonapp.Abon', on_delete=models.CASCADE, null=True, default=None, blank=True) + ip = models.PositiveIntegerField() + octets = models.PositiveIntegerField(default=0) + packets = models.PositiveIntegerField(default=0) + + objects = StatManager() + + # ReadOnly + def save(self, *args, **kwargs): + pass + + # ReadOnly + def delete(self, *args, **kwargs): + pass + + @property + def table_name(self): + return self._meta.db_table + + def delete_month(self): + cursor = connection.cursor() + table_name = self._meta.db_table + sql = "DROP TABLE %s;" % table_name + cursor.execute(sql) + + @staticmethod + def percentile(N, percent, key=lambda x: x): + """ + Find the percentile of a list of values. + + @parameter N - is a list of values. Note N MUST BE already sorted. + @parameter percent - a float value from 0.0 to 1.0. + @parameter key - optional key function to compute value from each element of N. + + @return - the percentile of the values + """ + if not N: + return None + k = (len(N) - 1) * percent + f = math.floor(k) + c = math.ceil(k) + if f == c: + return key(N[int(k)]) + d0 = key(N[int(f)]) * (c - k) + d1 = key(N[int(c)]) * (k - f) + return d0 + d1 + + class Meta: + abstract = True + + +def getModel(want_date=now()): + class DynamicStatElem(StatElem): + class Meta: + db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y") + abstract = False + + return DynamicStatElem + + +class StatCache(models.Model): + last_time = UnixDateTimeField() + abon = models.OneToOneField('abonapp.Abon', on_delete=models.CASCADE, primary_key=True) + octets = models.PositiveIntegerField(default=0) + packets = models.PositiveIntegerField(default=0) + + def is_online(self): + return self.last_time > now() - timedelta(minutes=55) + + def is_today(self): + return date.today() == self.last_time.date() + + class Meta: + db_table = 'flowcache' + ordering = ('-last_time',) diff --git a/traf_stat/templates/statistics/index.html b/traf_stat/templates/statistics/index.html new file mode 100644 index 0000000..2c0dc43 --- /dev/null +++ b/traf_stat/templates/statistics/index.html @@ -0,0 +1,45 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block additional_link %} + + +{% endblock %} + +{% block page-header %}{% trans 'Traffic' %}{% endblock %} + +{% block main %} + +
+{% endblock %} \ No newline at end of file diff --git a/traf_stat/tests.py b/traf_stat/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/traf_stat/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/traf_stat/urls.py b/traf_stat/urls.py new file mode 100644 index 0000000..c9887c7 --- /dev/null +++ b/traf_stat/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from traf_stat.views import home + +app_name = 'traf_stat' + +urlpatterns = [ + path('', home, name='home'), +] diff --git a/traf_stat/views.py b/traf_stat/views.py new file mode 100644 index 0000000..d5eb1b8 --- /dev/null +++ b/traf_stat/views.py @@ -0,0 +1,9 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from djing.lib.decorators import only_admins + + +@login_required +@only_admins +def home(request): + return render(request, 'statistics/index.html')
## {% trans 'Sub' %} {% if order_by == 'username' %}{% endif %} {% trans 'Network' %} @@ -75,26 +75,25 @@ {% else %}
#} -{# #} -{# {% if human.statcache.is_online %}#} -{# #} -{# {% else %}#} -{# #} -{# {% endif %}#} -{# + {% if human.statcache.is_online %} + + {% else %} + + {% endif %} + {{ human.username }} {{ human.ip_address|default_if_none:'—' }} {{ human.fio|default:'—' }} {{ human.street|default:_('Not assigned') }}