test_sessions_v2.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import math
  2. from datetime import datetime
  3. import pytest
  4. import pytz
  5. from django.http import QueryDict
  6. from freezegun import freeze_time
  7. # from sentry.testutils import TestCase
  8. from sentry.snuba.sessions_v2 import (
  9. AllowedResolution,
  10. InvalidParams,
  11. QueryDefinition,
  12. get_constrained_date_range,
  13. get_timestamps,
  14. massage_sessions_result,
  15. )
  16. def _make_query(qs, allow_minute_resolution=True):
  17. allowed_resolution = (
  18. AllowedResolution.one_minute if allow_minute_resolution else AllowedResolution.one_hour
  19. )
  20. return QueryDefinition(QueryDict(qs), {}, allowed_resolution)
  21. def result_sorted(result):
  22. """sort the groups of the results array by the `by` object, ensuring a stable order"""
  23. def stable_dict(d):
  24. return tuple(sorted(d.items(), key=lambda t: t[0]))
  25. result["groups"].sort(key=lambda group: stable_dict(group["by"]))
  26. return result
  27. @freeze_time("2018-12-11 03:21:00")
  28. def test_round_range():
  29. start, end, interval = get_constrained_date_range({"statsPeriod": "2d"})
  30. assert start == datetime(2018, 12, 9, 4, tzinfo=pytz.utc)
  31. assert end == datetime(2018, 12, 11, 3, 22, tzinfo=pytz.utc)
  32. start, end, interval = get_constrained_date_range({"statsPeriod": "2d", "interval": "1d"})
  33. assert start == datetime(2018, 12, 10, tzinfo=pytz.utc)
  34. assert end == datetime(2018, 12, 11, 3, 22, tzinfo=pytz.utc)
  35. def test_invalid_interval():
  36. with pytest.raises(InvalidParams):
  37. start, end, interval = get_constrained_date_range({"interval": "0d"})
  38. def test_round_exact():
  39. start, end, interval = get_constrained_date_range(
  40. {"start": "2021-01-12T04:06:16", "end": "2021-01-17T08:26:13", "interval": "1d"},
  41. )
  42. assert start == datetime(2021, 1, 12, tzinfo=pytz.utc)
  43. assert end == datetime(2021, 1, 18, tzinfo=pytz.utc)
  44. def test_inclusive_end():
  45. start, end, interval = get_constrained_date_range(
  46. {"start": "2021-02-24T00:00:00", "end": "2021-02-25T00:00:00", "interval": "1h"},
  47. )
  48. assert start == datetime(2021, 2, 24, tzinfo=pytz.utc)
  49. assert end == datetime(2021, 2, 25, 1, tzinfo=pytz.utc)
  50. @freeze_time("2021-03-05T11:14:17.105Z")
  51. def test_interval_restrictions():
  52. # making sure intervals are cleanly divisible
  53. with pytest.raises(InvalidParams, match="The interval has to be less than one day."):
  54. _make_query("statsPeriod=4d&interval=2d&field=sum(session)")
  55. with pytest.raises(
  56. InvalidParams, match="The interval should divide one day without a remainder."
  57. ):
  58. _make_query("statsPeriod=6h&interval=59m&field=sum(session)")
  59. with pytest.raises(
  60. InvalidParams, match="The interval should divide one day without a remainder."
  61. ):
  62. _make_query("statsPeriod=4d&interval=5h&field=sum(session)")
  63. _make_query("statsPeriod=6h&interval=90m&field=sum(session)")
  64. with pytest.raises(
  65. InvalidParams,
  66. match="The interval has to be a multiple of the minimum interval of one hour.",
  67. ):
  68. _make_query("statsPeriod=6h&interval=90m&field=sum(session)", False)
  69. with pytest.raises(
  70. InvalidParams,
  71. match="The interval has to be a multiple of the minimum interval of one minute.",
  72. ):
  73. _make_query("statsPeriod=1h&interval=90s&field=sum(session)")
  74. # restrictions for minute resolution time range
  75. with pytest.raises(
  76. InvalidParams,
  77. match="The time-range when using one-minute resolution intervals is restricted to 6 hours.",
  78. ):
  79. _make_query("statsPeriod=7h&interval=15m&field=sum(session)")
  80. with pytest.raises(
  81. InvalidParams,
  82. match="The time-range when using one-minute resolution intervals is restricted to the last 30 days.",
  83. ):
  84. _make_query(
  85. "start=2021-01-05T11:14:17&end=2021-01-05T12:14:17&interval=15m&field=sum(session)"
  86. )
  87. with pytest.raises(
  88. InvalidParams, match="Your interval and date range would create too many results."
  89. ):
  90. _make_query("statsPeriod=90d&interval=1h&field=sum(session)")
  91. @freeze_time("2020-12-18T11:14:17.105Z")
  92. def test_timestamps():
  93. query = _make_query("statsPeriod=1d&interval=12h&field=sum(session)")
  94. expected_timestamps = ["2020-12-17T12:00:00Z", "2020-12-18T00:00:00Z"]
  95. actual_timestamps = get_timestamps(query)
  96. assert actual_timestamps == expected_timestamps
  97. @freeze_time("2021-03-08T09:34:00.000Z")
  98. def test_hourly_rounded_start():
  99. query = _make_query("statsPeriod=30m&interval=1m&field=sum(session)")
  100. actual_timestamps = get_timestamps(query)
  101. assert actual_timestamps[0] == "2021-03-08T09:00:00Z"
  102. assert actual_timestamps[-1] == "2021-03-08T09:34:00Z"
  103. assert len(actual_timestamps) == 35
  104. # in this case "45m" means from 08:49:00-09:34:00, but since we round start/end
  105. # to hours, we extend the start time to 08:00:00.
  106. query = _make_query("statsPeriod=45m&interval=1m&field=sum(session)")
  107. actual_timestamps = get_timestamps(query)
  108. assert actual_timestamps[0] == "2021-03-08T08:00:00Z"
  109. assert actual_timestamps[-1] == "2021-03-08T09:34:00Z"
  110. assert len(actual_timestamps) == 95
  111. def test_rounded_end():
  112. query = _make_query(
  113. "field=sum(session)&interval=1h&start=2021-02-24T00:00:00Z&end=2021-02-25T00:00:00Z"
  114. )
  115. expected_timestamps = [
  116. "2021-02-24T00:00:00Z",
  117. "2021-02-24T01:00:00Z",
  118. "2021-02-24T02:00:00Z",
  119. "2021-02-24T03:00:00Z",
  120. "2021-02-24T04:00:00Z",
  121. "2021-02-24T05:00:00Z",
  122. "2021-02-24T06:00:00Z",
  123. "2021-02-24T07:00:00Z",
  124. "2021-02-24T08:00:00Z",
  125. "2021-02-24T09:00:00Z",
  126. "2021-02-24T10:00:00Z",
  127. "2021-02-24T11:00:00Z",
  128. "2021-02-24T12:00:00Z",
  129. "2021-02-24T13:00:00Z",
  130. "2021-02-24T14:00:00Z",
  131. "2021-02-24T15:00:00Z",
  132. "2021-02-24T16:00:00Z",
  133. "2021-02-24T17:00:00Z",
  134. "2021-02-24T18:00:00Z",
  135. "2021-02-24T19:00:00Z",
  136. "2021-02-24T20:00:00Z",
  137. "2021-02-24T21:00:00Z",
  138. "2021-02-24T22:00:00Z",
  139. "2021-02-24T23:00:00Z",
  140. "2021-02-25T00:00:00Z",
  141. ]
  142. actual_timestamps = get_timestamps(query)
  143. assert len(actual_timestamps) == 25
  144. assert actual_timestamps == expected_timestamps
  145. def test_simple_query():
  146. query = _make_query("statsPeriod=1d&interval=12h&field=sum(session)")
  147. assert query.query_columns == ["sessions"]
  148. def test_groupby_query():
  149. query = _make_query("statsPeriod=1d&interval=12h&field=sum(session)&groupBy=release")
  150. assert sorted(query.query_columns) == ["release", "sessions"]
  151. assert query.query_groupby == ["release"]
  152. def test_virtual_groupby_query():
  153. query = _make_query("statsPeriod=1d&interval=12h&field=sum(session)&groupBy=session.status")
  154. assert sorted(query.query_columns) == [
  155. "sessions",
  156. "sessions_abnormal",
  157. "sessions_crashed",
  158. "sessions_errored",
  159. ]
  160. assert query.query_groupby == []
  161. query = _make_query(
  162. "statsPeriod=1d&interval=12h&field=count_unique(user)&groupBy=session.status"
  163. )
  164. assert sorted(query.query_columns) == [
  165. "users",
  166. "users_abnormal",
  167. "users_crashed",
  168. "users_errored",
  169. ]
  170. assert query.query_groupby == []
  171. @freeze_time("2020-12-18T11:14:17.105Z")
  172. def test_massage_empty():
  173. query = _make_query("statsPeriod=1d&interval=1d&field=sum(session)")
  174. result_totals = []
  175. result_timeseries = []
  176. expected_result = {
  177. "start": "2020-12-18T00:00:00Z",
  178. "end": "2020-12-18T11:15:00Z",
  179. "query": "",
  180. "intervals": ["2020-12-18T00:00:00Z"],
  181. "groups": [],
  182. }
  183. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  184. assert actual_result == expected_result
  185. @freeze_time("2020-12-18T11:14:17.105Z")
  186. def test_massage_unbalanced_results():
  187. query = _make_query("statsPeriod=1d&interval=1d&field=sum(session)&groupBy=release")
  188. result_totals = [
  189. {"release": "test-example-release", "sessions": 1},
  190. ]
  191. result_timeseries = []
  192. expected_result = {
  193. "start": "2020-12-18T00:00:00Z",
  194. "end": "2020-12-18T11:15:00Z",
  195. "query": "",
  196. "intervals": ["2020-12-18T00:00:00Z"],
  197. "groups": [
  198. {
  199. "by": {"release": "test-example-release"},
  200. "series": {"sum(session)": [0]},
  201. "totals": {"sum(session)": 1},
  202. }
  203. ],
  204. }
  205. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  206. assert actual_result == expected_result
  207. result_totals = []
  208. result_timeseries = [
  209. {
  210. "release": "test-example-release",
  211. "sessions": 1,
  212. "bucketed_started": "2020-12-18T00:00:00+00:00",
  213. },
  214. ]
  215. expected_result = {
  216. "start": "2020-12-18T00:00:00Z",
  217. "end": "2020-12-18T11:15:00Z",
  218. "query": "",
  219. "intervals": ["2020-12-18T00:00:00Z"],
  220. "groups": [
  221. {
  222. "by": {"release": "test-example-release"},
  223. "series": {"sum(session)": [1]},
  224. "totals": {"sum(session)": 0},
  225. }
  226. ],
  227. }
  228. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  229. assert actual_result == expected_result
  230. @freeze_time("2020-12-18T11:14:17.105Z")
  231. def test_massage_simple_timeseries():
  232. """A timeseries is filled up when it only receives partial data"""
  233. query = _make_query("statsPeriod=1d&interval=6h&field=sum(session)")
  234. result_totals = [{"sessions": 4}]
  235. # snuba returns the datetimes as strings for now
  236. result_timeseries = [
  237. {"sessions": 2, "bucketed_started": "2020-12-18T06:00:00+00:00"},
  238. {"sessions": 2, "bucketed_started": "2020-12-17T12:00:00+00:00"},
  239. ]
  240. expected_result = {
  241. "start": "2020-12-17T12:00:00Z",
  242. "end": "2020-12-18T11:15:00Z",
  243. "query": "",
  244. "intervals": [
  245. "2020-12-17T12:00:00Z",
  246. "2020-12-17T18:00:00Z",
  247. "2020-12-18T00:00:00Z",
  248. "2020-12-18T06:00:00Z",
  249. ],
  250. "groups": [
  251. {"by": {}, "series": {"sum(session)": [2, 0, 0, 2]}, "totals": {"sum(session)": 4}}
  252. ],
  253. }
  254. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  255. assert actual_result == expected_result
  256. @freeze_time("2020-12-18T11:14:17.105Z")
  257. def test_massage_unordered_timeseries():
  258. query = _make_query("statsPeriod=1d&interval=6h&field=sum(session)")
  259. result_totals = [{"sessions": 10}]
  260. # snuba returns the datetimes as strings for now
  261. result_timeseries = [
  262. {"sessions": 3, "bucketed_started": "2020-12-18T00:00:00+00:00"},
  263. {"sessions": 2, "bucketed_started": "2020-12-17T18:00:00+00:00"},
  264. {"sessions": 4, "bucketed_started": "2020-12-18T06:00:00+00:00"},
  265. {"sessions": 1, "bucketed_started": "2020-12-17T12:00:00+00:00"},
  266. ]
  267. expected_result = {
  268. "start": "2020-12-17T12:00:00Z",
  269. "end": "2020-12-18T11:15:00Z",
  270. "query": "",
  271. "intervals": [
  272. "2020-12-17T12:00:00Z",
  273. "2020-12-17T18:00:00Z",
  274. "2020-12-18T00:00:00Z",
  275. "2020-12-18T06:00:00Z",
  276. ],
  277. "groups": [
  278. {"by": {}, "series": {"sum(session)": [1, 2, 3, 4]}, "totals": {"sum(session)": 10}}
  279. ],
  280. }
  281. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  282. assert actual_result == expected_result
  283. @freeze_time("2020-12-18T11:14:17.105Z")
  284. def test_massage_no_timeseries():
  285. query = _make_query("statsPeriod=1d&interval=6h&field=sum(session)&groupby=projects")
  286. result_totals = [{"sessions": 4}]
  287. # snuba returns the datetimes as strings for now
  288. result_timeseries = None
  289. expected_result = {
  290. "start": "2020-12-17T12:00:00Z",
  291. "end": "2020-12-18T11:15:00Z",
  292. "query": "",
  293. "intervals": [
  294. "2020-12-17T12:00:00Z",
  295. "2020-12-17T18:00:00Z",
  296. "2020-12-18T00:00:00Z",
  297. "2020-12-18T06:00:00Z",
  298. ],
  299. "groups": [{"by": {}, "totals": {"sum(session)": 4}}],
  300. }
  301. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  302. assert actual_result == expected_result
  303. def test_massage_exact_timeseries():
  304. query = _make_query(
  305. "start=2020-12-17T15:12:34Z&end=2020-12-18T11:14:17Z&interval=6h&field=sum(session)"
  306. )
  307. result_totals = [{"sessions": 4}]
  308. result_timeseries = [
  309. {"sessions": 2, "bucketed_started": "2020-12-18T06:00:00+00:00"},
  310. {"sessions": 2, "bucketed_started": "2020-12-17T12:00:00+00:00"},
  311. ]
  312. expected_result = {
  313. "start": "2020-12-17T12:00:00Z",
  314. "end": "2020-12-18T12:00:00Z",
  315. "query": "",
  316. "intervals": [
  317. "2020-12-17T12:00:00Z",
  318. "2020-12-17T18:00:00Z",
  319. "2020-12-18T00:00:00Z",
  320. "2020-12-18T06:00:00Z",
  321. ],
  322. "groups": [
  323. {"by": {}, "series": {"sum(session)": [2, 0, 0, 2]}, "totals": {"sum(session)": 4}}
  324. ],
  325. }
  326. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  327. assert actual_result == expected_result
  328. @freeze_time("2020-12-18T11:14:17.105Z")
  329. def test_massage_groupby_timeseries():
  330. query = _make_query("statsPeriod=1d&interval=6h&field=sum(session)&groupBy=release")
  331. result_totals = [
  332. {"release": "test-example-release", "sessions": 4},
  333. {"release": "test-example-release-2", "sessions": 1},
  334. ]
  335. # snuba returns the datetimes as strings for now
  336. result_timeseries = [
  337. {
  338. "release": "test-example-release",
  339. "sessions": 2,
  340. "bucketed_started": "2020-12-18T06:00:00+00:00",
  341. },
  342. {
  343. "release": "test-example-release-2",
  344. "sessions": 1,
  345. "bucketed_started": "2020-12-18T06:00:00+00:00",
  346. },
  347. {
  348. "release": "test-example-release",
  349. "sessions": 2,
  350. "bucketed_started": "2020-12-17T12:00:00+00:00",
  351. },
  352. ]
  353. expected_result = {
  354. "start": "2020-12-17T12:00:00Z",
  355. "end": "2020-12-18T11:15:00Z",
  356. "query": "",
  357. "intervals": [
  358. "2020-12-17T12:00:00Z",
  359. "2020-12-17T18:00:00Z",
  360. "2020-12-18T00:00:00Z",
  361. "2020-12-18T06:00:00Z",
  362. ],
  363. "groups": [
  364. {
  365. "by": {"release": "test-example-release"},
  366. "series": {"sum(session)": [2, 0, 0, 2]},
  367. "totals": {"sum(session)": 4},
  368. },
  369. {
  370. "by": {"release": "test-example-release-2"},
  371. "series": {"sum(session)": [0, 0, 0, 1]},
  372. "totals": {"sum(session)": 1},
  373. },
  374. ],
  375. }
  376. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  377. assert actual_result == expected_result
  378. @freeze_time("2020-12-18T13:25:15.769Z")
  379. def test_massage_virtual_groupby_timeseries():
  380. query = _make_query(
  381. "statsPeriod=1d&interval=6h&field=sum(session)&field=count_unique(user)&groupBy=session.status"
  382. )
  383. result_totals = [
  384. {
  385. "users": 1,
  386. "users_crashed": 1,
  387. "sessions": 31,
  388. "sessions_errored": 15,
  389. "users_errored": 1,
  390. "sessions_abnormal": 6,
  391. "sessions_crashed": 8,
  392. "users_abnormal": 0,
  393. }
  394. ]
  395. # snuba returns the datetimes as strings for now
  396. result_timeseries = [
  397. {
  398. "sessions_errored": 1,
  399. "users": 1,
  400. "users_crashed": 1,
  401. "sessions_abnormal": 0,
  402. "sessions": 3,
  403. "users_errored": 1,
  404. "users_abnormal": 0,
  405. "sessions_crashed": 1,
  406. "bucketed_started": "2020-12-18T12:00:00+00:00",
  407. },
  408. {
  409. "sessions_errored": 0,
  410. "users": 1,
  411. "users_crashed": 0,
  412. "sessions_abnormal": 0,
  413. "sessions": 3,
  414. "users_errored": 0,
  415. "users_abnormal": 0,
  416. "sessions_crashed": 0,
  417. "bucketed_started": "2020-12-18T06:00:00+00:00",
  418. },
  419. {
  420. "sessions_errored": 10,
  421. "users": 1,
  422. "users_crashed": 0,
  423. "sessions_abnormal": 2,
  424. "sessions": 15,
  425. "users_errored": 0,
  426. "users_abnormal": 0,
  427. "sessions_crashed": 4,
  428. "bucketed_started": "2020-12-18T00:00:00+00:00",
  429. },
  430. {
  431. "sessions_errored": 4,
  432. "users": 1,
  433. "users_crashed": 0,
  434. "sessions_abnormal": 4,
  435. "sessions": 10,
  436. "users_errored": 0,
  437. "users_abnormal": 0,
  438. "sessions_crashed": 3,
  439. "bucketed_started": "2020-12-17T18:00:00+00:00",
  440. },
  441. ]
  442. expected_result = {
  443. "start": "2020-12-17T18:00:00Z",
  444. "end": "2020-12-18T13:26:00Z",
  445. "query": "",
  446. "intervals": [
  447. "2020-12-17T18:00:00Z",
  448. "2020-12-18T00:00:00Z",
  449. "2020-12-18T06:00:00Z",
  450. "2020-12-18T12:00:00Z",
  451. ],
  452. "groups": [
  453. {
  454. "by": {"session.status": "abnormal"},
  455. "series": {"count_unique(user)": [0, 0, 0, 0], "sum(session)": [4, 2, 0, 0]},
  456. "totals": {"count_unique(user)": 0, "sum(session)": 6},
  457. },
  458. {
  459. "by": {"session.status": "crashed"},
  460. "series": {"count_unique(user)": [0, 0, 0, 1], "sum(session)": [3, 4, 0, 1]},
  461. "totals": {"count_unique(user)": 1, "sum(session)": 8},
  462. },
  463. {
  464. "by": {"session.status": "errored"},
  465. "series": {"count_unique(user)": [0, 0, 0, 0], "sum(session)": [0, 4, 0, 0]},
  466. "totals": {"count_unique(user)": 0, "sum(session)": 1},
  467. },
  468. {
  469. "by": {"session.status": "healthy"},
  470. "series": {"count_unique(user)": [1, 1, 1, 0], "sum(session)": [6, 5, 3, 2]},
  471. # while in one of the time slots, we have a healthy user, it is
  472. # the *same* user as the one experiencing a crash later on,
  473. # so in the *whole* time window, that one user is not counted as healthy,
  474. # so the `0` here is expected, as thats an example of the `count_unique` behavior.
  475. "totals": {"count_unique(user)": 0, "sum(session)": 16},
  476. },
  477. ],
  478. }
  479. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  480. assert actual_result == expected_result
  481. @freeze_time("2020-12-18T13:25:15.769Z")
  482. def test_clamping_in_massage_sessions_results_with_groupby_timeseries():
  483. query = _make_query(
  484. "statsPeriod=12h&interval=6h&field=sum(session)&field=count_unique(user)&groupBy=session.status"
  485. )
  486. # snuba returns the datetimes as strings for now
  487. result_timeseries = [
  488. {
  489. "sessions": 7,
  490. "sessions_errored": 3,
  491. "sessions_crashed": 2,
  492. "sessions_abnormal": 2,
  493. "users": 7,
  494. "users_errored": 3,
  495. "users_crashed": 2,
  496. "users_abnormal": 2,
  497. "bucketed_started": "2020-12-18T12:00:00+00:00",
  498. },
  499. {
  500. "sessions": 5,
  501. "sessions_errored": 10,
  502. "sessions_crashed": 0,
  503. "sessions_abnormal": 0,
  504. "users": 5,
  505. "users_errored": 10,
  506. "users_crashed": 0,
  507. "users_abnormal": 0,
  508. "bucketed_started": "2020-12-18T06:00:00+00:00",
  509. },
  510. ]
  511. expected_result = {
  512. "start": "2020-12-18T06:00:00Z",
  513. "end": "2020-12-18T13:26:00Z",
  514. "query": "",
  515. "intervals": [
  516. "2020-12-18T06:00:00Z",
  517. "2020-12-18T12:00:00Z",
  518. ],
  519. "groups": [
  520. {
  521. "by": {"session.status": "abnormal"},
  522. "series": {"count_unique(user)": [0, 2], "sum(session)": [0, 2]},
  523. "totals": {"count_unique(user)": 0, "sum(session)": 0},
  524. },
  525. {
  526. "by": {"session.status": "crashed"},
  527. "series": {"count_unique(user)": [0, 2], "sum(session)": [0, 2]},
  528. "totals": {"count_unique(user)": 0, "sum(session)": 0},
  529. },
  530. {
  531. "by": {"session.status": "errored"},
  532. "series": {"count_unique(user)": [10, 0], "sum(session)": [10, 0]},
  533. "totals": {"count_unique(user)": 0, "sum(session)": 0},
  534. },
  535. {
  536. "by": {"session.status": "healthy"},
  537. "series": {"count_unique(user)": [0, 4], "sum(session)": [0, 4]},
  538. "totals": {"count_unique(user)": 0, "sum(session)": 0},
  539. },
  540. ],
  541. }
  542. actual_result = result_sorted(massage_sessions_result(query, [], result_timeseries))
  543. assert actual_result == expected_result
  544. @freeze_time("2020-12-18T11:14:17.105Z")
  545. def test_nan_duration():
  546. query = _make_query(
  547. "statsPeriod=1d&interval=6h&field=avg(session.duration)&field=p50(session.duration)"
  548. )
  549. result_totals = [
  550. {
  551. "duration_avg": math.nan,
  552. "duration_quantiles": [math.inf, math.inf, math.inf, math.inf, math.inf, math.inf],
  553. },
  554. ]
  555. result_timeseries = [
  556. {
  557. "duration_avg": math.inf,
  558. "duration_quantiles": [math.inf, math.inf, math.inf, math.inf, math.inf, math.inf],
  559. "bucketed_started": "2020-12-18T06:00:00+00:00",
  560. },
  561. {
  562. "duration_avg": math.nan,
  563. "duration_quantiles": [math.nan, math.nan, math.nan, math.nan, math.nan, math.nan],
  564. "bucketed_started": "2020-12-17T12:00:00+00:00",
  565. },
  566. ]
  567. expected_result = {
  568. "start": "2020-12-17T12:00:00Z",
  569. "end": "2020-12-18T11:15:00Z",
  570. "query": "",
  571. "intervals": [
  572. "2020-12-17T12:00:00Z",
  573. "2020-12-17T18:00:00Z",
  574. "2020-12-18T00:00:00Z",
  575. "2020-12-18T06:00:00Z",
  576. ],
  577. "groups": [
  578. {
  579. "by": {},
  580. "series": {
  581. "avg(session.duration)": [None, None, None, None],
  582. "p50(session.duration)": [None, None, None, None],
  583. },
  584. "totals": {"avg(session.duration)": None, "p50(session.duration)": None},
  585. },
  586. ],
  587. }
  588. actual_result = result_sorted(massage_sessions_result(query, result_totals, result_timeseries))
  589. assert actual_result == expected_result