blob: 5db2e36e93f6b90cec550b5f6a7b8200784c78c6 [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