Add hiccup statistic page
diff --git a/crashreports/serializers.py b/crashreports/serializers.py
new file mode 100644
index 0000000..c551198
--- /dev/null
+++ b/crashreports/serializers.py
@@ -0,0 +1,10 @@
+from rest_framework import serializers
+from models import Crashreport
+from rest_framework import permissions
+
+class CrashReportSerializer(serializers.ModelSerializer):
+ permission_classes = (permissions.IsAuthenticated)
+ class Meta:
+ model = Crashreport
+ fields = ('pk','uuid', 'uptime', 'build_fingerprint', 'boot_reason',
+ 'power_on_reason', 'power_off_reason', 'aux_data', 'date')
diff --git a/crashreports/static/crashreports/style.css b/crashreports/static/crashreports/style.css
new file mode 100644
index 0000000..8d4af5f
--- /dev/null
+++ b/crashreports/static/crashreports/style.css
@@ -0,0 +1,28 @@
+h2 {
+ margin-top:60px;
+}
+h1 {
+ margin-top:50px;
+}
+
+#container {
+ margin: auto;
+ width: 90%;
+
+}
+
+div.chart {
+ margin: 5px;
+ width: 500px;
+ height: 500px;
+ float: left;
+ -webkit-box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+ -moz-box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+ box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+}
+
+body {
+ font-family: sans-serif;
+ text-align: center;
+ background-color: #eee;
+}
diff --git a/crashreports/templates/crashreports/hiccup_stats.html b/crashreports/templates/crashreports/hiccup_stats.html
new file mode 100644
index 0000000..d390324
--- /dev/null
+++ b/crashreports/templates/crashreports/hiccup_stats.html
@@ -0,0 +1,159 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <META HTTP-EQUIV="refresh" CONTENT="300">
+ <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" charset="utf-8" >
+ <link rel="stylesheet" type="text/css" href="/static/crashreports/style.css" charset="utf-8" >
+
+ <script src="http://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js" charset="utf-8"></script>
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
+
+ </head>
+
+ <body>
+ <h1> Hiccup Stats </h1>
+ <div id="container">
+ <div id = "all_fingerprints" class="chart">
+ </div>
+ <div id = "probable_crashes" class="chart">
+ </div >
+ <div id = "Crash_Distribution" class="chart">
+ </div >
+ <div id = "Crash_Distribution_crashes" class="chart">
+ </div >
+ <div id = "SMPL_chart" class="chart">
+ </div >
+ </div>
+ <script>
+
+ console.log("test");
+
+ // Load the Visualization API and the corechart package.
+ google.charts.load('current', {'packages':['corechart']});
+
+ // Set a callback to run when the Google Visualization API is loaded.
+ google.charts.setOnLoadCallback(drawChart);
+
+ function aggregate_build_fingerprints(data) {
+ totals = {}
+ data = clear_fake_crashes(data)
+ for (var entry in data) {
+ key = ""
+ if (data[entry].build_fingerprint.includes("a:user")) {
+ key = "DDRFIX";
+ } else {
+ key = "Normal"
+ }
+ if (!totals[key]) {
+ console.log(data[entry].build_fingerprint);
+ totals[key] = 0;
+ }
+ totals[key] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+ function aggregate_uuid(data) {
+ data = clear_fake_crashes(data)
+ totals = {}
+ for (var entry in data) {
+ if (!totals[data[entry].uuid]) {
+ console.log(data[entry].uuid);
+ totals[data[entry].uuid] = 0;
+ }
+ totals[data[entry].uuid] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+
+ function clear_fake_crashes(data) {
+ ret = {}
+ for (var entry in data) {
+ if (data[entry].boot_reason=="FAKECRASH") {
+ continue;
+ }
+ ret[entry]=data[entry];
+ }
+ return ret;
+ }
+
+ function aggregate_SMPL(data) {
+ totals = {}
+ data = clear_fake_crashes(data)
+ for (var entry in data) {
+ key = ""
+ if (data[entry].power_on_reason.includes("SMPL")) {
+ key = "SMPL";
+ } else if (data[entry].power_off_reason.includes("SOFT")) {
+ key = "Crash";
+ } else {
+ continue;
+ }
+ if (!totals[key]) {
+ console.log(data[entry].build_fingerprint);
+ totals[key] = 0;
+ }
+ totals[key] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+ function drawChart() {
+ drawChartFromDate("2016-09-09","");
+ }
+
+ function drawChartFromDate(startDate,endDate) {
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "All Crashreports", 'all_fingerprints',aggregate_build_fingerprints);
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate+"&boot_reason=keyboard+power+on", "Probable Crashes", 'probable_crashes', aggregate_build_fingerprints);
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "SMPL or Crash", 'SMPL_chart', aggregate_SMPL);
+ drawUuidHistogram("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "Crash Distribution", 'Crash_Distribution');
+ drawUuidHistogram("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate+"&boot_reason=keyboard+power+on", "Crash Distribution (probable Crashes)", 'Crash_Distribution_crashes');
+ }
+
+ function drawChartFingerprints(url, title, element, aggregate_function) { $.getJSON( url, function( data ) {
+ var totals_fingerprint = aggregate_function(data);
+ var options = {
+ title: title,
+ pieHole: 0.4,
+ };
+ var chart = new google.visualization.PieChart(document.getElementById(element));
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Build Fingerprint');
+ data.addColumn('number', 'Number of Crashreports');
+ data.addRows(totals_fingerprint);
+ chart.draw(data, options);
+ });
+ }
+
+ function drawUuidHistogram(url, title, element) { $.getJSON( url, function( data ) {
+ var totals_fingerprint = aggregate_uuid(data);
+ var options = {
+ title: title,
+ pieHole: 0.4,
+ };
+ var chart = new google.visualization.Histogram(document.getElementById(element));
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Build Fingerprint');
+ data.addColumn('number', 'Number of Crashreports');
+ data.addRows(totals_fingerprint);
+ chart.draw(data, options);
+ });
+ }
+</script>
+</body>
+</html>
diff --git a/crashreports/templates/registration/login.html b/crashreports/templates/registration/login.html
new file mode 100644
index 0000000..397eadf
--- /dev/null
+++ b/crashreports/templates/registration/login.html
@@ -0,0 +1,66 @@
+{% extends "admin/base_site.html" %}
+{% load i18n static %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}" />
+{{ form.media }}
+{% endblock %}
+
+{% block bodyclass %}{{ block.super }} login{% endblock %}
+
+{% block usertools %}{% endblock %}
+
+{% block nav-global %}{% endblock %}
+
+{% block content_title %}{% endblock %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block content %}
+{% if form.errors and not form.non_field_errors %}
+<p class="errornote">
+{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
+</p>
+{% endif %}
+
+{% if form.non_field_errors %}
+{% for error in form.non_field_errors %}
+<p class="errornote">
+ {{ error }}
+</p>
+{% endfor %}
+{% endif %}
+
+<div id="content-main">
+
+{% if user.is_authenticated %}
+<p class="errornote">
+{% blocktrans trimmed %}
+ You are authenticated as {{ username }}, but are not authorized to
+ access this page. Would you like to login to a different account?
+{% endblocktrans %}
+</p>
+{% endif %}
+
+<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
+ <div class="form-row">
+ {{ form.username.errors }}
+ {{ form.username.label_tag }} {{ form.username }}
+ </div>
+ <div class="form-row">
+ {{ form.password.errors }}
+ {{ form.password.label_tag }} {{ form.password }}
+ <input type="hidden" name="next" value="{{ next }}" />
+ </div>
+ {% url 'admin_password_reset' as password_reset_url %}
+ {% if password_reset_url %}
+ <div class="password-reset-link">
+ <a href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a>
+ </div>
+ {% endif %}
+ <div class="submit-row">
+ <label> </label><input type="submit" value="{% trans 'Log in' %}" />
+ </div>
+</form>
+
+</div>
+{% endblock %}
diff --git a/crashreports/urls.py b/crashreports/urls.py
index 66ad6e1..e121032 100644
--- a/crashreports/urls.py
+++ b/crashreports/urls.py
@@ -1,7 +1,13 @@
-from django.conf.urls import url
+from django.conf.urls import url, include
from . import views
+from rest_framework import routers
+from rest_framework import filters
+
+router = routers.DefaultRouter()
+router.register(r'crashreports', views.CrashreportViewSet)
urlpatterns = [
url(r'^crashreport/', views.index, name='index'),
- url(r'^get_crash_statistic', views.get_crash_statistic, name='get_crash_statistic'),
- url(r'', views.empty, name='empty'),]
+ url(r'^crashreports/hiccup_stats/', views.hiccup_stats, name='home'),
+ url(r'^', include(router.urls)),
+]
diff --git a/crashreports/views.py b/crashreports/views.py
index 5beff69..54a09f0 100644
--- a/crashreports/views.py
+++ b/crashreports/views.py
@@ -12,10 +12,21 @@
from django.contrib.auth.decorators import login_required
from django.db.models import Count
+from rest_framework import viewsets
+from serializers import CrashReportSerializer
+from rest_framework.permissions import BasePermission
+from rest_framework import filters
+from rest_framework import generics
+import django_filters
+from django.template import loader
+
import datetime
import time
+from ratelimit.decorators import ratelimit
+
+@ratelimit( key='ip', rate='100/h')
@csrf_exempt
def index(request):
# Handle file upload`
@@ -40,19 +51,36 @@
else:
return HttpResponse(status=400)
else:
- return HttpResponse(status=400)
+ return HttpResponse(status=400)
+
+class IsCreationOrIsAuthenticated(BasePermission):
+ def has_permission(self, request, view):
+ if not request.user.is_authenticated():
+ if view.action == 'create':
+ return True
+ else:
+ return False
+ else:
+ return True
+
+class ListFilter(django_filters.Filter):
+ def filter(self, qs, value):
+ value_list = value.split(u',')
+ return super(ListFilter, self).filter(qs, django_filters.fields.Lookup(value_list, 'in'))
-def empty(request):
- return HttpResponse(status=204)
+class CrashreportFilter(filters.FilterSet):
+ start_date = django_filters.DateTimeFilter(name="date", lookup_expr='gte')
+ end_date = django_filters.DateTimeFilter(name="date", lookup_expr='lte')
+ boot_reason = ListFilter(name='boot_reason')
+ class Meta:
+ model = Crashreport
+ fields = ['build_fingerprint','boot_reason', 'power_on_reason', 'power_off_reason']
-@login_required
-def get_crash_statistic(request):
- from_date = request.GET.get('from_date', "2016-01-01")
- to_date = request.GET.get('to_date', "2017-01-01")
- entries = Crashreport.objects.filter(date__range=[from_date, to_date]).extra({'date_created' : "date(date)"}).values('date_created').annotate(created_count=Count('id'))
- for entry in entries:
- entry['date_created']=time.mktime(datetime.datetime.strptime(entry['date_created'], "%Y-%m-%d").timetuple())*1000
- return render_to_response('crashreports/json/crashreport_by_day.html/', {
- 'entries' : entries
- })
+
+class CrashreportViewSet(viewsets.ModelViewSet):
+ queryset = Crashreport.objects.all()
+ serializer_class = CrashReportSerializer
+ permission_classes = [IsCreationOrIsAuthenticated]
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_class = CrashreportFilter