Rename ingest_order to digest_order and clarify event_count

* issue.event_count to digested_event_count
* event.ingest_order to event.digest_order
* issue.ingest_order to digest_order

This is generally more correct/explicit, and is also in preparation
of doing work on-digest (which may or may not happen)
This commit is contained in:
Klaas van Schelven
2024-07-16 15:23:40 +02:00
parent d56a8663a7
commit c01d332e18
18 changed files with 118 additions and 53 deletions

View File

@@ -22,8 +22,8 @@ def create_event(project=None, issue=None, timestamp=None, event_data=None):
event_data = create_event_data()
max_current = Event.objects.filter(project=project).aggregate(
Max("ingest_order"))["ingest_order__max"]
issue_ingest_order = max_current + 1 if max_current is not None else 1
Max("digest_order"))["digest_order__max"]
issue_digest_order = max_current + 1 if max_current is not None else 1
return Event.objects.create(
project=project,
@@ -34,7 +34,7 @@ def create_event(project=None, issue=None, timestamp=None, event_data=None):
has_exception=True,
has_logentry=True,
data=json.dumps(event_data),
ingest_order=issue_ingest_order,
digest_order=issue_digest_order,
irrelevance_for_retention=0,
)

View File

@@ -0,0 +1,24 @@
# Generated by Django 4.2.13 on 2024-07-16 13:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('issues', '0004_rename_event_count_issue_digested_event_count'),
('projects', '0004_project_quota_exceeded_until'),
('events', '0009_event_events_even_issue_i_90497b_idx'),
]
operations = [
migrations.RenameField(
model_name='event',
old_name='ingest_order',
new_name='digest_order',
),
migrations.AlterUniqueTogether(
name='event',
unique_together={('project', 'event_id'), ('issue', 'digest_order')},
),
]

View File

