Beispiel #1
0
def test_translate_results_missing_slots(_1, _2, monkeypatch):
    monkeypatch.setattr("sentry.sentry_metrics.indexer.reverse_resolve",
                        MockIndexer().reverse_resolve)
    query_params = MultiValueDict({
        "field": [
            "sum(sentry.sessions.session)",
        ],
        "interval": ["1d"],
        "statsPeriod": ["3d"],
    })
    query_definition = QueryDefinition(query_params)

    results = {
        "metrics_counters": {
            "totals": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "sum(sentry.sessions.session)": 400,
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "bucketed_time": "2021-08-23T00:00Z",
                        "sum(sentry.sessions.session)": 100,
                    },
                    # no data for 2021-08-24
                    {
                        "metric_id": 9,  # session
                        "bucketed_time": "2021-08-25T00:00Z",
                        "sum(sentry.sessions.session)": 300,
                    },
                ],
            },
        },
    }

    intervals = list(get_intervals(query_definition))
    assert SnubaResultConverter(1, query_definition, intervals,
                                results).translate_results() == [
                                    {
                                        "by": {},
                                        "totals": {
                                            "sum(sentry.sessions.session)":
                                            400,
                                        },
                                        "series": {
                                            # No data for 2021-08-24
                                            "sum(sentry.sessions.session)":
                                            [100, 0, 300],
                                        },
                                    },
                                ]
def test_translate_results_missing_slots(_1, _2):
    query_params = MultiValueDict(
        {
            "field": [
                "sum(session)",
            ],
            "interval": ["1d"],
            "statsPeriod": ["3d"],
        }
    )
    query_definition = QueryDefinition(query_params)

    results = {
        "metrics_counters": {
            "totals": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "value": 400,
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "bucketed_time": datetime(2021, 8, 23, tzinfo=pytz.utc),
                        "value": 100,
                    },
                    # no data for 2021-08-24
                    {
                        "metric_id": 9,  # session
                        "bucketed_time": datetime(2021, 8, 25, tzinfo=pytz.utc),
                        "value": 300,
                    },
                ],
            },
        },
    }

    intervals = list(get_intervals(query_definition))
    assert SnubaResultConverter(1, query_definition, intervals, results).translate_results() == [
        {
            "by": {},
            "totals": {
                "sum(session)": 400,
            },
            "series": {
                # No data for 2021-08-24
                "sum(session)": [100, 0, 300],
            },
        },
    ]
def test_translate_results(_1, _2):
    query_params = MultiValueDict(
        {
            "groupBy": ["session.status"],
            "field": [
                "sum(session)",
                "max(session.duration)",
                "p50(session.duration)",
                "p95(session.duration)",
            ],
            "interval": ["1d"],
            "statsPeriod": ["2d"],
        }
    )
    query_definition = QueryDefinition(query_params)

    intervals = list(get_intervals(query_definition))
    results = {
        "metrics_counters": {
            "totals": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,  # session.status:healthy
                        "value": 300,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 0,  # session.status:abnormal
                        "value": 330,
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,
                        "bucketed_time": datetime(2021, 8, 24, tzinfo=pytz.utc),
                        "value": 100,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 0,
                        "bucketed_time": datetime(2021, 8, 24, tzinfo=pytz.utc),
                        "value": 110,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,
                        "bucketed_time": datetime(2021, 8, 25, tzinfo=pytz.utc),
                        "value": 200,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 0,
                        "bucketed_time": datetime(2021, 8, 25, tzinfo=pytz.utc),
                        "value": 220,
                    },
                ],
            },
        },
        "metrics_distributions": {
            "totals": {
                "data": [
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "max": 123.4,
                        "percentiles": [1, 2, 3, 4, 5],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 0,
                        "max": 456.7,
                        "percentiles": [1.5, 2.5, 3.5, 4.5, 5.5],
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "bucketed_time": datetime(2021, 8, 24, tzinfo=pytz.utc),
                        "max": 10.1,
                        "percentiles": [1.1, 2.1, 3.1, 4.1, 5.1],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 0,
                        "bucketed_time": datetime(2021, 8, 24, tzinfo=pytz.utc),
                        "max": 20.2,
                        "percentiles": [1.2, 2.2, 3.2, 4.2, 5.2],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "bucketed_time": datetime(2021, 8, 25, tzinfo=pytz.utc),
                        "max": 30.3,
                        "percentiles": [1.3, 2.3, 3.3, 4.3, 5.3],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 0,
                        "bucketed_time": datetime(2021, 8, 25, tzinfo=pytz.utc),
                        "max": 40.4,
                        "percentiles": [1.4, 2.4, 3.4, 4.4, 5.4],
                    },
                ],
            },
        },
    }

    assert SnubaResultConverter(1, query_definition, intervals, results).translate_results() == [
        {
            "by": {"session.status": "healthy"},
            "totals": {
                "sum(session)": 300,
                "max(session.duration)": 123.4,
                "p50(session.duration)": 1,
                "p95(session.duration)": 4,
            },
            "series": {
                "sum(session)": [100, 200],
                "max(session.duration)": [10.1, 30.3],
                "p50(session.duration)": [1.1, 1.3],
                "p95(session.duration)": [4.1, 4.3],
            },
        },
        {
            "by": {"session.status": "abnormal"},
            "totals": {
                "sum(session)": 330,
                "max(session.duration)": 456.7,
                "p50(session.duration)": 1.5,
                "p95(session.duration)": 4.5,
            },
            "series": {
                "sum(session)": [110, 220],
                "max(session.duration)": [20.2, 40.4],
                "p50(session.duration)": [1.2, 1.4],
                "p95(session.duration)": [4.2, 4.4],
            },
        },
    ]
