test_sessions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. from __future__ import absolute_import
  2. import time
  3. import pytz
  4. from datetime import datetime
  5. from sentry.testutils import SnubaTestCase, TestCase
  6. from sentry.snuba.sessions import (
  7. get_oldest_health_data_for_releases,
  8. check_has_health_data,
  9. get_project_releases_by_stability,
  10. get_release_adoption,
  11. get_release_health_data_overview,
  12. _make_stats,
  13. )
  14. def format_timestamp(dt):
  15. if not isinstance(dt, datetime):
  16. dt = datetime.utcfromtimestamp(dt)
  17. return dt.strftime("%Y-%m-%dT%H:%M:%S+00:00")
  18. def make_24h_stats(ts):
  19. return _make_stats(datetime.utcfromtimestamp(ts).replace(tzinfo=pytz.utc), 3600, 24)
  20. class SnubaSessionsTest(TestCase, SnubaTestCase):
  21. def setUp(self):
  22. super(SnubaSessionsTest, self).setUp()
  23. self.received = time.time()
  24. self.session_started = time.time() // 60 * 60
  25. self.session_release = "foo@1.0.0"
  26. self.session_crashed_release = "foo@2.0.0"
  27. self.store_session(
  28. {
  29. "session_id": "5d52fd05-fcc9-4bf3-9dc9-267783670341",
  30. "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666",
  31. "status": "exited",
  32. "seq": 0,
  33. "release": self.session_release,
  34. "environment": "prod",
  35. "retention_days": 90,
  36. "org_id": self.project.organization_id,
  37. "project_id": self.project.id,
  38. "duration": 60.0,
  39. "errors": 0,
  40. "started": self.session_started,
  41. "received": self.received,
  42. }
  43. )
  44. self.store_session(
  45. {
  46. "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c",
  47. "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666",
  48. "status": "ok",
  49. "seq": 0,
  50. "release": self.session_release,
  51. "environment": "prod",
  52. "retention_days": 90,
  53. "org_id": self.project.organization_id,
  54. "project_id": self.project.id,
  55. "duration": None,
  56. "errors": 0,
  57. "started": self.session_started,
  58. "received": self.received,
  59. }
  60. )
  61. self.store_session(
  62. {
  63. "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c",
  64. "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666",
  65. "status": "exited",
  66. "seq": 1,
  67. "release": self.session_release,
  68. "environment": "prod",
  69. "retention_days": 90,
  70. "org_id": self.project.organization_id,
  71. "project_id": self.project.id,
  72. "duration": 30.0,
  73. "errors": 0,
  74. "started": self.session_started,
  75. "received": self.received,
  76. }
  77. )
  78. self.store_session(
  79. {
  80. "session_id": "a148c0c5-06a2-423b-8901-6b43b812cf82",
  81. "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102666",
  82. "status": "crashed",
  83. "seq": 0,
  84. "release": self.session_crashed_release,
  85. "environment": "prod",
  86. "retention_days": 90,
  87. "org_id": self.project.organization_id,
  88. "project_id": self.project.id,
  89. "duration": 60.0,
  90. "errors": 0,
  91. "started": self.session_started,
  92. "received": self.received,
  93. }
  94. )
  95. def test_get_oldest_health_data_for_releases(self):
  96. data = get_oldest_health_data_for_releases([(self.project.id, self.session_release)])
  97. assert data == {
  98. (self.project.id, self.session_release): format_timestamp(
  99. self.session_started // 3600 * 3600
  100. )
  101. }
  102. def test_check_has_health_data(self):
  103. data = check_has_health_data(
  104. [(self.project.id, self.session_release), (self.project.id, "dummy-release")]
  105. )
  106. assert data == set([(self.project.id, self.session_release)])
  107. def test_get_project_releases_by_stability(self):
  108. # Add an extra session with a different `distinct_id` so that sorting by users
  109. # is stable
  110. self.store_session(
  111. {
  112. "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c",
  113. "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102665",
  114. "status": "ok",
  115. "seq": 0,
  116. "release": self.session_release,
  117. "environment": "prod",
  118. "retention_days": 90,
  119. "org_id": self.project.organization_id,
  120. "project_id": self.project.id,
  121. "duration": None,
  122. "errors": 0,
  123. "started": self.session_started,
  124. "received": self.received,
  125. }
  126. )
  127. for scope in "sessions", "users":
  128. data = get_project_releases_by_stability(
  129. [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h"
  130. )
  131. assert data == [
  132. (self.project.id, self.session_release),
  133. (self.project.id, self.session_crashed_release),
  134. ]
  135. def test_get_release_adoption(self):
  136. data = get_release_adoption(
  137. [
  138. (self.project.id, self.session_release),
  139. (self.project.id, self.session_crashed_release),
  140. (self.project.id, "dummy-release"),
  141. ]
  142. )
  143. assert data == {
  144. (self.project.id, self.session_release): {
  145. "sessions_24h": 2,
  146. "users_24h": 1,
  147. "adoption": 100.0,
  148. },
  149. (self.project.id, self.session_crashed_release): {
  150. "sessions_24h": 1,
  151. "users_24h": 1,
  152. "adoption": 100.0,
  153. },
  154. }
  155. def test_get_release_adoption_lowered(self):
  156. self.store_session(
  157. {
  158. "session_id": "4574c381-acc5-4e05-b10b-f16cdc2f385a",
  159. "distinct_id": "da50f094-10b4-40fb-89fb-cb3aa9014148",
  160. "status": "crashed",
  161. "seq": 0,
  162. "release": self.session_crashed_release,
  163. "environment": "prod",
  164. "retention_days": 90,
  165. "org_id": self.project.organization_id,
  166. "project_id": self.project.id,
  167. "duration": 60.0,
  168. "errors": 0,
  169. "started": self.session_started,
  170. "received": self.received,
  171. }
  172. )
  173. data = get_release_adoption(
  174. [
  175. (self.project.id, self.session_release),
  176. (self.project.id, self.session_crashed_release),
  177. (self.project.id, "dummy-release"),
  178. ]
  179. )
  180. assert data == {
  181. (self.project.id, self.session_release): {
  182. "sessions_24h": 2,
  183. "users_24h": 1,
  184. "adoption": 50.0,
  185. },
  186. (self.project.id, self.session_crashed_release): {
  187. "sessions_24h": 2,
  188. "users_24h": 2,
  189. "adoption": 100.0,
  190. },
  191. }
  192. def test_get_release_health_data_overview_users(self):
  193. data = get_release_health_data_overview(
  194. [
  195. (self.project.id, self.session_release),
  196. (self.project.id, self.session_crashed_release),
  197. ],
  198. summary_stats_period="24h",
  199. health_stats_period="24h",
  200. stat="users",
  201. )
  202. stats = make_24h_stats(self.received - (24 * 3600))
  203. stats[-1] = [stats[-1][0], 1]
  204. stats_ok = stats_crash = stats
  205. assert data == {
  206. (self.project.id, self.session_crashed_release): {
  207. "total_sessions": 1,
  208. "sessions_errored": 0,
  209. "total_sessions_24h": 1,
  210. "total_users": 1,
  211. "duration_p90": None,
  212. "sessions_crashed": 1,
  213. "total_users_24h": 1,
  214. "stats": {"24h": stats_crash},
  215. "crash_free_users": 0.0,
  216. "adoption": 100.0,
  217. "has_health_data": True,
  218. "crash_free_sessions": 0.0,
  219. "duration_p50": None,
  220. },
  221. (self.project.id, self.session_release): {
  222. "total_sessions": 2,
  223. "sessions_errored": 0,
  224. "total_sessions_24h": 2,
  225. "total_users": 1,
  226. "duration_p90": 57.0,
  227. "sessions_crashed": 0,
  228. "total_users_24h": 1,
  229. "stats": {"24h": stats_ok},
  230. "crash_free_users": 100.0,
  231. "adoption": 100.0,
  232. "has_health_data": True,
  233. "crash_free_sessions": 100.0,
  234. "duration_p50": 45.0,
  235. },
  236. }
  237. def test_get_release_health_data_overview_sessions(self):
  238. data = get_release_health_data_overview(
  239. [
  240. (self.project.id, self.session_release),
  241. (self.project.id, self.session_crashed_release),
  242. ],
  243. summary_stats_period="24h",
  244. health_stats_period="24h",
  245. stat="sessions",
  246. )
  247. stats = make_24h_stats(self.received - (24 * 3600))
  248. stats_ok = stats[:-1] + [[stats[-1][0], 2]]
  249. stats_crash = stats[:-1] + [[stats[-1][0], 1]]
  250. assert data == {
  251. (self.project.id, self.session_crashed_release): {
  252. "total_sessions": 1,
  253. "sessions_errored": 0,
  254. "total_sessions_24h": 1,
  255. "total_users": 1,
  256. "duration_p90": None,
  257. "sessions_crashed": 1,
  258. "total_users_24h": 1,
  259. "stats": {"24h": stats_crash},
  260. "crash_free_users": 0.0,
  261. "adoption": 100.0,
  262. "has_health_data": True,
  263. "crash_free_sessions": 0.0,
  264. "duration_p50": None,
  265. },
  266. (self.project.id, self.session_release): {
  267. "total_sessions": 2,
  268. "sessions_errored": 0,
  269. "total_sessions_24h": 2,
  270. "total_users": 1,
  271. "duration_p90": 57.0,
  272. "sessions_crashed": 0,
  273. "total_users_24h": 1,
  274. "stats": {"24h": stats_ok},
  275. "crash_free_users": 100.0,
  276. "adoption": 100.0,
  277. "has_health_data": True,
  278. "crash_free_sessions": 100.0,
  279. "duration_p50": 45.0,
  280. },
  281. }