def test_sessions_metrics_equal_num_keys(self): """ Tests whether the number of keys in the metrics implementation of sessions data is the same as in the sessions implementation. Runs twice. Firstly, against sessions implementation to populate the cache. Then, against the metrics implementation, and compares with cached results. """ interval_days = "1d" groupbyes = _session_groupby_powerset() for groupby in groupbyes: with patch( "sentry.api.endpoints.organization_sessions.release_health", SessionsReleaseHealthBackend(), ): sessions_data = result_sorted(self.get_sessions_data(groupby, interval_days)) with patch( "sentry.release_health.metrics_sessions_v2.indexer.resolve", MockIndexer().resolve ), patch( "sentry.api.endpoints.organization_sessions.release_health", MetricsReleaseHealthBackend(), ): metrics_data = result_sorted(self.get_sessions_data(groupby, interval_days)) errors = compare_results( sessions=sessions_data, metrics=metrics_data, rollup=interval_days * 24 * 60 * 60, # days to seconds ) assert len(errors) == 0
def parametrize_backend(cls): """ hack to parametrize test-classes by backend. Ideally we'd move over to pytest-style tests so we can use `pytest.mark.parametrize`, but hopefully we won't have more than one backend in the future. """ assert not hasattr(cls, "backend") cls.backend = SessionsReleaseHealthBackend() class MetricsTest(SessionMetricsTestCase, cls): __doc__ = f"Repeat tests from {cls} with metrics" backend = MetricsReleaseHealthBackend() MetricsTest.__name__ = f"{cls.__name__}Metrics" globals()[MetricsTest.__name__] = MetricsTest class DuplexTest(cls): __doc__ = f"Repeat tests from {cls} with duplex backend" backend = DuplexReleaseHealthBackend( metrics_start=datetime.now(pytz.utc) - timedelta(days=120)) DuplexTest.__name__ = f"{cls.__name__}Duplex" globals()[DuplexTest.__name__] = DuplexTest return cls
def __init__( self, metrics_start: datetime, ): self.sessions = SessionsReleaseHealthBackend() self.metrics = MetricsReleaseHealthBackend() self.metrics_start = metrics_start
def __init__( self, metrics_start: datetime, ): self.sessions = SessionsReleaseHealthBackend() self.metrics = MetricsReleaseHealthBackend() self.metrics_start = max( metrics_start, # The sessions backend never returns data beyond 90 days, so any # query beyond 90 days will return truncated results. # We assume that the release health duplex backend is sufficiently # often reinstantiated, at least once per day, not only due to # deploys but also because uwsgi/celery are routinely restarting # processes datetime.now(timezone.utc) - timedelta(days=89), )
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 ( self.backend.check_releases_have_health_data( self.organization.id, [p.id for p in projects], [r.version for r in releases], start, end, ) == {v.version for v in expected} ) def test_empty(self): # Test no errors when no session data project_release_1 = self.create_release(self.project) self.run_test([], [self.project], [project_release_1]) def test(self): other_project = self.create_project() release_1 = self.create_release( self.project, version="1", additional_projects=[other_project] ) release_2 = self.create_release(other_project, version="2") self.bulk_store_sessions( [ self.build_session(release=release_1), self.build_session(project_id=other_project, release=release_1), self.build_session(project_id=other_project, release=release_2), ] ) self.run_test([release_1], [self.project], [release_1]) self.run_test([release_1], [self.project], [release_1, release_2]) 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 GetProjectReleasesCountTest(TestCase, SnubaTestCase): backend = SessionsReleaseHealthBackend() def test_empty(self): # Test no errors when no session data org = self.create_organization() proj = self.create_project(organization=org) assert (self.backend.get_project_releases_count( org.id, [proj.id], "crash_free_users", stats_period="14d") == 0) def test(self): project_release_1 = self.create_release(self.project) other_project = self.create_project() other_project_release_1 = self.create_release(other_project) self.bulk_store_sessions([ self.build_session(environment=self.environment.name, release=project_release_1.version), self.build_session( environment="staging", project_id=other_project.id, release=other_project_release_1.version, ), ]) assert (self.backend.get_project_releases_count( self.organization.id, [self.project.id], "sessions") == 1) assert (self.backend.get_project_releases_count( self.organization.id, [self.project.id], "users") == 1) assert (self.backend.get_project_releases_count( self.organization.id, [self.project.id, other_project.id], "sessions") == 2) assert (self.backend.get_project_releases_count( self.organization.id, [self.project.id, other_project.id], "users", ) == 2) assert (self.backend.get_project_releases_count( self.organization.id, [self.project.id, other_project.id], "sessions", environments=[self.environment.name], ) == 1)
class GetCrashFreeRateTestCase(TestCase, SnubaTestCase): """ TestClass that tests that `get_current_and_previous_crash_free_rates` returns the correct `currentCrashFreeRate` and `previousCrashFreeRate` for each project TestData: Project 1: In the last 24h -> 2 Exited Sessions / 2 Total Sessions -> 100% Crash free rate In the previous 24h (>24h & <48h) -> 2 Exited + 1 Crashed Sessions / 3 Sessions -> 66.7% Project 2: In the last 24h -> 1 Exited + 1 Crashed / 2 Total Sessions -> 50% Crash free rate In the previous 24h (>24h & <48h) -> 0 Sessions -> None Project 3: In the last 24h -> 0 Sessions -> None In the previous 24h (>24h & <48h) -> 4 Exited + 1 Crashed / 5 Total Sessions -> 80% """ backend = SessionsReleaseHealthBackend() def setUp(self): super().setUp() self.session_started = time.time() // 60 * 60 self.session_started_gt_24_lt_48 = self.session_started - 30 * 60 * 60 self.project2 = self.create_project( name="Bar2", slug="bar2", teams=[self.team], fire_project_created=True, organization=self.organization, ) self.project3 = self.create_project( name="Bar3", slug="bar3", teams=[self.team], fire_project_created=True, organization=self.organization, ) # Project 1 for _ in range(0, 2): self.store_session( generate_session_default_args( { "project_id": self.project.id, "org_id": self.project.organization_id, "status": "exited", } ) ) for idx in range(0, 3): status = "exited" if idx == 2: status = "crashed" self.store_session( generate_session_default_args( { "project_id": self.project.id, "org_id": self.project.organization_id, "status": status, "started": self.session_started_gt_24_lt_48, } ) ) # Project 2 for i in range(0, 2): status = "exited" if i == 1: status = "crashed" self.store_session( generate_session_default_args( { "project_id": self.project2.id, "org_id": self.project2.organization_id, "status": status, } ) ) # Project 3 for i in range(0, 5): status = "exited" if i == 4: status = "crashed" self.store_session( generate_session_default_args( { "project_id": self.project3.id, "org_id": self.project3.organization_id, "status": status, "started": self.session_started_gt_24_lt_48, } ) ) def test_get_current_and_previous_crash_free_rates(self): now = timezone.now() last_24h_start = now - 24 * timedelta(hours=1) last_48h_start = now - 2 * 24 * timedelta(hours=1) data = self.backend.get_current_and_previous_crash_free_rates( org_id=self.organization.id, project_ids=[self.project.id, self.project2.id, self.project3.id], current_start=last_24h_start, current_end=now, previous_start=last_48h_start, previous_end=last_24h_start, rollup=86400, ) assert data == { self.project.id: { "currentCrashFreeRate": 100, "previousCrashFreeRate": 66.66666666666667, }, self.project2.id: {"currentCrashFreeRate": 50.0, "previousCrashFreeRate": None}, self.project3.id: {"currentCrashFreeRate": None, "previousCrashFreeRate": 80.0}, } def test_get_current_and_previous_crash_free_rates_with_zero_sessions(self): now = timezone.now() last_48h_start = now - 2 * 24 * timedelta(hours=1) last_72h_start = now - 3 * 24 * timedelta(hours=1) last_96h_start = now - 4 * 24 * timedelta(hours=1) data = self.backend.get_current_and_previous_crash_free_rates( org_id=self.organization.id, project_ids=[self.project.id], current_start=last_72h_start, current_end=last_48h_start, previous_start=last_96h_start, previous_end=last_72h_start, rollup=86400, ) assert data == { self.project.id: { "currentCrashFreeRate": None, "previousCrashFreeRate": None, }, }
class SnubaSessionsTest(TestCase, SnubaTestCase): backend = SessionsReleaseHealthBackend() def setUp(self): super().setUp() self.received = time.time() self.session_started = time.time() // 60 * 60 self.session_release = "[email protected]" self.session_crashed_release = "[email protected]" self.store_session( { "session_id": "5d52fd05-fcc9-4bf3-9dc9-267783670341", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "exited", "seq": 0, "release": self.session_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 60.0, "errors": 0, "started": self.session_started, "received": self.received, } ) self.store_session( { "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "ok", "seq": 0, "release": self.session_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": None, "errors": 0, "started": self.session_started, "received": self.received, } ) self.store_session( { "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "exited", "seq": 1, "release": self.session_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 30.0, "errors": 0, "started": self.session_started, "received": self.received, } ) self.store_session( { "session_id": "a148c0c5-06a2-423b-8901-6b43b812cf82", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "crashed", "seq": 0, "release": self.session_crashed_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 60.0, "errors": 0, "started": self.session_started, "received": self.received, } ) def test_get_oldest_health_data_for_releases(self): data = self.backend.get_oldest_health_data_for_releases( [(self.project.id, self.session_release)] ) assert data == { (self.project.id, self.session_release): format_timestamp( self.session_started // 3600 * 3600 ) } def test_check_has_health_data(self): data = self.backend.check_has_health_data( [(self.project.id, self.session_release), (self.project.id, "dummy-release")] ) assert data == {(self.project.id, self.session_release)} def test_check_has_health_data_without_releases_should_exclude_sessions_gt_90_days(self): """ Test that ensures that `check_has_health_data` returns a set of projects that has health data within the last 90d if only a list of project ids is provided and that any project with session data older than 90 days should be exluded """ project2 = self.create_project( name="Bar2", slug="bar2", teams=[self.team], fire_project_created=True, organization=self.organization, ) date_100_days_ago = to_timestamp( (datetime.utcnow() - timedelta(days=100)).replace(tzinfo=pytz.utc) ) self.store_session( generate_session_default_args( { "started": date_100_days_ago // 60 * 60, "received": date_100_days_ago, "project_id": project2.id, "org_id": project2.organization_id, "status": "exited", } ) ) data = self.backend.check_has_health_data([self.project.id, project2.id]) assert data == {self.project.id} def test_check_has_health_data_without_releases_should_include_sessions_lte_90_days(self): """ Test that ensures that `check_has_health_data` returns a set of projects that has health data within the last 90d if only a list of project ids is provided and any project with session data earlier than 90 days should be included """ project2 = self.create_project( name="Bar2", slug="bar2", teams=[self.team], fire_project_created=True, organization=self.organization, ) self.store_session( generate_session_default_args( {"project_id": project2.id, "org_id": project2.organization_id, "status": "exited"} ) ) data = self.backend.check_has_health_data([self.project.id, project2.id]) assert data == {self.project.id, project2.id} def test_check_has_health_data_does_not_crash_when_sending_projects_list_as_set(self): data = self.backend.check_has_health_data({self.project.id}) assert data == {self.project.id} def test_get_project_releases_by_stability(self): # Add an extra session with a different `distinct_id` so that sorting by users # is stable self.store_session( { "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102665", "status": "ok", "seq": 0, "release": self.session_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": None, "errors": 0, "started": self.session_started, "received": self.received, } ) for scope in "sessions", "users": data = get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h" ) assert data == [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ] def test_get_project_releases_by_stability_for_crash_free_sort(self): """ Test that ensures that using crash free rate sort options, returns a list of ASC releases according to the chosen crash_free sort option """ for scope in "crash_free_sessions", "crash_free_users": data = get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h" ) assert data == [ (self.project.id, self.session_crashed_release), (self.project.id, self.session_release), ] def test_get_project_releases_by_stability_for_releases_with_users_data(self): """ Test that ensures if releases contain no users data, then those releases should not be returned on `users` and `crash_free_users` sorts """ self.store_session( { "session_id": "bd1521fc-d27c-11eb-b8bc-0242ac130003", "status": "ok", "seq": 0, "release": "release-with-no-users", "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": None, "errors": 0, "started": self.session_started, "received": self.received, } ) data = get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope="users", stats_period="24h" ) assert set(data) == { (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), } data = get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope="crash_free_users", stats_period="24h" ) assert set(data) == { (self.project.id, self.session_crashed_release), (self.project.id, self.session_release), } def test_get_release_adoption(self): data = self.backend.get_release_adoption( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), (self.project.id, "dummy-release"), ] ) assert data == { (self.project.id, self.session_release): { "sessions_24h": 2, "users_24h": 1, "adoption": 100.0, "sessions_adoption": 66.66666666666666, "project_sessions_24h": 3, "project_users_24h": 1, }, (self.project.id, self.session_crashed_release): { "sessions_24h": 1, "users_24h": 1, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "project_sessions_24h": 3, "project_users_24h": 1, }, } def test_get_release_adoption_lowered(self): self.store_session( { "session_id": "4574c381-acc5-4e05-b10b-f16cdc2f385a", "distinct_id": "da50f094-10b4-40fb-89fb-cb3aa9014148", "status": "crashed", "seq": 0, "release": self.session_crashed_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 60.0, "errors": 0, "started": self.session_started, "received": self.received, } ) data = self.backend.get_release_adoption( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), (self.project.id, "dummy-release"), ] ) assert data == { (self.project.id, self.session_release): { "sessions_24h": 2, "users_24h": 1, "adoption": 50.0, "sessions_adoption": 50.0, "project_sessions_24h": 4, "project_users_24h": 2, }, (self.project.id, self.session_crashed_release): { "sessions_24h": 2, "users_24h": 2, "adoption": 100.0, "sessions_adoption": 50.0, "project_sessions_24h": 4, "project_users_24h": 2, }, } def test_get_release_health_data_overview_users(self): data = get_release_health_data_overview( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ], summary_stats_period="24h", health_stats_period="24h", stat="users", ) stats = make_24h_stats(self.received - (24 * 3600)) stats[-1] = [stats[-1][0], 1] stats_ok = stats_crash = stats assert data == { (self.project.id, self.session_crashed_release): { "total_sessions": 1, "sessions_errored": 0, "total_sessions_24h": 1, "total_users": 1, "duration_p90": None, "sessions_crashed": 1, "total_users_24h": 1, "stats": {"24h": stats_crash}, "crash_free_users": 0.0, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "has_health_data": True, "crash_free_sessions": 0.0, "duration_p50": None, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, (self.project.id, self.session_release): { "total_sessions": 2, "sessions_errored": 0, "total_sessions_24h": 2, "total_users": 1, "duration_p90": 57.0, "sessions_crashed": 0, "total_users_24h": 1, "stats": {"24h": stats_ok}, "crash_free_users": 100.0, "adoption": 100.0, "sessions_adoption": 66.66666666666666, "has_health_data": True, "crash_free_sessions": 100.0, "duration_p50": 45.0, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, } def test_get_release_health_data_overview_sessions(self): data = get_release_health_data_overview( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ], summary_stats_period="24h", health_stats_period="24h", stat="sessions", ) stats = make_24h_stats(self.received - (24 * 3600)) stats_ok = stats[:-1] + [[stats[-1][0], 2]] stats_crash = stats[:-1] + [[stats[-1][0], 1]] assert data == { (self.project.id, self.session_crashed_release): { "total_sessions": 1, "sessions_errored": 0, "total_sessions_24h": 1, "total_users": 1, "duration_p90": None, "sessions_crashed": 1, "total_users_24h": 1, "stats": {"24h": stats_crash}, "crash_free_users": 0.0, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "has_health_data": True, "crash_free_sessions": 0.0, "duration_p50": None, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, (self.project.id, self.session_release): { "total_sessions": 2, "sessions_errored": 0, "total_sessions_24h": 2, "total_users": 1, "duration_p90": 57.0, "sessions_crashed": 0, "total_users_24h": 1, "stats": {"24h": stats_ok}, "crash_free_users": 100.0, "sessions_adoption": 66.66666666666666, "adoption": 100.0, "has_health_data": True, "crash_free_sessions": 100.0, "duration_p50": 45.0, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, } def test_fetching_release_sessions_time_bounds_for_different_release(self): """ Test that ensures only session bounds for releases are calculated according to their respective release """ # Same release session self.store_session( { "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "exited", "seq": 1, "release": self.session_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 30.0, "errors": 0, "started": self.session_started - 3600 * 2, "received": self.received - 3600 * 2, } ) # Different release session self.store_session( { "session_id": "a148c0c5-06a2-423b-8901-6b43b812cf82", "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666", "status": "crashed", "seq": 0, "release": self.session_crashed_release, "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": self.project.id, "duration": 60.0, "errors": 0, "started": self.session_started - 3600 * 2, "received": self.received - 3600 * 2, } ) if isinstance(self.backend, MetricsReleaseHealthBackend): truncation = {"second": 0} else: truncation = {"minute": 0} expected_formatted_lower_bound = ( datetime.utcfromtimestamp(self.session_started - 3600 * 2) .replace(**truncation) .isoformat()[:19] + "Z" ) expected_formatted_upper_bound = ( datetime.utcfromtimestamp(self.session_started).replace(**truncation).isoformat()[:19] + "Z" ) # Test for self.session_release data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release=self.session_release, org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": expected_formatted_lower_bound, "sessions_upper_bound": expected_formatted_upper_bound, } # Test for self.session_crashed_release data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release=self.session_crashed_release, org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": expected_formatted_lower_bound, "sessions_upper_bound": expected_formatted_upper_bound, } def test_fetching_release_sessions_time_bounds_for_different_release_with_no_sessions(self): """ Test that ensures if no sessions are available for a specific release then the bounds should be returned as None """ data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release="different_release", org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": None, "sessions_upper_bound": None, } def test_get_crash_free_breakdown(self): start = timezone.now() - timedelta(days=4) data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release=self.session_release, start=start, environments=["prod"], ) # Last returned date is generated within function, should be close to now: last_date = data[-1].pop("date") assert timezone.now() - last_date < timedelta(seconds=1) assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": 100.0, "crash_free_users": 100.0, "total_sessions": 2, "total_users": 1, }, ] data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release=self.session_crashed_release, start=start, environments=["prod"], ) data[-1].pop("date") assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": 0.0, "crash_free_users": 0.0, "total_sessions": 1, "total_users": 1, }, ] data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release="non-existing", start=start, environments=["prod"], ) data[-1].pop("date") assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "total_sessions": 0, "total_users": 0, }, ] def test_basic_release_model_adoptions(self): """ Test that the basic (project,release) data is returned """ proj_id = self.project.id data = self.backend.get_changed_project_release_model_adoptions([proj_id]) assert set(data) == {(proj_id, "[email protected]"), (proj_id, "[email protected]")} def test_old_release_model_adoptions(self): """ Test that old entries (older that 72 h) are not returned """ _100h = 100 * 60 * 60 # 100 hours in seconds proj_id = self.project.id self.store_session( { "session_id": "f6a01ae0-7fa7-44df-afb9-ae32ef1c8102", "distinct_id": "5849e12a-220a-4bda-8c72-4e35391c341f", "status": "crashed", "seq": 0, "release": "[email protected]", "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": proj_id, "duration": 60.0, "errors": 0, "started": self.session_started - _100h, "received": self.received - 3600 * 2, } ) data = self.backend.get_changed_project_release_model_adoptions([proj_id]) assert set(data) == {(proj_id, "[email protected]"), (proj_id, "[email protected]")} def test_multi_proj_release_model_adoptions(self): """Test that the api works with multiple projects""" proj_id = self.project.id new_proj_id = proj_id + 1 self.store_session( { "session_id": "f6a01ae0-7fa7-44df-afb9-ae32ef1c8102", "distinct_id": "5849e12a-220a-4bda-8c72-4e35391c341f", "status": "crashed", "seq": 0, "release": "[email protected]", "environment": "prod", "retention_days": 90, "org_id": self.project.organization_id, "project_id": new_proj_id, "duration": 60.0, "errors": 0, "started": self.session_started, "received": self.received - 3600 * 2, } ) data = self.backend.get_changed_project_release_model_adoptions([proj_id, new_proj_id]) assert set(data) == { (proj_id, "[email protected]"), (proj_id, "[email protected]"), (new_proj_id, "[email protected]"), }
class SnubaSessionsTest(TestCase, SnubaTestCase): backend = SessionsReleaseHealthBackend() def setUp(self): super().setUp() self.received = time.time() self.session_started = time.time() // 60 * 60 self.session_release = "[email protected]" self.session_crashed_release = "[email protected]" session_1 = "5d52fd05-fcc9-4bf3-9dc9-267783670341" session_2 = "5e910c1a-6941-460e-9843-24103fb6a63c" session_3 = "a148c0c5-06a2-423b-8901-6b43b812cf82" user_1 = "39887d89-13b2-4c84-8c23-5d13d2102666" self.store_session( self.build_session( distinct_id=user_1, session_id=session_1, status="exited", release=self.session_release, environment="prod", started=self.session_started, received=self.received, )) self.store_session( self.build_session( distinct_id=user_1, session_id=session_2, release=self.session_release, environment="prod", duration=None, started=self.session_started, received=self.received, )) self.store_session( self.build_session( distinct_id=user_1, session_id=session_2, seq=1, duration=30, status="exited", release=self.session_release, environment="prod", started=self.session_started, received=self.received, )) self.store_session( self.build_session( distinct_id=user_1, session_id=session_3, status="crashed", release=self.session_crashed_release, environment="prod", started=self.session_started, received=self.received, )) def test_get_oldest_health_data_for_releases(self): data = self.backend.get_oldest_health_data_for_releases([ (self.project.id, self.session_release) ]) assert data == { (self.project.id, self.session_release): format_timestamp(self.session_started // 3600 * 3600) } def test_check_has_health_data(self): data = self.backend.check_has_health_data([ (self.project.id, self.session_release), (self.project.id, "dummy-release") ]) assert data == {(self.project.id, self.session_release)} def test_check_has_health_data_without_releases_should_exclude_sessions_gt_90_days( self): """ Test that ensures that `check_has_health_data` returns a set of projects that has health data within the last 90d if only a list of project ids is provided and that any project with session data older than 90 days should be exluded """ project2 = self.create_project( name="Bar2", slug="bar2", teams=[self.team], fire_project_created=True, organization=self.organization, ) date_100_days_ago = to_timestamp( (datetime.utcnow() - timedelta(days=100)).replace(tzinfo=pytz.utc)) self.store_session( self.build_session( **{ "started": date_100_days_ago // 60 * 60, "received": date_100_days_ago, "project_id": project2.id, "org_id": project2.organization_id, "status": "exited", })) data = self.backend.check_has_health_data( [self.project.id, project2.id]) assert data == {self.project.id} def test_check_has_health_data_without_releases_should_include_sessions_lte_90_days( self): """ Test that ensures that `check_has_health_data` returns a set of projects that has health data within the last 90d if only a list of project ids is provided and any project with session data earlier than 90 days should be included """ project2 = self.create_project( name="Bar2", slug="bar2", teams=[self.team], fire_project_created=True, organization=self.organization, ) self.store_session( self.build_session( **{ "project_id": project2.id, "org_id": project2.organization_id, "status": "exited", })) data = self.backend.check_has_health_data( [self.project.id, project2.id]) assert data == {self.project.id, project2.id} def test_check_has_health_data_does_not_crash_when_sending_projects_list_as_set( self): data = self.backend.check_has_health_data({self.project.id}) assert data == {self.project.id} def test_get_project_releases_by_stability(self): # Add an extra session with a different `distinct_id` so that sorting by users # is stable self.store_session( self.build_session( release=self.session_release, environment="prod", started=self.session_started, received=self.received, )) for scope in "sessions", "users": data = self.backend.get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h") assert data == [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ] def test_get_project_releases_by_stability_for_crash_free_sort(self): """ Test that ensures that using crash free rate sort options, returns a list of ASC releases according to the chosen crash_free sort option """ # add another user to session_release to make sure that they are sorted correctly self.store_session( self.build_session( status="exited", release=self.session_release, environment="prod", started=self.session_started, received=self.received, )) for scope in "crash_free_sessions", "crash_free_users": data = self.backend.get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h") assert data == [ (self.project.id, self.session_crashed_release), (self.project.id, self.session_release), ] def test_get_project_releases_by_stability_for_releases_with_users_data( self): """ Test that ensures if releases contain no users data, then those releases should not be returned on `users` and `crash_free_users` sorts """ self.store_session( self.build_session( distinct_id=None, release="release-with-no-users", environment="prod", started=self.session_started, received=self.received, )) data = self.backend.get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope="users", stats_period="24h") assert set(data) == { (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), } data = self.backend.get_project_releases_by_stability( [self.project.id], offset=0, limit=100, scope="crash_free_users", stats_period="24h") assert set(data) == { (self.project.id, self.session_crashed_release), (self.project.id, self.session_release), } def test_get_release_adoption(self): data = self.backend.get_release_adoption([ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), (self.project.id, "dummy-release"), ]) assert data == { (self.project.id, self.session_release): { "sessions_24h": 2, "users_24h": 1, "adoption": 100.0, "sessions_adoption": 66.66666666666666, "project_sessions_24h": 3, "project_users_24h": 1, }, (self.project.id, self.session_crashed_release): { "sessions_24h": 1, "users_24h": 1, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "project_sessions_24h": 3, "project_users_24h": 1, }, } def test_get_release_adoption_lowered(self): self.store_session( self.build_session( release=self.session_crashed_release, environment="prod", status="crashed", started=self.session_started, received=self.received, )) data = self.backend.get_release_adoption([ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), (self.project.id, "dummy-release"), ]) assert data == { (self.project.id, self.session_release): { "sessions_24h": 2, "users_24h": 1, "adoption": 50.0, "sessions_adoption": 50.0, "project_sessions_24h": 4, "project_users_24h": 2, }, (self.project.id, self.session_crashed_release): { "sessions_24h": 2, "users_24h": 2, "adoption": 100.0, "sessions_adoption": 50.0, "project_sessions_24h": 4, "project_users_24h": 2, }, } def test_get_release_health_data_overview_users(self): data = self.backend.get_release_health_data_overview( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ], summary_stats_period="24h", health_stats_period="24h", stat="users", ) stats = make_24h_stats(self.received - (24 * 3600)) stats[-1] = [stats[-1][0], 1] stats_ok = stats_crash = stats assert data == { (self.project.id, self.session_crashed_release): { "total_sessions": 1, "sessions_errored": 0, "total_sessions_24h": 1, "total_users": 1, "duration_p90": None, "sessions_crashed": 1, "total_users_24h": 1, "stats": { "24h": stats_crash }, "crash_free_users": 0.0, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "has_health_data": True, "crash_free_sessions": 0.0, "duration_p50": None, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, (self.project.id, self.session_release): { "total_sessions": 2, "sessions_errored": 0, "total_sessions_24h": 2, "total_users": 1, "duration_p90": 57.0, "sessions_crashed": 0, "total_users_24h": 1, "stats": { "24h": stats_ok }, "crash_free_users": 100.0, "adoption": 100.0, "sessions_adoption": 66.66666666666666, "has_health_data": True, "crash_free_sessions": 100.0, "duration_p50": 45.0, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, } def test_get_release_health_data_overview_sessions(self): data = self.backend.get_release_health_data_overview( [ (self.project.id, self.session_release), (self.project.id, self.session_crashed_release), ], summary_stats_period="24h", health_stats_period="24h", stat="sessions", ) stats = make_24h_stats(self.received - (24 * 3600)) stats_ok = stats[:-1] + [[stats[-1][0], 2]] stats_crash = stats[:-1] + [[stats[-1][0], 1]] assert data == { (self.project.id, self.session_crashed_release): { "total_sessions": 1, "sessions_errored": 0, "total_sessions_24h": 1, "total_users": 1, "duration_p90": None, "sessions_crashed": 1, "total_users_24h": 1, "stats": { "24h": stats_crash }, "crash_free_users": 0.0, "adoption": 100.0, "sessions_adoption": 33.33333333333333, "has_health_data": True, "crash_free_sessions": 0.0, "duration_p50": None, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, (self.project.id, self.session_release): { "total_sessions": 2, "sessions_errored": 0, "total_sessions_24h": 2, "total_users": 1, "duration_p90": 57.0, "sessions_crashed": 0, "total_users_24h": 1, "stats": { "24h": stats_ok }, "crash_free_users": 100.0, "sessions_adoption": 66.66666666666666, "adoption": 100.0, "has_health_data": True, "crash_free_sessions": 100.0, "duration_p50": 45.0, "total_project_sessions_24h": 3, "total_project_users_24h": 1, }, } def test_fetching_release_sessions_time_bounds_for_different_release(self): """ Test that ensures only session bounds for releases are calculated according to their respective release """ # Same release session self.store_session( self.build_session( release=self.session_release, environment="prod", status="exited", started=self.session_started - 3600 * 2, received=self.received - 3600 * 2, )) # Different release session self.store_session( self.build_session( release=self.session_crashed_release, environment="prod", status="crashed", started=self.session_started - 3600 * 2, received=self.received - 3600 * 2, )) if isinstance(self.backend, MetricsReleaseHealthBackend): truncation = {"second": 0} else: truncation = {"minute": 0} expected_formatted_lower_bound = ( datetime.utcfromtimestamp(self.session_started - 3600 * 2).replace( **truncation).isoformat()[:19] + "Z") expected_formatted_upper_bound = (datetime.utcfromtimestamp( self.session_started).replace(**truncation).isoformat()[:19] + "Z") # Test for self.session_release data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release=self.session_release, org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": expected_formatted_lower_bound, "sessions_upper_bound": expected_formatted_upper_bound, } # Test for self.session_crashed_release data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release=self.session_crashed_release, org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": expected_formatted_lower_bound, "sessions_upper_bound": expected_formatted_upper_bound, } def test_fetching_release_sessions_time_bounds_for_different_release_with_no_sessions( self): """ Test that ensures if no sessions are available for a specific release then the bounds should be returned as None """ data = self.backend.get_release_sessions_time_bounds( project_id=self.project.id, release="different_release", org_id=self.organization.id, environments=["prod"], ) assert data == { "sessions_lower_bound": None, "sessions_upper_bound": None, } def test_get_crash_free_breakdown(self): start = timezone.now() - timedelta(days=4) data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release=self.session_release, start=start, environments=["prod"], ) # Last returned date is generated within function, should be close to now: last_date = data[-1].pop("date") assert timezone.now() - last_date < timedelta(seconds=1) assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": 100.0, "crash_free_users": 100.0, "total_sessions": 2, "total_users": 1, }, ] data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release=self.session_crashed_release, start=start, environments=["prod"], ) data[-1].pop("date") assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": 0.0, "crash_free_users": 0.0, "total_sessions": 1, "total_users": 1, }, ] data = self.backend.get_crash_free_breakdown( project_id=self.project.id, release="non-existing", start=start, environments=["prod"], ) data[-1].pop("date") assert data == [ { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=1), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "date": start + timedelta(days=2), "total_sessions": 0, "total_users": 0, }, { "crash_free_sessions": None, "crash_free_users": None, "total_sessions": 0, "total_users": 0, }, ] def test_basic_release_model_adoptions(self): """ Test that the basic (project,release) data is returned """ proj_id = self.project.id data = self.backend.get_changed_project_release_model_adoptions( [proj_id]) assert set(data) == {(proj_id, "[email protected]"), (proj_id, "[email protected]")} def test_old_release_model_adoptions(self): """ Test that old entries (older that 72 h) are not returned """ _100h = 100 * 60 * 60 # 100 hours in seconds proj_id = self.project.id self.store_session( self.build_session( release="[email protected]", environment="prod", status="crashed", started=self.session_started - _100h, received=self.received - 3600 * 2, )) data = self.backend.get_changed_project_release_model_adoptions( [proj_id]) assert set(data) == {(proj_id, "[email protected]"), (proj_id, "[email protected]")} def test_multi_proj_release_model_adoptions(self): """Test that the api works with multiple projects""" proj_id = self.project.id new_proj_id = proj_id + 1 self.store_session( self.build_session( project_id=new_proj_id, release="[email protected]", environment="prod", status="crashed", started=self.session_started, received=self.received - 3600 * 2, )) data = self.backend.get_changed_project_release_model_adoptions( [proj_id, new_proj_id]) assert set(data) == { (proj_id, "[email protected]"), (proj_id, "[email protected]"), (new_proj_id, "[email protected]"), } @staticmethod def _add_timestamps_to_series(series, start: datetime): one_day = 24 * 60 * 60 day0 = one_day * int(start.timestamp() / one_day) def ts(days: int) -> int: return day0 + days * one_day return [[ts(i + 1), data] for i, data in enumerate(series)] def _test_get_project_release_stats(self, stat: OverviewStat, release: str, expected_series, expected_totals): end = timezone.now() start = end - timedelta(days=4) stats, totals = self.backend.get_project_release_stats( self.project.id, release=release, stat=stat, rollup=86400, start=start, end=end, ) # Let's not care about lists vs. tuples: stats = [[ts, data] for ts, data in stats] assert stats == self._add_timestamps_to_series(expected_series, start) assert totals == expected_totals def test_get_project_release_stats_users(self): self._test_get_project_release_stats( "users", self.session_release, [ { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": 45.0, "duration_p90": 57.0, "users": 1, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 1, }, ], { "users": 1, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 1, }, ) def test_get_project_release_stats_users_crashed(self): self._test_get_project_release_stats( "users", self.session_crashed_release, [ { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "users": 0, "users_abnormal": 0, "users_crashed": 0, "users_errored": 0, "users_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "users": 1, "users_abnormal": 0, "users_crashed": 1, "users_errored": 0, "users_healthy": 0, }, ], { "users": 1, "users_abnormal": 0, "users_crashed": 1, "users_errored": 0, "users_healthy": 0, }, ) def test_get_project_release_stats_sessions(self): self._test_get_project_release_stats( "sessions", self.session_release, [ { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": 45.0, "duration_p90": 57.0, "sessions": 2, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 2, }, ], { "sessions": 2, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 2, }, ) def test_get_project_release_stats_sessions_crashed(self): self._test_get_project_release_stats( "sessions", self.session_crashed_release, [ { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "sessions": 0, "sessions_abnormal": 0, "sessions_crashed": 0, "sessions_errored": 0, "sessions_healthy": 0, }, { "duration_p50": None, "duration_p90": None, "sessions": 1, "sessions_abnormal": 0, "sessions_crashed": 1, "sessions_errored": 0, "sessions_healthy": 0, }, ], { "sessions": 1, "sessions_abnormal": 0, "sessions_crashed": 1, "sessions_errored": 0, "sessions_healthy": 0, }, )
class CheckNumberOfSessions(TestCase, SnubaTestCase): backend = SessionsReleaseHealthBackend() def setUp(self): super().setUp() self.dev_env = self.create_environment(name="development", project=self.project) self.prod_env = self.create_environment(name="production", project=self.project) self.test_env = self.create_environment(name="test", project=self.project) self.another_project = self.create_project() self.third_project = self.create_project() self.now_dt = datetime(2000, 3, 20, 17, 40, 0) self._5_min_ago_dt = self.now_dt - timedelta(minutes=5) self._30_min_ago_dt = self.now_dt - timedelta(minutes=30) self._1_h_ago_dt = self.now_dt - timedelta(hours=1) self._2_h_ago_dt = self.now_dt - timedelta(hours=2) self._3_h_ago_dt = self.now_dt - timedelta(hours=3) self.now = self.now_dt.timestamp() self._5_min_ago = self._5_min_ago_dt.timestamp() self._30_min_ago = self._30_min_ago_dt.timestamp() self._1_h_ago = self._1_h_ago_dt.timestamp() self._2_h_ago = self._2_h_ago_dt.timestamp() self._3_h_ago = self._3_h_ago_dt.timestamp() def test_no_sessions(self): """ Tests that when there are no sessions the function behaves and returns 0 """ actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._30_min_ago_dt, end=self.now_dt, ) assert 0 == actual def test_sessions_in_environment(self): """ Tests that it correctly picks up the sessions for the selected environment in the selected time, not counting other environments and other times """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.build_session(environment=dev, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._2_h_ago, started=self._2_h_ago), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=self.prod_env.id, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 2 def test_sessions_in_all_environments(self): """ When the environment is not specified sessions from all environments are counted """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.build_session(environment=dev, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._2_h_ago, started=self._2_h_ago), self.build_session(environment=dev, received=self._2_h_ago, started=self._2_h_ago), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 3 def test_sessions_from_multiple_projects(self): """ Only sessions from the specified project are considered """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.build_session(environment=dev, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session( environment=prod, received=self._5_min_ago, project_id=self.another_project.id, started=self._5_min_ago, ), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 2 def test_sessions_per_project_no_sessions(self): """ Tests that no sessions are returned """ actual = self.backend.get_num_sessions_per_project( project_ids=[self.project.id, self.another_project.id], environment_ids=None, rollup=60, start=self._30_min_ago_dt, end=self.now_dt, ) assert [] == actual def test_sesions_per_project_multiple_projects(self): dev = self.dev_env.name prod = self.prod_env.name test = self.test_env.name p1 = self.project p2 = self.another_project p3 = self.third_project self.bulk_store_sessions([ # counted in p1 self.build_session(environment=dev, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=prod, received=self._5_min_ago, started=self._5_min_ago), self.build_session(environment=dev, received=self._30_min_ago, started=self._30_min_ago), # ignored in p1 # ignored env self.build_session(environment=test, received=self._30_min_ago, started=self._30_min_ago), # too old self.build_session(environment=prod, received=self._3_h_ago, started=self._3_h_ago), # counted in p2 self.build_session( environment=dev, received=self._5_min_ago, project_id=p2.id, started=self._5_min_ago, ), # ignored in p2 # ignored env self.build_session( environment=test, received=self._5_min_ago, project_id=p2.id, started=self._5_min_ago, ), # too old self.build_session( environment=prod, received=self._3_h_ago, project_id=p2.id, started=self._3_h_ago, ), # ignored p3 self.build_session( environment=dev, received=self._5_min_ago, project_id=p3.id, started=self._5_min_ago, ), ]) actual = self.backend.get_num_sessions_per_project( project_ids=[self.project.id, self.another_project.id], environment_ids=[self.dev_env.id, self.prod_env.id], rollup=60, start=self._2_h_ago_dt, end=self.now_dt, ) assert len(actual) == 2 for t in [(p1.id, 3), (p2.id, 1)]: assert t in actual
class CheckNumberOfSessions(TestCase, SnubaTestCase): backend = SessionsReleaseHealthBackend() def setUp(self): super().setUp() self.dev_env = self.create_environment(name="development", project=self.project) self.prod_env = self.create_environment(name="production", project=self.project) self.test_env = self.create_environment(name="test", project=self.project) self.another_project = self.create_project() self.third_project = self.create_project() self.now_dt = datetime.utcnow() self._5_min_ago_dt = self.now_dt - timedelta(minutes=5) self._30_min_ago_dt = self.now_dt - timedelta(minutes=30) self._1_h_ago_dt = self.now_dt - timedelta(hours=1) self._2_h_ago_dt = self.now_dt - timedelta(hours=2) self._3_h_ago_dt = self.now_dt - timedelta(hours=3) self.now = self.now_dt.timestamp() self._5_min_ago = self._5_min_ago_dt.timestamp() self._30_min_ago = self._30_min_ago_dt.timestamp() self._1_h_ago = self._1_h_ago_dt.timestamp() self._2_h_ago = self._2_h_ago_dt.timestamp() self._3_h_ago = self._3_h_ago_dt.timestamp() def make_session( self, environment, received=None, started=None, status="ok", release="[email protected]", project=None, ): if received is None: received = time.time() if started is None: started = received if project is None: project = self.project return { "session_id": str(uuid.uuid4()), "distinct_id": str(uuid.uuid4()), "status": status, "seq": 0, "release": release, "environment": environment, "retention_days": 90, "org_id": self.project.organization_id, "project_id": project.id, "duration": 60.0, "errors": 0, "started": started, "received": received, } def test_no_sessions(self): """ Tests that when there are no sessions the function behaves and returns 0 """ actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._30_min_ago_dt, end=self.now_dt, ) assert 0 == actual def test_sessions_in_environment(self): """ Tests that it correctly picks up the sessions for the selected environment in the selected time, not counting other environments and other times """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.make_session(environment=dev, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=prod, received=self._2_h_ago), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=self.prod_env.id, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 2 def test_sessions_in_all_environments(self): """ When the environment is not specified sessions from all environments are counted """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.make_session(environment=dev, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=prod, received=self._2_h_ago), self.make_session(environment=dev, received=self._2_h_ago), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 3 def test_sessions_from_multiple_projects(self): """ Only sessions from the specified project are considered """ dev = self.dev_env.name prod = self.prod_env.name self.bulk_store_sessions([ self.make_session(environment=dev, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago, project=self.another_project), ]) actual = self.backend.get_project_sessions_count( project_id=self.project.id, environment_id=None, rollup=60, start=self._1_h_ago_dt, end=self.now_dt, ) assert actual == 2 def test_sessions_per_project_no_sessions(self): """ Tests that no sessions are returned """ actual = self.backend.get_num_sessions_per_project( project_ids=[self.project.id, self.another_project.id], environment_ids=None, rollup=60, start=self._30_min_ago_dt, end=self.now_dt, ) assert [] == actual def test_sesions_per_project_multiple_projects(self): dev = self.dev_env.name prod = self.prod_env.name test = self.test_env.name p1 = self.project p2 = self.another_project p3 = self.third_project self.bulk_store_sessions([ # counted in p1 self.make_session(environment=dev, received=self._5_min_ago), self.make_session(environment=prod, received=self._5_min_ago), self.make_session(environment=dev, received=self._30_min_ago), # ignored in p1 # ignored env self.make_session(environment=test, received=self._30_min_ago), # too old self.make_session(environment=prod, received=self._3_h_ago), # counted in p2 self.make_session(environment=dev, received=self._5_min_ago, project=p2), # ignored in p2 # ignored env self.make_session(environment=test, received=self._5_min_ago, project=p2), # too old self.make_session(environment=prod, received=self._3_h_ago, project=p2), # ignored p3 self.make_session(environment=dev, received=self._5_min_ago, project=p3), ]) actual = self.backend.get_num_sessions_per_project( project_ids=[self.project.id, self.another_project.id], environment_ids=[self.dev_env.id, self.prod_env.id], rollup=60, start=self._2_h_ago_dt, end=self.now_dt, ) assert len(actual) == 2 for t in [(p1.id, 3), (p2.id, 1)]: assert t in actual