Beispiel #4
0
def run_sessions_query(
    org_id: int,
    query: QueryDefinition,
    span_op: str,
) -> SessionsQueryResult:
    """Convert a QueryDefinition to multiple snuba queries and reformat the results"""
    # This is necessary so that we do not mutate the query object shared between different
    # backend runs
    query_clone = deepcopy(query)

    data, metric_to_output_field = _fetch_data(org_id, query_clone)

    data_points = _flatten_data(org_id, data)

    intervals = list(get_intervals(query_clone))
    timestamp_index = {
        timestamp.isoformat(): index
        for index, timestamp in enumerate(intervals)
    }

    def default_for(field: SessionsQueryFunction) -> SessionsQueryValue:
        return 0 if field in ("sum(session)", "count_unique(user)") else None

    GroupKey = Tuple[Tuple[GroupByFieldName, Union[str, int]], ...]

    class Group(TypedDict):
        series: MutableMapping[SessionsQueryFunction, List[SessionsQueryValue]]
        totals: MutableMapping[SessionsQueryFunction, SessionsQueryValue]

    groups: MutableMapping[GroupKey, Group] = defaultdict(
        lambda: {
            "totals":
            {field: default_for(field)
             for field in query_clone.raw_fields},
            "series": {
                field: len(intervals) * [default_for(field)]
                for field in query_clone.raw_fields
            },
        })

    if len(data_points) == 0:
        # We're only interested in `session.status` group-byes. The rest of the
        # conditions require work (e.g. getting all environments) that we can't
        # get without querying the DB, including group-byes consisting of
        # multiple parameters (even if `session.status` is one of them).
        if query_clone.raw_groupby == ["session.status"]:
            for status in get_args(_SessionStatus):
                gkey: GroupKey = (("session.status", status), )
                groups[gkey]
    else:
        for key in data_points.keys():
            try:
                output_field = metric_to_output_field[key.metric_key,
                                                      key.column]
            except KeyError:
                continue  # secondary metric, like session.error

            by: MutableMapping[GroupByFieldName, Union[str, int]] = {}
            if key.release is not None:
                # Every session has a release, so this should not throw
                by["release"] = reverse_resolve(key.release)
            if key.environment is not None:
                # To match behavior of the old sessions backend, session data
                # without environment is grouped under the empty string.
                by["environment"] = reverse_resolve_weak(key.environment) or ""
            if key.project_id is not None:
                by["project"] = key.project_id

            for status_value in output_field.get_values(data_points, key):
                if status_value.session_status is not None:
                    by["session.status"] = status_value.session_status  # !

                group_key: GroupKey = tuple(sorted(by.items()))
                group: Group = groups[group_key]

                value = status_value.value
                if value is not None:
                    value = finite_or_none(value)

                if key.bucketed_time is None:
                    group["totals"][output_field.get_name()] = value
                else:
                    index = timestamp_index[key.bucketed_time]
                    group["series"][output_field.get_name()][index] = value

    groups_as_list: List[SessionsQueryGroup] = [{
        "by": dict(by),
        "totals": group["totals"],
        "series": group["series"],
    } for by, group in groups.items()]

    def format_datetime(dt: datetime) -> str:
        return dt.isoformat().replace("+00:00", "Z")

    return {
        "start": format_datetime(query_clone.start),
        "end": format_datetime(query_clone.end),
        "query": query_clone.query,
        "intervals": [format_datetime(dt) for dt in intervals],
        "groups": groups_as_list,
    }