@@ -147,7 +147,7 @@ class Event(models.Model):
# 1-based, because this is for human consumption only, and using 0-based internally when we don't actually do
# anything with this value other than showing it to humans is super-confusing. Sorry Dijkstra!
ingest_order = models.PositiveIntegerField(blank=False, null=False)
digest_order = models.PositiveIntegerField(blank=False, null=False)
# irrelevance_for_retention is set on-ingest based on the number of available events for an issue; it is combined
# with age-based-irrelevance to determine which events will be evicted when retention quota are met.
@@ -157,7 +157,7 @@ class Event(models.Model):
class Meta:
unique_together = [
("project", "event_id"),
("issue", "ingest_order"),
("issue", "digest_order"),
]
indexes = [
models.Index(fields=["project", "never_evict", "server_side_timestamp", "irrelevance_for_retention"]),
@@ -179,7 +179,7 @@ class Event(models.Model):
return get_title_for_exception_type_and_value(self.calculated_type, self.calculated_value)
@classmethod
def from_ingested(cls, event_metadata, ingest_order, stored_event_count, issue, parsed_data, denormalized_fields):
def from_ingested(cls, event_metadata, digest_order, stored_event_count, issue, parsed_data, denormalized_fields):
# 'from_ingested' may be a bit of a misnomer... the full 'from_ingested' is done in 'digest_event' in the views.
# below at least puts the parsed_data in the right place, and does some of the basic object set up (FKs to other
# objects etc).
@@ -215,7 +215,7 @@ class Event(models.Model):
debug_info=event_metadata["debug_info"],
ingest_order=ingest_order,
digest_order=digest_order,
irrelevance_for_retention=irrelevance_for_retention,
**denormalized_fields,

View File

@@ -57,7 +57,7 @@ def nonzero_leading_bits(n):
return len(s.rstrip('0'))
def get_random_irrelevance(event_count):
def get_random_irrelevance(stored_event_count):
"""
gets a fixed-at-creation irrelevance-score for an Event; the basic idea is: the more events you have for a certain
issue, the less relevant any new event will be _on average_; but when you have many events you will on average still
@@ -67,7 +67,7 @@ def get_random_irrelevance(event_count):
if `cnt` "hovers" around a certain value (which is likely to happen when there's repeated eviction/fill-up). ×2 is
simply to correct for random() (which returns .5 on average).
"""
return nonzero_leading_bits(round(random() * event_count * 2))
return nonzero_leading_bits(round(random() * stored_event_count * 2))
def should_evict(project, timestamp, stored_event_count):

View File

@@ -150,15 +150,15 @@ class BaseIngestAPIView(View):
if not Grouping.objects.filter(project_id=event_metadata["project_id"], grouping_key=grouping_key).exists():
# we don't have Project.issue_count here ('premature optimization') so we just do an aggregate instead.
max_current = Issue.objects.filter(project_id=event_metadata["project_id"]).aggregate(
Max("ingest_order"))["ingest_order__max"]
issue_ingest_order = max_current + 1 if max_current is not None else 1
Max("digest_order"))["digest_order__max"]
issue_digest_order = max_current + 1 if max_current is not None else 1
issue = Issue.objects.create(
ingest_order=issue_ingest_order,
digest_order=issue_digest_order,
project_id=event_metadata["project_id"],
first_seen=timestamp,
last_seen=timestamp,
event_count=1,
digested_event_count=1,
**denormalized_fields,
)
# even though in our data-model a given grouping does not imply a single Issue (in fact, that's the whole
@@ -179,7 +179,7 @@ class BaseIngestAPIView(View):
# update the denormalized fields
issue.last_seen = timestamp
issue.event_count += 1
issue.digested_event_count += 1
# NOTE: possibly expensive. "in theory" we can just do some bookkeeping for a denormalized value, but that may
# be hard to keep in-sync in practice. Let's check the actual cost first.
@@ -200,7 +200,7 @@ class BaseIngestAPIView(View):
# information available here, we could add it to the Event model.
event, event_created = Event.from_ingested(
event_metadata,
issue.event_count,
issue.digested_event_count,
issue_stored_event_count,
issue,
event_data,

View File

@@ -47,7 +47,7 @@ class IssueAdmin(admin.ModelAdmin):
'is_muted',
'unmute_on_volume_based_conditions',
'unmute_after',
'event_count',
'digested_event_count',
]
inlines = [
@@ -58,7 +58,7 @@ class IssueAdmin(admin.ModelAdmin):
list_display = [
"title",
"project",
"event_count",
"digested_event_count",
]
list_filter = [
"project",
@@ -71,5 +71,5 @@ class IssueAdmin(admin.ModelAdmin):
'friendly_id',
'calculated_type',
'calculated_value',
'event_count',
'digested_event_count',
]

View File

@@ -53,5 +53,5 @@ def denormalized_issue_fields():
return {
"first_seen": timezone.now(),
"last_seen": timezone.now(),
"event_count": 1,
"digested_event_count": 1,
}

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-07-16 13:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('issues', '0003_alter_turningpoint_triggering_event'),
]
operations = [
migrations.RenameField(
model_name='issue',
old_name='event_count',
new_name='digested_event_count',
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.13 on 2024-07-16 13:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('projects', '0004_project_quota_exceeded_until'),
('issues', '0004_rename_event_count_issue_digested_event_count'),
]
operations = [
migrations.RenameField(
model_name='issue',
old_name='ingest_order',
new_name='digest_order',
),
migrations.AlterUniqueTogether(
name='issue',
unique_together={('project', 'digest_order')},
),
]

View File

@@ -31,13 +31,13 @@ class Issue(models.Model):
project = models.ForeignKey(
"projects.Project", blank=False, null=True, on_delete=models.SET_NULL) # SET_NULL: cleanup 'later'
# 1-based for the same reasons as Event.ingest_order
ingest_order = models.PositiveIntegerField(blank=False, null=False)
# 1-based for the same reasons as Event.digest_order
digest_order = models.PositiveIntegerField(blank=False, null=False)
# denormalized/cached fields:
last_seen = models.DateTimeField(blank=False, null=False) # based on event.server_side_timestamp
first_seen = models.DateTimeField(blank=False, null=False) # based on event.server_side_timestamp
event_count = models.IntegerField(blank=False, null=False)
digested_event_count = models.IntegerField(blank=False, null=False)
calculated_type = models.CharField(max_length=255, blank=True, null=False, default="")
calculated_value = models.CharField(max_length=255, blank=True, null=False, default="")
transaction = models.CharField(max_length=200, blank=True, null=False, default="")
@@ -60,15 +60,15 @@ class Issue(models.Model):
unmute_after = models.DateTimeField(blank=True, null=True)
def save(self, *args, **kwargs):
if self.ingest_order is None:
if self.digest_order is None:
# testing-only; in production this should never happen and instead have been done in the ingest view.
max_current = self.ingest_order = Issue.objects.filter(project=self.project).aggregate(
models.Max("ingest_order"))["ingest_order__max"]
self.ingest_order = max_current + 1 if max_current is not None else 1
max_current = self.digest_order = Issue.objects.filter(project=self.project).aggregate(
models.Max("digest_order"))["digest_order__max"]
self.digest_order = max_current + 1 if max_current is not None else 1
super().save(*args, **kwargs)
def friendly_id(self):
return f"{ self.project.slug.upper() }-{ self.ingest_order }"
return f"{ self.project.slug.upper() }-{ self.digest_order }"
def get_absolute_url(self):
return f"/issues/issue/{ self.id }/event/last/"
@@ -104,7 +104,7 @@ class Issue(models.Model):
class Meta:
unique_together = [
("project", "ingest_order"),
("project", "digest_order"),
]
indexes = [
models.Index(fields=["first_seen"]),

View File

@@ -1,5 +1,5 @@
{% if event.ingest_order > 1 %}
<a href="{% url this_view issue_pk=issue.pk ingest_order=event.ingest_order|add:"-1" %}" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
{% if event.digest_order > 1 %}
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order|add:"-1" %}" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M9.78 4.22a.75.75 0 0 1 0 1.06L7.06 8l2.72 2.72a.75.75 0 1 1-1.06 1.06L5.47 8.53a.75.75 0 0 1 0-1.06l3.25-3.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /></svg>
</a>
{% else %}
@@ -8,8 +8,8 @@
</div>
{% endif %}
{% if event.ingest_order < issue.event_count %}
<a href="{% url this_view issue_pk=issue.pk ingest_order=event.ingest_order|add:"1" %}"" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
{% if event.digest_order < issue.digested_event_count %}
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order|add:"1" %}"" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
</a>
{% else %}

