Skip to content

Commit 457f8f8

Browse files
authored
Improve performance of API calls #1538 (#1547)
* Add pyinstrument to debug mode Reference:#1538 Signed-off-by: Philippe Ombredanne <[email protected]> * Apply minor cosmetic models.py changes Remove unused imports Move univers monkey patching to top level Reference:#1538 Signed-off-by: Philippe Ombredanne <[email protected]> * Annotate queryset for is_vulnerable * is_vulnerable is now a queryset annotation. * with_is_vulnerable Package manager method annotates the queryset * It is now called anywhere needed. The results with a single API call to http://127.0.0.1:8001/api/packages/63 are: * before 1.7 sec * after 0.25 sec Or an improvement where the old is 6.8 times slower than the new. Reference:#1538 Signed-off-by: Philippe Ombredanne <[email protected]> * Improve sorting of packages by version * simplify sorting code, revert to using lists * use cached_property where relevant Reference:#1538 Signed-off-by: Philippe Ombredanne <[email protected]> * Annotate queryset for is_vulnerable everywhere * Fix how we find vulnerable and non vulnerable versions * Complete using is_vulnerable in relevant querysets. * Adjust tests for clarity and DRY using small method to make the tests setup easier to read * Use proper typing annotation for PackageURL or str * Add new Package manager from_purl() method to create a Package from a PURL Reference:#1538 Signed-off-by: Philippe Ombredanne <[email protected]> * Do not print things in tests Signed-off-by: Philippe Ombredanne <[email protected]> --------- Signed-off-by: Philippe Ombredanne <[email protected]>
1 parent 53b84d1 commit 457f8f8

File tree

7 files changed

+226
-226
lines changed

7 files changed

+226
-226
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ dev =
118118
commoncode
119119
# debug
120120
django-debug-toolbar
121+
pyinstrument
121122

122123
[options.entry_points]
123124
console_scripts =

vulnerabilities/api.py

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def get_vulnerability(self, vuln):
108108

109109
purl = serializers.CharField(source="package_url")
110110

111+
is_vulnerable = serializers.BooleanField()
112+
111113
class Meta:
112114
model = Package
113115
fields = ["url", "purl", "is_vulnerable", "affected_by_vulnerabilities"]
@@ -248,21 +250,27 @@ def get_latest_non_vulnerable(self, package):
248250

249251
affected_by_vulnerabilities = serializers.SerializerMethodField("get_affected_vulnerabilities")
250252

251-
fixing_vulnerabilities = serializers.SerializerMethodField("get_fixed_vulnerabilities")
253+
fixing_vulnerabilities = serializers.SerializerMethodField("get_fixing_vulnerabilities")
254+
255+
is_vulnerable = serializers.BooleanField()
252256

253257
def get_fixed_packages(self, package):
254258
"""
255259
Return a queryset of all packages that fix a vulnerability with
256260
same type, namespace, name, subpath and qualifiers of the `package`
257261
"""
258-
return Package.objects.filter(
259-
name=package.name,
260-
namespace=package.namespace,
261-
type=package.type,
262-
qualifiers=package.qualifiers,
263-
subpath=package.subpath,
264-
packagerelatedvulnerability__fix=True,
265-
).distinct()
262+
return (
263+
Package.objects.filter(
264+
name=package.name,
265+
namespace=package.namespace,
266+
type=package.type,
267+
qualifiers=package.qualifiers,
268+
subpath=package.subpath,
269+
packagerelatedvulnerability__fix=True,
270+
)
271+
.with_is_vulnerable()
272+
.distinct()
273+
)
266274