def run_sessions_query(
    org_id: int,
    query: QueryDefinition,
    span_op: str,
) -> SessionsQueryResult:
    """Convert a QueryDefinition to multiple snuba queries and reformat the results"""
    data, metric_to_output_field = _fetch_data(org_id, query)

    data_points = _flatten_data(org_id, data)

    intervals = list(get_intervals(query))
    timestamp_index = {
        timestamp.isoformat(): index
        for index, timestamp in enumerate(intervals)
    }

    def default_for(field: SessionsQueryFunction) -> SessionsQueryValue:
        return 0 if field in ("sum(session)", "count_unique(user)") else None

    GroupKey = Tuple[Tuple[GroupByFieldName, Union[str, int]], ...]

    class Group(TypedDict):
        series: MutableMapping[SessionsQueryFunction, List[SessionsQueryValue]]
        totals: MutableMapping[SessionsQueryFunction, SessionsQueryValue]

    groups: MutableMapping[GroupKey, Group] = defaultdict(
        lambda: {
            "totals":
            {field: default_for(field)
             for field in query.raw_fields},
            "series": {
                field: len(intervals) * [default_for(field)]
                for field in query.raw_fields
            },
        })

    for key in data_points.keys():
        try:
            output_field = metric_to_output_field[key.metric_name, key.column]
        except KeyError:
            continue  # secondary metric, like session.error

        by: MutableMapping[GroupByFieldName, Union[str, int]] = {}
        if key.release is not None:
            # Note: If the tag value reverse-resolves to None here, it's a bug in the tag indexer
            by["release"] = _reverse_resolve_ensured(key.release)
        if key.environment is not None:
            by["environment"] = _reverse_resolve_ensured(key.environment)
        if key.project_id is not None:
            by["project"] = key.project_id

        for status_value in output_field.get_values(data_points, key):
            if status_value.session_status is not None:
                by["session.status"] = status_value.session_status

            group_key: GroupKey = tuple(sorted(by.items()))
            group = groups[group_key]

            value = status_value.value
            if value is not None:
                value = finite_or_none(value)

            if key.bucketed_time is None:
                group["totals"][output_field.get_name()] = value
            else:
                index = timestamp_index[key.bucketed_time]
                group["series"][output_field.get_name()][index] = value

    groups_as_list: List[SessionsQueryGroup] = [{
        "by": dict(by),
        "totals": group["totals"],
        "series": group["series"],
    } for by, group in groups.items()]

    def format_datetime(dt: datetime) -> str:
        return dt.isoformat().replace("+00:00", "Z")

    return {
        "start": format_datetime(query.start),
        "end": format_datetime(query.end),
        "query": query.query,
        "intervals": [format_datetime(dt) for dt in intervals],
        "groups": groups_as_list,
    }
Beispiel #6
0
def test_translate_results_derived_metrics(_1, _2, monkeypatch):
    monkeypatch.setattr("sentry.sentry_metrics.indexer.reverse_resolve",
                        MockIndexer().reverse_resolve)

    query_params = MultiValueDict({
        "groupBy": [],
        "field": [
            "session.errored",
            "session.crash_free_rate",
            "session.all",
        ],
        "interval": ["1d"],
        "statsPeriod": ["2d"],
    })
    query_definition = QueryDefinition(query_params)
    fields_in_entities = {
        "metrics_counters": [
            (None, "session.errored_preaggregated"),
            (None, "session.crash_free_rate"),
            (None, "session.all"),
        ],
        "metrics_sets": [
            (None, "session.errored_set"),
        ],
    }

    intervals = list(get_intervals(query_definition))
    results = {
        "metrics_counters": {
            "totals": {
                "data": [{
                    "session.crash_free_rate": 0.5,
                    "session.all": 8.0,
                    "session.errored_preaggregated": 3,
                }],
            },
            "series": {
                "data": [
                    {
                        "bucketed_time": "2021-08-24T00:00Z",
                        "session.crash_free_rate": 0.5,
                        "session.all": 4,
                        "session.errored_preaggregated": 1,
                    },
                    {
                        "bucketed_time": "2021-08-25T00:00Z",
                        "session.crash_free_rate": 0.5,
                        "session.all": 4,
                        "session.errored_preaggregated": 2,
                    },
                ],
            },
        },
        "metrics_sets": {
            "totals": {
                "data": [
                    {
                        "session.errored_set": 3,
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "bucketed_time": "2021-08-24T00:00Z",
                        "session.errored_set": 2
                    },
                    {
                        "bucketed_time": "2021-08-25T00:00Z",
                        "session.errored_set": 1
                    },
                ],
            },
        },
    }

    assert SnubaResultConverter(1, query_definition, fields_in_entities,
                                intervals, results).translate_results() == [
                                    {
                                        "by": {},
                                        "totals": {
                                            "session.all": 8,
                                            "session.crash_free_rate": 0.5,
                                            "session.errored": 6,
                                        },
                                        "series": {
                                            "session.all": [4, 4],
                                            "session.crash_free_rate":
                                            [0.5, 0.5],
                                            "session.errored": [3, 3],
                                        },
                                    },
                                ]