View File

@@ -118,7 +118,7 @@
</div>
<div class="flex p-4 bg-slate-200 border-b-2"><!-- bottom nav bar -->
{% if is_event_page %}<div>Event {{ event.ingest_order }} of {{ issue.event_count}} which occured at <span class="font-bold">{{ event.server_side_timestamp|date:"j M G:i T" }}</span></div>{% endif %}
{% if is_event_page %}<div>Event {{ event.digest_order }} of {{ issue.digested_event_count}} which occured at <span class="font-bold">{{ event.server_side_timestamp|date:"j M G:i T" }}</span></div>{% endif %}
<div class="ml-auto pr-4 font-bold text-slate-500">
<a href="/admin/issues/issue/{{ issue.id }}/change/">Issue Admin</a>
{% if is_event_page %}
@@ -170,10 +170,10 @@
<div class="mb-4">
<div class="text-sm font-bold text-slate-500">Nr. of events:</div>
<div>{{ issue.event_count }}</div>
<div>{{ issue.digested_event_count }}</div>
</div>
{% if issue.event_count > 1 %}
{% if issue.digested_event_count > 1 %}
<div class="mb-4">
<div class="text-sm font-bold text-slate-500">First seen:</div>
<div>{{ issue.first_seen|date:"j M G:i T" }}</div>

View File

@@ -7,7 +7,7 @@
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.digest_order }} of {{ issue.digested_event_count }})</div>
</div>
<div class="ml-auto flex-none">

View File

@@ -7,7 +7,7 @@
{# this is here to fool tailwind (because we're foolish enough to put html in python) <span class="text-xs"></span> #}
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.digest_order }} of {{ issue.digested_event_count }})</div>
</div>
<div class="ml-auto flex-none">

View File

