stoplight 5.8.3 → 6.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/LICENSE.md +1 -0
  4. data/README.md +132 -79
  5. data/UPGRADING.md +265 -6
  6. data/lib/stoplight/admin/actions/action.rb +4 -12
  7. data/lib/stoplight/admin/actions/lock.rb +12 -9
  8. data/lib/stoplight/admin/actions/lock_all.rb +27 -0
  9. data/lib/stoplight/admin/actions/remove.rb +12 -9
  10. data/lib/stoplight/admin/actions/stats.rb +15 -12
  11. data/lib/stoplight/admin/actions/unlock.rb +10 -9
  12. data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
  13. data/lib/stoplight/admin/assets/favicon.ico +0 -0
  14. data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
  15. data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
  16. data/lib/stoplight/admin/assets/icon.svg +7 -0
  17. data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
  18. data/lib/stoplight/admin/config_registry.rb +40 -0
  19. data/lib/stoplight/admin/dependencies.rb +29 -28
  20. data/lib/stoplight/admin/helpers.rb +50 -17
  21. data/lib/stoplight/admin/light_view.rb +184 -0
  22. data/lib/stoplight/admin/lights_stats.rb +7 -9
  23. data/lib/stoplight/admin/views/_card.erb +19 -21
  24. data/lib/stoplight/admin/views/index.erb +9 -3
  25. data/lib/stoplight/admin/views/layout.erb +45 -13
  26. data/lib/stoplight/admin.rb +110 -21
  27. data/lib/stoplight/data_store.rb +18 -8
  28. data/lib/stoplight/domain/config.rb +4 -1
  29. data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
  30. data/lib/stoplight/domain/failure.rb +4 -0
  31. data/lib/stoplight/domain/id.rb +13 -0
  32. data/lib/stoplight/domain/light.rb +35 -77
  33. data/lib/stoplight/domain/lock_control.rb +55 -0
  34. data/lib/stoplight/domain/matcher_validator.rb +24 -0
  35. data/lib/stoplight/domain/state_snapshot.rb +2 -6
  36. data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
  37. data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
  38. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
  39. data/lib/stoplight/domain/telemetry/bus.rb +90 -0
  40. data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
  41. data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
  42. data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
  43. data/lib/stoplight/domain/telemetry/failure.rb +13 -0
  44. data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
  45. data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
  46. data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
  47. data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
  48. data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
  49. data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
  50. data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
  51. data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
  52. data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
  53. data/lib/stoplight/domain/telemetry/settings.rb +21 -0
  54. data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
  55. data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
  56. data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
  57. data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
  58. data/lib/stoplight/domain/tracker/request.rb +17 -8
  59. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
  60. data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
  61. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
  62. data/lib/stoplight/error.rb +10 -1
  63. data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
  64. data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
  65. data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
  66. data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
  67. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
  68. data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
  69. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
  70. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
  71. data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
  72. data/lib/stoplight/infrastructure/redis/key.rb +25 -0
  73. data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
  74. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
  75. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
  76. data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
  77. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
  78. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
  79. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
  80. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
  81. data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
  82. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
  83. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
  84. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
  85. data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
  86. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
  87. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
  88. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
  89. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
  90. data/lib/stoplight/infrastructure/system_clock.rb +4 -0
  91. data/lib/stoplight/telemetry.rb +19 -0
  92. data/lib/stoplight/types.rb +15 -2
  93. data/lib/stoplight/version.rb +1 -1
  94. data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
  95. data/lib/stoplight/wiring/default.rb +1 -1
  96. data/lib/stoplight/wiring/default_config.rb +2 -1
  97. data/lib/stoplight/wiring/default_configuration.rb +3 -2
  98. data/lib/stoplight/wiring/external_caller.rb +27 -0
  99. data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
  100. data/lib/stoplight/wiring/global_state.rb +93 -0
  101. data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
  102. data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
  103. data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
  104. data/lib/stoplight/wiring/light_factory.rb +154 -97
  105. data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
  106. data/lib/stoplight/wiring/redis/backend.rb +1 -1
  107. data/lib/stoplight/wiring/system/storage.rb +69 -0
  108. data/lib/stoplight/wiring/system.rb +125 -56
  109. data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
  110. data/lib/stoplight.rb +50 -194
  111. data/sig/sinatra/base.rbs +6 -0
  112. data/sig/stdlib/securerandom.rbs +4 -0
  113. data/sig/stoplight/admin/actions/action.rbs +10 -0
  114. data/sig/stoplight/admin/actions/lock.rbs +17 -0
  115. data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
  116. data/sig/stoplight/admin/actions/remove.rbs +22 -0
  117. data/sig/stoplight/admin/actions/stats.rbs +28 -0
  118. data/sig/stoplight/admin/actions/unlock.rbs +17 -0
  119. data/sig/stoplight/admin/config_registry.rbs +20 -0
  120. data/sig/stoplight/admin/dependencies.rbs +16 -0
  121. data/sig/stoplight/admin/helpers.rbs +17 -0
  122. data/sig/stoplight/admin/light_view.rbs +39 -0
  123. data/sig/stoplight/admin/lights_stats.rbs +30 -0
  124. data/sig/stoplight/admin.rbs +14 -0
  125. data/sig/stoplight/color.rbs +3 -3
  126. data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
  127. data/sig/stoplight/data_store/memory.rbs +9 -0
  128. data/sig/stoplight/data_store/redis.rbs +17 -0
  129. data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
  130. data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
  131. data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
  132. data/sig/stoplight/domain/id.rbs +7 -0
  133. data/sig/stoplight/domain/light.rbs +39 -0
  134. data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
  135. data/sig/stoplight/domain/lock_control.rbs +16 -0
  136. data/sig/stoplight/domain/matcher_validator.rbs +11 -0
  137. data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
  138. data/sig/stoplight/domain/ports/data_store.rbs +9 -0
  139. data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
  140. data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
  141. data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
  142. data/sig/stoplight/domain/ports/registry.rbs +13 -0
  143. data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
  144. data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
  145. data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
  146. data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
  147. data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
  148. data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
  149. data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
  150. data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
  151. data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
  152. data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
  153. data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
  154. data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
  155. data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
  156. data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
  157. data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
  158. data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
  159. data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
  160. data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
  161. data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
  162. data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
  163. data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
  164. data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
  165. data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
  166. data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
  167. data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
  168. data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
  169. data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
  170. data/sig/stoplight/domain/telemetry/types.rbs +32 -0
  171. data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
  172. data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
  173. data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
  174. data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
  175. data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
  176. data/sig/stoplight/domain/types.rbs +25 -0
  177. data/sig/stoplight/error.rbs +8 -0
  178. data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
  179. data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
  180. data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
  181. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
  182. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
  183. data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
  184. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
  185. data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
  186. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
  187. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
  188. data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
  189. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
  190. data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
  191. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
  192. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
  193. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
  194. data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
  195. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
  196. data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
  197. data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
  198. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
  199. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
  200. data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
  201. data/sig/stoplight/infrastructure/types.rbs +6 -0
  202. data/sig/stoplight/notifier.rbs +1 -1
  203. data/sig/stoplight/ports/system.rbs +16 -10
  204. data/sig/stoplight/state.rbs +3 -3
  205. data/sig/stoplight/telemetry.rbs +17 -0
  206. data/sig/stoplight/types.rbs +9 -0
  207. data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
  208. data/sig/stoplight/wiring/default.rbs +26 -0
  209. data/sig/stoplight/wiring/default_configuration.rbs +39 -0
  210. data/sig/stoplight/wiring/external_caller.rbs +13 -0
  211. data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
  212. data/sig/stoplight/wiring/global_state.rbs +36 -0
  213. data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
  214. data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
  215. data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
  216. data/sig/stoplight/wiring/light_factory.rbs +78 -0
  217. data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
  218. data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
  219. data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
  220. data/sig/stoplight/wiring/system/storage.rbs +35 -0
  221. data/sig/stoplight/wiring/system.rbs +30 -0
  222. data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
  223. data/sig/stoplight/wiring/types.rbs +6 -0
  224. data/sig/stoplight.rbs +37 -54
  225. metadata +190 -143
  226. data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
  227. data/lib/stoplight/admin/actions/lock_green.rb +0 -23
  228. data/lib/stoplight/admin/actions/lock_red.rb +0 -23
  229. data/lib/stoplight/admin/lights_repository/light.rb +0 -171
  230. data/lib/stoplight/admin/lights_repository.rb +0 -87
  231. data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
  232. data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
  233. data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
  234. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
  235. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
  236. data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
  237. data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
  238. data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
  239. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
  240. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
  241. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
  242. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
  243. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
  244. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
  245. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
  246. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
  247. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
  248. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
  249. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
  250. data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
  251. data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
  252. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
  253. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
  254. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
  255. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
  256. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
  257. data/lib/stoplight/wiring/light_builder.rb +0 -198
  258. data/lib/stoplight/wiring/notifier_factory.rb +0 -26
  259. data/lib/stoplight/wiring/system/light_builder.rb +0 -47
  260. data/lib/stoplight/wiring/system/light_factory.rb +0 -64
  261. data/sig/_private/stoplight/domain/light.rbs +0 -25
  262. data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
  263. data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
  264. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
  265. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
  266. data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
  267. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
  268. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
  269. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
  270. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
  271. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
  272. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
  273. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
  274. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
  275. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
  276. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
  277. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
  278. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
  279. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
  280. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
  281. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
  282. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
  283. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
  284. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
  285. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
  286. data/sig/_private/stoplight/system/light_builder.rbs +0 -23
  287. data/sig/_private/stoplight/system/light_factory.rbs +0 -17
  288. data/sig/_private/stoplight/types.rbs +0 -6
  289. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
  290. data/sig/_private/stoplight/wiring/default.rbs +0 -26
  291. data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
  292. data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
  293. data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
  294. data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
  295. data/sig/_private/stoplight/wiring/system.rbs +0 -15
  296. data/sig/_private/stoplight.rbs +0 -48
  297. data/sig/stoplight/data_store.rbs +0 -19
  298. data/sig/stoplight/ports/configuration.rbs +0 -19
  299. data/sig/stoplight/ports/exception_matcher.rbs +0 -8
  300. data/sig/stoplight/ports/light.rbs +0 -12
  301. data/sig/stoplight/ports/light_info.rbs +0 -5
  302. data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
  303. /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
  304. /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
  305. /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
  306. /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
  307. /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
  308. /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
  309. /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
  310. /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
  311. /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
  312. /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
  313. /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
  314. /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
  315. /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
  316. /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
  317. /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
  318. /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
  319. /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
  320. /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
  321. /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
  322. /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
  323. /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Infrastructure
