Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(releasehealth): Implement check_releases_have_health_data on metrics backend [INGEST-249] #28823

Merged
merged 6 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/sentry/api/endpoints/organization_releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.exceptions import ParseError
from rest_framework.response import Response

from sentry import analytics, features
from sentry import analytics, features, releasehealth
from sentry.api.base import EnvironmentMixin, ReleaseAnalyticsMixin
from sentry.api.bases import NoProjects
from sentry.api.bases.organization import OrganizationReleasesBaseEndpoint
Expand Down Expand Up @@ -42,7 +42,6 @@
from sentry.signals import release_created
from sentry.snuba.sessions import (
STATS_PERIODS,
check_releases_have_health_data,
get_changed_project_release_model_adoptions,
get_oldest_health_data_for_releases,
get_project_releases_by_stability,
Expand Down Expand Up @@ -326,7 +325,7 @@ def qs_load_func(queryset, total_offset, qs_offset, limit):
: total_offset + limit
]
)
releases_with_session_data = check_releases_have_health_data(
releases_with_session_data = releasehealth.check_releases_have_health_data(
organization.id,
filter_params["project_id"],
release_versions,
Expand Down
15 changes: 15 additions & 0 deletions src/sentry/releasehealth/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ReleaseHealthBackend(Service): # type: ignore
"get_current_and_previous_crash_free_rates",
"get_release_adoption",
"check_has_health_data",
"check_releases_have_health_data",
)

def get_current_and_previous_crash_free_rates(
Expand Down Expand Up @@ -123,3 +124,17 @@ def check_has_health_data(
release)
"""
raise NotImplementedError()

def check_releases_have_health_data(
self,
organization_id: OrganizationId,
project_ids: Sequence[ProjectId],
release_versions: Sequence[ReleaseName],
start: datetime,
end: datetime,
) -> Set[ReleaseName]:

"""
Returns a set of all release versions that have health data within a given period of time.
"""
raise NotImplementedError()
53 changes: 44 additions & 9 deletions src/sentry/releasehealth/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,16 +386,10 @@ def check_has_health_data(
where_clause.append(Condition(Column(release_column_name), Op.IN, releases_ids))
column_names = ["project_id", release_column_name]

# def extract_raw_info(row: Mapping[str, Union[int, str]]) -> ProjectOrRelease:
# return row["project_id"], reverse_tag_value(org_id, row.get(release_column_name)) # type: ignore

else:
column_names = ["project_id"]

# def extract_raw_info(row: Mapping[str, Union[int, str]]) -> ProjectOrRelease:
# return row["project_id"] # type: ignore

def extract_raw_info_func(
def extract_row_info_func(
include_releases: bool,
) -> Callable[[Mapping[str, Union[int, str]]], ProjectOrRelease]:
def f(row: Mapping[str, Union[int, str]]) -> ProjectOrRelease:
Expand All @@ -406,7 +400,7 @@ def f(row: Mapping[str, Union[int, str]]) -> ProjectOrRelease:

return f

extract_raw_info = extract_raw_info_func(includes_releases)
extract_row_info = extract_row_info_func(includes_releases)

query_cols = [Column(column_name) for column_name in column_names]
group_by_clause = query_cols
Expand All @@ -423,4 +417,45 @@ def f(row: Mapping[str, Union[int, str]]) -> ProjectOrRelease:
query, referrer="releasehealth.metrics.check_has_health_data", use_cache=False
)

return {extract_raw_info(raw) for raw in result["data"]}
return {extract_row_info(row) for row in result["data"]}

def check_releases_have_health_data(
self,
organization_id: OrganizationId,
project_ids: Sequence[ProjectId],
release_versions: Sequence[ReleaseName],
start: datetime,
end: datetime,
) -> Set[ReleaseName]:

release_column_name = tag_key(organization_id, "release")
releases_ids = [
release_id
for release_id in [
try_get_tag_value(organization_id, release) for release in release_versions
]
if release_id is not None
]
query = Query(
dataset=Dataset.Metrics.value,
match=Entity("metrics_counters"),
select=[Column(release_column_name)],
where=[
Condition(Column("org_id"), Op.EQ, organization_id),
Condition(Column("project_id"), Op.IN, project_ids),
Condition(Column("metric_id"), Op.EQ, metric_id(organization_id, "session")),
Condition(Column(release_column_name), Op.IN, releases_ids),
Condition(Column("timestamp"), Op.GTE, start),
Condition(Column("timestamp"), Op.LT, end),
],
groupby=[Column(release_column_name)],
)

result = raw_snql_query(
query, referrer="releasehealth.metrics.check_releases_have_health_data", use_cache=False
)

def extract_row_info(row: Mapping[str, Union[OrganizationId, str]]) -> ReleaseName:
return reverse_tag_value(organization_id, row.get(release_column_name)) # type: ignore
Copy link
Contributor

@ahmedetefy ahmedetefy Sep 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding here a # type: ignore is unnecessary imo.. I'd much rather not have ReleaseName as a type and return a str here since ReleaseName is just basically an str

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please # type: ignore comments should not be used liberally, and there must be a good reason to actually add them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, #type: ignore should not be used liberally, it is necessary here to avoid error generated by row.get():

Argument 2 to "reverse_tag_value" has incompatible type "Union[int, str, None]"; expected "int"

Regarding the point on ReleaseName I disagree though. Any way ReleaseName is used in more APIs.


return {extract_row_info(row) for row in result["data"]}
17 changes: 17 additions & 0 deletions src/sentry/releasehealth/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)
from sentry.snuba.sessions import (
_check_has_health_data,
_check_releases_have_health_data,
_get_release_adoption,
get_current_and_previous_crash_free_rates,
)
Expand Down Expand Up @@ -54,3 +55,19 @@ def check_has_health_data(
self, projects_list: Sequence[ProjectOrRelease]
) -> Set[ProjectOrRelease]:
return _check_has_health_data(projects_list) # type: ignore

def check_releases_have_health_data(
self,
organization_id: OrganizationId,
project_ids: Sequence[ProjectId],
release_versions: Sequence[ReleaseName],
start: datetime,
end: datetime,
) -> Set[ReleaseName]:
return _check_releases_have_health_data( # type: ignore
organization_id,
project_ids,
release_versions,
start,
end,
)
6 changes: 2 additions & 4 deletions src/sentry/snuba/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def data_tuple(x):
return {data_tuple(x) for x in raw_query(**raw_query_args)["data"]}


def check_releases_have_health_data(
def _check_releases_have_health_data(
organization_id: int,
project_ids: List[int],
release_versions: List[str],
Expand Down Expand Up @@ -274,9 +274,7 @@ def get_project_releases_count(
where=where,
having=having,
)
data = snuba.raw_snql_query(query, referrer="snuba.sessions.check_releases_have_health_data")[
"data"
]
data = snuba.raw_snql_query(query, referrer="snuba.sessions.get_project_releases_count")["data"]
return data[0]["count"] if data else 0


Expand Down
4 changes: 4 additions & 0 deletions src/sentry/testutils/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,10 @@ def store_session(self, session):
if status != "ok": # terminal
self._push_metric(session, "distribution", "session.duration", {}, session["duration"])

def bulk_store_sessions(self, sessions):
for session in sessions:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: self.store(session) for session in sessions

Copy link
Contributor Author

@RaduW RaduW Sep 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what are you suggesting ?
are you suggesting:

def bulk_store_sessions(self, sessions):
         [self.store_session(session) for session in sessions ]

That would generate a list for no good reason

or:

def bulk_store_sessions(self, sessions):
         (self.store_session(session) for session in sessions)

that would create an iterator that would not do anything (since it is not iterated over).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nvm, ignore this plz.. misread, thought you were returning a list

self.store_session(session)

@classmethod
def _push_metric(cls, session, type, name, tags, value):
def metric_id(name):
Expand Down
15 changes: 12 additions & 3 deletions tests/snuba/sessions/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from sentry.releasehealth.sessions import SessionsReleaseHealthBackend
from sentry.snuba.sessions import (
_make_stats,
check_releases_have_health_data,
get_adjacent_releases_based_on_adoption,
get_oldest_health_data_for_releases,
get_project_releases_by_stability,
Expand Down Expand Up @@ -568,7 +567,7 @@ def test_fetching_release_sessions_time_bounds_for_different_release_with_no_ses

class SnubaSessionsTestMetrics(ReleaseHealthMetricsTestCase, SnubaSessionsTest):
"""
Same tests as in SnunbaSessionsTest but using the Metrics backendx
Same tests as in SnunbaSessionsTest but using the Metrics backend
"""

pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this indicate that tests will be added in the future for metrics? If so I would clarify that with a ToDo comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class runs the same tests with SnubaSessionsTest from which is derived but with the metrics backend due to it being derived from ReleaseHealthMetricsTestCase , no further code needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay, I did not get that from the comment .. I thought you were just using the class as a placeholder to write future tests.. maybe Runs the same tests as in SnubaSessionsTestClass but using the Metrics backend

Expand Down Expand Up @@ -1853,13 +1852,15 @@ def test(self):


class CheckReleasesHaveHealthDataTest(TestCase, SnubaTestCase):
backend = SessionsReleaseHealthBackend()

def run_test(self, expected, projects, releases, start=None, end=None):
if not start:
start = datetime.now() - timedelta(days=1)
if not end:
end = datetime.now()
assert (
check_releases_have_health_data(
self.backend.check_releases_have_health_data(
self.organization.id,
[p.id for p in projects],
[r.version for r in releases],
Expand Down Expand Up @@ -1892,3 +1893,11 @@ def test(self):
self.run_test([release_1], [other_project], [release_1])
self.run_test([release_1, release_2], [other_project], [release_1, release_2])
self.run_test([release_1, release_2], [self.project, other_project], [release_1, release_2])


class CheckReleasesHaveHealthDataTestMetrics(
ReleaseHealthMetricsTestCase, CheckReleasesHaveHealthDataTest
):
"""Repeat tests with metrics backend"""

pass