blob: 7da1a5f9c5a80d51bfe92783f40ac11343d1253c [file] [log] [blame]
"""REST API for accessing the crashreports statistics."""
import zipfile
from rest_framework import generics
from rest_framework import serializers
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from rest_framework.views import APIView
from django.core.exceptions import ObjectDoesNotExist
from django.db import connection
from django.db.models.expressions import F
from django_filters.rest_framework import (
DjangoFilterBackend, DateFilter,
FilterSet, CharFilter, BooleanFilter
)
from crashreport_stats.models import (
Version, VersionDaily, RadioVersion, RadioVersionDaily
)
from crashreports.models import (
Device, Crashreport, HeartBeat, LogFile
)
from crashreports.permissions import (
HasRightsOrIsDeviceOwnerDeviceCreation, HasStatsAccess
)
from . import raw_querys
def dictfetchall(cursor):
"""Return all rows from a cursor as a dict."""
desc = cursor.description
return [
dict(zip([col[0] for col in desc], row))
for row in cursor.fetchall()
]
class DeviceUpdateHistory(APIView):
"""View the update history of a specific device."""
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
def get(self, request, uuid, format=None):
"""Get the update history of a device.
Args:
request: Http request
uuid: The UUID of the device
format: Optional response format parameter
Returns: The update history of the requested device.
"""
cursor = connection.cursor()
raw_querys.execute_device_update_history_query(
cursor,
{
'uuid': uuid
})
res = dictfetchall(cursor)
return Response(res)
class DeviceReportHistory(APIView):
"""View the report history of a specific device."""
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
def get(self, request, uuid, format=None):
"""Get the report history of a device.
Args:
request: Http request
uuid: The UUID of the device
format: Optional response format parameter
Returns: The report history of the requested device.
"""
cursor = connection.cursor()
raw_querys.execute_device_report_history(
cursor,
{
'uuid': uuid
})
res = dictfetchall(cursor)
return Response(res)
class Status(APIView):
"""View the number of devices, crashreports and heartbeats."""
permission_classes = (HasStatsAccess,)
def get(self, request, format=None, ):
"""Get the number of devices, crashreports and heartbeats.
Args:
request: Http request
format: Optional response format parameter
Returns: The number of devices, crashreports and heartbeats.
"""
num_devices = Device.objects.count()
num_crashreports = Crashreport.objects.count()
num_heartbeats = HeartBeat.objects.count()
return Response({
'devices': num_devices,
'crashreports': num_crashreports,
'heartbeats': num_heartbeats
})
class DeviceStat(APIView):
"""View an overview of the statistics of a device."""
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
def get(self, request, uuid, format=None, ):
"""Get some general statistics for a device.
Args:
request: Http request
uuid: The UUID of the device
format: Optional response format parameter
Returns: Some general information of the device in a dictionary.
"""
device = Device.objects.filter(uuid=uuid)
last_active = HeartBeat.objects.filter(device=device).order_by(
'-date')[0].date
heartbeats = HeartBeat.objects.filter(device=device).count()
crashreports = Crashreport.objects.filter(device=device).filter(
boot_reason__in=Crashreport.CRASH_BOOT_REASONS).count()
crashes_per_day = crashreports*1.0/heartbeats if heartbeats > 0 else 0
smpls = Crashreport.objects.filter(device=device).filter(
boot_reason__in=Crashreport.SMPL_BOOT_REASONS).count()
smpl_per_day = smpls*1.0/heartbeats if heartbeats > 0 else 0
return Response(
{
'uuid': uuid,
'last_active': last_active,
'heartbeats': heartbeats,
'crashreports': crashreports,
'crashes_per_day': crashes_per_day,
'smpls': smpls,
'smpl_per_day': smpl_per_day,
'board_date': device[0].board_date,
})
class LogFileDownload(APIView):
"""View for downloading log files."""
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
def get(self, request, id_logfile, format=None):
"""Get a logfile.
Args:
request: Http request
id_logfile: The id of the log file
format: Optional response format parameter
Returns: The log file with the corresponding id.
"""
try:
logfile = LogFile.objects.get(id=id_logfile)
except ObjectDoesNotExist:
raise NotFound(detail="Logfile does not exist.")
zip_file = zipfile.ZipFile(logfile.logfile.path)
ret = {}
for file in zip_file.filelist:
file_open = zip_file.open(file)
ret[file.filename] = file_open.read()
return Response(ret)
class _VersionStatsFilter(FilterSet):
first_seen_before = DateFilter(field_name="first_seen_on",
lookup_expr='lte')
first_seen_after = DateFilter(field_name="first_seen_on",
lookup_expr='gte')
released_before = DateFilter(field_name="released_on", lookup_expr='lte')
released_after = DateFilter(field_name="released_on", lookup_expr='gte')
class _VersionStatsSerializer(serializers.ModelSerializer):
permission_classes = (HasStatsAccess,)
class _VersionStatsListView(generics.ListAPIView):
permission_classes = (HasStatsAccess,)
filter_backends = (DjangoFilterBackend,)
class _DailyVersionStatsFilter(FilterSet):
date_start = DateFilter(field_name="date", lookup_expr='gte')
date_end = DateFilter(field_name="date", lookup_expr='lte')
class _DailyVersionStatsSerializer(serializers.ModelSerializer):
permission_classes = (HasStatsAccess,)
class _DailyVersionStatsListView(generics.ListAPIView):
permission_classes = (HasStatsAccess,)
filter_backends = (DjangoFilterBackend,)
class VersionSerializer(_VersionStatsSerializer):
"""Serializer for the Version class."""
class Meta: # noqa: D106
model = Version
fields = '__all__'
class VersionFilter(_VersionStatsFilter):
"""Filter for Version instances."""
class Meta: # noqa: D106
model = Version
fields = '__all__'
class VersionListView(_VersionStatsListView):
"""View for listing versions."""
queryset = Version.objects.all().order_by('-heartbeats')
filter_class = VersionFilter
serializer_class = VersionSerializer
class VersionDailyFilter(_DailyVersionStatsFilter):
"""Filter for VersionDaily instances."""
version__build_fingerprint = CharFilter()
version__is_official_release = BooleanFilter()
version__is_beta_release = BooleanFilter()
class Meta: # noqa: D106
model = VersionDaily
fields = '__all__'
class VersionDailySerializer(_DailyVersionStatsSerializer):
"""Serializer for VersionDaily instances."""
build_fingerprint = serializers.CharField()
class Meta: # noqa: D106
model = VersionDaily
fields = '__all__'
class VersionDailyListView(_DailyVersionStatsListView):
"""View for listing VersionDaily instances."""
queryset = (
VersionDaily
.objects
.annotate(build_fingerprint=F('version__build_fingerprint'))
.all()
.order_by('date')
)
filter_class = VersionDailyFilter
filter_fields = (
'version__build_fingerprint',
'version__is_official_release',
'version__is_beta_release',
)
serializer_class = VersionDailySerializer
class RadioVersionSerializer(_VersionStatsSerializer):
"""Serializer for RadioVersion instances."""
class Meta: # noqa: D106
model = RadioVersion
fields = '__all__'
class RadioVersionFilter(_VersionStatsFilter):
"""Filter for RadioVersion instances."""
class Meta: # noqa: D106
model = RadioVersion
fields = '__all__'
class RadioVersionListView(_VersionStatsListView):
"""View for listing RadioVersion instances."""
queryset = RadioVersion.objects.all().order_by('-heartbeats')
serializer_class = RadioVersionSerializer
filter_class = RadioVersionFilter
class RadioVersionDailyFilter(_DailyVersionStatsFilter):
"""Filter for RadioVersionDaily instances."""
version__radio_version = CharFilter()
version__is_official_release = BooleanFilter()
version__is_beta_release = BooleanFilter()
class Meta: # noqa: D106
model = RadioVersionDaily
fields = '__all__'
class RadioVersionDailySerializer(_DailyVersionStatsSerializer):
"""Serializer for RadioVersionDaily instances."""
radio_version = serializers.CharField()
class Meta: # noqa: D106
model = RadioVersionDaily
fields = '__all__'
class RadioVersionDailyListView(_DailyVersionStatsListView):
"""View for listing RadioVersionDaily instances."""
queryset = (
RadioVersionDaily.objects.annotate(
radio_version=F('version__radio_version'))
.all()
.order_by('date')
)
filter_class = RadioVersionDailyFilter
filter_fields = (
'version__radio_version',
'version__is_official_release',
'version__is_beta_release',
)
serializer_class = RadioVersionDailySerializer