5
+ module Redis
6
+ module Storage
7
+ class Registry
8
+ def initialize(redis:, key_space:, clock:, config_serializer:)
9
+ @redis = redis
10
+ @key_space = key_space
11
+ @clock = clock
12
+ @config_serializer = config_serializer
13
+ end
14
+
15
+ def ids
16
+ @redis.with do |conn|
17
+ conn.hkeys(key)
18
+ end
19
+ end
20
+
21
+ def register(config)
22
+ @redis.with do |conn|
23
+ conn.hset(
24
+ key,
25
+ config.id,
26
+ {
27
+ meta: {
28
+ version: 1,
29
+ registered_at: @clock.current_time.to_i
30
+ },
31
+ config: @config_serializer.call(config)
32
+ }.to_json
33
+ )
34
+ end
35
+ end
36
+
37
+ def unregister(id)
38
+ @redis.with do |conn|
39
+ conn.hdel(key, id)
40
+ end
41
+ end
42
+
43
+ def all_configs
44
+ raw_lights_info = @redis.with do |conn|
45
+ conn.hgetall(key)
46
+ end
47
+ return [] unless raw_lights_info
48
+ raw_lights_info.filter_map do |_, config|
49
+ next unless config
50
+
51
+ begin
52
+ JSON.parse(config)["config"]
53
+ rescue JSON::ParserError
54
+ nil
55
+ end
56
+ end
57
+ end
58
+
59
+ def config_for(id)
60
+ raw_light_info = @redis.with do |conn|
61
+ conn.hget(key, id)
62
+ end
63
+ return unless raw_light_info
64
+ light_info = JSON.parse(raw_light_info)
65
+ light_info["config"]
66
+ end
67
+
68
+ private def key = @key_space.join("lights")
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -4,12 +4,63 @@ module Stoplight
4
4
  module Infrastructure
