Skip to content

Commit 02945c3

Browse files
authored
Add compute costs to challenge stats (#2536)
1 parent 5ad1286 commit 02945c3

File tree

3 files changed

+61
-33
lines changed

3 files changed

+61
-33
lines changed

app/grandchallenge/pages/templates/pages/challenge_statistics.html

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{% extends "base.html" %}
22
{% load url %}
33
{% load naturaldelta %}
4+
{% load dict_lookup %}
45

56
{% block title %}
67
Statistics
@@ -22,29 +23,20 @@
2223
{% block content %}
2324
<h3 class="mb-3">Challenge statistics</h3>
2425
{% if average_job_durations %}
25-
<div class="table-responsive">
26-
<table class="table table-hover table-sm w-50">
27-
<tbody>
28-
{% for phase, durations in average_job_durations.items %}
29-
<tr>
30-
<td>
31-
Average algorithm job duration for {{ phase }}:
32-
</td>
33-
<td>
34-
{{ durations.average_duration|naturaldelta }}
35-
</td>
36-
</tr>
37-
<tr>
38-
<td>
39-
Total algorithm job duration for {{ phase }}:
40-
</td>
41-
<td>
42-
{{ durations.total_duration|naturaldelta }}
43-
</td>
44-
</tr>
45-
{% endfor %}
46-
</tbody>
47-
</table>
26+
<div class="row equal-height mx-n2">
27+
{% for phase, durations in average_job_durations.items %}
28+
<div class="card m-1">
29+
<div class="card-header">{{ phase }}</div>
30+
<div class="card-body">
31+
<ul>
32+
<li>Average algorithm job duration: {{ durations.average_duration|naturaldelta }}</li>
33+
<li>Total duration of all jobs for this phase: {{ durations.total_duration|naturaldelta }}</li>
34+
<li>Number of archive items: {% get_dict_values num_archive_items phase as item_count %} {{ item_count }}</li>
35+
<li>Average compute cost per algorithm job: {% get_dict_values compute_costs phase as cost %} {% if cost %}{{ cost }} €{% else %} NA {% endif %}</li>
36+
</ul>
37+
</div>
38+
</div>
39+
{% endfor %}
4840
</div>
4941
{% else %}
5042
No statistics to show for this challenge.

app/grandchallenge/pages/views.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib import messages
22
from django.contrib.auth.mixins import UserPassesTestMixin
3-
from django.db.models import Q
3+
from django.db.models import Count, Q
44
from django.http import Http404
55
from django.views.generic import (
66
CreateView,
@@ -15,8 +15,8 @@
1515
PermissionRequiredMixin as ObjectPermissionRequiredMixin,
1616
)
1717

18+
from config import settings
1819
from grandchallenge.algorithms.models import Job
19-
from grandchallenge.core.mixins import UserIsStaffMixin
2020
from grandchallenge.evaluation.utils import SubmissionKindChoices
2121
from grandchallenge.pages.forms import PageCreateForm, PageUpdateForm
2222
from grandchallenge.pages.models import Page
@@ -161,24 +161,50 @@ def get_average_job_duration_for_phase(phase):
161161
return duration_dict
162162

163163

164-
class ChallengeStatistics(LoginRequiredMixin, UserIsStaffMixin, TemplateView):
164+
class ChallengeStatistics(
165+
LoginRequiredMixin, UserPassesTestMixin, TemplateView
166+
):
165167
template_name = "pages/challenge_statistics.html"
166168

167169
def get_context_data(self, **kwargs):
168170
context = super().get_context_data()
169-
phases = self.request.challenge.phase_set.filter(
170-
submission_kind=SubmissionKindChoices.ALGORITHM
171-
).all()
171+
phases = (
172+
self.request.challenge.phase_set.filter(
173+
submission_kind=SubmissionKindChoices.ALGORITHM
174+
)
175+
.annotate(archive_item_count=Count("archive__items"))
176+
.all()
177+
)
172178
duration_dict = {}
179+
archive_items_dict = {}
180+
compute_costs_dict = {}
173181
for phase in phases:
174-
duration_dict[phase.title] = get_average_job_duration_for_phase(
175-
phase=phase
176-
)
182+
avg_duration = get_average_job_duration_for_phase(phase=phase)
183+
duration_dict[phase.title] = avg_duration
184+
archive_items_dict[phase.title] = phase.archive_item_count
185+
try:
186+
compute_costs_dict[phase.title] = round(
187+
phase.archive_item_count
188+
* avg_duration["average_duration"]
189+
* settings.CHALLENGES_COMPUTE_COST_CENTS_PER_HOUR
190+
/ 60
191+
/ 100,
192+
ndigits=2,
193+
)
194+
except TypeError:
195+
compute_costs_dict[phase.title] = None
177196

178197
context.update(
179198
{
180199
"average_job_durations": duration_dict,
200+
"num_archive_items": archive_items_dict,
201+
"compute_costs": compute_costs_dict,
181202
}
182203
)
183204

184205
return context
206+
207+
def test_func(self):
208+
return self.request.user.is_staff or self.request.user.has_perm(
209+
"challenges.view_challengerequest"
210+
)

app/tests/pages_tests/test_pages.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55
from django.db.models import BLANK_CHOICE_DASH
66
from django.utils.timezone import now
7+
from guardian.shortcuts import assign_perm
78

89
from grandchallenge.components.models import (
910
ComponentInterface,
@@ -324,8 +325,9 @@ def test_create_page_with_same_title(client, two_challenge_sets):
324325
def test_challenge_statistics_page_permissions(client):
325326
challenge = ChallengeFactory()
326327
staff = UserFactory(is_staff=True)
327-
admin, user = UserFactory.create_batch(2)
328+
admin, reviewer, user = UserFactory.create_batch(3)
328329
challenge.add_admin(admin)
330+
assign_perm("challenges.view_challengerequest", reviewer)
329331

330332
response = get_view_for_user(
331333
viewname="pages:statistics",
@@ -343,6 +345,14 @@ def test_challenge_statistics_page_permissions(client):
343345
)
344346
response.status_code = 404
345347

348+
response = get_view_for_user(
349+
viewname="pages:statistics",
350+
client=client,
351+
user=reviewer,
352+
challenge=challenge,
353+
)
354+
response.status_code = 200
355+
346356
response = get_view_for_user(
347357
viewname="pages:statistics",
348358
client=client,

0 commit comments

Comments
 (0)