@@ -140,7 +140,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" />
</svg>&nbsp;&nbsp;{% endif %}{{ issue.title|truncatechars:100 }}</a>
</div>
<div class="text-sm">from <b>{{ issue.first_seen|date:"j M G:i T" }}</b> | last <b>{{ issue.last_seen|date:"j M G:i T" }}</b> | with <b>{{ issue.event_count }}</b> events
<div class="text-sm">from <b>{{ issue.first_seen|date:"j M G:i T" }}</b> | last <b>{{ issue.last_seen|date:"j M G:i T" }}</b> | with <b>{{ issue.digested_event_count }}</b> events
{% if issue.transaction %}| <span class="font-bold">{{ issue.transaction }} </span>{% endif %}
</div>
</td>

View File

@@ -9,7 +9,7 @@
{# event-nav only #}
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.digest_order }} of {{ issue.digested_event_count }})</div>
</div>
<div class="ml-auto flex-none">
@@ -30,7 +30,7 @@
<div class="flex">
<div class="overflow-hidden">
{% if forloop.counter0 == 0 %}
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i T" }} (Event {{ event.digest_order }} of {{ issue.digested_event_count }})</div>
{% endif %}
<h1 class="text-2xl font-bold {% if forloop.counter0 > 0 %}mt-4{% endif %} text-ellipsis whitespace-nowrap overflow-hidden">{{ exception.type }}</h1>
<div class="text-lg mb-4 text-ellipsis whitespace-nowrap overflow-hidden">{{ exception.value }}</div>

View File

@@ -17,9 +17,9 @@ urlpatterns = [
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/details/', issue_event_details, name="event_details"),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/breadcrumbs/', issue_event_breadcrumbs, name="event_breadcrumbs"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/', issue_event_stacktrace, name="event_stacktrace"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/details/', issue_event_details, name="event_details"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/breadcrumbs/', issue_event_breadcrumbs,
path('issue/<uuid:issue_pk>/event/<int:digest_order>/', issue_event_stacktrace, name="event_stacktrace"),
path('issue/<uuid:issue_pk>/event/<int:digest_order>/details/', issue_event_details, name="event_details"),
path('issue/<uuid:issue_pk>/event/<int:digest_order>/breadcrumbs/', issue_event_breadcrumbs,
name="event_breadcrumbs"),
path('issue/<uuid:issue_pk>/history/', issue_history),

View File

@@ -257,7 +257,7 @@ def _handle_post(request, issue):
return HttpResponseRedirect(request.path_info)
def _get_event(issue, event_pk, ingest_order):
def _get_event(issue, event_pk, digest_order):
if event_pk is not None:
# we match on both internal and external id, trying internal first
try:
@@ -265,19 +265,19 @@ def _get_event(issue, event_pk, ingest_order):
except Event.DoesNotExist:
return get_object_or_404(Event, issue=issue, event_id=event_pk)
elif ingest_order is not None:
return get_object_or_404(Event, issue=issue, ingest_order=ingest_order)
elif digest_order is not None:
return get_object_or_404(Event, issue=issue, digest_order=digest_order)
else:
raise ValueError("either event_pk or ingest_order must be provided")
raise ValueError("either event_pk or digest_order must be provided")
@atomic_for_request_method
@issue_membership_required
def issue_event_stacktrace(request, issue, event_pk=None, ingest_order=None):
def issue_event_stacktrace(request, issue, event_pk=None, digest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = _get_event(issue, event_pk, ingest_order)
event = _get_event(issue, event_pk, digest_order)
parsed_data = json.loads(event.data)
@@ -320,11 +320,11 @@ def issue_event_stacktrace(request, issue, event_pk=None, ingest_order=None):
@atomic_for_request_method
@issue_membership_required
def issue_event_breadcrumbs(request, issue, event_pk=None, ingest_order=None):
def issue_event_breadcrumbs(request, issue, event_pk=None, digest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = _get_event(issue, event_pk, ingest_order)
event = _get_event(issue, event_pk, digest_order)
parsed_data = json.loads(event.data)
@@ -348,11 +348,11 @@ def _date_with_milis_html(timestamp):
@atomic_for_request_method
@issue_membership_required
def issue_event_details(request, issue, event_pk=None, ingest_order=None):
def issue_event_details(request, issue, event_pk=None, digest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = _get_event(issue, event_pk, ingest_order)
event = _get_event(issue, event_pk, digest_order)
parsed_data = json.loads(event.data)
key_info = [