5
5
  module Redis
6
6
  module Storage
7
- class Scripting < Infrastructure::Redis::DataStore::Scripting
8
- SCRIPTS_ROOT = __dir__ => String
7
+ class Scripting
8
+ SCRIPTS_ROOT = T.must(__dir__)
9
9
  private_constant :SCRIPTS_ROOT
10
10
 
11
+ INCLUDE_DIRECTIVE = /^--\s*@include\s+([\w\-\/]+)$/
12
+ private_constant :INCLUDE_DIRECTIVE
13
+
11
14
  class << self
12
- def default_scripts_root = SCRIPTS_ROOT
15
+ def default_scripts_path = [SCRIPTS_ROOT]
16
+ end
17
+
18
+ def initialize(redis:, scripts_path: self.class.default_scripts_path)
19
+ @scripts_path = scripts_path
20
+ @redis = redis
21
+ @shas = Hash.new do |hash, script_name|
22
+ hash[script_name] = Digest::SHA1.hexdigest(resolve_source(script_name))
23
+ end
24
+ end
25
+
26
+ def call(script_name, keys: [], args: [])
27
+ retried = false
28
+
29
+ begin
30
+ @redis.then do |client|
31
+ client.evalsha(@shas[script_name], keys: keys.map(&:to_s), argv: args.map(&:to_s))
32
+ end
33
+ rescue ::Redis::CommandError => error
34
+ if error.message.include?("NOSCRIPT") && !retried
35
+ retried = true
36
+ reload_script(script_name)
37
+ retry
38
+ else
39
+ raise error
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def reload_script(script_name)
47
+ warn "Stoplight is unable to find the script '#{script_name}' in Redis's script cache. Reloading and retrying..."
48
+ @redis.then { |client| client.script(:load, resolve_source(script_name)) }
49
+ end
50
+
51
+ # Redis's Lua sandbox has no `require`, so an included script is spliced in as source text.
52
+ def resolve_source(script_name)
53
+ file_name = "#{script_name}.lua"
54
+
55
+ @scripts_path.each do |path|
56
+ script_path = File.join(path, file_name)
57
+ if File.exist?(script_path)
58
+ source = File.read(script_path)
59
+ return source.gsub(INCLUDE_DIRECTIVE) { resolve_source(T.must(Regexp.last_match(1))) }
60
+ end
61
+ end
62
+
63
+ raise Errno::ENOENT, "No such file #{file_name} @ #{@scripts_path}"
13
64
  end