Beispiel #7
0
def test_translate_results(_1, _2, monkeypatch):
    monkeypatch.setattr("sentry.sentry_metrics.indexer.reverse_resolve",
                        MockIndexer().reverse_resolve)

    query_params = MultiValueDict({
        "groupBy": ["session.status"],
        "field": [
            "sum(sentry.sessions.session)",
            "max(sentry.sessions.session.duration)",
            "p50(sentry.sessions.session.duration)",
            "p95(sentry.sessions.session.duration)",
        ],
        "interval": ["1d"],
        "statsPeriod": ["2d"],
    })
    query_definition = QueryDefinition(query_params)
    fields_in_entities = {
        "metrics_counters": [("sum", "sentry.sessions.session")],
        "metrics_distributions": [
            ("max", "sentry.sessions.session.duration"),
            ("p50", "sentry.sessions.session.duration"),
            ("p95", "sentry.sessions.session.duration"),
        ],
    }

    intervals = list(get_intervals(query_definition))
    results = {
        "metrics_counters": {
            "totals": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,  # session.status:healthy
                        "sum(sentry.sessions.session)": 300,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 14,  # session.status:abnormal
                        "sum(sentry.sessions.session)": 330,
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,
                        "bucketed_time": "2021-08-24T00:00Z",
                        "sum(sentry.sessions.session)": 100,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 14,
                        "bucketed_time": "2021-08-24T00:00Z",
                        "sum(sentry.sessions.session)": 110,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 4,
                        "bucketed_time": "2021-08-25T00:00Z",
                        "sum(sentry.sessions.session)": 200,
                    },
                    {
                        "metric_id": 9,  # session
                        "tags[8]": 14,
                        "bucketed_time": "2021-08-25T00:00Z",
                        "sum(sentry.sessions.session)": 220,
                    },
                ],
            },
        },
        "metrics_distributions": {
            "totals": {
                "data": [
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "max(sentry.sessions.session.duration)": 123.4,
                        "p50(sentry.sessions.session.duration)": [1],
                        "p95(sentry.sessions.session.duration)": [4],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 14,
                        "max(sentry.sessions.session.duration)": 456.7,
                        "p50(sentry.sessions.session.duration)": [1.5],
                        "p95(sentry.sessions.session.duration)": [4.5],
                    },
                ],
            },
            "series": {
                "data": [
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "bucketed_time": "2021-08-24T00:00Z",
                        "max(sentry.sessions.session.duration)": 10.1,
                        "p50(sentry.sessions.session.duration)": [1.1],
                        "p95(sentry.sessions.session.duration)": [4.1],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 14,
                        "bucketed_time": "2021-08-24T00:00Z",
                        "max(sentry.sessions.session.duration)": 20.2,
                        "p50(sentry.sessions.session.duration)": [1.2],
                        "p95(sentry.sessions.session.duration)": [4.2],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 4,
                        "bucketed_time": "2021-08-25T00:00Z",
                        "max(sentry.sessions.session.duration)": 30.3,
                        "p50(sentry.sessions.session.duration)": [1.3],
                        "p95(sentry.sessions.session.duration)": [4.3],
                    },
                    {
                        "metric_id": 7,  # session.duration
                        "tags[8]": 14,
                        "bucketed_time": "2021-08-25T00:00Z",
                        "max(sentry.sessions.session.duration)": 40.4,
                        "p50(sentry.sessions.session.duration)": [1.4],
                        "p95(sentry.sessions.session.duration)": [4.4],
                    },
                ],
            },
        },
    }

    assert SnubaResultConverter(
        1, query_definition, fields_in_entities, intervals,
        results).translate_results() == [
            {
                "by": {
                    "session.status": "healthy"
                },
                "totals": {
                    "sum(sentry.sessions.session)": 300,
                    "max(sentry.sessions.session.duration)": 123.4,
                    "p50(sentry.sessions.session.duration)": 1,
                    "p95(sentry.sessions.session.duration)": 4,
                },
                "series": {
                    "sum(sentry.sessions.session)": [100, 200],
                    "max(sentry.sessions.session.duration)": [10.1, 30.3],
                    "p50(sentry.sessions.session.duration)": [1.1, 1.3],
                    "p95(sentry.sessions.session.duration)": [4.1, 4.3],
                },
            },
            {
                "by": {
                    "session.status": "abnormal"
                },
                "totals": {
                    "sum(sentry.sessions.session)": 330,
                    "max(sentry.sessions.session.duration)": 456.7,
                    "p50(sentry.sessions.session.duration)": 1.5,
                    "p95(sentry.sessions.session.duration)": 4.5,
                },
                "series": {
                    "sum(sentry.sessions.session)": [110, 220],
                    "max(sentry.sessions.session.duration)": [20.2, 40.4],
                    "p50(sentry.sessions.session.duration)": [1.2, 1.4],
                    "p95(sentry.sessions.session.duration)": [4.2, 4.4],
                },
            },
        ]