blob: 7da1a5f9c5a80d51bfe92783f40ac11343d1253c [file] [log] [blame]
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02001"""REST API for accessing the crashreports statistics."""
2import zipfile
3
Dirk Vogt62ff7f22017-05-04 16:07:21 +02004from rest_framework import generics
5from rest_framework import serializers
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02006from rest_framework.exceptions import NotFound
Dirk Vogt62ff7f22017-05-04 16:07:21 +02007from rest_framework.response import Response
8from rest_framework.views import APIView
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02009
10from django.core.exceptions import ObjectDoesNotExist
Dirk Vogt62ff7f22017-05-04 16:07:21 +020011from django.db import connection
Dirk Vogt1accb672017-05-10 14:07:42 +020012from django.db.models.expressions import F
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020013
14from django_filters.rest_framework import (
15 DjangoFilterBackend, DateFilter,
16 FilterSet, CharFilter, BooleanFilter
17)
18
19from crashreport_stats.models import (
20 Version, VersionDaily, RadioVersion, RadioVersionDaily
21)
22from crashreports.models import (
23 Device, Crashreport, HeartBeat, LogFile
24)
25from crashreports.permissions import (
26 HasRightsOrIsDeviceOwnerDeviceCreation, HasStatsAccess
27)
28from . import raw_querys
29
Dirk Vogt62ff7f22017-05-04 16:07:21 +020030
31def dictfetchall(cursor):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020032 """Return all rows from a cursor as a dict."""
Dirk Vogt62ff7f22017-05-04 16:07:21 +020033 desc = cursor.description
34 return [
35 dict(zip([col[0] for col in desc], row))
36 for row in cursor.fetchall()
37 ]
38
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020039
Dirk Vogt62ff7f22017-05-04 16:07:21 +020040class DeviceUpdateHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020041 """View the update history of a specific device."""
42
Dirk Vogt62ff7f22017-05-04 16:07:21 +020043 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020044
Dirk Vogt62ff7f22017-05-04 16:07:21 +020045 def get(self, request, uuid, format=None):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020046 """Get the update history of a device.
47
48 Args:
49 request: Http request
50 uuid: The UUID of the device
51 format: Optional response format parameter
52
53 Returns: The update history of the requested device.
54
55 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +020056 cursor = connection.cursor()
57 raw_querys.execute_device_update_history_query(
58 cursor,
59 {
60 'uuid': uuid
61 })
62 res = dictfetchall(cursor)
63 return Response(res)
64
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020065
Dirk Vogt62ff7f22017-05-04 16:07:21 +020066class DeviceReportHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020067 """View the report history of a specific device."""
68
Dirk Vogt62ff7f22017-05-04 16:07:21 +020069 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020070
Dirk Vogt62ff7f22017-05-04 16:07:21 +020071 def get(self, request, uuid, format=None):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020072 """Get the report history of a device.
73
74 Args:
75 request: Http request
76 uuid: The UUID of the device
77 format: Optional response format parameter
78
79 Returns: The report history of the requested device.
80
81 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +020082 cursor = connection.cursor()
83 raw_querys.execute_device_report_history(
84 cursor,
85 {
86 'uuid': uuid
87 })
88 res = dictfetchall(cursor)
89 return Response(res)
90
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020091
Dirk Vogt571168c2017-12-08 16:54:12 +010092class Status(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020093 """View the number of devices, crashreports and heartbeats."""
94
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +020095 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020096
Dirk Vogt571168c2017-12-08 16:54:12 +010097 def get(self, request, format=None, ):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020098 """Get the number of devices, crashreports and heartbeats.
99
100 Args:
101 request: Http request
102 format: Optional response format parameter
103
104 Returns: The number of devices, crashreports and heartbeats.
105
106 """
Dirk Vogt571168c2017-12-08 16:54:12 +0100107 num_devices = Device.objects.count()
108 num_crashreports = Crashreport.objects.count()
109 num_heartbeats = HeartBeat.objects.count()
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200110 return Response({
111 'devices': num_devices,
112 'crashreports': num_crashreports,
113 'heartbeats': num_heartbeats
Dirk Vogt571168c2017-12-08 16:54:12 +0100114 })
115
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200116
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200117class DeviceStat(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200118 """View an overview of the statistics of a device."""
119
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200120 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200121
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200122 def get(self, request, uuid, format=None, ):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200123 """Get some general statistics for a device.
124
125 Args:
126 request: Http request
127 uuid: The UUID of the device
128 format: Optional response format parameter
129
130 Returns: Some general information of the device in a dictionary.
131
132 """
133 device = Device.objects.filter(uuid=uuid)
134 last_active = HeartBeat.objects.filter(device=device).order_by(
135 '-date')[0].date
136 heartbeats = HeartBeat.objects.filter(device=device).count()
137 crashreports = Crashreport.objects.filter(device=device).filter(
138 boot_reason__in=Crashreport.CRASH_BOOT_REASONS).count()
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200139 crashes_per_day = crashreports*1.0/heartbeats if heartbeats > 0 else 0
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200140 smpls = Crashreport.objects.filter(device=device).filter(
141 boot_reason__in=Crashreport.SMPL_BOOT_REASONS).count()
142 smpl_per_day = smpls*1.0/heartbeats if heartbeats > 0 else 0
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200143 return Response(
144 {
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200145 'uuid': uuid,
146 'last_active': last_active,
147 'heartbeats': heartbeats,
148 'crashreports': crashreports,
149 'crashes_per_day': crashes_per_day,
150 'smpls': smpls,
151 'smpl_per_day': smpl_per_day,
152 'board_date': device[0].board_date,
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200153 })
154
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200155
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200156class LogFileDownload(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200157 """View for downloading log files."""
158
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200159 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200160
161 def get(self, request, id_logfile, format=None):
162 """Get a logfile.
163
164 Args:
165 request: Http request
166 id_logfile: The id of the log file
167 format: Optional response format parameter
168
169 Returns: The log file with the corresponding id.
170
171 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200172 try:
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200173 logfile = LogFile.objects.get(id=id_logfile)
174 except ObjectDoesNotExist:
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200175 raise NotFound(detail="Logfile does not exist.")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200176 zip_file = zipfile.ZipFile(logfile.logfile.path)
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200177 ret = {}
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200178 for file in zip_file.filelist:
179 file_open = zip_file.open(file)
180 ret[file.filename] = file_open.read()
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200181 return Response(ret)
Dirk Vogt1accb672017-05-10 14:07:42 +0200182
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400183
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200184class _VersionStatsFilter(FilterSet):
185 first_seen_before = DateFilter(field_name="first_seen_on",
186 lookup_expr='lte')
187 first_seen_after = DateFilter(field_name="first_seen_on",
188 lookup_expr='gte')
189 released_before = DateFilter(field_name="released_on", lookup_expr='lte')
190 released_after = DateFilter(field_name="released_on", lookup_expr='gte')
191
Dirk Vogt1accb672017-05-10 14:07:42 +0200192
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400193class _VersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200194 permission_classes = (HasStatsAccess,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200195
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200196
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400197class _VersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200198 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200199 filter_backends = (DjangoFilterBackend,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200200
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200201
202class _DailyVersionStatsFilter(FilterSet):
203 date_start = DateFilter(field_name="date", lookup_expr='gte')
204 date_end = DateFilter(field_name="date", lookup_expr='lte')
205
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400206
207class _DailyVersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200208 permission_classes = (HasStatsAccess,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400209
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200210
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400211class _DailyVersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200212 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200213 filter_backends = (DjangoFilterBackend,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400214
215
216class VersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200217 """Serializer for the Version class."""
218
219 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400220 model = Version
221 fields = '__all__'
222
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200223
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400224class VersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200225 """Filter for Version instances."""
226
227 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400228 model = Version
229 fields = '__all__'
230
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200231
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400232class VersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200233 """View for listing versions."""
234
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400235 queryset = Version.objects.all().order_by('-heartbeats')
236 filter_class = VersionFilter
237 serializer_class = VersionSerializer
238
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400239
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200240class VersionDailyFilter(_DailyVersionStatsFilter):
241 """Filter for VersionDaily instances."""
242
243 version__build_fingerprint = CharFilter()
244 version__is_official_release = BooleanFilter()
245 version__is_beta_release = BooleanFilter()
246
247 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200248 model = VersionDaily
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400249 fields = '__all__'
Dirk Vogt1accb672017-05-10 14:07:42 +0200250
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200251
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400252class VersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200253 """Serializer for VersionDaily instances."""
254
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400255 build_fingerprint = serializers.CharField()
256
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200257 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200258 model = VersionDaily
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400259 fields = '__all__'
Dirk Vogt1accb672017-05-10 14:07:42 +0200260
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200261
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400262class VersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200263 """View for listing VersionDaily instances."""
264
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400265 queryset = (
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200266 VersionDaily
267 .objects
268 .annotate(build_fingerprint=F('version__build_fingerprint'))
269 .all()
270 .order_by('date')
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400271 )
272 filter_class = VersionDailyFilter
273 filter_fields = (
274 'version__build_fingerprint',
275 'version__is_official_release',
276 'version__is_beta_release',
277 )
Dirk Vogt1accb672017-05-10 14:07:42 +0200278 serializer_class = VersionDailySerializer
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400279
280
281class RadioVersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200282 """Serializer for RadioVersion instances."""
283
284 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400285 model = RadioVersion
286 fields = '__all__'
287
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200288
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400289class RadioVersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200290 """Filter for RadioVersion instances."""
291
292 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400293 model = RadioVersion
294 fields = '__all__'
295
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200296
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400297class RadioVersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200298 """View for listing RadioVersion instances."""
299
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400300 queryset = RadioVersion.objects.all().order_by('-heartbeats')
301 serializer_class = RadioVersionSerializer
302 filter_class = RadioVersionFilter
303
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400304
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200305class RadioVersionDailyFilter(_DailyVersionStatsFilter):
306 """Filter for RadioVersionDaily instances."""
307
308 version__radio_version = CharFilter()
309 version__is_official_release = BooleanFilter()
310 version__is_beta_release = BooleanFilter()
311
312 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400313 model = RadioVersionDaily
314 fields = '__all__'
315
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200316
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400317class RadioVersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200318 """Serializer for RadioVersionDaily instances."""
319
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400320 radio_version = serializers.CharField()
321
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200322 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400323 model = RadioVersionDaily
324 fields = '__all__'
325
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200326
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400327class RadioVersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200328 """View for listing RadioVersionDaily instances."""
329
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400330 queryset = (
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200331 RadioVersionDaily.objects.annotate(
332 radio_version=F('version__radio_version'))
333 .all()
334 .order_by('date')
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400335 )
336 filter_class = RadioVersionDailyFilter
337 filter_fields = (
338 'version__radio_version',
339 'version__is_official_release',
340 'version__is_beta_release',
341 )
342 serializer_class = RadioVersionDailySerializer