14
65
  end
15
66
  end
@@ -1,5 +1,7 @@
1
+ -- @include now
2
+
1
3
  local meta_key = KEYS[1]
2
- local current_ts = tonumber(ARGV[1])
4
+ local current_ts = now() / 1000
3
5
 
4
6
  -- 1 if the field is a new field in the hash and the value was set
5
7
  local became_green = redis.call('HSETNX', meta_key, 'recovered_at', current_ts)
@@ -1,6 +1,8 @@
1
+ -- @include now
2
+
1
3
  local meta_key = KEYS[1]
2
- local current_ts = tonumber(ARGV[1])
3
- local recovery_scheduled_after_ts = tonumber(ARGV[2])
4
+ local recovery_scheduled_after_ts = tonumber(ARGV[1])
5
+ local current_ts = now() / 1000
4
6
 
5
7
  -- 1 if the field is a new field in the hash and the value was set
6
8
  local became_red = redis.call('HSETNX', meta_key, 'breached_at', current_ts)
@@ -1,5 +1,7 @@
1
+ -- @include now
2
+
1
3
  local meta_key = KEYS[1]
2
- local current_ts = tonumber(ARGV[1])
4
+ local current_ts = now() / 1000
3
5
 
4
6
  -- HSETNX returns 1 if field is new and was set, 0 if field already exists
5
7
  local became_yellow = redis.call('HSETNX', meta_key, 'recovery_started_at', current_ts)
@@ -22,7 +22,7 @@ module Stoplight
22
22
  # clock: SystemClock.new,
23
23
  # redis: Redis.new,
24
24
  # scripting: Scripting.new(redis:),
25
- # key_space: KeySpace.build(light_name: "payments", system_name: "main"),
25
+ # key_space:,
26
26
  # cool_off_time: 60
27
27
  # )
28
28
  #
@@ -46,7 +46,7 @@ module Stoplight
46
46
  @clock = clock
47
47
  @cool_off_time = cool_off_time
48
48
 
49
- @state_key = key_space.key(:state)
49
+ @state_key = key_space.join("state")
50
50
  end
51
51
 
52
52
  def set_state(state)
@@ -102,8 +102,8 @@ module Stoplight
102
102
  #
103
103
  def transition_to_green
104
104
  became_green = scripting.call(
105
- :"state/transition_to_green",
106
- args: [clock.current_time.to_f],
105
+ "state/transition_to_green",
106
+ args: [],
107
107
  keys: [state_key]
108
108
  )
109
109
  became_green == 1
@@ -113,8 +113,8 @@ module Stoplight
113
113
  #
114
114
  def transition_to_yellow
115
115
  became_yellow = scripting.call(
116
- :"state/transition_to_yellow",
117
- args: [clock.current_time.to_f],
116
+ "state/transition_to_yellow",
117
+ args: [],
118
118
  keys: [state_key]
119
119
  )
120
120
  became_yellow == 1
@@ -127,8 +127,8 @@ module Stoplight
127
127
  recovery_scheduled_after_ts = current_ts + cool_off_time
128
128
 
