Files
sentinel/tests/sentry/snuba/metrics/test_mqb_query_transformer.py
2024-02-15 17:01:50 +00:00

1963 lines
68 KiB
Python

import datetime
import re
import pytest
from snuba_sdk.aliased_expression import AliasedExpression
from snuba_sdk.column import Column
from snuba_sdk.conditions import BooleanCondition, BooleanOp, Condition, Op
from snuba_sdk.entity import Entity
from snuba_sdk.expressions import Granularity, Limit, Offset
from snuba_sdk.function import Function
from snuba_sdk.orderby import Direction, OrderBy
from snuba_sdk.query import Query
from sentry.snuba.metrics import (
MetricConditionField,
MetricField,
MetricGroupByField,
MetricOrderByField,
MetricsQuery,
)
from sentry.snuba.metrics.mqb_query_transformer import (
MQBQueryTransformationException,
transform_mqb_query_to_metrics_query,
)
from sentry.snuba.metrics.naming_layer.mri import TransactionMRI
"""
Notes:
- Parameter argument order for non column arguments should match the order of arguments after (aggregate_filter,
org_id) or just (aggregate_filter) in the SnQL generator functions. As an example, if we define a histogram
function in SnQL as:-
Function(
function="histogram",
parameters=[
Column("d:transactions/duration@millisecond"),
0, # histogram_from
5, # histogram_to
5, # num_buckets
],
alias="histogram_transaction_duration",
)
then the order of arguments after the Column argument (i.e. numeric arguments) should be histogram_from, histogram_to,
num_buckets matching the order in the following SnQL generator function definition
def histogram_snql_factory(aggregate_filter, histogram_from, histogram_to, histogram_buckets, alias)
These SnQL function generators can be found in `sentry/src/sentry/snuba/metrics/fields/base.py`, and these are
defined for all derived operations i.e. operations not supported by clickhouse like rate, count_web_vitals,
histogram (as it requires extra logic over the clickhouse function supported by datasketch), team_key_transaction,
and count_transaction_name (for unparameterized and None)
All non clickhouse functions (derived functions) are listed here
https://github.com/getsentry/sentry/blob/0eb312411989c2dbde3cf0b5094d47829d01c854/src/sentry/snuba/metrics/fields/base.py#L1505-L1540
- Originally, it was agreed that all derived metrics such as failure_rate, user_misery and the other ones listed here
https://github.com/getsentry/sentry/blob/4d3efb171ac2fc3ac77a846ec3d96f0da829ed12/src/sentry/snuba/metrics/naming_layer/mri.py#L98-L106
would be passed as SnQL functions. However, this won't work without expanding the snuba-sdk Function regex to accept MRI
format which does not really make sense to do, and hence it is best if they are provided in Column, or AliasedExpression
as the Column regex has been expanded to accept MRI, and those derived metrics do not accept any arguments anyways.
- Granularity is not handled here yet and so defaulted to 3600 but the metrics service layer handles granularity
based on time bounds provided and if this behaviour is intended to be different, that logic will me modified within
the metrics layer. However, passing granularity is abstracted to metrics layer (There is an ongoing discussion about
specifically this)
"""
VALID_QUERIES_INTEGRATION_TEST_CASES = [
# Totals Query
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count_web_vitals",
parameters=[Column("d:transactions/measurements.cls@millisecond"), "good"],
alias="count_web_vitals_measurements_cls_good",
),
Function(
function="count_web_vitals",
parameters=[Column("d:transactions/measurements.fid@millisecond"), "meh"],
alias="count_web_vitals_measurements_fid_meh",
),
Function(
function="count_web_vitals",
parameters=[Column("d:transactions/measurements.fp@millisecond"), "good"],
alias="count_web_vitals_measurements_fp_good",
),
Function(
function="count_web_vitals",
parameters=[Column("d:transactions/measurements.lcp@millisecond"), "good"],
alias="count_web_vitals_measurements_lcp_good",
),
Function(
function="count_web_vitals",
parameters=[Column("d:transactions/measurements.fcp@millisecond"), "meh"],
alias="count_web_vitals_measurements_fcp_meh",
),
],
groupby=[
AliasedExpression(
exp=Column(
name="tags[transaction]",
),
alias="transaction",
)
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 35, 447729),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 35, 447729),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[11],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=11,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=86400),
totals=None,
),
MetricsQuery(
org_id=11,
project_ids=[11],
select=[
MetricField(
op="count_web_vitals",
metric_mri="d:transactions/measurements.cls@millisecond",
params={"measurement_rating": "good"},
alias="count_web_vitals_measurements_cls_good",
),
MetricField(
op="count_web_vitals",
metric_mri="d:transactions/measurements.fid@millisecond",
params={"measurement_rating": "meh"},
alias="count_web_vitals_measurements_fid_meh",
),
MetricField(
op="count_web_vitals",
metric_mri="d:transactions/measurements.fp@millisecond",
params={"measurement_rating": "good"},
alias="count_web_vitals_measurements_fp_good",
),
MetricField(
op="count_web_vitals",
metric_mri="d:transactions/measurements.lcp@millisecond",
params={"measurement_rating": "good"},
alias="count_web_vitals_measurements_lcp_good",
),
MetricField(
op="count_web_vitals",
metric_mri="d:transactions/measurements.fcp@millisecond",
params={"measurement_rating": "meh"},
alias="count_web_vitals_measurements_fcp_meh",
),
],
start=datetime.datetime(2022, 3, 24, 11, 11, 35, 447729),
end=datetime.datetime(2022, 6, 22, 11, 11, 35, 447729),
granularity=Granularity(granularity=86400),
where=[],
having=[],
groupby=[MetricGroupByField("transaction")],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="count_web_vitals test case",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="p95",
parameters=[Column("d:transactions/duration@millisecond")],
alias="p95",
),
Function(
function="rate",
parameters=[
Column("d:transactions/duration@millisecond"),
7776000.0,
60,
],
alias="epm",
),
AliasedExpression(
exp=Column("e:transactions/failure_rate@ratio"),
alias="failure_rate",
),
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
],
groupby=[
AliasedExpression(
exp=Column("tags[transaction.status]"),
alias="transaction.status",
),
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
AliasedExpression(exp=Column("tags[transaction]"), alias="title"),
Column("project_id"),
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[13],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=14,
),
Condition(
lhs=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
),
],
having=[],
orderby=[
OrderBy(
exp=Function(
function="p95",
parameters=[Column("d:transactions/duration@millisecond")],
alias="p95",
),
direction=Direction.ASC,
),
OrderBy(
exp=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
direction=Direction.ASC,
),
],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=86400),
totals=None,
),
MetricsQuery(
org_id=14,
project_ids=[13],
select=[
MetricField(
op="p95",
metric_mri="d:transactions/duration@millisecond",
alias="p95",
),
MetricField(
op="rate",
metric_mri="d:transactions/duration@millisecond",
params={"denominator": 60, "numerator": 7776000.0},
alias="epm",
),
MetricField(
op=None,
metric_mri="e:transactions/failure_rate@ratio",
alias="failure_rate",
),
MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
],
start=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
end=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
granularity=Granularity(granularity=86400),
where=[
MetricConditionField(
lhs=MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
)
],
having=[],
groupby=[
MetricGroupByField("transaction.status"),
MetricGroupByField(
MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
),
MetricGroupByField("transaction", alias="title"),
MetricGroupByField("project_id"),
],
orderby=[
MetricOrderByField(
field=MetricField(
op="p95",
metric_mri="d:transactions/duration@millisecond",
alias="p95",
),
direction=Direction.ASC,
),
MetricOrderByField(
field=MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
direction=Direction.ASC,
),
],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="team_key_transaction + groupby & select aliasing test case",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="p95",
parameters=[Column("d:transactions/duration@millisecond")],
alias="p95",
),
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
],
groupby=[
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[13],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=14,
),
Condition(
lhs=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
),
],
having=[],
orderby=[
OrderBy(
exp=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
direction=Direction.ASC,
),
OrderBy(
exp=Function(
function="p95",
parameters=[Column("d:transactions/duration@millisecond")],
alias="p95",
),
direction=Direction.ASC,
),
],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=86400),
totals=None,
),
MetricsQuery(
org_id=14,
project_ids=[13],
select=[
MetricField(
op="p95",
metric_mri="d:transactions/duration@millisecond",
alias="p95",
),
MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
],
start=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
end=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
granularity=Granularity(granularity=86400),
where=[
MetricConditionField(
lhs=MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
)
],
groupby=[
MetricGroupByField(
MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
),
],
having=[],
orderby=[
MetricOrderByField(
field=MetricField(
op="team_key_transaction",
metric_mri="d:transactions/duration@millisecond",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
direction=Direction.ASC,
),
MetricOrderByField(
field=MetricField(
op="p95",
metric_mri="d:transactions/duration@millisecond",
alias="p95",
),
direction=Direction.ASC,
),
],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="team_key_transaction transformation with p95 in select",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
AliasedExpression(
exp=Column("e:transactions/user_misery@ratio"),
alias="user_misery",
),
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
],
groupby=[
Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[13],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=14,
),
Condition(
lhs=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
),
],
having=[],
orderby=[
OrderBy(
exp=Function(
function="team_key_transaction",
parameters=[
Column(TransactionMRI.TEAM_KEY_TRANSACTION.value),
[(13, "foo_transaction")],
],
alias="team_key_transaction",
),
direction=Direction.ASC,
),
],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=86400),
totals=None,
),
MetricsQuery(
org_id=14,
project_ids=[13],
select=[
MetricField(
op=None,
metric_mri="e:transactions/user_misery@ratio",
alias="user_misery",
),
MetricField(
op="team_key_transaction",
metric_mri="s:transactions/user@none",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
],
start=datetime.datetime(2022, 3, 24, 11, 11, 36, 75132),
end=datetime.datetime(2022, 6, 22, 11, 11, 36, 75132),
granularity=Granularity(granularity=86400),
where=[
MetricConditionField(
lhs=MetricField(
op="team_key_transaction",
metric_mri="s:transactions/user@none",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
op=Op.EQ,
rhs=1,
)
],
having=[],
groupby=[
MetricGroupByField(
MetricField(
op="team_key_transaction",
metric_mri="s:transactions/user@none",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
),
],
orderby=[
MetricOrderByField(
field=MetricField(
op="team_key_transaction",
metric_mri="s:transactions/user@none",
params={"team_key_condition_rhs": [(13, "foo_transaction")]},
alias="team_key_transaction",
),
direction=Direction.ASC,
),
],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="team_key_transaction transformation with single order by and user_misery in select",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
AliasedExpression(
exp=Column("e:transactions/apdex@ratio"),
alias="apdex",
),
Function(
function="p75",
parameters=[
Column("d:transactions/measurements.cls@millisecond"),
],
alias="p75_measurements_cls",
),
Function(
function="rate",
parameters=[
Column("d:transactions/duration@millisecond"),
7776000.0,
60,
],
alias="tpm",
),
Function(
function="p75",
parameters=[Column("d:transactions/measurements.fid@millisecond")],
alias="p75_measurements_fid",
),
Function(
function="p75",
parameters=[Column("d:transactions/measurements.fcp@millisecond")],
alias="p75_measurements_fcp",
),
Function(
function="p75",
parameters=[Column("d:transactions/measurements.lcp@millisecond")],
alias="p75_measurements_lcp",
),
],
groupby=[
AliasedExpression(
exp=Column("tags[transaction]"),
alias="transaction",
),
Column("project_id"),
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 37, 278535),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 37, 278535),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[18],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=19,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=86400),
totals=None,
),
MetricsQuery(
org_id=19,
project_ids=[18],
select=[
MetricField(
op=None,
metric_mri="e:transactions/apdex@ratio",
alias="apdex",
),
MetricField(
op="p75",
metric_mri="d:transactions/measurements.cls@millisecond",
alias="p75_measurements_cls",
),
MetricField(
op="rate",
metric_mri="d:transactions/duration@millisecond",
params={"numerator": 7776000.0, "denominator": 60},
alias="tpm",
),
MetricField(
op="p75",
metric_mri="d:transactions/measurements.fid@millisecond",
alias="p75_measurements_fid",
),
MetricField(
op="p75",
metric_mri="d:transactions/measurements.fcp@millisecond",
alias="p75_measurements_fcp",
),
MetricField(
op="p75",
metric_mri="d:transactions/measurements.lcp@millisecond",
alias="p75_measurements_lcp",
),
],
start=datetime.datetime(2022, 3, 24, 11, 11, 37, 278535),
end=datetime.datetime(2022, 6, 22, 11, 11, 37, 278535),
granularity=Granularity(granularity=86400),
where=[],
having=[],
groupby=[
MetricGroupByField("transaction", alias=None),
MetricGroupByField("project_id", alias=None),
],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="apdex + percentiles test cases",
),
pytest.param(
Query(
match=Entity("generic_metrics_sets"),
select=[
Function(
function="count_unique",
parameters=[
Column("s:transactions/user@none"),
],
alias="count_unique_user",
),
],
groupby=[
Column("project_id"),
AliasedExpression(
exp=Column(
name="tags[transaction]",
),
alias="transaction",
),
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 11, 11, 33, 21219),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 11, 11, 33, 21219),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[2],
),
Condition(
lhs=Column(name="org_id"),
op=Op.EQ,
rhs=2,
),
Condition(
lhs=Function(
function="tuple",
parameters=[
Column(
name="tags[transaction]",
)
],
alias=None,
),
op=Op.IN,
rhs=Function(
function="tuple",
parameters=[("foo_transaction",), ("bar_transaction",)],
alias=None,
),
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=51),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=2,
project_ids=[2],
select=[
MetricField(
op="count_unique",
metric_mri="s:transactions/user@none",
alias="count_unique_user",
)
],
start=datetime.datetime(2022, 3, 24, 11, 11, 33, 21219),
end=datetime.datetime(2022, 6, 22, 11, 11, 33, 21219),
granularity=Granularity(granularity=3600),
where=[
Condition(
lhs=Function(
function="tuple",
parameters=[
Column(
name="tags[transaction]",
)
],
alias=None,
),
op=Op.IN,
rhs=Function(
function="tuple",
parameters=[("foo_transaction",), ("bar_transaction",)],
alias=None,
),
),
],
having=[],
groupby=[
MetricGroupByField("project_id", alias=None),
MetricGroupByField("transaction", alias=None),
],
limit=Limit(limit=51),
offset=Offset(offset=0),
include_series=False,
),
id="tuple condition + metrics sets test case",
),
pytest.param(
Query(
match=Entity("generic_metrics_sets"),
select=[
Function(
function="count_unique",
parameters=[
Column("s:transactions/user@none"),
],
alias="count_unique_user",
)
],
groupby=[
Function(
function="toStartOfInterval",
parameters=[
Column(name="timestamp"),
Function(
function="toIntervalSecond",
parameters=[3600],
alias=None,
),
"Universal",
],
alias="time",
)
],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 6, 21, 10, 0, tzinfo=None),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 21, 12, 0, tzinfo=None),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[2],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=2,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=60),
totals=None,
),
MetricsQuery(
org_id=2,
project_ids=[2],
select=[
MetricField(
op="count_unique",
metric_mri="s:transactions/user@none",
alias="count_unique_user",
)
],
start=datetime.datetime(2022, 6, 21, 10, 0, tzinfo=None),
end=datetime.datetime(2022, 6, 21, 12, 0, tzinfo=None),
granularity=Granularity(granularity=60),
where=[],
having=[],
groupby=None,
include_series=True,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
interval=3600,
),
id="series query test case",
),
# Histogram Query
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="histogram",
parameters=[
Column("d:transactions/duration@millisecond"),
0, # histogram_from
5, # histogram_to
5, # num_buckets
],
alias="histogram_transaction_duration",
),
],
groupby=[],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField(
op="histogram",
metric_mri="d:transactions/duration@millisecond",
params={"histogram_from": 0, "histogram_to": 5, "histogram_buckets": 5},
alias="histogram_transaction_duration",
)
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[],
having=[],
groupby=None,
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="histogram query test case",
),
# Count transaction name
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "is_null"],
alias="null_transaction_count",
),
Function(
function="count_transaction_name",
parameters=[
Column("d:transactions/duration@millisecond"),
"is_unparameterized",
],
alias="unparameterized_transaction_count",
),
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField(
op="count_transaction_name",
metric_mri="d:transactions/duration@millisecond",
params={"transaction_name": "is_null"},
alias="null_transaction_count",
),
MetricField(
op="count_transaction_name",
metric_mri="d:transactions/duration@millisecond",
params={"transaction_name": "is_unparameterized"},
alias="unparameterized_transaction_count",
),
MetricField(
op="count_transaction_name",
metric_mri="d:transactions/duration@millisecond",
params={"transaction_name": "has_value"},
alias="has_value_transaction_count",
),
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[],
having=[],
groupby=None,
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="count_transaction_name query test case",
),
# "has" condition in where
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count",
parameters=[
Column("d:transactions/duration@millisecond"),
],
alias="duration_count",
),
],
groupby=[],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
Condition(
lhs=Function(
function="has",
parameters=[
Column(
name="tags.key",
),
"transaction",
],
),
op=Op.EQ,
rhs=1,
),
],
having=[],
orderby=[],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField(
op="count",
metric_mri="d:transactions/duration@millisecond",
alias="duration_count",
),
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[
Condition(
lhs=Function(
function="has",
parameters=[
Column(
name="tags.key",
),
"transaction",
],
),
op=Op.EQ,
rhs=1,
)
],
having=[],
groupby=None,
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="has operator query test case",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count",
parameters=[
Column("d:transactions/duration@millisecond"),
],
alias="duration_count",
),
],
groupby=[Column("project_id")],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
],
having=[],
orderby=[
OrderBy(
Column("project_id"),
Direction.ASC,
)
],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField(
op="count",
metric_mri="d:transactions/duration@millisecond",
alias="duration_count",
),
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[],
groupby=[MetricGroupByField(field="project_id")],
having=[],
orderby=[MetricOrderByField(field="project_id", direction=Direction.ASC)],
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="order by project id column",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count",
parameters=[
Column("d:transactions/duration@millisecond"),
],
alias="count",
),
],
groupby=[Column("project_id")],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
],
having=[Condition(Column("count"), Op.GT, 1000)],
orderby=[
OrderBy(
Column("project_id"),
Direction.ASC,
)
],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField("count", "d:transactions/duration@millisecond", alias="count"),
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[],
groupby=[MetricGroupByField(field="project_id")],
having=[Condition(Column("count"), Op.GT, 1000)],
orderby=[MetricOrderByField(field="project_id", direction=Direction.ASC)],
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="having clauses are passed through",
),
pytest.param(
Query(
match=Entity("generic_metrics_distributions"),
select=[
Function(
function="count",
parameters=[
Column("d:transactions/duration@millisecond"),
],
alias="duration_count",
),
],
groupby=[Column("project_id")],
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
BooleanCondition(
op=BooleanOp.AND,
conditions=[
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="bar",
),
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="foo",
),
],
),
],
having=[],
orderby=[
OrderBy(
Column("project_id"),
Direction.ASC,
)
],
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
),
MetricsQuery(
org_id=3,
project_ids=[3],
select=[
MetricField(
op="count",
metric_mri="d:transactions/duration@millisecond",
alias="duration_count",
),
],
start=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
end=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
granularity=Granularity(3600),
where=[
BooleanCondition(
op=BooleanOp.AND,
conditions=[
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="bar",
),
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="foo",
),
],
)
],
groupby=[MetricGroupByField(field="project_id")],
having=[],
orderby=[MetricOrderByField(field="project_id", direction=Direction.ASC)],
include_series=False,
include_totals=True,
limit=Limit(limit=50),
offset=Offset(offset=0),
),
id="where with boolean condition",
),
]
@pytest.mark.parametrize(
"input, output",
VALID_QUERIES_INTEGRATION_TEST_CASES,
)
def test_mqb_to_metrics_query_transformer(input, output):
assert transform_mqb_query_to_metrics_query(input) == output
def _construct_snuba_sdk_query(
select,
groupby=None,
orderby=None,
where=None,
having=None,
entity="generic_metrics_distributions",
):
if groupby is None:
groupby = []
if orderby is None:
orderby = []
if where is None:
where = []
if having is None:
having = []
return Query(
match=Entity(entity),
select=select,
groupby=groupby,
array_join=None,
where=[
Condition(
lhs=Column(
name="timestamp",
),
op=Op.GTE,
rhs=datetime.datetime(2022, 3, 24, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="timestamp",
),
op=Op.LT,
rhs=datetime.datetime(2022, 6, 22, 14, 52, 59, 179755),
),
Condition(
lhs=Column(
name="project_id",
),
op=Op.IN,
rhs=[3],
),
Condition(
lhs=Column(
name="org_id",
),
op=Op.EQ,
rhs=3,
),
*where,
],
having=having,
orderby=orderby,
limitby=None,
limit=Limit(limit=50),
offset=Offset(offset=0),
granularity=Granularity(granularity=3600),
totals=None,
)
INVALID_QUERIES_INTEGRATION_TEST_CASES = [
# invalid orderby metrics expression with histogram
# invalid orderby derived metric ?
# invalid condition by adding metric field in the condition that does not support it
# Select Statement validation
pytest.param(
_construct_snuba_sdk_query(
select=[
AliasedExpression(
exp=Column(name="tags[transaction]"),
alias="transaction",
),
],
),
"Invalid Metric MRI: tags[transaction]",
id="invalid select by requesting a tag in a Column",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Column(name="tags[transaction]"),
],
),
"Invalid Metric MRI: tags[transaction]",
id="invalid select by requesting a tag in an aliased expression",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=["has_value"],
alias="has_value_transaction_count",
),
],
),
"The first parameter of a function should be a column of the metric MRI",
id="invalid select function since column is not provided in the first position of parameters",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions.duration@millisecond")],
alias="has_value_transaction_count",
),
],
),
"Too few function parameters are provided. The arguments required for function count_transaction_name are ["
"'transaction_name']",
id="invalid select function since not all required arguments are provided",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="p95",
parameters=[],
alias="has_value_transaction_count",
),
],
),
"The first parameter of a function should be a column of the metric MRI",
id="invalid select function since first parameter is not an instance of Column with MRI",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="bar",
),
],
),
"Unsupported select field "
"Condition(lhs=Column(name='tag[transaction]', entity=None, subscriptable='tag', key='transaction'), "
"op=<Op.EQ: '='>, rhs='bar')",
id="Condition is not a valid select statement",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="transform",
parameters=[
Column(name="project_id"),
[6],
["bar"],
"",
],
alias="project",
),
],
),
"Function 'transform' is not supported",
id="Unsupported function in select statement",
),
# Groupby validation
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[Column("transaction")],
),
"Unsupported groupby field 'transaction'",
id="invalid groupby string not starting with tags",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[AliasedExpression(exp=Column("transaction"), alias="transaction")],
),
"Unsupported groupby field 'transaction'",
id="invalid groupby string not starting with tags",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
),
"Cannot group by function count_transaction_name",
id="invalid groupby metric field",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[
Function(
function="transform",
parameters=[
Column(name="project_id"),
[6],
["bar"],
"",
],
alias="project",
),
],
),
"Cannot group by function transform",
id="Unsupported function in groupby statement",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
groupby=[
Condition(
lhs=Column("tag[transaction]"),
op=Op.EQ,
rhs="bar",
),
],
),
"Unsupported groupby field Condition(lhs=Column(name='tag[transaction]', entity=None, subscriptable='tag', key='transaction'), op=<Op.EQ: '='>, rhs='bar')",
id="Unsupported Condition in groupby statement",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
where=[
Condition(
lhs=Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
op=Op.EQ,
rhs=1,
)
],
),
"Cannot filter by function count_transaction_name",
id="Unsupported derived op in where clause",
),
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="count_transaction_name",
parameters=[Column("d:transactions/duration@millisecond"), "has_value"],
alias="has_value_transaction_count",
),
],
where=[
Condition(
lhs=Function(
function="p95",
parameters=[Column("d:transactions/duration@millisecond")],
alias="p95",
),
op=Op.EQ,
rhs=1,
)
],
),
"Unsupported function 'p95' in where",
id="Unsupported function/operation in where clause",
),
# Validate OrderBy statements
pytest.param(
_construct_snuba_sdk_query(
select=[
Function(
function="histogram",
parameters=[
Column("d:transactions/duration@millisecond"),
0, # histogram_from
5, # histogram_to
5, # num_buckets
],
alias="histogram_transaction_duration",
),
],
orderby=[
OrderBy(
Function(
function="histogram",
parameters=[
Column("d:transactions/duration@millisecond"),
0, # histogram_from
5, # histogram_to
5, # num_buckets
],
alias="histogram_transaction_duration",
),
Direction.ASC,
)
],
),
"Operation histogram cannot be used to order a query",
id="histogram is not supported in orderby",
),
]
@pytest.mark.parametrize(
"input, error_message",
INVALID_QUERIES_INTEGRATION_TEST_CASES,
)
def test_invalid_mqb_queries(input, error_message):
with pytest.raises(MQBQueryTransformationException, match=re.escape(error_message)):
transform_mqb_query_to_metrics_query(input)