test_rolling_number.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. import pytest
  2. from .utils import MockedTime
  3. from hystrix.rolling_number import RollingNumber, RollingNumberEvent
  4. def test_create_buckets():
  5. _time = MockedTime()
  6. counter = RollingNumber(200, 10, _time=_time)
  7. # confirm the initial settings
  8. assert counter.milliseconds == 200
  9. assert counter.bucket_numbers == 10
  10. assert counter.buckets_size_in_milliseconds() == 20
  11. # We start out with 0 buckets in the queue
  12. assert counter.buckets.size == 0
  13. # add a success in each interval which should result in all 10 buckets
  14. # being created with 1 success in each
  15. for r in range(counter.bucket_numbers):
  16. counter.increment(RollingNumberEvent.SUCCESS)
  17. _time.increment(counter.buckets_size_in_milliseconds())
  18. # confirm we have all 10 buckets
  19. assert counter.buckets.size == 10
  20. # add 1 more and we should still only have 10 buckets since that's the max
  21. counter.increment(RollingNumberEvent.SUCCESS)
  22. assert counter.buckets.size == 10
  23. def test_reset_buckets():
  24. _time = MockedTime()
  25. counter = RollingNumber(200, 10, _time=_time)
  26. # We start out with 0 buckets in the queue
  27. assert counter.buckets.size == 0
  28. # Add 1
  29. counter.increment(RollingNumberEvent.SUCCESS)
  30. # Confirm we have 1 bucket
  31. assert counter.buckets.size == 1
  32. # Confirm we still have 1 bucket
  33. assert counter.buckets.size == 1
  34. # Add 1
  35. counter.increment(RollingNumberEvent.SUCCESS)
  36. # We should now have a single bucket with no values in it instead of 2 or
  37. # more buckets
  38. assert counter.buckets.size == 1
  39. def test_empty_buckets_fill_in():
  40. _time = MockedTime()
  41. counter = RollingNumber(200, 10, _time=_time)
  42. # We start out with 0 buckets in the queue
  43. assert counter.buckets.size == 0
  44. # Add 1
  45. counter.increment(RollingNumberEvent.SUCCESS)
  46. # Confirm we have 1 bucket
  47. assert counter.buckets.size == 1
  48. # Wait past 3 bucket time periods (the 1st bucket then 2 empty ones)
  49. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  50. # Add another
  51. counter.increment(RollingNumberEvent.SUCCESS)
  52. # We should have 4 (1 + 2 empty + 1 new one) buckets
  53. assert counter.buckets.size == 4
  54. def test_increment_in_single_bucket():
  55. _time = MockedTime()
  56. counter = RollingNumber(200, 10, _time=_time)
  57. # We start out with 0 buckets in the queue
  58. assert counter.buckets.size == 0
  59. # Increment
  60. counter.increment(RollingNumberEvent.SUCCESS)
  61. counter.increment(RollingNumberEvent.SUCCESS)
  62. counter.increment(RollingNumberEvent.SUCCESS)
  63. counter.increment(RollingNumberEvent.SUCCESS)
  64. counter.increment(RollingNumberEvent.FAILURE)
  65. counter.increment(RollingNumberEvent.FAILURE)
  66. counter.increment(RollingNumberEvent.TIMEOUT)
  67. # Confirm we have 1 bucket
  68. assert counter.buckets.size == 1
  69. # The count should match
  70. assert counter.buckets.last().adder(RollingNumberEvent.SUCCESS).sum() == 4
  71. assert counter.buckets.last().adder(RollingNumberEvent.FAILURE).sum() == 2
  72. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 2
  73. assert counter.buckets.last().adder(RollingNumberEvent.TIMEOUT).sum() == 1
  74. def test_increment_in_multiple_buckets():
  75. _time = MockedTime()
  76. counter = RollingNumber(200, 10, _time=_time)
  77. # We start out with 0 buckets in the queue
  78. assert counter.buckets.size == 0
  79. # Increment
  80. counter.increment(RollingNumberEvent.SUCCESS)
  81. counter.increment(RollingNumberEvent.SUCCESS)
  82. counter.increment(RollingNumberEvent.SUCCESS)
  83. counter.increment(RollingNumberEvent.SUCCESS)
  84. counter.increment(RollingNumberEvent.FAILURE)
  85. counter.increment(RollingNumberEvent.FAILURE)
  86. counter.increment(RollingNumberEvent.TIMEOUT)
  87. counter.increment(RollingNumberEvent.TIMEOUT)
  88. counter.increment(RollingNumberEvent.SHORT_CIRCUITED)
  89. # Sleep to get to a new bucket
  90. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  91. # Increment
  92. counter.increment(RollingNumberEvent.SUCCESS)
  93. counter.increment(RollingNumberEvent.SUCCESS)
  94. counter.increment(RollingNumberEvent.FAILURE)
  95. counter.increment(RollingNumberEvent.FAILURE)
  96. counter.increment(RollingNumberEvent.FAILURE)
  97. counter.increment(RollingNumberEvent.TIMEOUT)
  98. counter.increment(RollingNumberEvent.SHORT_CIRCUITED)
  99. # Confirm we have 4 bucket
  100. assert counter.buckets.size == 4
  101. # The count of the last buckets
  102. assert counter.buckets.last().adder(RollingNumberEvent.SUCCESS).sum() == 2
  103. assert counter.buckets.last().adder(RollingNumberEvent.FAILURE).sum() == 3
  104. assert counter.buckets.last().adder(RollingNumberEvent.TIMEOUT).sum() == 1
  105. assert counter.buckets.last().adder(RollingNumberEvent.SHORT_CIRCUITED).sum() == 1
  106. # The total count
  107. assert counter.rolling_sum(RollingNumberEvent.SUCCESS) == 6
  108. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 5
  109. assert counter.rolling_sum(RollingNumberEvent.TIMEOUT) == 3
  110. assert counter.rolling_sum(RollingNumberEvent.SHORT_CIRCUITED) == 2
  111. # Wait until window passes
  112. _time.increment(counter.milliseconds)
  113. # Increment
  114. counter.increment(RollingNumberEvent.SUCCESS)
  115. # The total count should now include only the last bucket after a reset
  116. # since the window passed
  117. assert counter.rolling_sum(RollingNumberEvent.SUCCESS) == 1
  118. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 0
  119. assert counter.rolling_sum(RollingNumberEvent.TIMEOUT) == 0
  120. assert counter.rolling_sum(RollingNumberEvent.SHORT_CIRCUITED) == 0
  121. def test_success():
  122. counter_event(RollingNumberEvent.SUCCESS)
  123. def test_failure():
  124. counter_event(RollingNumberEvent.FAILURE)
  125. def test_timeout():
  126. counter_event(RollingNumberEvent.TIMEOUT)
  127. def test_short_circuited():
  128. counter_event(RollingNumberEvent.SHORT_CIRCUITED)
  129. def test_thread_pool_rejected():
  130. counter_event(RollingNumberEvent.THREAD_POOL_REJECTED)
  131. def test_fallback_success():
  132. counter_event(RollingNumberEvent.FALLBACK_SUCCESS)
  133. def test_fallback_failure():
  134. counter_event(RollingNumberEvent.FALLBACK_FAILURE)
  135. def test_fallback_regection():
  136. counter_event(RollingNumberEvent.FALLBACK_REJECTION)
  137. def test_exception_throw():
  138. counter_event(RollingNumberEvent.EXCEPTION_THROWN)
  139. def test_thread_execution():
  140. counter_event(RollingNumberEvent.THREAD_EXECUTION)
  141. def test_collapsed():
  142. counter_event(RollingNumberEvent.COLLAPSED)
  143. def test_response_from_cache():
  144. counter_event(RollingNumberEvent.RESPONSE_FROM_CACHE)
  145. def test_counter_retrieval_refreshes_buckets():
  146. _time = MockedTime()
  147. counter = RollingNumber(200, 10, _time=_time)
  148. # We start out with 0 buckets in the queue
  149. assert counter.buckets.size == 0
  150. # Increment
  151. counter.increment(RollingNumberEvent.SUCCESS)
  152. counter.increment(RollingNumberEvent.SUCCESS)
  153. counter.increment(RollingNumberEvent.SUCCESS)
  154. counter.increment(RollingNumberEvent.SUCCESS)
  155. counter.increment(RollingNumberEvent.FAILURE)
  156. counter.increment(RollingNumberEvent.FAILURE)
  157. # Sleep to get to a new bucketV
  158. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  159. # We should have 1 bucket since nothing has triggered the update of
  160. # buckets in the elapsed time
  161. assert counter.buckets.size == 1
  162. # The total counts
  163. assert counter.rolling_sum(RollingNumberEvent.SUCCESS) == 4
  164. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 2
  165. # We should have 4 buckets as the counter should have triggered
  166. # the buckets being created to fill in time
  167. assert counter.buckets.size == 4
  168. # Wait until window passes
  169. _time.increment(counter.milliseconds)
  170. # The total counts should all be 0 (and the buckets cleared by the get,
  171. #not only increment)
  172. assert counter.rolling_sum(RollingNumberEvent.SUCCESS) == 0
  173. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 0
  174. # Increment
  175. counter.increment(RollingNumberEvent.SUCCESS)
  176. # The total count should now include only the last bucket after a reset
  177. # since the window passed
  178. assert counter.rolling_sum(RollingNumberEvent.SUCCESS) == 1
  179. assert counter.rolling_sum(RollingNumberEvent.FAILURE) == 0
  180. def test_update_max_1():
  181. _time = MockedTime()
  182. counter = RollingNumber(200, 10, _time=_time)
  183. # We start out with 0 buckets in the queue
  184. assert counter.buckets.size == 0
  185. # Increment
  186. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 10)
  187. # We should have 1
  188. assert counter.buckets.size == 1
  189. # The count should be 10
  190. assert counter.buckets.last().max_updater(RollingNumberEvent.THREAD_MAX_ACTIVE).max() == 10
  191. assert counter.rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE) == 10
  192. # Sleep to get to a new bucket
  193. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  194. # Increment again is latest bucket
  195. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 20)
  196. # We should have 4
  197. assert counter.buckets.size == 4
  198. # The max
  199. assert counter.buckets.last().max_updater(RollingNumberEvent.THREAD_MAX_ACTIVE).max() == 20
  200. # Count per buckets
  201. values = counter.values(RollingNumberEvent.THREAD_MAX_ACTIVE)
  202. assert values[0] == 20 # Latest bucket
  203. assert values[1] == 0
  204. assert values[2] == 0
  205. assert values[3] == 10 # Oldest bucket
  206. def test_update_max_2():
  207. _time = MockedTime()
  208. counter = RollingNumber(200, 10, _time=_time)
  209. # We start out with 0 buckets in the queue
  210. assert counter.buckets.size == 0
  211. # Increment
  212. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 10)
  213. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 30)
  214. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 20)
  215. # We should have 1
  216. assert counter.buckets.size == 1
  217. # The count should be 30
  218. assert counter.buckets.last().max_updater(RollingNumberEvent.THREAD_MAX_ACTIVE).max() == 30
  219. assert counter.rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE) == 30
  220. # Sleep to get to a new bucket
  221. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  222. # Increment again is latest bucket
  223. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 30)
  224. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 30)
  225. counter.update_rolling_max(RollingNumberEvent.THREAD_MAX_ACTIVE, 50)
  226. # We should have 4
  227. assert counter.buckets.size == 4
  228. # The count
  229. assert counter.buckets.last().max_updater(RollingNumberEvent.THREAD_MAX_ACTIVE).max() == 50
  230. assert counter.value_of_latest_bucket(RollingNumberEvent.THREAD_MAX_ACTIVE) == 50
  231. # Values per buckets
  232. values = counter.values(RollingNumberEvent.THREAD_MAX_ACTIVE)
  233. assert values[0] == 50 # Latest bucket
  234. assert values[1] == 0
  235. assert values[2] == 0
  236. assert values[3] == 30 # Oldest bucket
  237. def test_max_value():
  238. _time = MockedTime()
  239. counter = RollingNumber(200, 10, _time=_time)
  240. # TODO: Change tests to use this aproache for events
  241. event = RollingNumberEvent.THREAD_MAX_ACTIVE
  242. # We start out with 0 buckets in the queue
  243. assert counter.buckets.size == 0
  244. # Increment
  245. counter.update_rolling_max(event, 10)
  246. # Sleep to get to a new bucket
  247. _time.increment(counter.buckets_size_in_milliseconds())
  248. # Increment
  249. counter.update_rolling_max(event, 30)
  250. # Sleep to get to a new bucket
  251. _time.increment(counter.buckets_size_in_milliseconds())
  252. # Increment
  253. counter.update_rolling_max(event, 40)
  254. # Sleep to get to a new bucket
  255. _time.increment(counter.buckets_size_in_milliseconds())
  256. # Try Decrement
  257. counter.update_rolling_max(event, 15)
  258. # The count should be max
  259. counter.update_rolling_max(event, 40)
  260. def test_empty_sum():
  261. _time = MockedTime()
  262. counter = RollingNumber(200, 10, _time=_time)
  263. event = RollingNumberEvent.COLLAPSED
  264. assert counter.rolling_sum(event) == 0
  265. def test_empty_max():
  266. _time = MockedTime()
  267. counter = RollingNumber(200, 10, _time=_time)
  268. event = RollingNumberEvent.THREAD_MAX_ACTIVE
  269. assert counter.rolling_max(event) == 0
  270. def test_empty_latest_value():
  271. _time = MockedTime()
  272. counter = RollingNumber(200, 10, _time=_time)
  273. event = RollingNumberEvent.THREAD_MAX_ACTIVE
  274. assert counter.value_of_latest_bucket(event) == 0
  275. def test_rolling():
  276. _time = MockedTime()
  277. counter = RollingNumber(20, 2, _time=_time)
  278. event = RollingNumberEvent.THREAD_MAX_ACTIVE
  279. assert counter.cumulative_sum(event) == 0
  280. # Iterate over 20 buckets on a queue sized for 2
  281. for i in range(20):
  282. counter.current_bucket()
  283. _time.increment(counter.buckets_size_in_milliseconds())
  284. assert len(counter.values(event)) == 2
  285. counter.value_of_latest_bucket(event)
  286. def test_cumulative_counter_after_rolling():
  287. _time = MockedTime()
  288. counter = RollingNumber(20, 2, _time=_time)
  289. event = RollingNumberEvent.SUCCESS
  290. assert counter.cumulative_sum(event) == 0
  291. # Iterate over 20 buckets on a queue sized for 2
  292. for i in range(20):
  293. counter.increment(event)
  294. _time.increment(counter.buckets_size_in_milliseconds())
  295. assert len(counter.values(event)) == 2
  296. counter.value_of_latest_bucket(event)
  297. # Cumulative count should be 20 (for the number of loops above) regardless
  298. # of buckets rolling
  299. assert counter.cumulative_sum(event) == 20
  300. def test_cumulative_counter_after_rolling_and_reset():
  301. _time = MockedTime()
  302. counter = RollingNumber(20, 2, _time=_time)
  303. event = RollingNumberEvent.SUCCESS
  304. assert counter.cumulative_sum(event) == 0
  305. # Iterate over 20 buckets on a queue sized for 2
  306. for i in range(20):
  307. counter.increment(event)
  308. _time.increment(counter.buckets_size_in_milliseconds())
  309. assert len(counter.values(event)) == 2
  310. counter.value_of_latest_bucket(event)
  311. # simulate a reset occurring every once in a while
  312. # so we ensure the absolute sum is handling it okay
  313. if i == 5 or i == 15:
  314. counter.reset()
  315. # Cumulative count should be 20 (for the number of loops above) regardless
  316. # of buckets rolling
  317. assert counter.cumulative_sum(event) == 20
  318. def test_cumulative_counter_after_rolling_and_reset2():
  319. _time = MockedTime()
  320. counter = RollingNumber(20, 2, _time=_time)
  321. event = RollingNumberEvent.SUCCESS
  322. assert counter.cumulative_sum(event) == 0
  323. counter.increment(event)
  324. counter.increment(event)
  325. counter.increment(event)
  326. # Iterate over 20 buckets on a queue sized for 2
  327. for i in range(20):
  328. _time.increment(counter.buckets_size_in_milliseconds())
  329. # simulate a reset occurring every once in a while
  330. # so we ensure the absolute sum is handling it okay
  331. if i == 5 or i == 15:
  332. counter.reset()
  333. # No increments during the loop, just some before and after
  334. counter.increment(event)
  335. counter.increment(event)
  336. # Cumulative count should be 5 regardless of buckets rolling
  337. assert counter.cumulative_sum(event) == 5
  338. def test_cumulative_counter_after_rolling_and_reset3():
  339. _time = MockedTime()
  340. counter = RollingNumber(20, 2, _time=_time)
  341. event = RollingNumberEvent.SUCCESS
  342. assert counter.cumulative_sum(event) == 0
  343. counter.increment(event)
  344. counter.increment(event)
  345. counter.increment(event)
  346. # Iterate over 20 buckets on a queue sized for 2
  347. for i in range(20):
  348. _time.increment(counter.buckets_size_in_milliseconds())
  349. # Since we are rolling over the buckets it should reset naturally
  350. # No increments during the loop, just some before and after
  351. counter.increment(event)
  352. counter.increment(event)
  353. # Cumulative count should be 5 regardless of buckets rolling
  354. assert counter.cumulative_sum(event) == 5
  355. def test_milliseconds_buckets_size_error():
  356. _time = MockedTime()
  357. with pytest.raises(Exception):
  358. RollingNumber(100, 11, _time=_time)
  359. def test_rolling_number_event_is_counter():
  360. event = RollingNumberEvent(RollingNumberEvent.SUCCESS)
  361. assert event.is_counter() is True
  362. def test_rolling_number_event_is_max_updater():
  363. event = RollingNumberEvent(RollingNumberEvent.THREAD_MAX_ACTIVE)
  364. assert event.is_max_updater() is True
  365. def counter_event(event):
  366. _time = MockedTime()
  367. counter = RollingNumber(200, 10, _time=_time)
  368. # We start out with 0 buckets in the queue
  369. assert counter.buckets.size == 0
  370. # We start out with 0 sum
  371. assert counter.rolling_sum(event) == 0
  372. # Increment
  373. counter.increment(event)
  374. # We shoud have 1 bucket
  375. assert counter.buckets.size == 1
  376. # The count should be 1
  377. assert counter.buckets.last().adder(event).sum() == 1
  378. assert counter.rolling_sum(event) == 1
  379. # Sleep to get to a new bucket
  380. _time.increment(counter.buckets_size_in_milliseconds() * 3)
  381. # Incremenet again in latest bucket
  382. counter.increment(event)
  383. # We should have 4 buckets
  384. assert counter.buckets.size == 4
  385. # The count of the last bucket
  386. assert counter.buckets.last().adder(event).sum() == 1
  387. # The total count
  388. assert counter.rolling_sum(event) == 2