Files
sentinel/tests/snuba/api/endpoints/test_organization_events_mep.py
2024-02-15 17:01:50 +00:00

3340 lines
116 KiB
Python

from __future__ import annotations
from typing import Any
from unittest import mock
import pytest
from django.urls import reverse
from rest_framework.response import Response
from sentry.discover.models import TeamKeyTransaction
from sentry.models.projectteam import ProjectTeam
from sentry.models.transaction_threshold import (
ProjectTransactionThreshold,
ProjectTransactionThresholdOverride,
TransactionMetric,
)
from sentry.search.events import constants
from sentry.search.utils import map_device_class_level
from sentry.snuba.metrics.extraction import MetricSpecType, OnDemandMetricSpec
from sentry.snuba.metrics.naming_layer.mri import TransactionMRI
from sentry.snuba.metrics.naming_layer.public import TransactionMetricKey
from sentry.snuba.utils import DATASET_OPTIONS
from sentry.testutils.cases import MetricsEnhancedPerformanceTestCase
from sentry.testutils.helpers.datetime import before_now, iso_format
from sentry.testutils.silo import region_silo_test
from sentry.utils.samples import load_data
pytestmark = pytest.mark.sentry_metrics
@region_silo_test
class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
viewname = "sentry-api-0-organization-events"
# Poor intentionally omitted for test_measurement_rating_that_does_not_exist
METRIC_STRINGS = [
"foo_transaction",
"bar_transaction",
"baz_transaction",
"staging",
"measurement_rating",
"good",
"meh",
"d:transactions/measurements.something_custom@millisecond",
"d:transactions/measurements.runtime@hour",
"d:transactions/measurements.bytes_transfered@byte",
"d:transactions/measurements.datacenter_memory@petabyte",
"d:transactions/measurements.custom.kilobyte@kilobyte",
"d:transactions/measurements.longtaskcount@none",
"d:transactions/measurements.percent@ratio",
"d:transactions/measurements.custom_type@somethingcustom",
]
def setUp(self):
super().setUp()
self.min_ago = before_now(minutes=1)
self.two_min_ago = before_now(minutes=2)
self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
self.features = {
"organizations:performance-use-metrics": True,
}
def do_request(self, query, features=None):
if features is None:
features = {"organizations:discover-basic": True}
features.update(self.features)
self.login_as(user=self.user)
url = reverse(
self.viewname,
kwargs={"organization_slug": self.organization.slug},
)
with self.feature(features):
return self.client.get(url, query, format="json")
def test_no_projects(self):
response = self.do_request(
{
"dataset": "metricsEnhanced",
}
)
assert response.status_code == 200, response.content
def test_invalid_dataset(self):
response = self.do_request(
{
"dataset": "aFakeDataset",
"project": self.project.id,
}
)
assert response.status_code == 400, response.content
assert (
response.data["detail"]
== f"dataset must be one of: {', '.join([key for key in DATASET_OPTIONS.keys()])}"
)
def test_out_of_retention(self):
self.create_project()
with self.options({"system.event-retention-days": 10}):
query = {
"field": ["id", "timestamp"],
"orderby": ["-timestamp", "-id"],
"query": "event.type:transaction",
"start": iso_format(before_now(days=20)),
"end": iso_format(before_now(days=15)),
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 400, response.content
assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
def test_invalid_search_terms(self):
response = self.do_request(
{
"field": ["epm()"],
"query": "hi \n there",
"project": self.project.id,
"dataset": "metricsEnhanced",
}
)
assert response.status_code == 400, response.content
assert (
response.data["detail"]
== "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
)
def test_percentile_with_no_data(self):
response = self.do_request(
{
"field": ["p50()"],
"query": "",
"project": self.project.id,
"dataset": "metricsEnhanced",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["p50()"] == 0
def test_project_name(self):
self.store_transaction_metric(
1,
tags={"environment": "staging"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["project.name", "environment", "epm()"],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["project.name"] == self.project.slug
assert "project.id" not in data[0]
assert data[0]["environment"] == "staging"
assert meta["isMetricsData"]
assert field_meta["project.name"] == "string"
assert field_meta["environment"] == "string"
assert field_meta["epm()"] == "rate"
def test_project_id(self):
self.store_transaction_metric(
1,
tags={"environment": "staging"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["project_id", "environment", "epm()"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["project_id"] == self.project.id
assert data[0]["environment"] == "staging"
assert meta["isMetricsData"]
assert field_meta["project_id"] == "integer"
assert field_meta["environment"] == "string"
assert field_meta["epm()"] == "rate"
def test_project_dot_id(self):
self.store_transaction_metric(
1,
tags={"environment": "staging"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["project.id", "environment", "epm()"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["project.id"] == self.project.id
assert data[0]["environment"] == "staging"
assert meta["isMetricsData"]
assert field_meta["project.id"] == "integer"
assert field_meta["environment"] == "string"
assert field_meta["epm()"] == "rate"
def test_title_alias(self):
"""title is an alias to transaction name"""
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["title", "p50()"],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["title"] == "foo_transaction"
assert data[0]["p50()"] == 1
assert meta["isMetricsData"]
assert field_meta["title"] == "string"
assert field_meta["p50()"] == "duration"
def test_having_condition(self):
self.store_transaction_metric(
1,
tags={"environment": "staging", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
# shouldn't show up
100,
tags={"environment": "staging", "transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["transaction", "project", "p50(transaction.duration)"],
"query": "event.type:transaction p50(transaction.duration):<50",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["project"] == self.project.slug
assert data[0]["p50(transaction.duration)"] == 1
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["project"] == "string"
assert field_meta["p50(transaction.duration)"] == "duration"
def test_having_condition_with_preventing_aggregates(self):
self.store_transaction_metric(
1,
tags={"environment": "staging", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"environment": "staging", "transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["transaction", "project", "p50(transaction.duration)"],
"query": "event.type:transaction p50(transaction.duration):<50",
"dataset": "metricsEnhanced",
"preventMetricAggregates": "1",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
meta = response.data["meta"]
field_meta = meta["fields"]
assert not meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["project"] == "string"
assert field_meta["p50(transaction.duration)"] == "duration"
def test_having_condition_with_preventing_aggregate_metrics_only(self):
"""same as the previous test, but with the dataset on explicit metrics
which should throw a 400 error instead"""
response = self.do_request(
{
"field": ["transaction", "project", "p50(transaction.duration)"],
"query": "event.type:transaction p50(transaction.duration):<50",
"dataset": "metrics",
"preventMetricAggregates": "1",
"per_page": 50,
"project": self.project.id,
}
)
assert response.status_code == 400, response.content
def test_having_condition_not_selected(self):
self.store_transaction_metric(
1,
tags={"environment": "staging", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
# shouldn't show up
100,
tags={"environment": "staging", "transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["transaction", "project", "p50(transaction.duration)"],
"query": "event.type:transaction p75(transaction.duration):<50",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["project"] == self.project.slug
assert data[0]["p50(transaction.duration)"] == 1
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["project"] == "string"
assert field_meta["p50(transaction.duration)"] == "duration"
def test_non_metrics_tag_with_implicit_format(self):
self.store_transaction_metric(
1,
tags={"environment": "staging", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["test", "p50(transaction.duration)"],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
assert not response.data["meta"]["isMetricsData"]
def test_non_metrics_tag_with_implicit_format_metrics_dataset(self):
self.store_transaction_metric(
1,
tags={"environment": "staging", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["test", "p50(transaction.duration)"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 400, response.content
def test_performance_homepage_query(self):
self.store_transaction_metric(
1,
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"measurements.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
2,
"measurements.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
3,
"measurements.fid",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
4,
"measurements.cls",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"user",
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
},
timestamp=self.min_ago,
)
for dataset in ["metrics", "metricsEnhanced"]:
response = self.do_request(
{
"field": [
"transaction",
"project",
"tpm()",
"p75(measurements.fcp)",
"p75(measurements.lcp)",
"p75(measurements.fid)",
"p75(measurements.cls)",
"count_unique(user)",
"apdex()",
"count_miserable(user)",
"user_misery()",
"failure_rate()",
"failure_count()",
],
"orderby": "tpm()",
"query": "event.type:transaction",
"dataset": dataset,
"per_page": 50,
}
)
assert len(response.data["data"]) == 1
data = response.data["data"][0]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data["transaction"] == "foo_transaction"
assert data["project"] == self.project.slug
assert data["p75(measurements.fcp)"] == 1.0
assert data["p75(measurements.lcp)"] == 2.0
assert data["p75(measurements.fid)"] == 3.0
assert data["p75(measurements.cls)"] == 4.0
assert data["apdex()"] == 1.0
assert data["count_miserable(user)"] == 1.0
assert data["user_misery()"] == 0.058
assert data["failure_rate()"] == 1
assert data["failure_count()"] == 1
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["project"] == "string"
assert field_meta["p75(measurements.fcp)"] == "duration"
assert field_meta["p75(measurements.lcp)"] == "duration"
assert field_meta["p75(measurements.fid)"] == "duration"
assert field_meta["p75(measurements.cls)"] == "number"
assert field_meta["apdex()"] == "number"
assert field_meta["count_miserable(user)"] == "integer"
assert field_meta["user_misery()"] == "number"
assert field_meta["failure_rate()"] == "percentage"
assert field_meta["failure_count()"] == "integer"
assert field_meta["tpm()"] == "rate"
assert meta["units"]["tpm()"] == "1/minute"
def test_user_misery_and_team_key_sort(self):
self.store_transaction_metric(
1,
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"measurements.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
2,
"measurements.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
3,
"measurements.fid",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
4,
"measurements.cls",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"user",
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"team_key_transaction",
"transaction",
"project",
"tpm()",
"p75(measurements.fcp)",
"p75(measurements.lcp)",
"p75(measurements.fid)",
"p75(measurements.cls)",
"count_unique(user)",
"apdex()",
"count_miserable(user)",
"user_misery()",
"failure_rate()",
"failure_count()",
],
"orderby": ["team_key_transaction", "user_misery()"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"][0]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data["transaction"] == "foo_transaction"
assert data["project"] == self.project.slug
assert data["p75(measurements.fcp)"] == 1.0
assert data["p75(measurements.lcp)"] == 2.0
assert data["p75(measurements.fid)"] == 3.0
assert data["p75(measurements.cls)"] == 4.0
assert data["apdex()"] == 1.0
assert data["count_miserable(user)"] == 1.0
assert data["user_misery()"] == 0.058
assert data["failure_rate()"] == 1
assert data["failure_count()"] == 1
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["project"] == "string"
assert field_meta["p75(measurements.fcp)"] == "duration"
assert field_meta["p75(measurements.lcp)"] == "duration"
assert field_meta["p75(measurements.fid)"] == "duration"
assert field_meta["p75(measurements.cls)"] == "number"
assert field_meta["apdex()"] == "number"
assert field_meta["count_miserable(user)"] == "integer"
assert field_meta["user_misery()"] == "number"
assert field_meta["failure_rate()"] == "percentage"
assert field_meta["failure_count()"] == "integer"
def test_no_team_key_transactions(self):
self.store_transaction_metric(
1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago
)
self.store_transaction_metric(
100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
)
query = {
"team": "myteams",
"project": [self.project.id],
# TODO sort by transaction here once that's possible for order to match the same test without metrics
"orderby": "p95()",
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 0
assert data[1]["transaction"] == "bar_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
def test_team_key_transactions_my_teams(self):
team1 = self.create_team(organization=self.organization, name="Team A")
self.create_team_membership(team1, user=self.user)
self.project.add_team(team1)
team2 = self.create_team(organization=self.organization, name="Team B")
self.project.add_team(team2)
key_transactions = [
(team1, "foo_transaction"),
(team2, "baz_transaction"),
]
# Not a key transaction
self.store_transaction_metric(
100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
)
for team, transaction in key_transactions:
self.store_transaction_metric(
1, tags={"transaction": transaction}, timestamp=self.min_ago
)
TeamKeyTransaction.objects.create(
organization=self.organization,
transaction=transaction,
project_team=ProjectTeam.objects.get(project=self.project, team=team),
)
query = {
"team": "myteams",
"project": [self.project.id],
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
query["orderby"] = ["team_key_transaction", "p95()"]
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 3
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "baz_transaction"
assert data[1]["team_key_transaction"] == 0
assert data[1]["transaction"] == "bar_transaction"
assert data[2]["team_key_transaction"] == 1
assert data[2]["transaction"] == "foo_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# not specifying any teams should use my teams
query = {
"project": [self.project.id],
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
query["orderby"] = ["team_key_transaction", "p95()"]
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 3
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "baz_transaction"
assert data[1]["team_key_transaction"] == 0
assert data[1]["transaction"] == "bar_transaction"
assert data[2]["team_key_transaction"] == 1
assert data[2]["transaction"] == "foo_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
def test_team_key_transactions_orderby(self):
team1 = self.create_team(organization=self.organization, name="Team A")
team2 = self.create_team(organization=self.organization, name="Team B")
key_transactions = [
(team1, "foo_transaction", 1),
(team2, "baz_transaction", 100),
]
# Not a key transaction
self.store_transaction_metric(
100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
)
for team, transaction, value in key_transactions:
self.store_transaction_metric(
value, tags={"transaction": transaction}, timestamp=self.min_ago
)
self.create_team_membership(team, user=self.user)
self.project.add_team(team)
TeamKeyTransaction.objects.create(
organization=self.organization,
transaction=transaction,
project_team=ProjectTeam.objects.get(project=self.project, team=team),
)
query = {
"team": "myteams",
"project": [self.project.id],
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
# test ascending order
query["orderby"] = ["team_key_transaction", "p95()"]
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 3
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "bar_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "foo_transaction"
assert data[2]["team_key_transaction"] == 1
assert data[2]["transaction"] == "baz_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# test descending order
query["orderby"] = ["-team_key_transaction", "p95()"]
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 3
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 1
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "baz_transaction"
assert data[2]["team_key_transaction"] == 0
assert data[2]["transaction"] == "bar_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
def test_team_key_transactions_query(self):
team1 = self.create_team(organization=self.organization, name="Team A")
team2 = self.create_team(organization=self.organization, name="Team B")
key_transactions = [
(team1, "foo_transaction", 1),
(team2, "baz_transaction", 100),
]
# Not a key transaction
self.store_transaction_metric(
100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
)
for team, transaction, value in key_transactions:
self.store_transaction_metric(
value, tags={"transaction": transaction}, timestamp=self.min_ago
)
self.create_team_membership(team, user=self.user)
self.project.add_team(team)
TeamKeyTransaction.objects.create(
organization=self.organization,
transaction=transaction,
project_team=ProjectTeam.objects.get(project=self.project, team=team),
)
query = {
"team": "myteams",
"project": [self.project.id],
# use the order by to ensure the result order
"orderby": "p95()",
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
# key transactions
query["query"] = "has:team_key_transaction"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 1
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "baz_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# key transactions
query["query"] = "team_key_transaction:true"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 1
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "baz_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# not key transactions
query["query"] = "!has:team_key_transaction"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "bar_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# not key transactions
query["query"] = "team_key_transaction:false"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 0
assert data[0]["transaction"] == "bar_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
def test_team_key_transaction_not_exists(self):
team1 = self.create_team(organization=self.organization, name="Team A")
team2 = self.create_team(organization=self.organization, name="Team B")
key_transactions = [
(team1, "foo_transaction", 1),
(team2, "baz_transaction", 100),
]
for team, transaction, value in key_transactions:
self.store_transaction_metric(
value, tags={"transaction": transaction}, timestamp=self.min_ago
)
self.create_team_membership(team, user=self.user)
self.project.add_team(team)
TeamKeyTransaction.objects.create(
organization=self.organization,
transaction=transaction,
project_team=ProjectTeam.objects.get(project=self.project, team=team),
)
# Don't create a metric for this one
TeamKeyTransaction.objects.create(
organization=self.organization,
transaction="not_in_metrics",
project_team=ProjectTeam.objects.get(project=self.project, team=team1),
)
query = {
"team": "myteams",
"project": [self.project.id],
# use the order by to ensure the result order
"orderby": "p95()",
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"per_page": 50,
"dataset": "metricsEnhanced",
}
# key transactions
query["query"] = "has:team_key_transaction"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 1
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "baz_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# key transactions
query["query"] = "team_key_transaction:true"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["team_key_transaction"] == 1
assert data[0]["transaction"] == "foo_transaction"
assert data[1]["team_key_transaction"] == 1
assert data[1]["transaction"] == "baz_transaction"
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# not key transactions
query["query"] = "!has:team_key_transaction"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
# not key transactions
query["query"] = "team_key_transaction:false"
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert meta["isMetricsData"]
assert field_meta["team_key_transaction"] == "boolean"
assert field_meta["transaction"] == "string"
def test_too_many_team_key_transactions(self):
MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
with mock.patch(
"sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
):
team = self.create_team(organization=self.organization, name="Team A")
self.create_team_membership(team, user=self.user)
self.project.add_team(team)
project_team = ProjectTeam.objects.get(project=self.project, team=team)
transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
self.store_transaction_metric(
100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
)
TeamKeyTransaction.objects.bulk_create(
[
TeamKeyTransaction(
organization=self.organization,
project_team=project_team,
transaction=transactions[i],
)
for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
]
)
query = {
"team": "myteams",
"project": [self.project.id],
"orderby": "p95()",
"field": [
"team_key_transaction",
"transaction",
"transaction.status",
"project",
"epm()",
"failure_rate()",
"p95()",
],
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
assert (
sum(row["team_key_transaction"] for row in data)
== MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
)
assert meta["isMetricsData"]
def test_measurement_rating(self):
self.store_transaction_metric(
50,
metric="measurements.lcp",
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
15,
metric="measurements.fp",
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1500,
metric="measurements.fcp",
tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
125,
metric="measurements.fid",
tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.15,
metric="measurements.cls",
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"count_web_vitals(measurements.lcp, good)",
"count_web_vitals(measurements.fp, good)",
"count_web_vitals(measurements.fcp, meh)",
"count_web_vitals(measurements.fid, meh)",
"count_web_vitals(measurements.cls, good)",
"count_web_vitals(measurements.lcp, any)",
],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
assert data[0]["count_web_vitals(measurements.lcp, any)"] == 1
assert meta["isMetricsData"]
assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
assert field_meta["count_web_vitals(measurements.lcp, any)"] == "integer"
def test_measurement_rating_that_does_not_exist(self):
self.store_transaction_metric(
1,
metric="measurements.lcp",
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
assert meta["isMetricsData"]
assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
def test_count_web_vitals_invalid_vital(self):
query = {
"field": [
"count_web_vitals(measurements.foo, poor)",
],
"project": [self.project.id],
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 400, response.content
query = {
"field": [
"count_web_vitals(tags[lcp], poor)",
],
"project": [self.project.id],
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 400, response.content
query = {
"field": [
"count_web_vitals(transaction.duration, poor)",
],
"project": [self.project.id],
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 400, response.content
query = {
"field": [
"count_web_vitals(measurements.lcp, bad)",
],
"project": [self.project.id],
"dataset": "metricsEnhanced",
}
response = self.do_request(query)
assert response.status_code == 400, response.content
def test_count_unique_user_returns_zero(self):
self.store_transaction_metric(
50,
metric="user",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
50,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p50()",
"field": [
"transaction",
"count_unique(user)",
"p50()",
],
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["count_unique(user)"] == 1
assert data[1]["transaction"] == "bar_transaction"
assert data[1]["count_unique(user)"] == 0
assert meta["isMetricsData"]
def test_sum_transaction_duration(self):
self.store_transaction_metric(
50,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
150,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "sum(transaction.duration)",
"field": [
"transaction",
"sum(transaction.duration)",
],
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["sum(transaction.duration)"] == 300
assert meta["isMetricsData"]
def test_custom_measurements_simple(self):
self.store_transaction_metric(
1,
metric="measurements.something_custom",
internal_metric="d:transactions/measurements.something_custom@millisecond",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p50(measurements.something_custom)",
"field": [
"transaction",
"p50(measurements.something_custom)",
],
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
self.wait_for_metric_count(
self.project,
1,
metric="measurements.something_custom",
mri="d:transactions/measurements.something_custom@millisecond",
)
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["p50(measurements.something_custom)"] == 1
assert meta["isMetricsData"]
assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
def test_custom_measurement_size_meta_type(self):
self.store_transaction_metric(
100,
metric="measurements.custom_type",
internal_metric="d:transactions/measurements.custom_type@somethingcustom",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
metric="measurements.percent",
internal_metric="d:transactions/measurements.percent@ratio",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
metric="measurements.longtaskcount",
internal_metric="d:transactions/measurements.longtaskcount@none",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p50(measurements.longtaskcount)",
"field": [
"transaction",
"p50(measurements.longtaskcount)",
"p50(measurements.percent)",
"p50(measurements.custom_type)",
],
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["p50(measurements.longtaskcount)"] == 100
assert data[0]["p50(measurements.percent)"] == 100
assert data[0]["p50(measurements.custom_type)"] == 100
assert meta["isMetricsData"]
assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
assert meta["units"]["p50(measurements.longtaskcount)"] is None
assert meta["fields"]["p50(measurements.percent)"] == "percentage"
assert meta["units"]["p50(measurements.percent)"] is None
assert meta["fields"]["p50(measurements.custom_type)"] == "number"
assert meta["units"]["p50(measurements.custom_type)"] is None
def test_custom_measurement_none_type(self):
self.store_transaction_metric(
1,
metric="measurements.cls",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p75(measurements.cls)",
"field": [
"transaction",
"p75(measurements.cls)",
"p99(measurements.cls)",
"max(measurements.cls)",
],
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
self.wait_for_metric_count(
self.project,
1,
metric=TransactionMetricKey.MEASUREMENTS_CLS.value,
mri=TransactionMRI.MEASUREMENTS_CLS.value,
)
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["p75(measurements.cls)"] == 1
assert data[0]["p99(measurements.cls)"] == 1
assert data[0]["max(measurements.cls)"] == 1
assert meta["isMetricsData"]
assert meta["fields"]["p75(measurements.cls)"] == "number"
assert meta["units"]["p75(measurements.cls)"] is None
assert meta["fields"]["p99(measurements.cls)"] == "number"
assert meta["units"]["p99(measurements.cls)"] is None
assert meta["fields"]["max(measurements.cls)"] == "number"
assert meta["units"]["max(measurements.cls)"] is None
def test_custom_measurement_duration_filtering(self):
self.store_transaction_metric(
1,
metric="measurements.runtime",
internal_metric="d:transactions/measurements.runtime@hour",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
180,
metric="measurements.runtime",
internal_metric="d:transactions/measurements.runtime@hour",
entity="metrics_distributions",
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"field": [
"transaction",
"max(measurements.runtime)",
],
"query": "p50(measurements.runtime):>1wk",
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "bar_transaction"
assert data[0]["max(measurements.runtime)"] == 180
assert meta["isMetricsData"]
def test_custom_measurement_size_filtering(self):
self.store_transaction_metric(
1,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"field": [
"transaction",
"max(measurements.datacenter_memory)",
],
"query": "p50(measurements.datacenter_memory):>5pb",
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "bar_transaction"
assert data[0]["max(measurements.datacenter_memory)"] == 100
assert meta["units"]["max(measurements.datacenter_memory)"] == "petabyte"
assert meta["fields"]["max(measurements.datacenter_memory)"] == "size"
assert meta["isMetricsData"]
def test_has_custom_measurement(self):
self.store_transaction_metric(
33,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
transaction_data = load_data("transaction", timestamp=self.min_ago)
transaction_data["measurements"]["datacenter_memory"] = {
"value": 33,
"unit": "petabyte",
}
self.store_event(transaction_data, self.project.id)
measurement = "measurements.datacenter_memory"
response = self.do_request(
{
"field": ["transaction", measurement],
"query": "has:measurements.datacenter_memory",
"dataset": "discover",
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
response = self.do_request(
{
"field": ["transaction", measurement],
"query": "!has:measurements.datacenter_memory",
"dataset": "discover",
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
def test_environment_param(self):
self.create_environment(self.project, name="staging")
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "environment": "staging"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"environment": "staging",
"orderby": "p50(transaction.duration)",
"field": [
"transaction",
"environment",
"p50(transaction.duration)",
],
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["environment"] == "staging"
assert data[0]["p50(transaction.duration)"] == 1
assert meta["isMetricsData"]
@pytest.mark.xfail(reason="Started failing on ClickHouse 21.8")
def test_environment_query(self):
self.create_environment(self.project, name="staging")
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "environment": "staging"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p50(transaction.duration)",
"field": [
"transaction",
"environment",
"p50(transaction.duration)",
],
"query": "!has:environment",
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
self.wait_for_metric_count(self.project, 2)
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["environment"] is None or data[0]["environment"] == ""
assert data[0]["p50(transaction.duration)"] == 100
assert meta["isMetricsData"]
def test_has_transaction(self):
self.store_transaction_metric(
1,
tags={},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"orderby": "p50(transaction.duration)",
"field": [
"transaction",
"p50(transaction.duration)",
],
"query": "has:transaction",
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
self.wait_for_metric_count(self.project, 2)
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "<< unparameterized >>"
assert data[0]["p50(transaction.duration)"] == 1
assert data[1]["transaction"] == "foo_transaction"
assert data[1]["p50(transaction.duration)"] == 100
assert meta["isMetricsData"]
query = {
"project": [self.project.id],
"orderby": "p50(transaction.duration)",
"field": [
"transaction",
"p50(transaction.duration)",
],
"query": "!has:transaction",
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 400, response.content
def test_apdex_transaction_threshold(self):
ProjectTransactionThresholdOverride.objects.create(
transaction="foo_transaction",
project=self.project,
organization=self.project.organization,
threshold=600,
metric=TransactionMetric.LCP.value,
)
ProjectTransactionThresholdOverride.objects.create(
transaction="bar_transaction",
project=self.project,
organization=self.project.organization,
threshold=600,
metric=TransactionMetric.LCP.value,
)
self.store_transaction_metric(
1,
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"measurements.lcp",
tags={
"transaction": "bar_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"apdex()",
],
"orderby": ["apdex()"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["transaction"] == "bar_transaction"
# Threshold is lcp based
assert data[0]["apdex()"] == 1
assert data[1]["transaction"] == "foo_transaction"
# Threshold is lcp based
assert data[1]["apdex()"] == 0
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["apdex()"] == "number"
def test_apdex_project_threshold(self):
ProjectTransactionThreshold.objects.create(
project=self.project,
organization=self.project.organization,
threshold=600,
metric=TransactionMetric.LCP.value,
)
self.store_transaction_metric(
1,
tags={
"transaction": "foo_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
"measurements.lcp",
tags={
"transaction": "bar_transaction",
constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"apdex()",
],
"orderby": ["apdex()"],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["transaction"] == "bar_transaction"
# Threshold is lcp based
assert data[0]["apdex()"] == 1
assert data[1]["transaction"] == "foo_transaction"
# Threshold is lcp based
assert data[1]["apdex()"] == 0
assert meta["isMetricsData"]
assert field_meta["transaction"] == "string"
assert field_meta["apdex()"] == "number"
def test_apdex_satisfaction_param(self):
for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
query = {
"project": [self.project.id],
"field": [
"transaction",
function,
],
"statsPeriod": "24h",
"dataset": "metricsEnhanced",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 0
meta = response.data["meta"]
assert not meta["isMetricsData"], function
query = {
"project": [self.project.id],
"field": [
"transaction",
function,
],
"statsPeriod": "24h",
"dataset": "metrics",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 400, function
assert b"threshold parameter" in response.content, function
def test_mobile_metrics(self):
self.store_transaction_metric(
0.4,
"measurements.frames_frozen_rate",
tags={
"transaction": "bar_transaction",
},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"field": [
"transaction",
"p50(measurements.frames_frozen_rate)",
],
"statsPeriod": "24h",
"dataset": "metrics",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
def test_merge_null_unparam(self):
self.store_transaction_metric(
1,
# Transaction: unparam
tags={
"transaction": "<< unparameterized >>",
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
2,
# Transaction:null
tags={},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"field": [
"transaction",
"p50(transaction.duration)",
],
"statsPeriod": "24h",
"dataset": "metrics",
"per_page": 50,
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
assert response.data["data"][0]["p50(transaction.duration)"] == 1.5
def test_unparam_filter(self):
self.store_transaction_metric(
1,
# Transaction: unparam
tags={
"transaction": "<< unparameterized >>",
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
2,
# Transaction:null
tags={},
timestamp=self.min_ago,
)
self.store_transaction_metric(
3,
tags={
"transaction": "foo_transaction",
},
timestamp=self.min_ago,
)
query = {
"project": [self.project.id],
"field": [
"transaction",
"count()",
],
"query": 'transaction:"<< unparameterized >>"',
"statsPeriod": "24h",
"dataset": "metrics",
"per_page": 50,
}
self.wait_for_metric_count(self.project, 3)
response = self.do_request(query)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
assert response.data["data"][0]["transaction"] == "<< unparameterized >>"
assert response.data["data"][0]["count()"] == 2
def test_custom_measurements_without_function(self):
self.store_transaction_metric(
33,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
transaction_data = load_data("transaction", timestamp=self.min_ago)
transaction_data["measurements"]["datacenter_memory"] = {
"value": 33,
"unit": "petabyte",
}
self.store_event(transaction_data, self.project.id)
measurement = "measurements.datacenter_memory"
response = self.do_request(
{
"field": ["transaction", measurement],
"query": "measurements.datacenter_memory:33pb",
"dataset": "discover",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0][measurement] == 33
meta = response.data["meta"]
field_meta = meta["fields"]
unit_meta = meta["units"]
assert field_meta[measurement] == "size"
assert unit_meta[measurement] == "petabyte"
assert not meta["isMetricsData"]
def test_custom_measurements_with_function(self):
self.store_transaction_metric(
33,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
transaction_data = load_data("transaction", timestamp=self.min_ago)
transaction_data["measurements"]["datacenter_memory"] = {
"value": 33,
"unit": "petabyte",
}
self.store_event(transaction_data, self.project.id)
measurement = "p50(measurements.datacenter_memory)"
response = self.do_request(
{
"field": ["transaction", measurement],
"query": "measurements.datacenter_memory:33pb",
"dataset": "discover",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0][measurement] == 33
meta = response.data["meta"]
field_meta = meta["fields"]
unit_meta = meta["units"]
assert field_meta[measurement] == "size"
assert unit_meta[measurement] == "petabyte"
assert not meta["isMetricsData"]
def test_custom_measurements_equation(self):
self.store_transaction_metric(
33,
metric="measurements.datacenter_memory",
internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
entity="metrics_distributions",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
transaction_data = load_data("transaction", timestamp=self.min_ago)
transaction_data["measurements"]["datacenter_memory"] = {
"value": 33,
"unit": "petabyte",
}
self.store_event(transaction_data, self.project.id)
response = self.do_request(
{
"field": [
"transaction",
"measurements.datacenter_memory",
"equation|measurements.datacenter_memory / 3",
],
"query": "",
"dataset": "discover",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["measurements.datacenter_memory"] == 33
assert data[0]["equation|measurements.datacenter_memory / 3"] == 11
meta = response.data["meta"]
assert not meta["isMetricsData"]
def test_transaction_wildcard(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"p90()",
],
"query": "transaction:foo*",
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["p90()"] == 1
meta = response.data["meta"]
assert meta["isMetricsData"]
assert data[0]["transaction"] == "foo_transaction"
def test_transaction_status_wildcard(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"p90()",
],
"query": "transaction.status:f*bar",
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["p90()"] == 1
meta = response.data["meta"]
assert meta["isMetricsData"]
def test_http_error_rate(self):
self.store_transaction_metric(
1,
tags={
"transaction": "foo_transaction",
"transaction.status": "foobar",
"http.status_code": "500",
},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
tags={"transaction": "bar_transaction", "http.status_code": "400"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"http_error_rate()",
],
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["http_error_rate()"] == 0.5
meta = response.data["meta"]
assert meta["isMetricsData"]
def test_time_spent(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"time_spent_percentage()",
],
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 2
assert data[0]["time_spent_percentage()"] == 0.5
meta = response.data["meta"]
assert meta["isMetricsData"]
def test_has_filter(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"p50()",
],
# For the metrics dataset, has on metrics should be no-ops
"query": "has:measurements.frames_frozen_rate",
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["p50()"] == 1
meta = response.data["meta"]
assert meta["isMetricsData"]
response = self.do_request(
{
"field": [
"transaction",
"p50()",
],
"query": "has:transaction.status",
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 1
assert data[0]["p50()"] == 1
meta = response.data["meta"]
assert meta["isMetricsData"]
def test_not_has_filter(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"p50()",
],
"query": "!has:transaction.status",
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
assert len(data) == 0
meta = response.data["meta"]
assert meta["isMetricsData"]
response = self.do_request(
{
"field": [
"transaction",
"p50()",
],
# Doing !has on the metrics dataset doesn't really make sense
"query": "!has:measurements.frames_frozen_rate",
"dataset": "metrics",
}
)
assert response.status_code == 400, response.content
def test_p50_with_count(self):
"""Implicitly test the fact that percentiles are their own 'dataset'"""
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": ["title", "p50()", "count()"],
"query": "event.type:transaction",
"dataset": "metrics",
"project": self.project.id,
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["title"] == "foo_transaction"
assert data[0]["p50()"] == 1
assert data[0]["count()"] == 1
assert meta["isMetricsData"]
assert field_meta["title"] == "string"
assert field_meta["p50()"] == "duration"
assert field_meta["count()"] == "integer"
def test_p75_with_count_and_more_groupby(self):
"""Implicitly test the fact that percentiles are their own 'dataset'"""
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
5,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
5,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"title",
"project",
"p75()",
"count()",
],
"query": "event.type:transaction",
"orderby": "count()",
"dataset": "metrics",
"project": self.project.id,
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 2
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["title"] == "foo_transaction"
assert data[0]["p75()"] == 1
assert data[0]["count()"] == 1
assert data[1]["title"] == "bar_transaction"
assert data[1]["p75()"] == 5
assert data[1]["count()"] == 2
assert meta["isMetricsData"]
assert field_meta["title"] == "string"
assert field_meta["p75()"] == "duration"
assert field_meta["count()"] == "integer"
def test_title_and_transaction_alias(self):
# Title and transaction are aliases to the same column
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"title",
"transaction",
"p75()",
],
"query": "event.type:transaction",
"orderby": "p75()",
"dataset": "metrics",
"project": self.project.id,
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["title"] == "foo_transaction"
assert data[0]["transaction"] == "foo_transaction"
assert data[0]["p75()"] == 1
assert meta["isMetricsData"]
assert field_meta["title"] == "string"
assert field_meta["transaction"] == "string"
assert field_meta["p75()"] == "duration"
def test_maintain_sort_order_across_datasets(self):
self.store_transaction_metric(
1,
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1,
metric="user",
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
5,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
5,
tags={"transaction": "bar_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"title",
"project",
"count()",
"count_unique(user)",
],
"query": "event.type:transaction",
"orderby": "count()",
"dataset": "metrics",
"project": self.project.id,
"per_page": 50,
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert len(data) == 2
assert data[0]["title"] == "foo_transaction"
assert data[0]["count()"] == 1
assert data[0]["count_unique(user)"] == 0
assert data[1]["title"] == "bar_transaction"
assert data[1]["count()"] == 2
assert data[1]["count_unique(user)"] == 1
assert meta["isMetricsData"]
assert field_meta["title"] == "string"
assert field_meta["count()"] == "integer"
assert field_meta["count_unique(user)"] == "integer"
def test_avg_compare(self):
self.store_transaction_metric(
100,
timestamp=self.min_ago,
tags={"release": "foo"},
)
self.store_transaction_metric(
10,
timestamp=self.min_ago,
tags={"release": "bar"},
)
for function_name in [
"avg_compare(transaction.duration, release, foo, bar)",
'avg_compare(transaction.duration, release, "foo", "bar")',
]:
response = self.do_request(
{
"field": [function_name],
"query": "",
"project": self.project.id,
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
meta = response.data["meta"]
assert len(data) == 1
assert data[0][function_name] == -0.9
assert meta["dataset"] == "metrics"
assert meta["fields"][function_name] == "percent_change"
def test_avg_if(self):
self.store_transaction_metric(
100,
timestamp=self.min_ago,
tags={"release": "foo"},
)
self.store_transaction_metric(
200,
timestamp=self.min_ago,
tags={"release": "foo"},
)
self.store_transaction_metric(
10,
timestamp=self.min_ago,
tags={"release": "bar"},
)
response = self.do_request(
{
"field": ["avg_if(transaction.duration, release, foo)"],
"query": "",
"project": self.project.id,
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
meta = response.data["meta"]
assert len(data) == 1
assert data[0]["avg_if(transaction.duration, release, foo)"] == 150
assert meta["dataset"] == "metrics"
assert meta["fields"]["avg_if(transaction.duration, release, foo)"] == "duration"
def test_device_class(self):
self.store_transaction_metric(
100,
timestamp=self.min_ago,
tags={"device.class": "1"},
)
self.store_transaction_metric(
200,
timestamp=self.min_ago,
tags={"device.class": "2"},
)
self.store_transaction_metric(
300,
timestamp=self.min_ago,
tags={"device.class": ""},
)
response = self.do_request(
{
"field": ["device.class", "p95()"],
"query": "",
"orderby": "p95()",
"project": self.project.id,
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
meta = response.data["meta"]
assert len(data) == 3
# Need to actually check the dict since the level for 1 isn't guaranteed to stay `low` or `medium`
assert data[0]["device.class"] == map_device_class_level("1")
assert data[1]["device.class"] == map_device_class_level("2")
assert data[2]["device.class"] == "Unknown"
assert meta["fields"]["device.class"] == "string"
def test_device_class_filter(self):
self.store_transaction_metric(
300,
timestamp=self.min_ago,
tags={"device.class": "1"},
)
# Need to actually check the dict since the level for 1 isn't guaranteed to stay `low`
level = map_device_class_level("1")
response = self.do_request(
{
"field": ["device.class", "count()"],
"query": f"device.class:{level}",
"orderby": "count()",
"project": self.project.id,
"dataset": "metrics",
}
)
assert response.status_code == 200, response.content
data = response.data["data"]
meta = response.data["meta"]
assert len(data) == 1
assert data[0]["device.class"] == level
assert meta["fields"]["device.class"] == "string"
def test_performance_score(self):
self.store_transaction_metric(
0.03,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.30,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.35,
metric="measurements.score.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.70,
metric="measurements.score.weight.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.38,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.00,
metric="measurements.score.fid",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
# These fid and ttfb scenarios shouldn't really be happening, but we can test them anyways
self.store_transaction_metric(
0.00,
metric="measurements.score.weight.fid",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.ttfb",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.00,
metric="measurements.score.weight.ttfb",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"performance_score(measurements.score.lcp)",
"performance_score(measurements.score.fcp)",
"performance_score(measurements.score.fid)",
"performance_score(measurements.score.ttfb)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["performance_score(measurements.score.lcp)"] == 0.7923076923076923
assert data[0]["performance_score(measurements.score.fcp)"] == 0.5
assert data[0]["performance_score(measurements.score.fid)"] == 0
assert data[0]["performance_score(measurements.score.ttfb)"] == 0
assert meta["isMetricsData"]
assert field_meta["performance_score(measurements.score.lcp)"] == "number"
def test_performance_score_boundaries(self):
# Scores shouldn't exceed 1 or go below 0, but we can test these boundaries anyways
self.store_transaction_metric(
0.65,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.30,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
-0.35,
metric="measurements.score.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.70,
metric="measurements.score.weight.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.3,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"performance_score(measurements.score.lcp)",
"performance_score(measurements.score.fcp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["performance_score(measurements.score.lcp)"] == 1.0
assert data[0]["performance_score(measurements.score.fcp)"] == 0.0
assert meta["isMetricsData"]
assert field_meta["performance_score(measurements.score.lcp)"] == "number"
def test_weighted_performance_score(self):
self.store_transaction_metric(
0.03,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.30,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.03,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.00,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.00,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"weighted_performance_score(measurements.score.lcp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.3433333333333333
assert meta["isMetricsData"]
assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
def test_invalid_performance_score_column(self):
self.store_transaction_metric(
0.03,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"performance_score(measurements.score.fp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 400, response.content
def test_invalid_weighted_performance_score_column(self):
self.store_transaction_metric(
0.03,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"weighted_performance_score(measurements.score.fp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 400, response.content
def test_no_weighted_performance_score_column(self):
self.store_transaction_metric(
0.0,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"weighted_performance_score(measurements.score.lcp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
field_meta = meta["fields"]
assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.0
assert meta["isMetricsData"]
assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
def test_opportunity_score(self):
self.store_transaction_metric(
0.03,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.30,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.40,
metric="measurements.score.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.70,
metric="measurements.score.weight.fcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.43,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.0,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.0,
metric="measurements.score.weight.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
1.0,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.0,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"opportunity_score(measurements.score.lcp)",
"opportunity_score(measurements.score.total)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["opportunity_score(measurements.score.lcp)"] == 0.27
assert data[0]["opportunity_score(measurements.score.total)"] == 1.57
assert meta["isMetricsData"]
def test_count_scores(self):
self.store_transaction_metric(
0.1,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.2,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.3,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.4,
metric="measurements.score.total",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
0.5,
metric="measurements.score.lcp",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"count_scores(measurements.score.total)",
"count_scores(measurements.score.lcp)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["count_scores(measurements.score.total)"] == 4
assert data[0]["count_scores(measurements.score.lcp)"] == 1
assert meta["isMetricsData"]
def test_count_starts(self):
self.store_transaction_metric(
200,
metric="measurements.app_start_warm",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
metric="measurements.app_start_warm",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
10,
metric="measurements.app_start_cold",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"count_starts(measurements.app_start_warm)",
"count_starts(measurements.app_start_cold)",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["count_starts(measurements.app_start_warm)"] == 2
assert data[0]["count_starts(measurements.app_start_cold)"] == 1
assert meta["isMetricsData"]
def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
self.store_transaction_metric(
200,
metric="measurements.app_start_warm",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
100,
metric="measurements.app_start_warm",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
self.store_transaction_metric(
10,
metric="measurements.app_start_cold",
tags={"transaction": "foo_transaction"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"count_total_starts()",
],
"query": "event.type:transaction",
"dataset": "metrics",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["count_total_starts()"] == 3
assert meta["isMetricsData"]
def test_timestamp_groupby(self):
self.store_transaction_metric(
0.03,
tags={"transaction": "foo_transaction", "user": "foo"},
timestamp=self.min_ago,
)
response = self.do_request(
{
"field": [
"transaction",
"timestamp",
"count()",
"count_unique(user)",
],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["transaction"] == "foo_transaction"
assert meta["dataset"] == "metricsEnhanced"
def test_on_demand_with_mep(self):
# Store faketag as an OnDemandMetricSpec, which will put faketag into the metrics indexer
spec = OnDemandMetricSpec(
field="count()",
query="user.email:[email protected]",
environment="prod",
groupbys=["faketag"],
spec_type=MetricSpecType.DYNAMIC_QUERY,
)
self.store_on_demand_metric(123, spec=spec)
# This is the event that we should actually return
transaction_data = load_data("transaction", timestamp=self.min_ago)
transaction_data["tags"].append(("faketag", "foo"))
self.store_event(transaction_data, self.project.id)
with self.feature({"organizations:mep-use-default-tags": True}):
response = self.do_request(
{
"field": [
"faketag",
"count()",
],
"query": "event.type:transaction",
"dataset": "metricsEnhanced",
"per_page": 50,
}
)
assert response.status_code == 200, response.content
assert len(response.data["data"]) == 1
data = response.data["data"]
meta = response.data["meta"]
assert data[0]["faketag"] == "foo"
assert not meta["isMetricsData"]
@region_silo_test
class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithOnDemandMetrics(
MetricsEnhancedPerformanceTestCase
):
viewname = "sentry-api-0-organization-events"
def setUp(self) -> None:
super().setUp()
self.url = reverse(self.viewname, kwargs={"organization_slug": self.organization.slug})
self.features = {"organizations:on-demand-metrics-extraction-widgets": True}
def _create_specs(
self, params: dict[str, Any], groupbys: list[str] | None = None
) -> list[OnDemandMetricSpec]:
"""Creates all specs based on the parameters that would be passed to the endpoint."""
specs = []
for field in params["field"]:
spec = OnDemandMetricSpec(
field=field,
query=params["query"],
environment=params.get("environment"),
groupbys=groupbys,
spec_type=MetricSpecType.DYNAMIC_QUERY,
)
specs.append(spec)
return specs
def _make_on_demand_request(self, params: dict[str, Any]) -> Response:
"""Ensures that the required parameters for an on-demand request are included."""
# Expected parameters for this helper function
params["dataset"] = "metricsEnhanced"
params["useOnDemandMetrics"] = "true"
params["onDemandType"] = "dynamic_query"
return self.do_request(params)
def _assert_on_demand_response(
self,
response: Response,
expected_on_demand_query: bool | None = True,
expected_dataset: str | None = "metricsEnhanced",
) -> None:
"""Basic assertions for an on-demand request."""
assert response.status_code == 200, response.content
meta = response.data["meta"]
assert meta.get("isMetricsExtractedData", False) is expected_on_demand_query
assert meta["dataset"] == expected_dataset
def test_is_metrics_extracted_data_is_included(self) -> None:
params = {"field": ["count()"], "query": "transaction.duration:>=91", "yAxis": "count()"}
specs = self._create_specs(params)
for spec in specs:
self.store_on_demand_metric(1, spec=spec)
response = self._make_on_demand_request(params)
self._assert_on_demand_response(response)
@pytest.mark.skip(reason="Re-enable when user misery is supported again.")
def test_transaction_user_misery(self) -> None:
user_misery_field = "user_misery(300)"
apdex_field = "apdex(300)"
params = {
"field": [user_misery_field, apdex_field],
"project": self.project.id,
"query": "",
}
specs = self._create_specs(params, groupbys=["transaction"])
for spec in specs:
self.store_on_demand_metric(1, spec=spec)
response = self._make_on_demand_request(params)
self._assert_on_demand_response(response, expected_on_demand_query=True)
assert response.data == {
"data": [{user_misery_field: 0.0, apdex_field: 0.0}],
"meta": {
"fields": {user_misery_field: "number", apdex_field: "number"},
"units": {user_misery_field: None, apdex_field: None},
"isMetricsData": True,
"isMetricsExtractedData": True,
"tips": {},
"datasetReason": "unchanged",
"dataset": "metricsEnhanced",
},
}
@region_silo_test
class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
OrganizationEventsMetricsEnhancedPerformanceEndpointTest
):
def setUp(self):
super().setUp()
self.features["organizations:use-metrics-layer"] = True
@pytest.mark.xfail(reason="Not supported")
def test_time_spent(self):
super().test_time_spent()
@pytest.mark.xfail(reason="Not supported")
def test_http_error_rate(self):
super().test_http_error_rate()
@pytest.mark.xfail(reason="Multiple aliases to same column not supported")
def test_title_and_transaction_alias(self):
super().test_title_and_transaction_alias()
@pytest.mark.xfail(reason="Sort order is flaking when querying multiple datasets")
def test_maintain_sort_order_across_datasets(self):
"""You may need to run this test a few times to get it to fail"""
super().test_maintain_sort_order_across_datasets()
@pytest.mark.xfail(reason="Not implemented")
def test_avg_compare(self):
super().test_avg_compare()
@pytest.mark.xfail(reason="Not implemented")
def test_avg_if(self):
super().test_avg_if()
@pytest.mark.xfail(reason="Not implemented")
def test_device_class(self):
super().test_device_class()
@pytest.mark.xfail(reason="Not implemented")
def test_device_class_filter(self):
super().test_device_class_filter()
@pytest.mark.xfail(reason="Not implemented")
def test_performance_score(self):
super().test_performance_score()
@pytest.mark.xfail(reason="Not implemented")
def test_performance_score_boundaries(self):
super().test_performance_score()
@pytest.mark.xfail(reason="Not implemented")
def test_weighted_performance_score(self):
super().test_weighted_performance_score()
@pytest.mark.xfail(reason="Not implemented")
def test_invalid_performance_score_column(self):
super().test_invalid_performance_score_column()
@pytest.mark.xfail(reason="Not implemented")
def test_invalid_weighted_performance_score_column(self):
super().test_invalid_weighted_performance_score_column()
@pytest.mark.xfail(reason="Not implemented")
def test_no_weighted_performance_score_column(self):
super().test_invalid_weighted_performance_score_column()
@pytest.mark.xfail(reason="Not implemented")
def test_opportunity_score(self):
super().test_opportunity_score()
@pytest.mark.xfail(reason="Not implemented")
def test_count_scores(self):
super().test_count_scores()
@pytest.mark.xfail(reason="Not implemented")
def test_count_starts(self):
super().test_count_starts()
@pytest.mark.xfail(reason="Not implemented")
def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
super().test_count_starts_returns_all_counts_when_no_arg_is_passed()
@pytest.mark.xfail(reason="Not implemented")
def test_timestamp_groupby(self):
super().test_timestamp_groupby()
@pytest.mark.xfail(reason="Not implemented")
def test_on_demand_with_mep(self):
super().test_on_demand_with_mep()