267275
def get_vulnerabilities_for_a_package(self, package, fix) -> dict:
268276
"""
@@ -285,7 +293,7 @@ def get_vulnerabilities_for_a_package(self, package, fix) -> dict:
285293
context={"request": self.context["request"]},
286294
).data
287295

288-
def get_fixed_vulnerabilities(self, package) -> dict:
296+
def get_fixing_vulnerabilities(self, package) -> dict:
289297
"""
290298
Return a mapping of vulnerabilities fixed in the given `package`.
291299
"""
@@ -322,12 +330,15 @@ class Meta:
322330
"version",
323331
"qualifiers",
324332
"subpath",
333+
"is_vulnerable",
325334
"next_non_vulnerable_version",
326335
"latest_non_vulnerable_version",
327336
"affected_by_vulnerabilities",
328337
"fixing_vulnerabilities",
329338
]
330339

340+
is_vulnerable = serializers.BooleanField()
341+
331342

332343
class PackageFilterSet(filters.FilterSet):
333344
purl = filters.CharFilter(method="filter_purl")
@@ -390,6 +401,9 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet):
390401
filterset_class = PackageFilterSet
391402
throttle_classes = [StaffUserRateThrottle, AnonRateThrottle]
392403

404+
def get_queryset(self):
405+
return super().get_queryset().with_is_vulnerable()
406+
393407
@extend_schema(
394408
request=PackageBulkSearchRequestSerializer,
395409
responses={200: PackageSerializer(many=True)},
@@ -436,6 +450,7 @@ def bulk_search(self, request):
436450
Package.objects.filter(plain_package_url__in=plain_purls)
437451
.order_by("plain_package_url")
438452
.distinct("plain_package_url")
453+
.with_is_vulnerable()
439454
)
440455

441456
if not purl_only:
@@ -449,7 +464,7 @@ def bulk_search(self, request):
449464
vulnerable_purls = [str(package.plain_package_url) for package in vulnerable_purls]
450465
return Response(data=vulnerable_purls)
451466

452-
query = Package.objects.filter(package_url__in=purls).distinct()
467+
query = Package.objects.filter(package_url__in=purls).distinct().with_is_vulnerable()
453468

454469
if not purl_only:
455470
return Response(PackageSerializer(query, many=True, context={"request": request}).data)
@@ -463,7 +478,9 @@ def all(self, request):
463478
"""
464479
Return the Package URLs of all packages known to be vulnerable.
465480
"""
466-
vulnerable_packages = Package.objects.vulnerable().only("package_url").distinct()
481+
vulnerable_packages = (
482+
Package.objects.vulnerable().only("package_url").distinct().with_is_vulnerable()
483+
)
467484
vulnerable_purls = [str(package.package_url) for package in vulnerable_packages]
468485
return Response(vulnerable_purls)
469486

@@ -494,11 +511,8 @@ def lookup(self, request):
494511
validated_data = serializer.validated_data
495512
purl = validated_data.get("purl")
496513

497-
return Response(
498-
PackageSerializer(
499-
Package.objects.for_purls([purl]), many=True, context={"request": request}
500-
).data
501-
)
514+
qs = self.get_queryset().for_purls([purl]).with_is_vulnerable()
515+
return Response(PackageSerializer(qs, many=True, context={"request": request}).data)
502516

503517
@extend_schema(
504518
request=PackageurlListSerializer,
@@ -529,7 +543,7 @@ def bulk_lookup(self, request):
529543

530544
return Response(
531545
PackageSerializer(
532-
Package.objects.for_purls(purls),
546+
Package.objects.for_purls(purls).with_is_vulnerable(),
533547
many=True,
534548
context={"request": request},
535549
).data
@@ -547,33 +561,47 @@ class VulnerabilityViewSet(viewsets.ReadOnlyModelViewSet):
547561
Lookup for vulnerabilities affecting packages.
548562
"""
549563

564+
queryset = Vulnerability.objects.all()
565+
550566
def get_fixed_packages_qs(self):
551567
"""
552568
Filter the packages that fixes a vulnerability
553569
on fields like name, namespace and type.
554570
"""
555-
package_filter_data = {"packagerelatedvulnerability__fix": True}
571+
return self.get_packages_qs().filter(packagerelatedvulnerability__fix=True)
556572

573+
def get_packages_qs(self):
574+
"""
575+
Filter the packages on type, namespace and name.
576+
"""
557577
query_params = self.request.query_params
558-
for field_name in ["name", "namespace", "type"]:
559-
value = query_params.get(field_name)
560-
if value:
578+
package_filter_data = {}
579+
for field_name in ("type", "namespace", "name"):
580+
if value := query_params.get(field_name):
561581
package_filter_data[field_name] = value
562582

563-
return PackageFilterSet(package_filter_data).qs
583+
return PackageFilterSet(package_filter_data).qs.with_is_vulnerable()
564584

565585
def get_queryset(self):
566586
"""
567587
Assign filtered packages queryset from `get_fixed_packages_qs`
568588
to a custom attribute `filtered_fixed_packages`
569589
"""
570-
return Vulnerability.objects.prefetch_related(
571-
"weaknesses",
572-
Prefetch(
573-
"packages",
574-
queryset=self.get_fixed_packages_qs(),
575-
to_attr="filtered_fixed_packages",
576-
),
590+
return (
591+
super()
592+
.get_queryset()
593+
.prefetch_related(
594+
Prefetch(
595+
"packages",
596+
queryset=self.get_packages_qs(),
597+
),
598+
"weaknesses",
599+
Prefetch(
600+
"packages",
601+
queryset=self.get_fixed_packages_qs(),
602+
to_attr="filtered_fixed_packages",
603+
),
604+
)
577605
)
578606

579607
serializer_class = VulnerabilitySerializer

0 commit comments

Comments
 (0)