129
129
  became_red = scripting.call(
130
- :"state/transition_to_red",
131
- args: [current_ts, recovery_scheduled_after_ts],
130
+ "state/transition_to_red",
131
+ args: [recovery_scheduled_after_ts],
132
132
  keys: [state_key]
133
133
  )
134
134
 
@@ -1,28 +1,27 @@
1
- local failure_ts = tonumber(ARGV[1])
2
- local failure_json = ARGV[2]
3
- local metrics_ttl = tonumber(ARGV[3])
1
+ -- @include now
4
2
 
5
3
  local metrics_key = KEYS[1]
4
+ local error_json = ARGV[1]
5
+ local metrics_ttl = tonumber(ARGV[2])
6
6
 
7
7
  -- Update metadata
8
- local meta = redis.call('HMGET', metrics_key, 'last_error_at', 'consecutive_errors')
9
- local prev_failure_ts = tonumber(meta[1])
10
- local prev_consecutive_errors = tonumber(meta[2])
8
+ local meta = redis.call('HMGET', metrics_key, 'consecutive_errors', "last_success_at")
9
+ local prev_consecutive_errors = tonumber(meta[1])
10
+ local last_success_at = meta[2]
11
+ local failure_ts = now() / 1000.0
11
12
 
12
- if not prev_failure_ts or failure_ts > prev_failure_ts then
13
- redis.call(
14
- 'HSET', metrics_key,
15
- 'last_error_at', failure_ts,
16
- 'last_error_json', failure_json,
17
- 'consecutive_errors', (prev_consecutive_errors or 0) + 1,
18
- 'consecutive_successes', 0
19
- )
20
- else
21
- redis.call(
22
- 'HSET', metrics_key,
23
- 'consecutive_errors', (prev_consecutive_errors or 0) + 1,
24
- 'consecutive_successes', 0
25
- )
26
- end
13
+ local consecutive_errors = (prev_consecutive_errors or 0) + 1
14
+ local consecutive_successes = 0
15
+
16
+ -- Always update with current timestamp (out-of-order impossible with Redis time)
17
+ redis.call(
18
+ 'HSET', metrics_key,
19
+ 'last_error_at', failure_ts,
20
+ 'last_error_json', error_json,
21
+ 'consecutive_errors', consecutive_errors,
22
+ 'consecutive_successes', consecutive_successes
23
+ )
27
24
 
28
25
  redis.call('EXPIRE', metrics_key, metrics_ttl)
26
+
27
+ return {last_success_at, error_json, consecutive_errors, consecutive_successes}
@@ -1,26 +1,18 @@
1
- local request_ts = tonumber(ARGV[1])
2
- local metrics_ttl = tonumber(ARGV[2])
1
+ -- @include now
3
2
 
4
3
  local metrics_key = KEYS[1]
4
+ local metrics_ttl = tonumber(ARGV[1])
5
5
 
6
6
  -- Update metadata
7
- local meta = redis.call('HMGET', metrics_key, 'last_success_at', 'consecutive_successes')
8
- local prev_success_ts = tonumber(meta[1])
9
- local prev_consecutive_successes = tonumber(meta[2])
7
+ local prev_consecutive_successes = tonumber(redis.call('HGET', metrics_key, 'consecutive_successes'))
8
+ local request_ts = now() / 1000.0
10
9
 
11
- if not prev_success_ts or request_ts > prev_success_ts then
12
- redis.call(
13
- 'HSET', metrics_key,
14
- 'last_success_at', request_ts,
15
- 'consecutive_errors', 0,
16
- 'consecutive_successes', (prev_consecutive_successes or 0) + 1
17
- )
18
- else
19
- redis.call(
20
- 'HSET', metrics_key,
21
- 'consecutive_errors', 0,
22
- 'consecutive_successes', (prev_consecutive_successes or 0) + 1
23
- )
24
- end
10
+ -- Always update with current timestamp (out-of-order impossible with Redis time)
11
+ redis.call(
12
+ 'HSET', metrics_key,
13
+ 'last_success_at', request_ts,
14
+ 'consecutive_errors', 0,
15
+ 'consecutive_successes', (prev_consecutive_successes or 0) + 1
16
+ )
25
17
 
26
18
  redis.call('EXPIRE', metrics_key, metrics_ttl)
@@ -58,7 +58,7 @@ module Stoplight
58
58
  @clock = clock
59
59
  @scripting = scripting
60
60
  @redis = redis
61
- @metrics_key = key_space.key(:metrics)
61
+ @metrics_key = key_space.join("metrics")
62
62
  end
63
63
 
64
64
  # Get metrics for the current light
