-
-
Notifications
You must be signed in to change notification settings - Fork 4k
/
organization_events_has_measurements.py
121 lines (101 loc) · 4.69 KB
/
organization_events_has_measurements.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from datetime import timedelta
import sentry_sdk
from django.core.cache import cache
from django.utils import timezone
from rest_framework import serializers
from rest_framework.request import Request
from rest_framework.response import Response
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases import NoProjects, OrganizationEventsV2EndpointBase
from sentry.snuba import discover
from sentry.utils.hashlib import md5_text
MEASUREMENT_TYPES = {
"web": [
"measurements.fp",
"measurements.fcp",
"measurements.lcp",
"measurements.fid",
"measurements.cls",
],
"mobile": [
"measurements.app_start_cold",
"measurements.app_start_warm",
"measurements.frames_total",
"measurements.frames_slow",
"measurements.frames_frozen",
"measurements.stall_count",
"measurements.stall_stall_total_time",
"measurements.stall_stall_longest_time",
],
}
class EventsHasMeasurementsQuerySerializer(serializers.Serializer):
transaction = serializers.CharField(max_length=200)
type = serializers.ChoiceField(choices=list(MEASUREMENT_TYPES.keys()))
def validate(self, data):
# only allow one project at a time in order to cache the results
# for a unique transaction
project_ids = self.context.get("params", {}).get("project_id", [])
if len(project_ids) != 1:
raise serializers.ValidationError("Only 1 project allowed.")
return data
@region_silo_endpoint
class OrganizationEventsHasMeasurementsEndpoint(OrganizationEventsV2EndpointBase):
publish_status = {
"GET": ApiPublishStatus.UNKNOWN,
}
def get(self, request: Request, organization) -> Response:
if not self.has_feature(organization, request):
return Response(status=404)
with sentry_sdk.start_span(op="discover.endpoint", description="parse params"):
try:
# This endpoint only allows for a single project + transaction, so no need
# to check `global-views`.
params = self.get_snuba_params(request, organization, check_global_views=False)
# Once an transaction begins containing measurement data, it is unlikely
# it will stop. So it makes more sense to always query the latest data.
#
# Additionally, to account for periods of low volume, increase the range
# to 7 days to have a better chance of finding an example event and provide
# a more consistent experience.
now = timezone.now()
params["start"] = now - timedelta(days=7)
params["end"] = now
except NoProjects:
return Response({"measurements": False})
data = {
"transaction": request.GET.get("transaction"),
"type": request.GET.get("type"),
}
serializer = EventsHasMeasurementsQuerySerializer(data=data, context={"params": params})
if not serializer.is_valid():
return Response(serializer.errors, status=400)
org_id = organization.id
project_id = params["project_id"][0]
md5_hash = md5_text(data["transaction"], data["type"]).hexdigest()
cache_key = f"check-events-measurements:{org_id}:{project_id}:{md5_hash}"
has_measurements = cache.get(cache_key)
# cache miss, need to make the query again
if has_measurements is None:
with self.handle_query_errors():
data = serializer.validated_data
# generate the appropriate query for the selected type
transaction_query = f'transaction:{data["transaction"]}'
measurements = MEASUREMENT_TYPES[data["type"]]
has_queries = [f"has:{measurement}" for measurement in measurements]
measurement_query = " OR ".join(has_queries)
query = f"{transaction_query} ({measurement_query})"
results = discover.query(
selected_columns=["id"],
query=query,
params=params,
limit=1, # Just want to check for existence of such an event
referrer="api.events.measurements",
auto_fields=True,
auto_aggregations=False,
use_aggregate_conditions=False,
)
has_measurements = len(results["data"]) > 0
# cache the results for 5 minutes
cache.set(cache_key, has_measurements, 300)
return Response({"measurements": has_measurements})