@@ -71,23 +71,15 @@ module Stoplight
71
71
  )
72
72
  end
73
73
 
74
- Domain::MetricsSnapshot.new(
75
- successes: nil, errors: nil,
76
- consecutive_errors: consecutive_errors.to_i,
77
- consecutive_successes: consecutive_successes.to_i,
78
- last_error: deserialize_failure(last_error_json),
79
- last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
80
- )
74
+ build_metrics_snapshot(consecutive_errors:, consecutive_successes:, last_error_json:, last_success_at:)
81
75
  end
82
76
 
83
77
  # Records successful circuit breaker execution
84
78
  #
85
79
  def record_success
86
- timestamp = clock.current_time.to_f
87
-
88
80
  scripting.call(
89
- :"unbounded_metrics/record_success",
90
- args: [timestamp, metrics_ttl],
81
+ "unbounded_metrics/record_success",
82
+ args: [metrics_ttl],
91
83
  keys: [metrics_key]
92
84
  )
93
85
  end
@@ -97,16 +89,18 @@ module Stoplight
97
89
  def record_failure(exception)
98
90
  timestamp = clock.current_time.to_f
99
91
 
100
- scripting.call(
101
- :"unbounded_metrics/record_failure",
102
- args: [timestamp, serialize_exception(exception, timestamp:), metrics_ttl],
92
+ last_success_at, last_error_json, consecutive_errors, consecutive_successes = scripting.call(
93
+ "unbounded_metrics/record_failure",
94
+ args: [serialize_exception(exception, timestamp:), metrics_ttl],
103
95
  keys: [metrics_key]
104
96
  )
97
+
98
+ build_metrics_snapshot(consecutive_errors:, consecutive_successes:, last_error_json:, last_success_at:)
105
99
  end
106
100
 
107
101
  def clear
108
102
  redis.with do |client|
109
- client.hdel(metrics_key, "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes")
103
+ client.hdel(metrics_key, "last_success_at", "last_error_at", "last_error_json", "consecutive_errors", "consecutive_successes")
110
104
  end
111
105
  end
112
106
 
@@ -116,6 +110,16 @@ module Stoplight
116
110
  attr_reader :scripting
117
111
  attr_reader :metrics_key
118
112
  attr_reader :clock
113
+
114
+ def build_metrics_snapshot(consecutive_errors:, consecutive_successes:, last_error_json:, last_success_at:)
115
+ Domain::MetricsSnapshot.new(
116
+ successes: nil, errors: nil,
117
+ consecutive_errors: consecutive_errors.to_i,
118
+ consecutive_successes: consecutive_successes.to_i,
119
+ last_error: deserialize_failure(last_error_json),
120
+ last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
121
+ )
122
+ end
119
123
  end
120
124
  end
121
125
  end
@@ -0,0 +1,44 @@
1
+ -- Subtracts each evicted bucket's counts from the running totals before deleting it, so
2
+ -- total_successes/total_failures stay exact. Window is the half-open interval
3
+ -- (bucket_ts - window_size, bucket_ts] - events at or before window_start are excluded.
4
+ --
5
+ -- O(K), K = buckets that had writes and are now stale. K <= window_size in steady state,
6
+ -- but a long idle gap can dump the whole backlog into one call on the next write.
7
+ --
8
+ -- HMGET/HDEL splat fields through unpack(), capped at 8000 args - verified across every
9
+ -- Redis/Valkey version this gem supports - so eviction runs in fixed-size batches.
10
+ -- ZRANGE/ZREMRANGEBYSCORE aren't variadic and skip that limit; keeping both in the same
11
+ -- atomic script call as the batched loop means ZREMRANGEBYSCORE always deletes exactly
12
+ -- what ZRANGE read.
13
+ local EVICT_BATCH_SIZE = 1000
14
+
15
+ local function evict_buckets(metrics_key, ts_index_key, window_start)
16
+ local buckets = redis.call('ZRANGE', ts_index_key, '-inf', window_start, 'BYSCORE')
17
+ if #buckets == 0 then
18
+ return
19
+ end
20
+
21
+ for from = 1, #buckets, EVICT_BATCH_SIZE do
22
+ local to = math.min(from + EVICT_BATCH_SIZE - 1, #buckets)
23
+
24
+ local fields = {}
25
+ for i = from, to do
26
+ fields[#fields + 1] = buckets[i] .. ':f'
27
+ fields[#fields + 1] = buckets[i] .. ':s'
28
+ end
29
+
30
+ local values = redis.call('HMGET', metrics_key, unpack(fields))
31
+
32
+ local failures, successes = 0, 0
33
+ for i = 1, #values, 2 do
34
+ failures = failures + (tonumber(values[i]) or 0)
35
+ successes = successes + (tonumber(values[i + 1]) or 0)
36
+ end
37
+
38
+ redis.call('HINCRBY', metrics_key, 'total_failures', -failures)
39
+ redis.call('HINCRBY', metrics_key, 'total_successes', -successes)
40
+ redis.call('HDEL', metrics_key, unpack(fields))
41
+ end
42
+
43
+ redis.call('ZREMRANGEBYSCORE', ts_index_key, '-inf', window_start)
44
+ end
@@ -1,26 +1,19 @@
1
- local number_of_metric_buckets = tonumber(ARGV[1])
2
- local window_start_ts = tonumber(ARGV[2])
3
- local window_end_ts = tonumber(ARGV[3])
4
- local metrics_fields = {}
5
- for idx = 4, #ARGV do
6
- table.insert(metrics_fields, ARGV[idx])
7
- end
1
+ -- @include now
2
+ -- @include window_metrics/_evict
8
3
 
9
- local metrics_key = KEYS[1]
4
+ local window_size = tonumber(ARGV[1])
10
5
 
11
- local function count_events(start_idx, bucket_count, start_ts)
12
- local total = 0
13
- for idx = start_idx, start_idx + bucket_count - 1 do
14
- total = total + tonumber(redis.call('ZCOUNT', KEYS[idx], start_ts, window_end_ts))
15
- end
16
- return total
17
- end
6
+ local metrics_key = KEYS[1]
7
+ local ts_index_key = KEYS[2]
18
8
 
19
- local offset = 2
20
- local successes = count_events(2, number_of_metric_buckets, window_start_ts)
9
+ local current_ts = now() / 1000.0
10
+ evict_buckets(metrics_key, ts_index_key, math.floor(current_ts) - window_size)
21
11
 
22
- offset = offset + number_of_metric_buckets
23
- local errors = count_events(offset, number_of_metric_buckets, window_start_ts)
12
+ -- copy arguments starting from index 2 to fields' tail
13
+ local fields = {'total_successes','total_failures', unpack(ARGV, 2) }
14
+ local metrics = redis.call('HMGET', metrics_key, unpack(fields))
24
15
 
25
- local metrics = redis.call('HMGET', metrics_key, unpack(metrics_fields))
26
- return {successes, errors, unpack(metrics)}
16
+ metrics[1] = tonumber(metrics[1]) or 0 -- total_successes
17
+ metrics[2] = tonumber(metrics[2]) or 0 -- total_failures
18
+
19
+ return metrics
@@ -1,36 +1,38 @@
1
- local failure_ts = tonumber(ARGV[1])
2
- local failure_id = ARGV[2]
3
- local failure_json = ARGV[3]
4
- local bucket_ttl = tonumber(ARGV[4])
5
- local metadata_ttl = tonumber(ARGV[5])
6
-
7
- local metrics_key = KEYS[1]
8
- local failures_key = KEYS[2]
9
-
10
- -- Record failure
11
- if failures_key ~= nil then
12
- redis.call('ZADD', failures_key, failure_ts, failure_id)
13
- redis.call('EXPIRE', failures_key, bucket_ttl) -- Not supported in Redis 6.2:, 'NX')
14
- end
15
-
16
- -- Update metadata
17
- local meta = redis.call('HMGET', metrics_key, 'last_error_at', 'consecutive_errors')
18
- local prev_failure_ts = tonumber(meta[1])
19
- local prev_consecutive_errors = tonumber(meta[2])
20
-
21
- if not prev_failure_ts or failure_ts > prev_failure_ts then
22
- redis.call(
23
- 'HSET', metrics_key,
24
- 'last_error_at', failure_ts,
25
- 'last_error_json', failure_json,
26
- 'consecutive_errors', (prev_consecutive_errors or 0) + 1,
27
- 'consecutive_successes', 0
28
- )
29
- else
30
- redis.call(
31
- 'HSET', metrics_key,
32
- 'consecutive_errors', (prev_consecutive_errors or 0) + 1,
33
- 'consecutive_successes', 0
34
- )
35
- end
36
- redis.call('EXPIRE', metrics_key, metadata_ttl) -- Not supported in Redis 6.2:, 'GT')
1
+ -- @include now
2
+ -- @include window_metrics/_evict
3
+
4
+ local failure_json = ARGV[1]
5
+ local window_size = tonumber(ARGV[2])
6
+ local metadata_ttl = tonumber(ARGV[3])
7
+
8
+ local metrics_key = KEYS[1]
9
+ local ts_index_key = KEYS[2]
10
+ local request_ts = now() / 1000.0
11
+ local bucket_ts = math.floor(request_ts)
12
+
13
+ evict_buckets(metrics_key, ts_index_key, bucket_ts - window_size)
14
+
15
+ local bucket = 'bucket:' .. bucket_ts
16
+ redis.call('HINCRBY', metrics_key, bucket .. ':f', 1)
17
+ redis.call('HINCRBY', metrics_key, 'total_failures', 1)
18
+ redis.call('ZADD', ts_index_key, bucket_ts, bucket)
19
+
20
+ local consecutive_errors = redis.call('HINCRBY', metrics_key, 'consecutive_errors', 1)
21
+
22
+ -- Always update with current timestamp (out-of-order impossible with Redis time)
23
+ redis.call(
24
+ 'HSET', metrics_key,
25
+ 'last_error_at', request_ts,
26
+ 'last_error_json', failure_json,
27
+ 'consecutive_successes', 0
28
+ )
29
+
30
+ redis.call('EXPIRE', metrics_key, metadata_ttl)
31
+ redis.call('EXPIRE', ts_index_key, metadata_ttl)
32
+
33
+ local result = redis.call('HMGET', metrics_key, 'last_success_at', 'total_successes', 'total_failures')
34
+ local last_success_at = result[1]
35
+ local total_successes = tonumber(result[2]) or 0
36
+ local total_failures = tonumber(result[3]) or 0
37
+
38
+ return {total_successes, total_failures, last_success_at, failure_json, consecutive_errors, 0}
@@ -1,35 +1,28 @@
1
- local request_ts = tonumber(ARGV[1])
2
- local request_id = ARGV[2]
3
- local bucket_ttl = tonumber(ARGV[3])
4
- local metadata_ttl = tonumber(ARGV[4])
1
+ -- @include now
2
+ -- @include window_metrics/_evict
5
3
 
6
- local metrics_key = KEYS[1]
7
- local successes_key = KEYS[2]
4
+ local window_size = tonumber(ARGV[1])
5
+ local metadata_ttl = tonumber(ARGV[2])
8
6
 
9
- -- Record success
10
- if successes_key ~= nil then
11
- redis.call('ZADD', successes_key, request_ts, request_id)
12
- redis.call('EXPIRE', successes_key, bucket_ttl) -- Not supported in Redis 6.2:, 'NX')
13
- end
7
+ local metrics_key = KEYS[1]
8
+ local ts_index_key = KEYS[2]
9
+ local request_ts = now() / 1000.0
10
+ local bucket_ts = math.floor(request_ts)
14
11
 
15
- -- Update metadata
16
- local meta = redis.call('HMGET', metrics_key, 'last_success_at', 'consecutive_successes')
17
- local prev_success_ts = tonumber(meta[1])
18
- local prev_consecutive_successes = tonumber(meta[2])
12
+ evict_buckets(metrics_key, ts_index_key, bucket_ts - window_size)
19
13
 
20
- if not prev_success_ts or request_ts > prev_success_ts then
21
- redis.call(
22
- 'HSET', metrics_key,
23
- 'last_success_at', request_ts,
24
- 'consecutive_errors', 0,
25
- 'consecutive_successes', (prev_consecutive_successes or 0) + 1
26
- )
27
- else
28
- redis.call(
29
- 'HSET', metrics_key,
30
- 'consecutive_errors', 0,
31
- 'consecutive_successes', (prev_consecutive_successes or 0) + 1
32
- )
33
- end
14
+ local bucket = 'bucket:' .. bucket_ts
15
+ redis.call('HINCRBY', metrics_key, bucket .. ':s', 1)
16
+ redis.call('HINCRBY', metrics_key, 'total_successes', 1)
17
+ redis.call('ZADD', ts_index_key, bucket_ts, bucket)
34
18
 
35
- redis.call('EXPIRE', metrics_key, metadata_ttl) -- Not supported in Redis 6.2:, 'GT')
19
+ -- Always update with current timestamp (out-of-order impossible with Redis time)
20
+ redis.call(
21
+ 'HSET', metrics_key,
22
+ 'last_success_at', request_ts,
23
+ 'consecutive_errors', 0
24
+ )
25
+
26
+ redis.call('HINCRBY', metrics_key, 'consecutive_successes', 1)
27
+ redis.call('EXPIRE', metrics_key, metadata_ttl)
28
+ redis.call('EXPIRE', ts_index_key, metadata_ttl)