redis-benchmarks-specification 0.2.42__py3-none-any.whl

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 (336) hide show
  1. redis_benchmarks_specification/__api__/Readme.md +7 -0
  2. redis_benchmarks_specification/__api__/__init__.py +5 -0
  3. redis_benchmarks_specification/__api__/api.py +87 -0
  4. redis_benchmarks_specification/__api__/app.py +191 -0
  5. redis_benchmarks_specification/__builder__/Readme.md +7 -0
  6. redis_benchmarks_specification/__builder__/__init__.py +5 -0
  7. redis_benchmarks_specification/__builder__/builder.py +1010 -0
  8. redis_benchmarks_specification/__builder__/schema.py +23 -0
  9. redis_benchmarks_specification/__cli__/__init__.py +5 -0
  10. redis_benchmarks_specification/__cli__/args.py +226 -0
  11. redis_benchmarks_specification/__cli__/cli.py +624 -0
  12. redis_benchmarks_specification/__cli__/stats.py +1304 -0
  13. redis_benchmarks_specification/__common__/__init__.py +0 -0
  14. redis_benchmarks_specification/__common__/builder_schema.py +256 -0
  15. redis_benchmarks_specification/__common__/env.py +96 -0
  16. redis_benchmarks_specification/__common__/github.py +280 -0
  17. redis_benchmarks_specification/__common__/package.py +28 -0
  18. redis_benchmarks_specification/__common__/runner.py +485 -0
  19. redis_benchmarks_specification/__common__/spec.py +143 -0
  20. redis_benchmarks_specification/__common__/suppress_warnings.py +20 -0
  21. redis_benchmarks_specification/__common__/timeseries.py +1621 -0
  22. redis_benchmarks_specification/__compare__/__init__.py +5 -0
  23. redis_benchmarks_specification/__compare__/args.py +240 -0
  24. redis_benchmarks_specification/__compare__/compare.py +3322 -0
  25. redis_benchmarks_specification/__init__.py +15 -0
  26. redis_benchmarks_specification/__runner__/__init__.py +5 -0
  27. redis_benchmarks_specification/__runner__/args.py +334 -0
  28. redis_benchmarks_specification/__runner__/remote_profiling.py +535 -0
  29. redis_benchmarks_specification/__runner__/runner.py +3837 -0
  30. redis_benchmarks_specification/__self_contained_coordinator__/__init__.py +5 -0
  31. redis_benchmarks_specification/__self_contained_coordinator__/args.py +210 -0
  32. redis_benchmarks_specification/__self_contained_coordinator__/artifacts.py +27 -0
  33. redis_benchmarks_specification/__self_contained_coordinator__/build_info.py +61 -0
  34. redis_benchmarks_specification/__self_contained_coordinator__/clients.py +58 -0
  35. redis_benchmarks_specification/__self_contained_coordinator__/cpuset.py +17 -0
  36. redis_benchmarks_specification/__self_contained_coordinator__/docker.py +108 -0
  37. redis_benchmarks_specification/__self_contained_coordinator__/post_processing.py +19 -0
  38. redis_benchmarks_specification/__self_contained_coordinator__/prepopulation.py +96 -0
  39. redis_benchmarks_specification/__self_contained_coordinator__/runners.py +740 -0
  40. redis_benchmarks_specification/__self_contained_coordinator__/self_contained_coordinator.py +2554 -0
  41. redis_benchmarks_specification/__setups__/__init__.py +0 -0
  42. redis_benchmarks_specification/__setups__/topologies.py +17 -0
  43. redis_benchmarks_specification/__spec__/__init__.py +5 -0
  44. redis_benchmarks_specification/__spec__/args.py +78 -0
  45. redis_benchmarks_specification/__spec__/cli.py +259 -0
  46. redis_benchmarks_specification/__watchdog__/__init__.py +5 -0
  47. redis_benchmarks_specification/__watchdog__/args.py +54 -0
  48. redis_benchmarks_specification/__watchdog__/watchdog.py +175 -0
  49. redis_benchmarks_specification/commands/__init__.py +0 -0
  50. redis_benchmarks_specification/commands/commands.py +15 -0
  51. redis_benchmarks_specification/setups/builders/gcc:15.2.0-amd64-debian-bookworm-default.yml +20 -0
  52. redis_benchmarks_specification/setups/builders/gcc:15.2.0-arm64-debian-bookworm-default.yml +20 -0
  53. redis_benchmarks_specification/setups/platforms/aws-ec2-1node-c5.4xlarge.yml +27 -0
  54. redis_benchmarks_specification/setups/topologies/topologies.yml +153 -0
  55. redis_benchmarks_specification/test-suites/defaults.yml +32 -0
  56. redis_benchmarks_specification/test-suites/generate.py +114 -0
  57. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpire-5-fields-10B-values.yml +43 -0
  58. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpire-50-fields-10B-values.yml +53 -0
  59. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpireat-5-fields-10B-values.yml +43 -0
  60. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpireat-50-fields-10B-values.yml +53 -0
  61. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetall-50-fields-100B-values.yml +52 -0
  62. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-5-fields-10B-values.yml +43 -0
  63. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-50-fields-10B-values.yml +53 -0
  64. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-persist-50-fields-10B-values.yml +53 -0
  65. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpire-5-fields-10B-values.yml +43 -0
  66. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpire-50-fields-10B-values.yml +53 -0
  67. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpireat-5-fields-10B-values.yml +43 -0
  68. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpireat-50-fields-10B-values.yml +53 -0
  69. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-htll-50-fields-10B-values.yml +53 -0
  70. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-1000B-values-expiration.yml +35 -0
  71. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-expiration.yml +34 -0
  72. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-long-expiration.yml +35 -0
  73. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-short-expiration.yml +35 -0
  74. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-20-fields-with-1B-values-pipeline-30.yml +43 -0
  75. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-1000B-values-expiration.yml +36 -0
  76. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-expiration.yml +35 -0
  77. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-long-expiration.yml +36 -0
  78. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-short-expiration.yml +36 -0
  79. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-1000B-values-expiration.yml +45 -0
  80. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-1000B-values.yml +44 -0
  81. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-100B-values.yml +44 -0
  82. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-expiration.yml +44 -0
  83. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-long-expiration.yml +45 -0
  84. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-short-expiration.yml +45 -0
  85. redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values.yml +43 -0
  86. redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-hash-50-fields-with-10000B-values.yml +44 -0
  87. redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-list-rpush-bulkload-pipeline-50.yml +39 -0
  88. redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-list-with-10B-values-pipeline-50.yml +33 -0
  89. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values-pipeline-10.yml +33 -0
  90. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values.yml +33 -0
  91. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values-pipeline-10.yml +34 -0
  92. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values.yml +33 -0
  93. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +38 -0
  94. redis_benchmarks_specification/test-suites/memtier_benchmark-1Kkeys-hash-listpack-500-fields-update-20-fields-with-1B-to-64B-values.yml +75 -0
  95. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-100B-expire-use-case.yml +50 -0
  96. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-expire-use-case.yml +50 -0
  97. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-psetex-expire-use-case.yml +43 -0
  98. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-setex-expire-use-case.yml +43 -0
  99. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-1KiB-expire-use-case.yml +49 -0
  100. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-4KiB-expire-use-case.yml +50 -0
  101. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-bitmap-getbit-pipeline-10.yml +42 -0
  102. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-exists-pipeline-10.yml +41 -0
  103. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-expire-pipeline-10.yml +41 -0
  104. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-expireat-pipeline-10.yml +41 -0
  105. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-pexpire-pipeline-10.yml +41 -0
  106. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-count-500-pipeline-10.yml +41 -0
  107. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-count-500-pipeline-10.yml +42 -0
  108. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-count-5000-pipeline-10.yml +42 -0
  109. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-pipeline-10.yml +42 -0
  110. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-pipeline-10.yml +41 -0
  111. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-type-pipeline-10.yml +41 -0
  112. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-touch-pipeline-10.yml +41 -0
  113. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-ttl-pipeline-10.yml +41 -0
  114. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hexists.yml +45 -0
  115. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hget-hgetall-hkeys-hvals-with-100B-values.yml +48 -0
  116. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hgetall-50-fields-10B-values.yml +53 -0
  117. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hincrby.yml +42 -0
  118. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hincrbyfloat.yml +42 -0
  119. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-10-fields-with-10B-values-with-expiration-pipeline-10.yml +45 -0
  120. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-5-fields-with-100B-values-with-expiration-pipeline-10.yml +44 -0
  121. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-5-fields-with-10B-values-with-expiration-pipeline-10.yml +44 -0
  122. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-50-fields-with-10B-values-with-expiration-pipeline-10.yml +54 -0
  123. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hmget-5-fields-with-100B-values-pipeline-10.yml +44 -0
  124. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-transactions-multi-exec-pipeline-20.yml +43 -0
  125. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-100B-values.yml +44 -0
  126. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-10B-values.yml +44 -0
  127. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-1KiB-values.yml +44 -0
  128. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-rpoplpush-with-10B-values.yml +42 -0
  129. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10.yml +34 -0
  130. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values.yml +33 -0
  131. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-50-fields-with-10B-values-long-expiration-pipeline-10.yml +46 -0
  132. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-hmset-5-fields-with-1000B-values.yml +33 -0
  133. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-rpush-with-10B-values.yml +32 -0
  134. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-100B-values.yml +32 -0
  135. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-10B-values-pipeline-10.yml +33 -0
  136. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-10B-values.yml +32 -0
  137. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-1KiB-values.yml +32 -0
  138. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-19-digits-pipeline-10.yml +58 -0
  139. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-19-digits.yml +58 -0
  140. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-pipeline-10.yml +41 -0
  141. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements.yml +40 -0
  142. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values-pipeline-10.yml +33 -0
  143. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values.yml +33 -0
  144. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values-pipeline-10.yml +34 -0
  145. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values.yml +33 -0
  146. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-100B-values-pipeline-10.yml +32 -0
  147. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-100B-values.yml +35 -0
  148. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-10.yml +33 -0
  149. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-100-nokeyprefix.yml +29 -0
  150. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-100.yml +33 -0
  151. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-50.yml +33 -0
  152. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-500.yml +33 -0
  153. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values.yml +32 -0
  154. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-1KiB-values-pipeline-10.yml +32 -0
  155. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-1KiB-values.yml +32 -0
  156. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-20KiB-values.yml +35 -0
  157. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-listpack-with-100-elements-double-score.yml +91 -0
  158. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-with-10-elements-double-score.yml +35 -0
  159. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-with-10-elements-int-score.yml +34 -0
  160. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-append-1-100B-pipeline-10.yml +43 -0
  161. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-append-1-100B.yml +42 -0
  162. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-decr.yml +41 -0
  163. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-100B-pipeline-10.yml +41 -0
  164. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-100B.yml +41 -0
  165. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-10.yml +41 -0
  166. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +38 -0
  167. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-100.yml +41 -0
  168. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-50.yml +41 -0
  169. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-500.yml +41 -0
  170. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B.yml +41 -0
  171. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-1KiB-pipeline-10.yml +41 -0
  172. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-1KiB.yml +41 -0
  173. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-32B-pipeline-10.yml +40 -0
  174. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-32B.yml +40 -0
  175. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incr-pipeline-10.yml +30 -0
  176. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrby-pipeline-10.yml +30 -0
  177. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrby.yml +30 -0
  178. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrbyfloat-pipeline-10.yml +30 -0
  179. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrbyfloat.yml +30 -0
  180. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-int-encoding-strlen-pipeline-10.yml +40 -0
  181. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mget-1KiB.yml +41 -0
  182. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-expire-pipeline-10.yml +45 -0
  183. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-expire.yml +45 -0
  184. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-pipeline-10.yml +43 -0
  185. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B.yml +42 -0
  186. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-1KB-pipeline-10.yml +42 -0
  187. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-1KB.yml +41 -0
  188. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-32B-pipeline-10.yml +43 -0
  189. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-32B.yml +42 -0
  190. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-512B-pipeline-10.yml +43 -0
  191. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-512B.yml +42 -0
  192. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-with-expiration-240B-400_conns.yml +47 -0
  193. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-set-with-ex-100B-pipeline-10.yml +41 -0
  194. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setex-100B-pipeline-10.yml +41 -0
  195. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-1.yml +43 -0
  196. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-10.yml +43 -0
  197. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-4KiB-pipeline-1.yml +43 -0
  198. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-4KiB-pipeline-10.yml +43 -0
  199. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-512B-pipeline-1.yml +43 -0
  200. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-512B-pipeline-10.yml +43 -0
  201. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setrange-100B-pipeline-10.yml +42 -0
  202. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setrange-100B.yml +42 -0
  203. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-100M-bits-bitmap-bitcount.yml +45 -0
  204. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-1Billion-bits-bitmap-bitcount.yml +45 -0
  205. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-2-elements-geopos.yml +38 -0
  206. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-2-elements-geosearch-fromlonlat-withcoord.yml +39 -0
  207. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geodist-pipeline-10.yml +36 -0
  208. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geodist.yml +36 -0
  209. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geohash-pipeline-10.yml +35 -0
  210. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geohash.yml +34 -0
  211. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geopos-pipeline-10.yml +35 -0
  212. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geopos.yml +34 -0
  213. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-bybox.yml +36 -0
  214. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-pipeline-10.yml +36 -0
  215. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat.yml +36 -0
  216. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-1K-fields-hgetall-pipeline-10.yml +285 -0
  217. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-1K-fields-hgetall.yml +284 -0
  218. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-100B-values-cursor-count-1000.yml +291 -0
  219. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-10B-values-cursor-count-100.yml +291 -0
  220. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-10B-values.yml +290 -0
  221. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-50-fields-10B-values.yml +54 -0
  222. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10-elements-lrange-all-elements-pipeline-10.yml +37 -0
  223. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10-elements-lrange-all-elements.yml +36 -0
  224. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-int-7bit-uint-lrange-all-elements-pipeline-10.yml +44 -0
  225. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-int-lrange-all-elements-pipeline-10.yml +52 -0
  226. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-llen-pipeline-10.yml +52 -0
  227. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-lrange-all-elements-pipeline-10.yml +52 -0
  228. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-lrange-all-elements.yml +51 -0
  229. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-integer.yml +41 -0
  230. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-string-pipeline-10.yml +42 -0
  231. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-string.yml +41 -0
  232. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-linsert-lrem-integer.yml +45 -0
  233. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-linsert-lrem-string.yml +45 -0
  234. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lpos-integer.yml +41 -0
  235. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lpos-string.yml +41 -0
  236. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements-pipeline-10.yml +202 -0
  237. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements.yml +201 -0
  238. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-2K-elements-quicklist-lrange-all-elements-longs.yml +258 -0
  239. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-hash-1K-fields-with-5B-values.yml +282 -0
  240. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-zset-with-5-elements-parsing-float-score.yml +36 -0
  241. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-zset-with-5-elements-parsing-hexa-score.yml +36 -0
  242. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-pfadd-4KB-values-pipeline-10.yml +32 -0
  243. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smembers-pipeline-10.yml +37 -0
  244. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smembers.yml +36 -0
  245. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smismember.yml +38 -0
  246. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sismember-is-a-member.yml +53 -0
  247. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sismember-not-a-member.yml +53 -0
  248. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-smembers.yml +50 -0
  249. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-smismember.yml +54 -0
  250. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sscan.yml +50 -0
  251. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10M-elements-sismember-50pct-chance.yml +41 -0
  252. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10M-elements-srem-50pct-chance.yml +40 -0
  253. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-smembers.yml +200 -0
  254. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan-cursor-count-100.yml +201 -0
  255. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan.yml +200 -0
  256. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1M-elements-sismember-50pct-chance.yml +40 -0
  257. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-200K-elements-sadd-constant.yml +41 -0
  258. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-2M-elements-sadd-increasing.yml +32 -0
  259. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zincrby-1M-elements-pipeline-1.yml +40 -0
  260. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-100K-elements-pipeline-1.yml +40 -0
  261. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-10M-elements-pipeline-1.yml +41 -0
  262. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-1M-elements-pipeline-1.yml +40 -0
  263. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrem-5M-elements-pipeline-1.yml +47 -0
  264. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-1.yml +41 -0
  265. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-10.yml +41 -0
  266. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrank-1M-elements-pipeline-1.yml +40 -0
  267. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-10-elements-zrange-all-elements-long-scores.yml +41 -0
  268. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-10-elements-zrange-all-elements.yml +40 -0
  269. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrange-all-elements.yml +66 -0
  270. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements-long-scores.yml +66 -0
  271. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements.yml +66 -0
  272. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zscan.yml +65 -0
  273. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1K-elements-zrange-all-elements.yml +322 -0
  274. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1K-elements-zscan.yml +321 -0
  275. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zcard-pipeline-10.yml +39 -0
  276. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zremrangebyscore-pipeline-10.yml +41 -0
  277. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zrevrange-5-elements.yml +40 -0
  278. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zrevrange-withscores-5-elements-pipeline-10.yml +41 -0
  279. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zscore-pipeline-10.yml +40 -0
  280. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-600K-elements-zrangestore-1K-elements.yml +41 -0
  281. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-600K-elements-zrangestore-300K-elements.yml +43 -0
  282. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-listpack-zrank-100-elements-pipeline-1.yml +50 -0
  283. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-lua-eval-hset-expire.yml +37 -0
  284. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-lua-evalsha-hset-expire.yml +41 -0
  285. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sdiff.yml +57 -0
  286. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sinter.yml +57 -0
  287. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sunion.yml +57 -0
  288. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-stream-5-entries-xread-all-entries-pipeline-10.yml +46 -0
  289. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-stream-5-entries-xread-all-entries.yml +46 -0
  290. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-zset-300-elements-skiplist-encoded-zunion.yml +434 -0
  291. redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-zset-300-elements-skiplist-encoded-zunionstore.yml +434 -0
  292. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-load-string-with-512B-values-pipeline-10.yml +37 -0
  293. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-load-string-with-512B-values.yml +37 -0
  294. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-400_conns.yml +45 -0
  295. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-40_conns.yml +45 -0
  296. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-2000_conns.yml +46 -0
  297. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-400_conns.yml +46 -0
  298. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-40_conns.yml +46 -0
  299. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-400_conns.yml +45 -0
  300. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-2000_conns.yml +46 -0
  301. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-400_conns.yml +46 -0
  302. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-5200_conns.yml +46 -0
  303. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-50-50-with-512B-values-with-expiration-pipeline-10-400_conns.yml +43 -0
  304. redis_benchmarks_specification/test-suites/memtier_benchmark-connection-hello-pipeline-10.yml +32 -0
  305. redis_benchmarks_specification/test-suites/memtier_benchmark-connection-hello.yml +32 -0
  306. redis_benchmarks_specification/test-suites/memtier_benchmark-multiple-hll-pfcount-100B-values.yml +34 -0
  307. redis_benchmarks_specification/test-suites/memtier_benchmark-multiple-hll-pfmerge-100B-values.yml +34 -0
  308. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-connection-ping-pipeline-10.yml +29 -0
  309. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-100-subscribers.yml +40 -0
  310. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-1000-subscribers.yml +40 -0
  311. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-5000-subscribers.yml +40 -0
  312. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-50K-subscribers-5k-conns.yml +40 -0
  313. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-publish-1K-channels-10B-no-subscribers.yml +30 -0
  314. redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-server-time-pipeline-10.yml +29 -0
  315. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-10.yml +68 -0
  316. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-100.yml +69 -0
  317. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-1000.yml +68 -0
  318. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-rate-limiting-lua-100k-sessions.yml +64 -0
  319. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-realtime-analytics-membership-pipeline-10.yml +56 -0
  320. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-realtime-analytics-membership.yml +56 -0
  321. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-hash-100k-sessions.yml +108 -0
  322. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-json-100k-sessions.yml +109 -0
  323. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-string-100k-sessions.yml +98 -0
  324. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-storage-100k-sessions.yml +205 -0
  325. redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-storage-1k-sessions.yml +205 -0
  326. redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xread-count-100.yml +36 -0
  327. redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xreadgroup-count-100-noack.yml +38 -0
  328. redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xreadgroup-count-100.yml +38 -0
  329. redis_benchmarks_specification/test-suites/memtier_benchmark-stream-concurrent-xadd-xreadgroup-70-30.yml +50 -0
  330. redis_benchmarks_specification/test-suites/template.txt +18 -0
  331. redis_benchmarks_specification/vector-search-test-suites/vector_db_benchmark_test.yml +41 -0
  332. redis_benchmarks_specification-0.2.42.dist-info/LICENSE +201 -0
  333. redis_benchmarks_specification-0.2.42.dist-info/METADATA +434 -0
  334. redis_benchmarks_specification-0.2.42.dist-info/RECORD +336 -0
  335. redis_benchmarks_specification-0.2.42.dist-info/WHEEL +4 -0
  336. redis_benchmarks_specification-0.2.42.dist-info/entry_points.txt +10 -0
@@ -0,0 +1,3322 @@
1
+ # BSD 3-Clause License
2
+ #
3
+ # Copyright (c) 2021., Redis Labs Modules
4
+ # All rights reserved.
5
+ #
6
+ import datetime
7
+ import logging
8
+ import re
9
+ import pandas as pd
10
+ import redis
11
+ import yaml
12
+ from pytablewriter import MarkdownTableWriter
13
+ import humanize
14
+ import datetime as dt
15
+ import os
16
+ from tqdm import tqdm
17
+ import argparse
18
+ import numpy as np
19
+ from concurrent.futures import ThreadPoolExecutor
20
+
21
+ from io import StringIO
22
+ import sys
23
+
24
+ # Import command categorization function
25
+ try:
26
+ from utils.summary import categorize_command
27
+ except ImportError:
28
+ # Fallback if utils.summary is not available
29
+ def categorize_command(command):
30
+ return "unknown"
31
+
32
+
33
+ # Optional matplotlib import for box plot generation
34
+ try:
35
+ import matplotlib.pyplot as plt
36
+
37
+ MATPLOTLIB_AVAILABLE = True
38
+ except ImportError:
39
+ MATPLOTLIB_AVAILABLE = False
40
+ logging.warning("matplotlib not available, box plot generation will be disabled")
41
+
42
+ from redis_benchmarks_specification.__common__.github import (
43
+ update_comment_if_needed,
44
+ create_new_pr_comment,
45
+ check_github_available_and_actionable,
46
+ check_regression_comment,
47
+ )
48
+ from redis_benchmarks_specification.__compare__.args import create_compare_arguments
49
+
50
+ from redis_benchmarks_specification.__common__.runner import get_benchmark_specs
51
+
52
+ from redis_benchmarks_specification.__common__.package import (
53
+ get_version_string,
54
+ populate_with_poetry_data,
55
+ )
56
+
57
+
58
+ WH_TOKEN = os.getenv("PERFORMANCE_WH_TOKEN", None)
59
+
60
+ LOG_LEVEL = logging.DEBUG
61
+ if os.getenv("VERBOSE", "0") == "0":
62
+ LOG_LEVEL = logging.INFO
63
+ LOG_FORMAT = "%(asctime)s %(levelname)-4s %(message)s"
64
+ LOG_DATEFMT = "%Y-%m-%d %H:%M:%S"
65
+
66
+
67
+ def get_overall_dashboard_keynames(
68
+ tf_github_org,
69
+ tf_github_repo,
70
+ tf_triggering_env,
71
+ build_variant_name=None,
72
+ running_platform=None,
73
+ test_name=None,
74
+ ):
75
+ build_variant_str = ""
76
+ if build_variant_name is not None:
77
+ build_variant_str = "/{}".format(build_variant_name)
78
+ running_platform_str = ""
79
+ if running_platform is not None:
80
+ running_platform_str = "/{}".format(running_platform)
81
+ sprefix = (
82
+ "ci.benchmarks.redis/"
83
+ + "{triggering_env}/{github_org}/{github_repo}".format(
84
+ triggering_env=tf_triggering_env,
85
+ github_org=tf_github_org,
86
+ github_repo=tf_github_repo,
87
+ )
88
+ )
89
+ testcases_setname = "{}:testcases".format(sprefix)
90
+ deployment_name_setname = "{}:deployment_names".format(sprefix)
91
+ project_archs_setname = "{}:archs".format(sprefix)
92
+ project_oss_setname = "{}:oss".format(sprefix)
93
+ project_branches_setname = "{}:branches".format(sprefix)
94
+ project_versions_setname = "{}:versions".format(sprefix)
95
+ project_compilers_setname = "{}:compilers".format(sprefix)
96
+ running_platforms_setname = "{}:platforms".format(sprefix)
97
+ build_variant_setname = "{}:build_variants".format(sprefix)
98
+ build_variant_prefix = "{sprefix}{build_variant_str}".format(
99
+ sprefix=sprefix,
100
+ build_variant_str=build_variant_str,
101
+ )
102
+ prefix = "{build_variant_prefix}{running_platform_str}".format(
103
+ build_variant_prefix=build_variant_prefix,
104
+ running_platform_str=running_platform_str,
105
+ )
106
+ tsname_project_total_success = "{}:total_success".format(
107
+ prefix,
108
+ )
109
+ tsname_project_total_failures = "{}:total_failures".format(
110
+ prefix,
111
+ )
112
+ testcases_metric_context_path_setname = ""
113
+ if test_name is not None:
114
+ testcases_metric_context_path_setname = (
115
+ "{testcases_setname}:metric_context_path:{test_name}".format(
116
+ testcases_setname=testcases_setname, test_name=test_name
117
+ )
118
+ )
119
+ testcases_and_metric_context_path_setname = (
120
+ "{testcases_setname}_AND_metric_context_path".format(
121
+ testcases_setname=testcases_setname
122
+ )
123
+ )
124
+ return (
125
+ prefix,
126
+ testcases_setname,
127
+ deployment_name_setname,
128
+ tsname_project_total_failures,
129
+ tsname_project_total_success,
130
+ running_platforms_setname,
131
+ build_variant_setname,
132
+ testcases_metric_context_path_setname,
133
+ testcases_and_metric_context_path_setname,
134
+ project_archs_setname,
135
+ project_oss_setname,
136
+ project_branches_setname,
137
+ project_versions_setname,
138
+ project_compilers_setname,
139
+ )
140
+
141
+
142
+ def get_start_time_vars(start_time=None):
143
+ if start_time is None:
144
+ start_time = dt.datetime.utcnow()
145
+ start_time_ms = int((start_time - dt.datetime(1970, 1, 1)).total_seconds() * 1000)
146
+ start_time_str = start_time.strftime("%Y-%m-%d-%H-%M-%S")
147
+ return start_time, start_time_ms, start_time_str
148
+
149
+
150
+ def get_project_compare_zsets(triggering_env, org, repo):
151
+ return "ci.benchmarks.redis/{}/{}/{}:compare:pull_requests:zset".format(
152
+ triggering_env, org, repo
153
+ )
154
+
155
+
156
+ def compare_command_logic(args, project_name, project_version):
157
+
158
+ logger = logging.getLogger()
159
+ logger.setLevel(LOG_LEVEL)
160
+
161
+ # create console handler and set level to debug
162
+ ch = logging.StreamHandler()
163
+ ch.setLevel(LOG_LEVEL)
164
+
165
+ # create formatter
166
+ formatter = logging.Formatter(LOG_FORMAT)
167
+
168
+ # add formatter to ch
169
+ ch.setFormatter(formatter)
170
+
171
+ # add ch to logger
172
+ logger.addHandler(ch)
173
+
174
+ logging.info(
175
+ "Using: {project_name} {project_version}".format(
176
+ project_name=project_name, project_version=project_version
177
+ )
178
+ )
179
+ logging.info(
180
+ "Checking connection to RedisTimeSeries with user: {}, host: {}, port: {}".format(
181
+ args.redistimeseries_user,
182
+ args.redistimeseries_host,
183
+ args.redistimeseries_port,
184
+ )
185
+ )
186
+ rts = redis.Redis(
187
+ host=args.redistimeseries_host,
188
+ port=args.redistimeseries_port,
189
+ password=args.redistimeseries_pass,
190
+ username=args.redistimeseries_user,
191
+ )
192
+ rts.ping()
193
+ default_baseline_branch, default_metrics_str = extract_default_branch_and_metric(
194
+ args.defaults_filename
195
+ )
196
+
197
+ tf_github_org = args.github_org
198
+ tf_github_repo = args.github_repo
199
+ tf_triggering_env = args.triggering_env
200
+ if args.baseline_deployment_name != "":
201
+ baseline_deployment_name = args.baseline_deployment_name
202
+ else:
203
+ baseline_deployment_name = args.deployment_name
204
+ if args.comparison_deployment_name != "":
205
+ comparison_deployment_name = args.comparison_deployment_name
206
+ else:
207
+ comparison_deployment_name = args.deployment_name
208
+
209
+ logging.info(
210
+ "Using baseline deployment_name={} and comparison deployment_name={} for the analysis".format(
211
+ baseline_deployment_name,
212
+ comparison_deployment_name,
213
+ )
214
+ )
215
+ from_ts_ms = args.from_timestamp
216
+ to_ts_ms = args.to_timestamp
217
+ from_date = args.from_date
218
+ to_date = args.to_date
219
+ baseline_branch = args.baseline_branch
220
+ if baseline_branch is None and default_baseline_branch is not None:
221
+ logging.info(
222
+ "Given --baseline-branch was null using the default baseline branch {}".format(
223
+ default_baseline_branch
224
+ )
225
+ )
226
+ baseline_branch = default_baseline_branch
227
+ if baseline_branch == "":
228
+ baseline_branch = None
229
+ comparison_branch = args.comparison_branch
230
+ simplify_table = args.simple_table
231
+ print_regressions_only = args.print_regressions_only
232
+ print_improvements_only = args.print_improvements_only
233
+ skip_unstable = args.skip_unstable
234
+ baseline_tag = args.baseline_tag
235
+ comparison_tag = args.comparison_tag
236
+ last_n_baseline = args.last_n
237
+ last_n_comparison = args.last_n
238
+ if last_n_baseline < 0:
239
+ last_n_baseline = args.last_n_baseline
240
+ if last_n_comparison < 0:
241
+ last_n_comparison = args.last_n_comparison
242
+ logging.info("Using last {} samples for baseline analysis".format(last_n_baseline))
243
+ logging.info(
244
+ "Using last {} samples for comparison analysis".format(last_n_comparison)
245
+ )
246
+ verbose = args.verbose
247
+ regressions_percent_lower_limit = args.regressions_percent_lower_limit
248
+ metric_name = args.metric_name
249
+ if (metric_name is None or metric_name == "") and default_metrics_str != "":
250
+ logging.info(
251
+ "Given --metric_name was null using the default metric names {}".format(
252
+ default_metrics_str
253
+ )
254
+ )
255
+ metric_name = default_metrics_str
256
+
257
+ if metric_name is None:
258
+ logging.error(
259
+ "You need to provider either "
260
+ + " --metric_name or provide a defaults file via --defaults_filename that contains exporter.redistimeseries.comparison.metrics array. Exiting..."
261
+ )
262
+ exit(1)
263
+ else:
264
+ logging.info("Using metric {}".format(metric_name))
265
+
266
+ metric_mode = args.metric_mode
267
+ test = args.test
268
+ use_metric_context_path = args.use_metric_context_path
269
+ github_token = args.github_token
270
+ pull_request = args.pull_request
271
+ testname_regex = args.testname_regex
272
+ auto_approve = args.auto_approve
273
+ running_platform = args.running_platform
274
+
275
+ # Handle separate baseline and comparison platform/environment arguments
276
+ # Fall back to general arguments if specific ones are not provided
277
+ running_platform_baseline = args.running_platform_baseline or args.running_platform
278
+ running_platform_comparison = (
279
+ args.running_platform_comparison or args.running_platform
280
+ )
281
+ triggering_env_baseline = args.triggering_env_baseline or args.triggering_env
282
+ triggering_env_comparison = args.triggering_env_comparison or args.triggering_env
283
+
284
+ baseline_target_version = args.baseline_target_version
285
+ comparison_target_version = args.comparison_target_version
286
+ baseline_target_branch = args.baseline_target_branch
287
+ comparison_target_branch = args.comparison_target_branch
288
+ baseline_github_repo = args.baseline_github_repo
289
+ comparison_github_repo = args.comparison_github_repo
290
+ baseline_github_org = args.baseline_github_org
291
+ comparison_github_org = args.comparison_github_org
292
+ baseline_hash = args.baseline_hash
293
+ comparison_hash = args.comparison_hash
294
+
295
+ # Log platform and environment information
296
+ if running_platform_baseline == running_platform_comparison:
297
+ if running_platform_baseline is not None:
298
+ logging.info(
299
+ "Using platform named: {} for both baseline and comparison.\n\n".format(
300
+ running_platform_baseline
301
+ )
302
+ )
303
+ else:
304
+ logging.info(
305
+ "Using platform named: {} for baseline and {} for comparison.\n\n".format(
306
+ running_platform_baseline, running_platform_comparison
307
+ )
308
+ )
309
+
310
+ if triggering_env_baseline == triggering_env_comparison:
311
+ logging.info(
312
+ "Using triggering environment: {} for both baseline and comparison.".format(
313
+ triggering_env_baseline
314
+ )
315
+ )
316
+ else:
317
+ logging.info(
318
+ "Using triggering environment: {} for baseline and {} for comparison.".format(
319
+ triggering_env_baseline, triggering_env_comparison
320
+ )
321
+ )
322
+
323
+ testsuites_folder = os.path.abspath(args.test_suites_folder)
324
+ logging.info("Using test-suites folder dir {}".format(testsuites_folder))
325
+ testsuite_spec_files = get_benchmark_specs(testsuites_folder)
326
+ logging.info(
327
+ "There are a total of {} test-suites being run in folder {}".format(
328
+ len(testsuite_spec_files), testsuites_folder
329
+ )
330
+ )
331
+ tests_with_config = {}
332
+ for test_file in testsuite_spec_files:
333
+ if args.defaults_filename in test_file:
334
+ continue
335
+ benchmark_config = {}
336
+ with open(test_file, "r") as stream:
337
+ try:
338
+ benchmark_config = yaml.safe_load(stream)
339
+ test_name = benchmark_config["name"]
340
+ tests_with_config[test_name] = benchmark_config
341
+ if "tested-groups" in benchmark_config:
342
+ origin_tested_groups = benchmark_config["tested-groups"]
343
+ else:
344
+ logging.warn("dont have test groups in {}".format(test_name))
345
+ if "tested-commands" in benchmark_config:
346
+ origin_tested_commands = benchmark_config["tested-commands"]
347
+ else:
348
+ logging.warn("dont have test commands in {}".format(test_name))
349
+ except Exception as e:
350
+ logging.error(
351
+ "while loading file {} and error was returned: {}".format(
352
+ test_file, e.__str__()
353
+ )
354
+ )
355
+ pass
356
+
357
+ fn = check_regression_comment
358
+ (
359
+ contains_regression_comment,
360
+ github_pr,
361
+ is_actionable_pr,
362
+ old_regression_comment_body,
363
+ pr_link,
364
+ regression_comment,
365
+ ) = check_github_available_and_actionable(
366
+ fn, github_token, pull_request, tf_github_org, tf_github_repo, verbose
367
+ )
368
+ grafana_link_base = "https://benchmarksredisio.grafana.net/d/1fWbtb7nz/experimental-oss-spec-benchmarks"
369
+
370
+ # Check if environment comparison mode is enabled
371
+ compare_by_env = getattr(args, "compare_by_env", False)
372
+
373
+ if compare_by_env:
374
+ logging.info("Environment comparison mode enabled")
375
+ # Extract environment list from deployment names in the user's example
376
+ env_list = [
377
+ "oss-standalone",
378
+ "oss-standalone-02-io-threads",
379
+ "oss-standalone-04-io-threads",
380
+ "oss-standalone-08-io-threads",
381
+ ]
382
+
383
+ (
384
+ detected_regressions,
385
+ table_output,
386
+ improvements_list,
387
+ regressions_list,
388
+ total_stable,
389
+ total_unstable,
390
+ total_comparison_points,
391
+ boxplot_data,
392
+ command_change,
393
+ ) = compute_env_comparison_table(
394
+ rts,
395
+ tf_github_org,
396
+ tf_github_repo,
397
+ triggering_env_baseline,
398
+ triggering_env_comparison,
399
+ metric_name,
400
+ comparison_branch,
401
+ baseline_branch,
402
+ baseline_tag,
403
+ comparison_tag,
404
+ env_list,
405
+ print_improvements_only,
406
+ print_regressions_only,
407
+ skip_unstable,
408
+ regressions_percent_lower_limit,
409
+ simplify_table,
410
+ test,
411
+ testname_regex,
412
+ verbose,
413
+ last_n_baseline,
414
+ last_n_comparison,
415
+ metric_mode,
416
+ from_date,
417
+ from_ts_ms,
418
+ to_date,
419
+ to_ts_ms,
420
+ use_metric_context_path,
421
+ running_platform_baseline,
422
+ running_platform_comparison,
423
+ baseline_target_version,
424
+ comparison_target_version,
425
+ baseline_hash,
426
+ comparison_hash,
427
+ baseline_github_repo,
428
+ comparison_github_repo,
429
+ baseline_target_branch,
430
+ comparison_target_branch,
431
+ baseline_github_org,
432
+ comparison_github_org,
433
+ args.regression_str,
434
+ args.improvement_str,
435
+ tests_with_config,
436
+ args.use_test_suites_folder,
437
+ testsuites_folder,
438
+ args.extra_filters,
439
+ getattr(args, "command_group_regex", ".*"),
440
+ getattr(args, "command_regex", ".*"),
441
+ )
442
+ else:
443
+ logging.info("Default test comparison mode")
444
+ (
445
+ detected_regressions,
446
+ table_output,
447
+ improvements_list,
448
+ regressions_list,
449
+ total_stable,
450
+ total_unstable,
451
+ total_comparison_points,
452
+ boxplot_data,
453
+ command_change,
454
+ ) = compute_regression_table(
455
+ rts,
456
+ tf_github_org,
457
+ tf_github_repo,
458
+ triggering_env_baseline,
459
+ triggering_env_comparison,
460
+ metric_name,
461
+ comparison_branch,
462
+ baseline_branch,
463
+ baseline_tag,
464
+ comparison_tag,
465
+ baseline_deployment_name,
466
+ comparison_deployment_name,
467
+ print_improvements_only,
468
+ print_regressions_only,
469
+ skip_unstable,
470
+ regressions_percent_lower_limit,
471
+ simplify_table,
472
+ test,
473
+ testname_regex,
474
+ verbose,
475
+ last_n_baseline,
476
+ last_n_comparison,
477
+ metric_mode,
478
+ from_date,
479
+ from_ts_ms,
480
+ to_date,
481
+ to_ts_ms,
482
+ use_metric_context_path,
483
+ running_platform_baseline,
484
+ running_platform_comparison,
485
+ baseline_target_version,
486
+ comparison_target_version,
487
+ baseline_hash,
488
+ comparison_hash,
489
+ baseline_github_repo,
490
+ comparison_github_repo,
491
+ baseline_target_branch,
492
+ comparison_target_branch,
493
+ baseline_github_org,
494
+ comparison_github_org,
495
+ args.regression_str,
496
+ args.improvement_str,
497
+ tests_with_config,
498
+ args.use_test_suites_folder,
499
+ testsuites_folder,
500
+ args.extra_filters,
501
+ getattr(args, "command_group_regex", ".*"),
502
+ getattr(args, "command_regex", ".*"),
503
+ )
504
+ total_regressions = len(regressions_list)
505
+ total_improvements = len(improvements_list)
506
+ prepare_regression_comment(
507
+ auto_approve,
508
+ baseline_branch,
509
+ baseline_tag,
510
+ comparison_branch,
511
+ comparison_tag,
512
+ contains_regression_comment,
513
+ github_pr,
514
+ grafana_link_base,
515
+ is_actionable_pr,
516
+ old_regression_comment_body,
517
+ pr_link,
518
+ regression_comment,
519
+ rts,
520
+ running_platform_baseline,
521
+ running_platform_comparison,
522
+ table_output,
523
+ tf_github_org,
524
+ tf_github_repo,
525
+ triggering_env_baseline,
526
+ triggering_env_comparison,
527
+ total_comparison_points,
528
+ total_improvements,
529
+ total_regressions,
530
+ total_stable,
531
+ total_unstable,
532
+ verbose,
533
+ args.regressions_percent_lower_limit,
534
+ regressions_list,
535
+ improvements_list,
536
+ args.improvement_str,
537
+ args.regression_str,
538
+ )
539
+
540
+ # Generate box plot if requested
541
+ if args.generate_boxplot and command_change:
542
+ if MATPLOTLIB_AVAILABLE:
543
+ logging.info(f"Generating box plot with {len(command_change)} commands...")
544
+ generate_command_performance_boxplot_from_command_data(
545
+ command_change,
546
+ args.boxplot_output,
547
+ args.regression_str,
548
+ args.improvement_str,
549
+ getattr(args, "command_group_regex", ".*"),
550
+ )
551
+ else:
552
+ logging.error(
553
+ "Box plot generation requested but matplotlib is not available"
554
+ )
555
+
556
+ return (
557
+ detected_regressions,
558
+ "",
559
+ total_improvements,
560
+ total_regressions,
561
+ total_stable,
562
+ total_unstable,
563
+ total_comparison_points,
564
+ )
565
+
566
+
567
+ def prepare_regression_comment(
568
+ auto_approve,
569
+ baseline_branch,
570
+ baseline_tag,
571
+ comparison_branch,
572
+ comparison_tag,
573
+ contains_regression_comment,
574
+ github_pr,
575
+ grafana_link_base,
576
+ is_actionable_pr,
577
+ old_regression_comment_body,
578
+ pr_link,
579
+ regression_comment,
580
+ rts,
581
+ running_platform_baseline,
582
+ running_platform_comparison,
583
+ table_output,
584
+ tf_github_org,
585
+ tf_github_repo,
586
+ triggering_env_baseline,
587
+ triggering_env_comparison,
588
+ total_comparison_points,
589
+ total_improvements,
590
+ total_regressions,
591
+ total_stable,
592
+ total_unstable,
593
+ verbose,
594
+ regressions_percent_lower_limit,
595
+ regressions_list=[],
596
+ improvements_list=[],
597
+ improvement_str="Improvement",
598
+ regression_str="Regression",
599
+ ):
600
+ if total_comparison_points > 0:
601
+ comment_body = "### Automated performance analysis summary\n\n"
602
+ comment_body += "This comment was automatically generated given there is performance data available.\n\n"
603
+ # Add platform information to comment
604
+ if running_platform_baseline == running_platform_comparison:
605
+ if running_platform_baseline is not None:
606
+ comment_body += "Using platform named: {} for both baseline and comparison.\n\n".format(
607
+ running_platform_baseline
608
+ )
609
+ else:
610
+ comment_body += "Using platform named: {} for baseline and {} for comparison.\n\n".format(
611
+ running_platform_baseline, running_platform_comparison
612
+ )
613
+
614
+ # Add triggering environment information to comment
615
+ if triggering_env_baseline == triggering_env_comparison:
616
+ comment_body += "Using triggering environment: {} for both baseline and comparison.\n\n".format(
617
+ triggering_env_baseline
618
+ )
619
+ else:
620
+ comment_body += "Using triggering environment: {} for baseline and {} for comparison.\n\n".format(
621
+ triggering_env_baseline, triggering_env_comparison
622
+ )
623
+ comparison_summary = "In summary:\n"
624
+ if total_stable > 0:
625
+ comparison_summary += (
626
+ "- Detected a total of {} stable tests between versions.\n".format(
627
+ total_stable,
628
+ )
629
+ )
630
+
631
+ if total_unstable > 0:
632
+ comparison_summary += (
633
+ "- Detected a total of {} highly unstable benchmarks.\n".format(
634
+ total_unstable
635
+ )
636
+ )
637
+ if total_improvements > 0:
638
+ comparison_summary += "- Detected a total of {} improvements above the improvement water line ({}).\n".format(
639
+ total_improvements, improvement_str
640
+ )
641
+ if len(improvements_list) > 0:
642
+ improvement_values = [l[1] for l in improvements_list]
643
+ improvement_df = pd.DataFrame(improvement_values)
644
+ median_improvement = round(float(improvement_df.median().iloc[0]), 1)
645
+ max_improvement = round(float(improvement_df.max().iloc[0]), 1)
646
+ min_improvement = round(float(improvement_df.min().iloc[0]), 1)
647
+ p25_improvement = round(float(improvement_df.quantile(0.25).iloc[0]), 1)
648
+ p75_improvement = round(float(improvement_df.quantile(0.75).iloc[0]), 1)
649
+
650
+ comparison_summary += f" - The median improvement ({improvement_str}) was {median_improvement}%, with values ranging from {min_improvement}% to {max_improvement}%.\n"
651
+ comparison_summary += f" - Quartile distribution: P25={p25_improvement}%, P50={median_improvement}%, P75={p75_improvement}%.\n"
652
+
653
+ if total_regressions > 0:
654
+ comparison_summary += "- Detected a total of {} regressions below the regression water line of {} ({}).\n".format(
655
+ total_regressions, regressions_percent_lower_limit, regression_str
656
+ )
657
+ if len(regressions_list) > 0:
658
+ regression_values = [l[1] for l in regressions_list]
659
+ regression_df = pd.DataFrame(regression_values)
660
+ median_regression = round(float(regression_df.median().iloc[0]), 1)
661
+ max_regression = round(float(regression_df.max().iloc[0]), 1)
662
+ min_regression = round(float(regression_df.min().iloc[0]), 1)
663
+ p25_regression = round(float(regression_df.quantile(0.25).iloc[0]), 1)
664
+ p75_regression = round(float(regression_df.quantile(0.75).iloc[0]), 1)
665
+
666
+ comparison_summary += f" - The median regression ({regression_str}) was {median_regression}%, with values ranging from {min_regression}% to {max_regression}%.\n"
667
+ comparison_summary += f" - Quartile distribution: P25={p25_regression}%, P50={median_regression}%, P75={p75_regression}%.\n"
668
+
669
+ comment_body += comparison_summary
670
+ comment_body += "\n"
671
+
672
+ if grafana_link_base is not None:
673
+ grafana_link = "{}/".format(grafana_link_base)
674
+ if baseline_tag is not None and comparison_tag is not None:
675
+ grafana_link += "?var-version={}&var-version={}".format(
676
+ baseline_tag, comparison_tag
677
+ )
678
+ if baseline_branch is not None and comparison_branch is not None:
679
+ grafana_link += "?var-branch={}&var-branch={}".format(
680
+ baseline_branch, comparison_branch
681
+ )
682
+ comment_body += "You can check a comparison in detail via the [grafana link]({})".format(
683
+ grafana_link
684
+ )
685
+
686
+ comment_body += "\n\n##" + table_output
687
+ print(comment_body)
688
+
689
+ if is_actionable_pr:
690
+ zset_project_pull_request = get_project_compare_zsets(
691
+ triggering_env_baseline,
692
+ tf_github_org,
693
+ tf_github_repo,
694
+ )
695
+ logging.info(
696
+ "Populating the pull request performance ZSETs: {} with branch {}".format(
697
+ zset_project_pull_request, comparison_branch
698
+ )
699
+ )
700
+ # Only add to Redis sorted set if comparison_branch is not None
701
+ if comparison_branch is not None:
702
+ _, start_time_ms, _ = get_start_time_vars()
703
+ res = rts.zadd(
704
+ zset_project_pull_request,
705
+ {comparison_branch: start_time_ms},
706
+ )
707
+ logging.info(
708
+ "Result of Populating the pull request performance ZSETs: {} with branch {}: {}".format(
709
+ zset_project_pull_request, comparison_branch, res
710
+ )
711
+ )
712
+ else:
713
+ logging.warning(
714
+ "Skipping Redis ZADD operation because comparison_branch is None"
715
+ )
716
+
717
+ if contains_regression_comment:
718
+ update_comment_if_needed(
719
+ auto_approve,
720
+ comment_body,
721
+ old_regression_comment_body,
722
+ regression_comment,
723
+ verbose,
724
+ )
725
+ else:
726
+ create_new_pr_comment(auto_approve, comment_body, github_pr, pr_link)
727
+
728
+ else:
729
+ logging.error("There was no comparison points to produce a table...")
730
+
731
+
732
+ def extract_default_branch_and_metric(defaults_filename):
733
+ default_baseline_branch = "unstable"
734
+ default_metrics_str = ""
735
+ if defaults_filename != "" and os.path.exists(defaults_filename):
736
+ logging.info(
737
+ "Loading configuration from defaults file: {}".format(defaults_filename)
738
+ )
739
+ with open(defaults_filename) as yaml_fd:
740
+ defaults_dict = yaml.safe_load(yaml_fd)
741
+ if "exporter" in defaults_dict:
742
+ exporter_dict = defaults_dict["exporter"]
743
+ if "comparison" in exporter_dict:
744
+ comparison_dict = exporter_dict["comparison"]
745
+ if "metrics" in comparison_dict:
746
+ metrics = comparison_dict["metrics"]
747
+ logging.info("Detected defaults metrics info. reading metrics")
748
+ default_metrics = []
749
+
750
+ for metric in metrics:
751
+ if metric.startswith("$."):
752
+ metric = metric[2:]
753
+ logging.info("Will use metric: {}".format(metric))
754
+ default_metrics.append(metric)
755
+ if len(default_metrics) == 1:
756
+ default_metrics_str = default_metrics[0]
757
+ if len(default_metrics) > 1:
758
+ default_metrics_str = "({})".format(
759
+ ",".join(default_metrics)
760
+ )
761
+ logging.info("Default metrics: {}".format(default_metrics_str))
762
+
763
+ if "baseline-branch" in comparison_dict:
764
+ default_baseline_branch = comparison_dict["baseline-branch"]
765
+ logging.info(
766
+ "Detected baseline branch in defaults file. {}".format(
767
+ default_baseline_branch
768
+ )
769
+ )
770
+ return default_baseline_branch, default_metrics_str
771
+
772
+
773
+ def compute_env_comparison_table(
774
+ rts,
775
+ tf_github_org,
776
+ tf_github_repo,
777
+ tf_triggering_env_baseline,
778
+ tf_triggering_env_comparison,
779
+ metric_name,
780
+ comparison_branch,
781
+ baseline_branch="unstable",
782
+ baseline_tag=None,
783
+ comparison_tag=None,
784
+ env_list=None,
785
+ print_improvements_only=False,
786
+ print_regressions_only=False,
787
+ skip_unstable=False,
788
+ regressions_percent_lower_limit=5.0,
789
+ simplify_table=False,
790
+ test="",
791
+ testname_regex=".*",
792
+ verbose=False,
793
+ last_n_baseline=-1,
794
+ last_n_comparison=-1,
795
+ metric_mode="higher-better",
796
+ from_date=None,
797
+ from_ts_ms=None,
798
+ to_date=None,
799
+ to_ts_ms=None,
800
+ use_metric_context_path=None,
801
+ running_platform_baseline=None,
802
+ running_platform_comparison=None,
803
+ baseline_target_version=None,
804
+ comparison_target_version=None,
805
+ comparison_hash=None,
806
+ baseline_hash=None,
807
+ baseline_github_repo="redis",
808
+ comparison_github_repo="redis",
809
+ baseline_target_branch=None,
810
+ comparison_target_branch=None,
811
+ baseline_github_org="redis",
812
+ comparison_github_org="redis",
813
+ regression_str="REGRESSION",
814
+ improvement_str="IMPROVEMENT",
815
+ tests_with_config={},
816
+ use_test_suites_folder=False,
817
+ test_suites_folder=None,
818
+ extra_filters="",
819
+ command_group_regex=".*",
820
+ command_regex=".*",
821
+ ):
822
+ """
823
+ Compute environment comparison table for a specific test across different environments.
824
+ """
825
+ START_TIME_NOW_UTC, _, _ = get_start_time_vars()
826
+ START_TIME_LAST_MONTH_UTC = START_TIME_NOW_UTC - datetime.timedelta(days=31)
827
+ if from_date is None:
828
+ from_date = START_TIME_LAST_MONTH_UTC
829
+ if to_date is None:
830
+ to_date = START_TIME_NOW_UTC
831
+ if from_ts_ms is None:
832
+ from_ts_ms = int(from_date.timestamp() * 1000)
833
+ if to_ts_ms is None:
834
+ to_ts_ms = int(to_date.timestamp() * 1000)
835
+
836
+ # Extract test names from the test parameter
837
+ test_names = []
838
+ if test != "":
839
+ test_names = test.split(",")
840
+
841
+ # If no specific test provided, we need at least one test for environment comparison
842
+ if not test_names:
843
+ logging.error(
844
+ "Environment comparison requires specifying at least one test via --test parameter"
845
+ )
846
+ return None, "", [], [], 0, 0, 0, [], False
847
+
848
+ # For environment comparison, we focus on the first test if multiple are provided
849
+ test_name = test_names[0]
850
+ if len(test_names) > 1:
851
+ logging.warning(
852
+ f"Environment comparison mode: using only the first test '{test_name}' from the provided list"
853
+ )
854
+
855
+ # Default environment list if not provided
856
+ if env_list is None:
857
+ env_list = [
858
+ "oss-standalone",
859
+ "oss-standalone-02-io-threads",
860
+ "oss-standalone-04-io-threads",
861
+ "oss-standalone-08-io-threads",
862
+ ]
863
+
864
+ logging.info(f"Comparing environments {env_list} for test '{test_name}'")
865
+
866
+ # Build baseline and comparison strings
867
+ baseline_str, by_str_baseline, comparison_str, by_str_comparison = get_by_strings(
868
+ baseline_branch,
869
+ comparison_branch,
870
+ baseline_tag,
871
+ comparison_tag,
872
+ baseline_target_version,
873
+ comparison_target_version,
874
+ baseline_hash,
875
+ comparison_hash,
876
+ baseline_target_branch,
877
+ comparison_target_branch,
878
+ )
879
+
880
+ test_filter = "test_name"
881
+
882
+ (
883
+ detected_regressions,
884
+ table_full,
885
+ table_stable,
886
+ table_unstable,
887
+ table_improvements,
888
+ table_regressions,
889
+ total_improvements,
890
+ total_regressions,
891
+ total_stable,
892
+ total_unstable,
893
+ total_comparison_points,
894
+ regressions_list,
895
+ improvements_list,
896
+ unstable_list,
897
+ baseline_only_list,
898
+ comparison_only_list,
899
+ no_datapoints_list,
900
+ group_change,
901
+ command_change,
902
+ boxplot_data,
903
+ ) = from_rts_to_env_comparison_table(
904
+ test_name,
905
+ env_list,
906
+ baseline_str,
907
+ comparison_str,
908
+ by_str_baseline,
909
+ by_str_comparison,
910
+ from_ts_ms,
911
+ to_ts_ms,
912
+ last_n_baseline,
913
+ last_n_comparison,
914
+ metric_mode,
915
+ metric_name,
916
+ print_improvements_only,
917
+ print_regressions_only,
918
+ skip_unstable,
919
+ regressions_percent_lower_limit,
920
+ rts,
921
+ simplify_table,
922
+ test_filter,
923
+ tf_triggering_env_baseline,
924
+ tf_triggering_env_comparison,
925
+ verbose,
926
+ running_platform_baseline,
927
+ running_platform_comparison,
928
+ baseline_github_repo,
929
+ comparison_github_repo,
930
+ baseline_github_org,
931
+ comparison_github_org,
932
+ regression_str,
933
+ improvement_str,
934
+ tests_with_config,
935
+ extra_filters,
936
+ )
937
+
938
+ # Generate table output
939
+ table_output = f"## Environment Comparison for Test: {test_name}\n\n"
940
+ table_output += f"**Metric:** {metric_name} ({metric_mode})\n\n"
941
+ table_output += (
942
+ f"**Baseline:** {baseline_github_org}/{baseline_github_repo} {baseline_str}\n\n"
943
+ )
944
+ table_output += f"**Comparison:** {comparison_github_org}/{comparison_github_repo} {comparison_str}\n\n"
945
+
946
+ if total_unstable > 0:
947
+ old_stdout = sys.stdout
948
+ sys.stdout = mystdout = StringIO()
949
+ table_output += "#### Unstable Environments\n\n"
950
+ writer_regressions = MarkdownTableWriter(
951
+ table_name="",
952
+ headers=[
953
+ "Environment",
954
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
955
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
956
+ "% change ({})".format(metric_mode),
957
+ "Note",
958
+ ],
959
+ value_matrix=table_unstable,
960
+ )
961
+ writer_regressions.dump(mystdout, False)
962
+ table_output += mystdout.getvalue()
963
+ table_output += "\n\n"
964
+ env_names_str = "|".join([l[0] for l in unstable_list])
965
+ table_output += f"Unstable environment names: {env_names_str}\n\n"
966
+ mystdout.close()
967
+ sys.stdout = old_stdout
968
+
969
+ if total_regressions > 0:
970
+ old_stdout = sys.stdout
971
+ sys.stdout = mystdout = StringIO()
972
+ table_output += "#### Regressions by Environment\n\n"
973
+ writer_regressions = MarkdownTableWriter(
974
+ table_name="",
975
+ headers=[
976
+ "Environment",
977
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
978
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
979
+ "% change ({})".format(metric_mode),
980
+ "Note",
981
+ ],
982
+ value_matrix=table_regressions,
983
+ )
984
+ writer_regressions.dump(mystdout, False)
985
+ table_output += mystdout.getvalue()
986
+ table_output += "\n\n"
987
+ env_names_str = "|".join([l[0] for l in regressions_list])
988
+ table_output += f"Regression environment names: {env_names_str}\n\n"
989
+ mystdout.close()
990
+ sys.stdout = old_stdout
991
+
992
+ if total_improvements > 0:
993
+ old_stdout = sys.stdout
994
+ sys.stdout = mystdout = StringIO()
995
+ table_output += "#### Improvements by Environment\n\n"
996
+ writer_regressions = MarkdownTableWriter(
997
+ table_name="",
998
+ headers=[
999
+ "Environment",
1000
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1001
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1002
+ "% change ({})".format(metric_mode),
1003
+ "Note",
1004
+ ],
1005
+ value_matrix=table_improvements,
1006
+ )
1007
+ writer_regressions.dump(mystdout, False)
1008
+ table_output += mystdout.getvalue()
1009
+ table_output += "\n\n"
1010
+ env_names_str = "|".join([l[0] for l in improvements_list])
1011
+ table_output += f"Improvements environment names: {env_names_str}\n\n"
1012
+ mystdout.close()
1013
+ sys.stdout = old_stdout
1014
+
1015
+ old_stdout = sys.stdout
1016
+ sys.stdout = mystdout = StringIO()
1017
+ writer_full = MarkdownTableWriter(
1018
+ table_name="",
1019
+ headers=[
1020
+ "Environment",
1021
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1022
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1023
+ "% change ({})".format(metric_mode),
1024
+ "Note",
1025
+ ],
1026
+ value_matrix=table_full,
1027
+ )
1028
+ table_output += f"<details>\n <summary>Full Environment Results for Test: {test_name}:</summary>\n\n"
1029
+
1030
+ writer_full.dump(mystdout, False)
1031
+
1032
+ sys.stdout = old_stdout
1033
+ table_output += mystdout.getvalue()
1034
+ table_output += "\n</details>\n"
1035
+
1036
+ len_baseline_only_list = len(baseline_only_list)
1037
+ if len_baseline_only_list > 0:
1038
+ table_output += f"\n WARNING: There were {len_baseline_only_list} environments with datapoints only on baseline.\n\n"
1039
+ baseline_only_env_names_str = "|".join([l for l in baseline_only_list])
1040
+ table_output += (
1041
+ f" Baseline only environment names: {baseline_only_env_names_str}\n\n"
1042
+ )
1043
+
1044
+ len_comparison_only_list = len(comparison_only_list)
1045
+ if len_comparison_only_list > 0:
1046
+ table_output += f"\n WARNING: There were {len_comparison_only_list} environments with datapoints only on comparison.\n\n"
1047
+ comparison_only_env_names_str = "|".join([l for l in comparison_only_list])
1048
+ table_output += (
1049
+ f" Comparison only environment names: {comparison_only_env_names_str}\n\n"
1050
+ )
1051
+
1052
+ len_no_datapoints_list = len(no_datapoints_list)
1053
+ if len_no_datapoints_list > 0:
1054
+ table_output += f"\n WARNING: There were {len_no_datapoints_list} environments with no datapoints.\n\n"
1055
+ no_datapoints_env_names_str = "|".join([l for l in no_datapoints_list])
1056
+ table_output += (
1057
+ f" No datapoints environment names: {no_datapoints_env_names_str}\n\n"
1058
+ )
1059
+
1060
+ return (
1061
+ detected_regressions,
1062
+ table_output,
1063
+ improvements_list,
1064
+ regressions_list,
1065
+ total_stable,
1066
+ total_unstable,
1067
+ total_comparison_points,
1068
+ boxplot_data,
1069
+ command_change,
1070
+ )
1071
+
1072
+
1073
+ def compute_regression_table(
1074
+ rts,
1075
+ tf_github_org,
1076
+ tf_github_repo,
1077
+ tf_triggering_env_baseline,
1078
+ tf_triggering_env_comparison,
1079
+ metric_name,
1080
+ comparison_branch,
1081
+ baseline_branch="unstable",
1082
+ baseline_tag=None,
1083
+ comparison_tag=None,
1084
+ baseline_deployment_name="oss-standalone",
1085
+ comparison_deployment_name="oss-standalone",
1086
+ print_improvements_only=False,
1087
+ print_regressions_only=False,
1088
+ skip_unstable=False,
1089
+ regressions_percent_lower_limit=5.0,
1090
+ simplify_table=False,
1091
+ test="",
1092
+ testname_regex=".*",
1093
+ verbose=False,
1094
+ last_n_baseline=-1,
1095
+ last_n_comparison=-1,
1096
+ metric_mode="higher-better",
1097
+ from_date=None,
1098
+ from_ts_ms=None,
1099
+ to_date=None,
1100
+ to_ts_ms=None,
1101
+ use_metric_context_path=None,
1102
+ running_platform_baseline=None,
1103
+ running_platform_comparison=None,
1104
+ baseline_target_version=None,
1105
+ comparison_target_version=None,
1106
+ comparison_hash=None,
1107
+ baseline_hash=None,
1108
+ baseline_github_repo="redis",
1109
+ comparison_github_repo="redis",
1110
+ baseline_target_branch=None,
1111
+ comparison_target_branch=None,
1112
+ baseline_github_org="redis",
1113
+ comparison_github_org="redis",
1114
+ regression_str="REGRESSION",
1115
+ improvement_str="IMPROVEMENT",
1116
+ tests_with_config={},
1117
+ use_test_suites_folder=True,
1118
+ test_suites_folder=None,
1119
+ extra_filters="",
1120
+ command_group_regex=".*",
1121
+ command_regex=".*",
1122
+ ):
1123
+ START_TIME_NOW_UTC, _, _ = get_start_time_vars()
1124
+ START_TIME_LAST_MONTH_UTC = START_TIME_NOW_UTC - datetime.timedelta(days=31)
1125
+ if from_date is None:
1126
+ from_date = START_TIME_LAST_MONTH_UTC
1127
+ if to_date is None:
1128
+ to_date = START_TIME_NOW_UTC
1129
+ if from_ts_ms is None:
1130
+ from_ts_ms = int(from_date.timestamp() * 1000)
1131
+ if to_ts_ms is None:
1132
+ to_ts_ms = int(to_date.timestamp() * 1000)
1133
+ from_human_str = humanize.naturaltime(
1134
+ dt.datetime.utcfromtimestamp(from_ts_ms / 1000)
1135
+ )
1136
+ to_human_str = humanize.naturaltime(dt.datetime.utcfromtimestamp(to_ts_ms / 1000))
1137
+ logging.info(
1138
+ "Using a time-delta from {} to {}".format(from_human_str, to_human_str)
1139
+ )
1140
+ baseline_str, by_str_baseline, comparison_str, by_str_comparison = get_by_strings(
1141
+ baseline_branch,
1142
+ comparison_branch,
1143
+ baseline_tag,
1144
+ comparison_tag,
1145
+ baseline_target_version,
1146
+ comparison_target_version,
1147
+ comparison_hash,
1148
+ baseline_hash,
1149
+ baseline_target_branch,
1150
+ comparison_target_branch,
1151
+ )
1152
+ logging.info(f"Using baseline filter {by_str_baseline}={baseline_str}")
1153
+ logging.info(f"Using comparison filter {by_str_comparison}={comparison_str}")
1154
+ (
1155
+ prefix,
1156
+ testcases_setname,
1157
+ _,
1158
+ tsname_project_total_failures,
1159
+ tsname_project_total_success,
1160
+ _,
1161
+ _,
1162
+ _,
1163
+ testcases_metric_context_path_setname,
1164
+ _,
1165
+ _,
1166
+ _,
1167
+ _,
1168
+ _,
1169
+ ) = get_overall_dashboard_keynames(
1170
+ tf_github_org, tf_github_repo, tf_triggering_env_baseline
1171
+ )
1172
+ test_names = []
1173
+ used_key = testcases_setname
1174
+ test_filter = "test_name"
1175
+ if use_metric_context_path:
1176
+ test_filter = "test_name:metric_context_path"
1177
+ used_key = testcases_metric_context_path_setname
1178
+ tags_regex_string = re.compile(testname_regex)
1179
+ if test != "":
1180
+ test_names = test.split(",")
1181
+ logging.info("Using test name {}".format(test_names))
1182
+ elif use_test_suites_folder:
1183
+ test_names = get_test_names_from_yaml_files(
1184
+ test_suites_folder, tags_regex_string
1185
+ )
1186
+ else:
1187
+ test_names = get_test_names_from_db(
1188
+ rts, tags_regex_string, test_names, used_key
1189
+ )
1190
+
1191
+ # Apply command regex filtering to tests_with_config
1192
+ tests_with_config = filter_tests_by_command_regex(tests_with_config, command_regex)
1193
+
1194
+ # Apply command group regex filtering to tests_with_config
1195
+ tests_with_config = filter_tests_by_command_group_regex(
1196
+ tests_with_config, command_group_regex
1197
+ )
1198
+ test_names = list(tests_with_config.keys())
1199
+
1200
+ (
1201
+ detected_regressions,
1202
+ table_full,
1203
+ table_stable,
1204
+ table_unstable,
1205
+ table_improvements,
1206
+ table_regressions,
1207
+ total_improvements,
1208
+ total_regressions,
1209
+ total_stable,
1210
+ total_unstable,
1211
+ total_comparison_points,
1212
+ regressions_list,
1213
+ improvements_list,
1214
+ unstable_list,
1215
+ baseline_only_list,
1216
+ comparison_only_list,
1217
+ no_datapoints_list,
1218
+ group_change,
1219
+ command_change,
1220
+ boxplot_data,
1221
+ ) = from_rts_to_regression_table(
1222
+ baseline_deployment_name,
1223
+ comparison_deployment_name,
1224
+ baseline_str,
1225
+ comparison_str,
1226
+ by_str_baseline,
1227
+ by_str_comparison,
1228
+ from_ts_ms,
1229
+ to_ts_ms,
1230
+ last_n_baseline,
1231
+ last_n_comparison,
1232
+ metric_mode,
1233
+ metric_name,
1234
+ print_improvements_only,
1235
+ print_regressions_only,
1236
+ skip_unstable,
1237
+ regressions_percent_lower_limit,
1238
+ rts,
1239
+ simplify_table,
1240
+ test_filter,
1241
+ test_names,
1242
+ tf_triggering_env_baseline,
1243
+ tf_triggering_env_comparison,
1244
+ verbose,
1245
+ running_platform_baseline,
1246
+ running_platform_comparison,
1247
+ baseline_github_repo,
1248
+ comparison_github_repo,
1249
+ baseline_github_org,
1250
+ comparison_github_org,
1251
+ regression_str,
1252
+ improvement_str,
1253
+ tests_with_config,
1254
+ extra_filters,
1255
+ )
1256
+ logging.info(
1257
+ "Printing differential analysis between {} and {}".format(
1258
+ baseline_str, comparison_str
1259
+ )
1260
+ )
1261
+
1262
+ table_output = "# Comparison between {} and {}.\n\nTime Period from {}. (environment used: {})\n\n".format(
1263
+ baseline_str,
1264
+ comparison_str,
1265
+ from_human_str,
1266
+ baseline_deployment_name,
1267
+ )
1268
+
1269
+ table_output += "<details>\n <summary>By GROUP change csv:</summary>\n\n"
1270
+ table_output += (
1271
+ "\ncommand_group,min_change,q1_change,median_change,q3_change,max_change \n"
1272
+ )
1273
+ for group_name, changes_list in group_change.items():
1274
+ min_change = min(changes_list)
1275
+ q1_change = np.percentile(changes_list, 25)
1276
+ median_change = np.median(changes_list)
1277
+ q3_change = np.percentile(changes_list, 75)
1278
+ max_change = max(changes_list)
1279
+ table_output += f"{group_name},{min_change:.3f},{q1_change:.3f},{median_change:.3f},{q3_change:.3f},{max_change:.3f}\n"
1280
+ table_output += "\n</details>\n"
1281
+ table_output += "\n\n"
1282
+ table_output += "<details>\n <summary>By COMMAND change csv:</summary>\n\n"
1283
+ table_output += (
1284
+ "\ncommand,min_change,q1_change,median_change,q3_change,max_change \n"
1285
+ )
1286
+
1287
+ # Filter commands by command group regex if specified
1288
+ filtered_command_change = command_change
1289
+ if command_group_regex != ".*":
1290
+ group_regex = re.compile(command_group_regex)
1291
+ filtered_command_change = {}
1292
+ for command_name, changes_list in command_change.items():
1293
+ command_group = categorize_command(command_name.lower())
1294
+ if re.search(group_regex, command_group):
1295
+ filtered_command_change[command_name] = changes_list
1296
+
1297
+ for command_name, changes_list in filtered_command_change.items():
1298
+ min_change = min(changes_list)
1299
+ q1_change = np.percentile(changes_list, 25)
1300
+ median_change = np.median(changes_list)
1301
+ q3_change = np.percentile(changes_list, 75)
1302
+ max_change = max(changes_list)
1303
+ table_output += f"{command_name},{min_change:.3f},{q1_change:.3f},{median_change:.3f},{q3_change:.3f},{max_change:.3f}\n"
1304
+ table_output += "\n</details>\n"
1305
+
1306
+ if total_unstable > 0:
1307
+ old_stdout = sys.stdout
1308
+ sys.stdout = mystdout = StringIO()
1309
+ table_output += "#### Unstable Table\n\n"
1310
+ writer_regressions = MarkdownTableWriter(
1311
+ table_name="",
1312
+ headers=[
1313
+ "Test Case",
1314
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1315
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1316
+ "% change ({})".format(metric_mode),
1317
+ "Note",
1318
+ ],
1319
+ value_matrix=table_unstable,
1320
+ )
1321
+ writer_regressions.dump(mystdout, False)
1322
+ table_output += mystdout.getvalue()
1323
+ table_output += "\n\n"
1324
+ test_names_str = "|".join([l[0] for l in unstable_list])
1325
+ table_output += f"Unstable test regexp names: {test_names_str}\n\n"
1326
+ mystdout.close()
1327
+ sys.stdout = old_stdout
1328
+
1329
+ if total_regressions > 0:
1330
+ old_stdout = sys.stdout
1331
+ sys.stdout = mystdout = StringIO()
1332
+ table_output += "#### Regressions Table\n\n"
1333
+ writer_regressions = MarkdownTableWriter(
1334
+ table_name="",
1335
+ headers=[
1336
+ "Test Case",
1337
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1338
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1339
+ "% change ({})".format(metric_mode),
1340
+ "Note",
1341
+ ],
1342
+ value_matrix=table_regressions,
1343
+ )
1344
+ writer_regressions.dump(mystdout, False)
1345
+ table_output += mystdout.getvalue()
1346
+ table_output += "\n\n"
1347
+ test_names_str = "|".join([l[0] for l in regressions_list])
1348
+ table_output += f"Regressions test regexp names: {test_names_str}\n\n"
1349
+ mystdout.close()
1350
+ sys.stdout = old_stdout
1351
+
1352
+ if total_improvements > 0:
1353
+ old_stdout = sys.stdout
1354
+ sys.stdout = mystdout = StringIO()
1355
+ table_output += "#### Improvements Table\n\n"
1356
+ writer_regressions = MarkdownTableWriter(
1357
+ table_name="",
1358
+ headers=[
1359
+ "Test Case",
1360
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1361
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1362
+ "% change ({})".format(metric_mode),
1363
+ "Note",
1364
+ ],
1365
+ value_matrix=table_improvements,
1366
+ )
1367
+ writer_regressions.dump(mystdout, False)
1368
+ table_output += mystdout.getvalue()
1369
+ table_output += "\n\n"
1370
+ test_names_str = "|".join([l[0] for l in improvements_list])
1371
+ table_output += f"Improvements test regexp names: {test_names_str}\n\n"
1372
+ mystdout.close()
1373
+ sys.stdout = old_stdout
1374
+
1375
+ old_stdout = sys.stdout
1376
+ sys.stdout = mystdout = StringIO()
1377
+ writer_full = MarkdownTableWriter(
1378
+ table_name="",
1379
+ headers=[
1380
+ "Test Case",
1381
+ f"Baseline {baseline_github_org}/{baseline_github_repo} {baseline_str} (median obs. +- std.dev)",
1382
+ f"Comparison {comparison_github_org}/{comparison_github_repo} {comparison_str} (median obs. +- std.dev)",
1383
+ "% change ({})".format(metric_mode),
1384
+ "Note",
1385
+ ],
1386
+ value_matrix=table_full,
1387
+ )
1388
+ table_output += "<details>\n <summary>Full Results table:</summary>\n\n"
1389
+
1390
+ writer_full.dump(mystdout, False)
1391
+
1392
+ sys.stdout = old_stdout
1393
+ table_output += mystdout.getvalue()
1394
+ table_output += "\n</details>\n"
1395
+ len_baseline_only_list = len(baseline_only_list)
1396
+ if len_baseline_only_list > 0:
1397
+ table_output += f"\n WARNING: There were {len_baseline_only_list} benchmarks with datapoints only on baseline.\n\n"
1398
+ baseline_only_test_names_str = "|".join([l for l in baseline_only_list])
1399
+ table_output += (
1400
+ f" Baseline only test regexp names: {baseline_only_test_names_str}\n\n"
1401
+ )
1402
+ len_comparison_only_list = len(comparison_only_list)
1403
+ if len_comparison_only_list > 0:
1404
+ table_output += f"\n WARNING: There were {len_comparison_only_list} benchmarks with datapoints only on comparison.\n\n"
1405
+ comparison_only_test_names_str = "|".join([l for l in comparison_only_list])
1406
+ table_output += (
1407
+ f" Comparison only test regexp names: {comparison_only_test_names_str}\n\n"
1408
+ )
1409
+ len_no_datapoints = len(no_datapoints_list)
1410
+ if len_no_datapoints > 0:
1411
+ table_output += f"\n WARNING: There were {len_no_datapoints} benchmarks with NO datapoints for both baseline and comparison.\n\n"
1412
+ table_output += "<details>\n <summary>NO datapoints for both baseline and comparison:</summary>\n\n"
1413
+ no_datapoints_test_names_str = "|".join([l for l in no_datapoints_list])
1414
+ table_output += (
1415
+ f" NO DATAPOINTS test regexp names: {no_datapoints_test_names_str}\n\n"
1416
+ )
1417
+ table_output += "\n</details>\n"
1418
+
1419
+ return (
1420
+ detected_regressions,
1421
+ table_output,
1422
+ improvements_list,
1423
+ regressions_list,
1424
+ total_stable,
1425
+ total_unstable,
1426
+ total_comparison_points,
1427
+ boxplot_data,
1428
+ command_change,
1429
+ )
1430
+
1431
+
1432
+ def get_by_error(name, by_str_arr):
1433
+ by_string = ",".join(by_str_arr)
1434
+ return f"--{name}-branch, --{name}-tag, --{name}-target-branch, --{name}-hash, and --{name}-target-version are mutually exclusive. You selected a total of {len(by_str_arr)}: {by_string}. Pick one..."
1435
+
1436
+
1437
+ def get_by_strings(
1438
+ baseline_branch,
1439
+ comparison_branch,
1440
+ baseline_tag,
1441
+ comparison_tag,
1442
+ baseline_target_version=None,
1443
+ comparison_target_version=None,
1444
+ baseline_hash=None,
1445
+ comparison_hash=None,
1446
+ baseline_target_branch=None,
1447
+ comparison_target_branch=None,
1448
+ ):
1449
+ baseline_covered = False
1450
+ comparison_covered = False
1451
+ by_str_baseline = ""
1452
+ by_str_comparison = ""
1453
+ baseline_str = ""
1454
+ comparison_str = ""
1455
+ baseline_by_arr = []
1456
+ comparison_by_arr = []
1457
+
1458
+ ################# BASELINE BY ....
1459
+
1460
+ if baseline_branch is not None:
1461
+ by_str_baseline = "branch"
1462
+ baseline_covered = True
1463
+ baseline_str = baseline_branch
1464
+ baseline_by_arr.append(by_str_baseline)
1465
+
1466
+ if baseline_tag is not None:
1467
+ by_str_baseline = "version"
1468
+ if baseline_covered:
1469
+ baseline_by_arr.append(by_str_baseline)
1470
+ logging.error(get_by_error("baseline", baseline_by_arr))
1471
+ exit(1)
1472
+ baseline_covered = True
1473
+ baseline_str = baseline_tag
1474
+
1475
+ if baseline_target_version is not None:
1476
+ by_str_baseline = "target+version"
1477
+ if baseline_covered:
1478
+ baseline_by_arr.append(by_str_baseline)
1479
+ logging.error(get_by_error("baseline", baseline_by_arr))
1480
+ exit(1)
1481
+ baseline_covered = True
1482
+ baseline_str = baseline_target_version
1483
+
1484
+ if baseline_hash is not None:
1485
+ by_str_baseline = "hash"
1486
+ if baseline_covered:
1487
+ baseline_by_arr.append(by_str_baseline)
1488
+ logging.error(get_by_error("baseline", baseline_by_arr))
1489
+ exit(1)
1490
+ baseline_covered = True
1491
+ baseline_str = baseline_hash
1492
+ if baseline_target_branch is not None:
1493
+ by_str_baseline = "target+branch"
1494
+ if baseline_covered:
1495
+ baseline_by_arr.append(by_str_baseline)
1496
+ logging.error(get_by_error("baseline", baseline_by_arr))
1497
+ exit(1)
1498
+ baseline_covered = True
1499
+ baseline_str = baseline_target_branch
1500
+
1501
+ ################# COMPARISON BY ....
1502
+
1503
+ if comparison_branch is not None:
1504
+ by_str_comparison = "branch"
1505
+ comparison_covered = True
1506
+ comparison_str = comparison_branch
1507
+
1508
+ if comparison_tag is not None:
1509
+ # check if we had already covered comparison
1510
+ if comparison_covered:
1511
+ logging.error(
1512
+ "--comparison-branch and --comparison-tag, --comparison-hash, --comparison-target-branch, and --comparison-target-table are mutually exclusive. Pick one..."
1513
+ )
1514
+ exit(1)
1515
+ comparison_covered = True
1516
+ by_str_comparison = "version"
1517
+ comparison_str = comparison_tag
1518
+ if comparison_target_version is not None:
1519
+ # check if we had already covered comparison
1520
+ if comparison_covered:
1521
+ logging.error(
1522
+ "--comparison-branch, --comparison-tag, --comparison-hash, --comparison-target-branch, and --comparison-target-table are mutually exclusive. Pick one..."
1523
+ )
1524
+ exit(1)
1525
+ comparison_covered = True
1526
+ by_str_comparison = "target+version"
1527
+ comparison_str = comparison_target_version
1528
+
1529
+ if comparison_target_branch is not None:
1530
+ # check if we had already covered comparison
1531
+ if comparison_covered:
1532
+ logging.error(
1533
+ "--comparison-branch, --comparison-tag, --comparison-hash, --comparison-target-branch, and --comparison-target-table are mutually exclusive. Pick one..."
1534
+ )
1535
+ exit(1)
1536
+ comparison_covered = True
1537
+ by_str_comparison = "target+branch"
1538
+ comparison_str = comparison_target_branch
1539
+
1540
+ if comparison_hash is not None:
1541
+ # check if we had already covered comparison
1542
+ # if comparison_covered:
1543
+ # logging.error(
1544
+ # "--comparison-branch, --comparison-tag, --comparison-hash, --comparison-target-branch, and --comparison-target-table are mutually exclusive. Pick one..."
1545
+ # )
1546
+ # exit(1)
1547
+ comparison_covered = True
1548
+ by_str_comparison = "hash"
1549
+ comparison_str = comparison_hash
1550
+
1551
+ if baseline_covered is False:
1552
+ logging.error(
1553
+ "You need to provider either "
1554
+ + "( --baseline-branch, --baseline-tag, --baseline-hash, --baseline-target-branch or --baseline-target-version ) "
1555
+ )
1556
+ exit(1)
1557
+ if comparison_covered is False:
1558
+ logging.error(
1559
+ "You need to provider either "
1560
+ + "( --comparison-branch, --comparison-tag, --comparison-hash, --comparison-target-branch or --comparison-target-version ) "
1561
+ )
1562
+ exit(1)
1563
+ return baseline_str, by_str_baseline, comparison_str, by_str_comparison
1564
+
1565
+
1566
+ def process_single_test_comparison(
1567
+ test_name,
1568
+ tests_with_config,
1569
+ original_metric_mode,
1570
+ baseline_str,
1571
+ comparison_str,
1572
+ by_str_baseline,
1573
+ by_str_comparison,
1574
+ metric_name,
1575
+ test_filter,
1576
+ baseline_github_repo,
1577
+ comparison_github_repo,
1578
+ tf_triggering_env_baseline,
1579
+ tf_triggering_env_comparison,
1580
+ extra_filters,
1581
+ baseline_deployment_name,
1582
+ comparison_deployment_name,
1583
+ baseline_github_org,
1584
+ comparison_github_org,
1585
+ running_platform_baseline,
1586
+ running_platform_comparison,
1587
+ rts,
1588
+ from_ts_ms,
1589
+ to_ts_ms,
1590
+ last_n_baseline,
1591
+ last_n_comparison,
1592
+ verbose,
1593
+ regressions_percent_lower_limit,
1594
+ simplify_table,
1595
+ regression_str,
1596
+ improvement_str,
1597
+ progress,
1598
+ ):
1599
+ """
1600
+ Process comparison analysis for a single test.
1601
+
1602
+ Returns a dictionary containing all the results and side effects that need to be
1603
+ accumulated by the caller.
1604
+ """
1605
+ tested_groups = []
1606
+ tested_commands = []
1607
+ if test_name in tests_with_config:
1608
+ test_spec = tests_with_config[test_name]
1609
+ if "tested-groups" in test_spec:
1610
+ tested_groups = test_spec["tested-groups"]
1611
+ if "tested-commands" in test_spec:
1612
+ tested_commands = test_spec["tested-commands"]
1613
+ else:
1614
+ logging.error(f"Test does not contain spec info: {test_name}")
1615
+
1616
+ metric_mode = original_metric_mode
1617
+ compare_version = "main"
1618
+ # GE
1619
+ github_link = "https://github.com/redis/redis-benchmarks-specification/blob"
1620
+ test_path = f"redis_benchmarks_specification/test-suites/{test_name}.yml"
1621
+ test_link = f"[{test_name}]({github_link}/{compare_version}/{test_path})"
1622
+ multi_value_baseline = check_multi_value_filter(baseline_str)
1623
+ multi_value_comparison = check_multi_value_filter(comparison_str)
1624
+
1625
+ filters_baseline = [
1626
+ "metric={}".format(metric_name),
1627
+ "{}={}".format(test_filter, test_name),
1628
+ "github_repo={}".format(baseline_github_repo),
1629
+ "triggering_env={}".format(tf_triggering_env_baseline),
1630
+ ]
1631
+ if extra_filters != "":
1632
+ filters_baseline.append(extra_filters)
1633
+ if baseline_str != "":
1634
+ filters_baseline.append("{}={}".format(by_str_baseline, baseline_str))
1635
+ if baseline_deployment_name != "":
1636
+ filters_baseline.append("deployment_name={}".format(baseline_deployment_name))
1637
+ if baseline_github_org != "":
1638
+ filters_baseline.append(f"github_org={baseline_github_org}")
1639
+ if running_platform_baseline is not None and running_platform_baseline != "":
1640
+ filters_baseline.append("running_platform={}".format(running_platform_baseline))
1641
+ filters_comparison = [
1642
+ "metric={}".format(metric_name),
1643
+ "{}={}".format(test_filter, test_name),
1644
+ "github_repo={}".format(comparison_github_repo),
1645
+ "triggering_env={}".format(tf_triggering_env_comparison),
1646
+ ]
1647
+ if comparison_str != "":
1648
+ filters_comparison.append("{}={}".format(by_str_comparison, comparison_str))
1649
+ if comparison_deployment_name != "":
1650
+ filters_comparison.append(
1651
+ "deployment_name={}".format(comparison_deployment_name)
1652
+ )
1653
+ if extra_filters != "":
1654
+ filters_comparison.append(extra_filters)
1655
+ if comparison_github_org != "":
1656
+ filters_comparison.append(f"github_org={comparison_github_org}")
1657
+ if "hash" not in by_str_baseline:
1658
+ filters_baseline.append("hash==")
1659
+ if "hash" not in by_str_comparison:
1660
+ filters_comparison.append("hash==")
1661
+ if running_platform_comparison is not None and running_platform_comparison != "":
1662
+ filters_comparison.append(
1663
+ "running_platform={}".format(running_platform_comparison)
1664
+ )
1665
+ baseline_timeseries = rts.ts().queryindex(filters_baseline)
1666
+ comparison_timeseries = rts.ts().queryindex(filters_comparison)
1667
+
1668
+ # avoiding target time-series
1669
+ comparison_timeseries = [x for x in comparison_timeseries if "target" not in x]
1670
+ baseline_timeseries = [x for x in baseline_timeseries if "target" not in x]
1671
+ progress.update()
1672
+ if verbose:
1673
+ logging.info(
1674
+ "Baseline timeseries for {}: {}. test={}".format(
1675
+ baseline_str, len(baseline_timeseries), test_name
1676
+ )
1677
+ )
1678
+ logging.info(
1679
+ "Comparison timeseries for {}: {}. test={}".format(
1680
+ comparison_str, len(comparison_timeseries), test_name
1681
+ )
1682
+ )
1683
+ if len(baseline_timeseries) > 1 and multi_value_baseline is False:
1684
+ baseline_timeseries = get_only_Totals(baseline_timeseries)
1685
+
1686
+ # Initialize result dictionary
1687
+ result = {
1688
+ "skip_test": False,
1689
+ "no_datapoints_baseline": False,
1690
+ "no_datapoints_comparison": False,
1691
+ "no_datapoints_both": False,
1692
+ "baseline_only": False,
1693
+ "comparison_only": False,
1694
+ "detected_regression": False,
1695
+ "detected_improvement": False,
1696
+ "unstable": False,
1697
+ "should_add_line": False,
1698
+ "line": None,
1699
+ "percentage_change": 0.0,
1700
+ "tested_groups": tested_groups,
1701
+ "tested_commands": tested_commands,
1702
+ "boxplot_data": None,
1703
+ }
1704
+
1705
+ if len(baseline_timeseries) == 0:
1706
+ logging.warning(
1707
+ f"No datapoints for test={test_name} for baseline timeseries {baseline_timeseries}"
1708
+ )
1709
+ result["no_datapoints_baseline"] = True
1710
+ result["no_datapoints_both"] = True
1711
+
1712
+ if len(comparison_timeseries) == 0:
1713
+ logging.warning(
1714
+ f"No datapoints for test={test_name} for comparison timeseries {comparison_timeseries}"
1715
+ )
1716
+ result["no_datapoints_comparison"] = True
1717
+ result["no_datapoints_both"] = True
1718
+
1719
+ if len(baseline_timeseries) != 1 and multi_value_baseline is False:
1720
+ if verbose:
1721
+ logging.warning(
1722
+ "Skipping this test given the value of timeseries !=1. Baseline timeseries {}".format(
1723
+ len(baseline_timeseries)
1724
+ )
1725
+ )
1726
+ if len(baseline_timeseries) > 1:
1727
+ logging.warning(
1728
+ "\t\tTime-series: {}".format(", ".join(baseline_timeseries))
1729
+ )
1730
+ result["skip_test"] = True
1731
+ return result
1732
+
1733
+ if len(comparison_timeseries) > 1 and multi_value_comparison is False:
1734
+ comparison_timeseries = get_only_Totals(comparison_timeseries)
1735
+ if len(comparison_timeseries) != 1 and multi_value_comparison is False:
1736
+ if verbose:
1737
+ logging.warning(
1738
+ "Comparison timeseries {}".format(len(comparison_timeseries))
1739
+ )
1740
+ result["skip_test"] = True
1741
+ return result
1742
+
1743
+ baseline_v = "N/A"
1744
+ comparison_v = "N/A"
1745
+ baseline_values = []
1746
+ baseline_datapoints = []
1747
+ comparison_values = []
1748
+ comparison_datapoints = []
1749
+ percentage_change = 0.0
1750
+ baseline_v_str = "N/A"
1751
+ comparison_v_str = "N/A"
1752
+ largest_variance = 0
1753
+ baseline_pct_change = "N/A"
1754
+ comparison_pct_change = "N/A"
1755
+
1756
+ note = ""
1757
+ try:
1758
+ for ts_name_baseline in baseline_timeseries:
1759
+ datapoints_inner = rts.ts().revrange(ts_name_baseline, from_ts_ms, to_ts_ms)
1760
+ baseline_datapoints.extend(datapoints_inner)
1761
+ (
1762
+ baseline_pct_change,
1763
+ baseline_v,
1764
+ largest_variance,
1765
+ ) = get_v_pct_change_and_largest_var(
1766
+ baseline_datapoints,
1767
+ baseline_pct_change,
1768
+ baseline_v,
1769
+ baseline_values,
1770
+ largest_variance,
1771
+ last_n_baseline,
1772
+ verbose,
1773
+ )
1774
+ for ts_name_comparison in comparison_timeseries:
1775
+ datapoints_inner = rts.ts().revrange(
1776
+ ts_name_comparison, from_ts_ms, to_ts_ms
1777
+ )
1778
+ comparison_datapoints.extend(datapoints_inner)
1779
+
1780
+ (
1781
+ comparison_pct_change,
1782
+ comparison_v,
1783
+ largest_variance,
1784
+ ) = get_v_pct_change_and_largest_var(
1785
+ comparison_datapoints,
1786
+ comparison_pct_change,
1787
+ comparison_v,
1788
+ comparison_values,
1789
+ largest_variance,
1790
+ last_n_comparison,
1791
+ verbose,
1792
+ )
1793
+
1794
+ waterline = regressions_percent_lower_limit
1795
+ # if regressions_percent_lower_limit < largest_variance:
1796
+ # note = "waterline={:.1f}%.".format(largest_variance)
1797
+ # waterline = largest_variance
1798
+
1799
+ except redis.exceptions.ResponseError as e:
1800
+ logging.error(
1801
+ "Detected a redis.exceptions.ResponseError. {}".format(e.__str__())
1802
+ )
1803
+ pass
1804
+ except ZeroDivisionError as e:
1805
+ logging.error("Detected a ZeroDivisionError. {}".format(e.__str__()))
1806
+ pass
1807
+
1808
+ unstable = False
1809
+
1810
+ if baseline_v != "N/A" and comparison_v == "N/A":
1811
+ logging.warning(
1812
+ f"Baseline contains datapoints but comparison not for test: {test_name}"
1813
+ )
1814
+ result["baseline_only"] = True
1815
+ if comparison_v != "N/A" and baseline_v == "N/A":
1816
+ logging.warning(
1817
+ f"Comparison contains datapoints but baseline not for test: {test_name}"
1818
+ )
1819
+ result["comparison_only"] = True
1820
+ if (
1821
+ baseline_v != "N/A"
1822
+ and comparison_pct_change != "N/A"
1823
+ and comparison_v != "N/A"
1824
+ and baseline_pct_change != "N/A"
1825
+ ):
1826
+ if comparison_pct_change > 10.0 or baseline_pct_change > 10.0:
1827
+ note = "UNSTABLE (very high variance)"
1828
+ unstable = True
1829
+ result["unstable"] = True
1830
+
1831
+ baseline_v_str = prepare_value_str(
1832
+ baseline_pct_change,
1833
+ baseline_v,
1834
+ baseline_values,
1835
+ simplify_table,
1836
+ metric_name,
1837
+ )
1838
+ comparison_v_str = prepare_value_str(
1839
+ comparison_pct_change,
1840
+ comparison_v,
1841
+ comparison_values,
1842
+ simplify_table,
1843
+ metric_name,
1844
+ )
1845
+
1846
+ if metric_mode == "higher-better":
1847
+ percentage_change = (float(comparison_v) / float(baseline_v) - 1) * 100.0
1848
+ else:
1849
+ # lower-better
1850
+ percentage_change = (
1851
+ -(float(baseline_v) - float(comparison_v)) / float(baseline_v)
1852
+ ) * 100.0
1853
+
1854
+ # Collect data for box plot
1855
+ result["boxplot_data"] = (test_name, percentage_change)
1856
+ else:
1857
+ logging.warn(
1858
+ f"Missing data for test {test_name}. baseline_v={baseline_v} (pct_change={baseline_pct_change}), comparison_v={comparison_v} (pct_change={comparison_pct_change}) "
1859
+ )
1860
+
1861
+ result["percentage_change"] = percentage_change
1862
+
1863
+ if baseline_v != "N/A" or comparison_v != "N/A":
1864
+ detected_regression = False
1865
+ detected_improvement = False
1866
+ noise_waterline = 3
1867
+
1868
+ # For higher-better metrics: negative change = regression, positive change = improvement
1869
+ # For lower-better metrics: positive change = regression, negative change = improvement
1870
+ if metric_mode == "higher-better":
1871
+ # Higher is better: negative change is bad (regression), positive change is good (improvement)
1872
+ if percentage_change < 0.0:
1873
+ if -waterline >= percentage_change:
1874
+ detected_regression = True
1875
+ note = note + f" {regression_str}"
1876
+ elif percentage_change < -noise_waterline:
1877
+ if simplify_table is False:
1878
+ note = note + f" potential {regression_str}"
1879
+ else:
1880
+ if simplify_table is False:
1881
+ note = note + " No Change"
1882
+
1883
+ if percentage_change > 0.0:
1884
+ if percentage_change > waterline:
1885
+ detected_improvement = True
1886
+ note = note + f" {improvement_str}"
1887
+ elif percentage_change > noise_waterline:
1888
+ if simplify_table is False:
1889
+ note = note + f" potential {improvement_str}"
1890
+ else:
1891
+ if simplify_table is False:
1892
+ note = note + " No Change"
1893
+ else:
1894
+ # Lower is better: positive change is bad (regression), negative change is good (improvement)
1895
+ if percentage_change > 0.0:
1896
+ if percentage_change >= waterline:
1897
+ detected_regression = True
1898
+ note = note + f" {regression_str}"
1899
+ elif percentage_change > noise_waterline:
1900
+ if simplify_table is False:
1901
+ note = note + f" potential {regression_str}"
1902
+ else:
1903
+ if simplify_table is False:
1904
+ note = note + " No Change"
1905
+
1906
+ if percentage_change < 0.0:
1907
+ if -percentage_change > waterline:
1908
+ detected_improvement = True
1909
+ note = note + f" {improvement_str}"
1910
+ elif -percentage_change > noise_waterline:
1911
+ if simplify_table is False:
1912
+ note = note + f" potential {improvement_str}"
1913
+ else:
1914
+ if simplify_table is False:
1915
+ note = note + " No Change"
1916
+
1917
+ result["detected_regression"] = detected_regression
1918
+ result["detected_improvement"] = detected_improvement
1919
+
1920
+ line = get_line(
1921
+ baseline_v_str,
1922
+ comparison_v_str,
1923
+ note,
1924
+ percentage_change,
1925
+ test_link,
1926
+ )
1927
+ result["line"] = line
1928
+ else:
1929
+ logging.warning(
1930
+ "There were no datapoints both for baseline and comparison for test: {test_name}"
1931
+ )
1932
+ result["no_datapoints_both"] = True
1933
+
1934
+ return result
1935
+
1936
+
1937
+ def process_single_env_comparison(
1938
+ env_name,
1939
+ test_name,
1940
+ tests_with_config,
1941
+ original_metric_mode,
1942
+ baseline_str,
1943
+ comparison_str,
1944
+ by_str_baseline,
1945
+ by_str_comparison,
1946
+ metric_name,
1947
+ test_filter,
1948
+ baseline_github_repo,
1949
+ comparison_github_repo,
1950
+ tf_triggering_env_baseline,
1951
+ tf_triggering_env_comparison,
1952
+ extra_filters,
1953
+ baseline_github_org,
1954
+ comparison_github_org,
1955
+ running_platform_baseline,
1956
+ running_platform_comparison,
1957
+ rts,
1958
+ from_ts_ms,
1959
+ to_ts_ms,
1960
+ last_n_baseline,
1961
+ last_n_comparison,
1962
+ verbose,
1963
+ regressions_percent_lower_limit,
1964
+ simplify_table,
1965
+ regression_str,
1966
+ improvement_str,
1967
+ progress,
1968
+ ):
1969
+ """
1970
+ Process comparison analysis for a single environment for a specific test.
1971
+ This is similar to process_single_test_comparison but focuses on environment comparison.
1972
+ """
1973
+ result = {
1974
+ "baseline_only": False,
1975
+ "comparison_only": False,
1976
+ "unstable": False,
1977
+ "detected_regression": False,
1978
+ "detected_improvement": False,
1979
+ "no_datapoints_both": False,
1980
+ "percentage_change": "N/A",
1981
+ "line": [],
1982
+ "boxplot_data": None,
1983
+ }
1984
+
1985
+ baseline_datapoints = []
1986
+ comparison_datapoints = []
1987
+ baseline_values = []
1988
+ comparison_values = []
1989
+ baseline_v = "N/A"
1990
+ comparison_v = "N/A"
1991
+ baseline_pct_change = "N/A"
1992
+ comparison_pct_change = "N/A"
1993
+ percentage_change = "N/A"
1994
+ largest_variance = 0.0
1995
+
1996
+ metric_mode = original_metric_mode
1997
+ compare_version = "main"
1998
+ # GE
1999
+ github_link = "https://github.com/redis/redis-benchmarks-specification/blob"
2000
+ test_path = f"redis_benchmarks_specification/test-suites/{test_name}.yml"
2001
+ test_link = f"[{test_name}]({github_link}/{compare_version}/{test_path})"
2002
+ multi_value_baseline = check_multi_value_filter(baseline_str)
2003
+ multi_value_comparison = check_multi_value_filter(comparison_str)
2004
+
2005
+ # Build filters for baseline environment
2006
+ filters_baseline = [
2007
+ "metric={}".format(metric_name),
2008
+ "{}={}".format(test_filter, test_name),
2009
+ "github_repo={}".format(baseline_github_repo),
2010
+ "triggering_env={}".format(tf_triggering_env_baseline),
2011
+ "deployment_name={}".format(env_name), # Use env_name as deployment_name
2012
+ ]
2013
+ if extra_filters != "":
2014
+ filters_baseline.append(extra_filters)
2015
+ if baseline_str != "":
2016
+ filters_baseline.append("{}={}".format(by_str_baseline, baseline_str))
2017
+ if baseline_github_org != "":
2018
+ filters_baseline.append(f"github_org={baseline_github_org}")
2019
+ if running_platform_baseline is not None and running_platform_baseline != "":
2020
+ filters_baseline.append("running_platform={}".format(running_platform_baseline))
2021
+
2022
+ # Build filters for comparison environment (same environment, different branch/version)
2023
+ filters_comparison = [
2024
+ "metric={}".format(metric_name),
2025
+ "{}={}".format(test_filter, test_name),
2026
+ "github_repo={}".format(comparison_github_repo),
2027
+ "triggering_env={}".format(tf_triggering_env_comparison),
2028
+ "deployment_name={}".format(env_name), # Use same env_name
2029
+ ]
2030
+ if comparison_str != "":
2031
+ filters_comparison.append("{}={}".format(by_str_comparison, comparison_str))
2032
+ if extra_filters != "":
2033
+ filters_comparison.append(extra_filters)
2034
+ if comparison_github_org != "":
2035
+ filters_comparison.append(f"github_org={comparison_github_org}")
2036
+ if "hash" not in by_str_baseline:
2037
+ filters_baseline.append("hash==")
2038
+ if "hash" not in by_str_comparison:
2039
+ filters_comparison.append("hash==")
2040
+ if running_platform_comparison is not None and running_platform_comparison != "":
2041
+ filters_comparison.append(
2042
+ "running_platform={}".format(running_platform_comparison)
2043
+ )
2044
+
2045
+ baseline_timeseries = rts.ts().queryindex(filters_baseline)
2046
+ comparison_timeseries = rts.ts().queryindex(filters_comparison)
2047
+
2048
+ # avoiding target time-series
2049
+ comparison_timeseries = [x for x in comparison_timeseries if "target" not in x]
2050
+ baseline_timeseries = [x for x in baseline_timeseries if "target" not in x]
2051
+ progress.update()
2052
+ if verbose:
2053
+ logging.info(
2054
+ "Baseline timeseries for {} (env: {}): {}. test={}".format(
2055
+ baseline_str, env_name, len(baseline_timeseries), test_name
2056
+ )
2057
+ )
2058
+ logging.info(
2059
+ "Comparison timeseries for {} (env: {}): {}. test={}".format(
2060
+ comparison_str, env_name, len(comparison_timeseries), test_name
2061
+ )
2062
+ )
2063
+ if len(baseline_timeseries) > 1 and multi_value_baseline is False:
2064
+ baseline_timeseries = get_only_Totals(baseline_timeseries)
2065
+ if len(comparison_timeseries) > 1 and multi_value_comparison is False:
2066
+ comparison_timeseries = get_only_Totals(comparison_timeseries)
2067
+
2068
+ note = ""
2069
+ try:
2070
+ for ts_name_baseline in baseline_timeseries:
2071
+ datapoints_inner = rts.ts().revrange(ts_name_baseline, from_ts_ms, to_ts_ms)
2072
+ baseline_datapoints.extend(datapoints_inner)
2073
+ (
2074
+ baseline_pct_change,
2075
+ baseline_v,
2076
+ largest_variance,
2077
+ ) = get_v_pct_change_and_largest_var(
2078
+ baseline_datapoints,
2079
+ baseline_pct_change,
2080
+ baseline_v,
2081
+ baseline_values,
2082
+ largest_variance,
2083
+ last_n_baseline,
2084
+ verbose,
2085
+ )
2086
+ for ts_name_comparison in comparison_timeseries:
2087
+ datapoints_inner = rts.ts().revrange(
2088
+ ts_name_comparison, from_ts_ms, to_ts_ms
2089
+ )
2090
+ comparison_datapoints.extend(datapoints_inner)
2091
+
2092
+ (
2093
+ comparison_pct_change,
2094
+ comparison_v,
2095
+ largest_variance,
2096
+ ) = get_v_pct_change_and_largest_var(
2097
+ comparison_datapoints,
2098
+ comparison_pct_change,
2099
+ comparison_v,
2100
+ comparison_values,
2101
+ largest_variance,
2102
+ last_n_comparison,
2103
+ verbose,
2104
+ )
2105
+
2106
+ waterline = regressions_percent_lower_limit
2107
+
2108
+ except redis.exceptions.ResponseError as e:
2109
+ logging.error(
2110
+ "Detected a redis.exceptions.ResponseError. {}".format(e.__str__())
2111
+ )
2112
+ pass
2113
+ except ZeroDivisionError as e:
2114
+ logging.error("Detected a ZeroDivisionError. {}".format(e.__str__()))
2115
+ pass
2116
+
2117
+ unstable = False
2118
+
2119
+ if baseline_v != "N/A" and comparison_v == "N/A":
2120
+ logging.warning(
2121
+ f"Baseline contains datapoints but comparison not for env: {env_name}, test: {test_name}"
2122
+ )
2123
+ result["baseline_only"] = True
2124
+ if comparison_v != "N/A" and baseline_v == "N/A":
2125
+ logging.warning(
2126
+ f"Comparison contains datapoints but baseline not for env: {env_name}, test: {test_name}"
2127
+ )
2128
+ result["comparison_only"] = True
2129
+ if (
2130
+ baseline_v != "N/A"
2131
+ and comparison_pct_change != "N/A"
2132
+ and comparison_v != "N/A"
2133
+ and baseline_pct_change != "N/A"
2134
+ ):
2135
+ if comparison_pct_change > 10.0 or baseline_pct_change > 10.0:
2136
+ note = "UNSTABLE (very high variance)"
2137
+ unstable = True
2138
+ result["unstable"] = True
2139
+
2140
+ baseline_v_str = prepare_value_str(
2141
+ baseline_pct_change,
2142
+ baseline_v,
2143
+ baseline_values,
2144
+ simplify_table,
2145
+ metric_name,
2146
+ )
2147
+ comparison_v_str = prepare_value_str(
2148
+ comparison_pct_change,
2149
+ comparison_v,
2150
+ comparison_values,
2151
+ simplify_table,
2152
+ metric_name,
2153
+ )
2154
+
2155
+ if metric_mode == "higher-better":
2156
+ percentage_change = (float(comparison_v) / float(baseline_v) - 1) * 100.0
2157
+ else:
2158
+ # lower-better
2159
+ percentage_change = (
2160
+ -(float(baseline_v) - float(comparison_v)) / float(baseline_v)
2161
+ ) * 100.0
2162
+
2163
+ # Collect data for box plot
2164
+ result["boxplot_data"] = (env_name, percentage_change)
2165
+ else:
2166
+ logging.warn(
2167
+ f"Missing data for env {env_name}, test {test_name}. baseline_v={baseline_v} (pct_change={baseline_pct_change}), comparison_v={comparison_v} (pct_change={comparison_pct_change}) "
2168
+ )
2169
+
2170
+ result["percentage_change"] = percentage_change
2171
+
2172
+ if (
2173
+ baseline_v != "N/A"
2174
+ and comparison_v != "N/A"
2175
+ and percentage_change != "N/A"
2176
+ and unstable is False
2177
+ ):
2178
+ detected_regression = False
2179
+ detected_improvement = False
2180
+ noise_waterline = 1.0
2181
+
2182
+ if percentage_change > 0.0:
2183
+ if percentage_change > waterline:
2184
+ detected_regression = True
2185
+ note = note + f" {regression_str}"
2186
+ elif percentage_change > noise_waterline:
2187
+ if simplify_table is False:
2188
+ note = note + f" potential {regression_str}"
2189
+ else:
2190
+ if simplify_table is False:
2191
+ note = note + " No Change"
2192
+
2193
+ if percentage_change < 0.0:
2194
+ if -percentage_change > waterline:
2195
+ detected_improvement = True
2196
+ note = note + f" {improvement_str}"
2197
+ elif -percentage_change > noise_waterline:
2198
+ if simplify_table is False:
2199
+ note = note + f" potential {improvement_str}"
2200
+ else:
2201
+ if simplify_table is False:
2202
+ note = note + " No Change"
2203
+
2204
+ result["detected_regression"] = detected_regression
2205
+ result["detected_improvement"] = detected_improvement
2206
+
2207
+ line = get_line(
2208
+ baseline_v_str,
2209
+ comparison_v_str,
2210
+ note,
2211
+ percentage_change,
2212
+ env_name, # Use env_name instead of test_link for environment comparison
2213
+ )
2214
+ result["line"] = line
2215
+ else:
2216
+ logging.warning(
2217
+ f"There were no datapoints both for baseline and comparison for env: {env_name}, test: {test_name}"
2218
+ )
2219
+ result["no_datapoints_both"] = True
2220
+
2221
+ return result
2222
+
2223
+
2224
+ def from_rts_to_env_comparison_table(
2225
+ test_name,
2226
+ env_list,
2227
+ baseline_str,
2228
+ comparison_str,
2229
+ by_str_baseline,
2230
+ by_str_comparison,
2231
+ from_ts_ms,
2232
+ to_ts_ms,
2233
+ last_n_baseline,
2234
+ last_n_comparison,
2235
+ metric_mode,
2236
+ metric_name,
2237
+ print_improvements_only,
2238
+ print_regressions_only,
2239
+ skip_unstable,
2240
+ regressions_percent_lower_limit,
2241
+ rts,
2242
+ simplify_table,
2243
+ test_filter,
2244
+ tf_triggering_env_baseline,
2245
+ tf_triggering_env_comparison,
2246
+ verbose,
2247
+ running_platform_baseline=None,
2248
+ running_platform_comparison=None,
2249
+ baseline_github_repo="redis",
2250
+ comparison_github_repo="redis",
2251
+ baseline_github_org="redis",
2252
+ comparison_github_org="redis",
2253
+ regression_str="REGRESSION",
2254
+ improvement_str="IMPROVEMENT",
2255
+ tests_with_config={},
2256
+ extra_filters="",
2257
+ ):
2258
+ """
2259
+ Compare environments for a single test instead of tests for a single environment.
2260
+ Returns comparison data organized by environment.
2261
+ """
2262
+ original_metric_mode = metric_mode
2263
+
2264
+ # Initialize result containers
2265
+ table_full = []
2266
+ table_stable = []
2267
+ table_unstable = []
2268
+ table_improvements = []
2269
+ table_regressions = []
2270
+ regressions_list = []
2271
+ improvements_list = []
2272
+ unstable_list = []
2273
+ baseline_only_list = []
2274
+ comparison_only_list = []
2275
+ no_datapoints_list = []
2276
+ boxplot_data = []
2277
+
2278
+ total_improvements = 0
2279
+ total_regressions = 0
2280
+ total_stable = 0
2281
+ total_unstable = 0
2282
+ total_comparison_points = 0
2283
+ detected_regressions = False
2284
+
2285
+ # Progress tracking
2286
+ from tqdm import tqdm
2287
+
2288
+ progress = tqdm(total=len(env_list), desc=f"Processing envs for {test_name[:30]}")
2289
+
2290
+ def process_env_wrapper(env_name):
2291
+ """Wrapper function to process a single environment and return env_name with result"""
2292
+ result = process_single_env_comparison(
2293
+ env_name,
2294
+ test_name,
2295
+ tests_with_config,
2296
+ original_metric_mode,
2297
+ baseline_str,
2298
+ comparison_str,
2299
+ by_str_baseline,
2300
+ by_str_comparison,
2301
+ metric_name,
2302
+ test_filter,
2303
+ baseline_github_repo,
2304
+ comparison_github_repo,
2305
+ tf_triggering_env_baseline,
2306
+ tf_triggering_env_comparison,
2307
+ extra_filters,
2308
+ baseline_github_org,
2309
+ comparison_github_org,
2310
+ running_platform_baseline,
2311
+ running_platform_comparison,
2312
+ rts,
2313
+ from_ts_ms,
2314
+ to_ts_ms,
2315
+ last_n_baseline,
2316
+ last_n_comparison,
2317
+ verbose,
2318
+ regressions_percent_lower_limit,
2319
+ simplify_table,
2320
+ regression_str,
2321
+ improvement_str,
2322
+ progress,
2323
+ )
2324
+ return env_name, result
2325
+
2326
+ # Process all environments
2327
+ for env_name in env_list:
2328
+ env_name, result = process_env_wrapper(env_name)
2329
+
2330
+ if result["no_datapoints_both"]:
2331
+ no_datapoints_list.append(env_name)
2332
+ continue
2333
+
2334
+ if result["baseline_only"]:
2335
+ baseline_only_list.append(env_name)
2336
+ continue
2337
+
2338
+ if result["comparison_only"]:
2339
+ comparison_only_list.append(env_name)
2340
+ continue
2341
+
2342
+ if result["unstable"]:
2343
+ unstable_list.append((env_name, result["line"]))
2344
+ table_unstable.append(result["line"])
2345
+ total_unstable += 1
2346
+ if skip_unstable is False:
2347
+ table_full.append(result["line"])
2348
+ total_comparison_points += 1
2349
+ continue
2350
+
2351
+ if result["detected_regression"]:
2352
+ detected_regressions = True
2353
+ regressions_list.append((env_name, result["percentage_change"]))
2354
+ table_regressions.append(result["line"])
2355
+ total_regressions += 1
2356
+
2357
+ if result["detected_improvement"]:
2358
+ improvements_list.append((env_name, result["percentage_change"]))
2359
+ table_improvements.append(result["line"])
2360
+ total_improvements += 1
2361
+
2362
+ if (
2363
+ not result["detected_regression"]
2364
+ and not result["detected_improvement"]
2365
+ and not result["unstable"]
2366
+ ):
2367
+ total_stable += 1
2368
+ table_stable.append(result["line"])
2369
+
2370
+ # Add to full table if not filtered out
2371
+ if not (
2372
+ print_improvements_only and not result["detected_improvement"]
2373
+ ) and not (print_regressions_only and not result["detected_regression"]):
2374
+ table_full.append(result["line"])
2375
+ total_comparison_points += 1
2376
+
2377
+ # Collect boxplot data
2378
+ if result["boxplot_data"]:
2379
+ boxplot_data.append(result["boxplot_data"])
2380
+
2381
+ progress.close()
2382
+
2383
+ return (
2384
+ detected_regressions,
2385
+ table_full,
2386
+ table_stable,
2387
+ table_unstable,
2388
+ table_improvements,
2389
+ table_regressions,
2390
+ total_improvements,
2391
+ total_regressions,
2392
+ total_stable,
2393
+ total_unstable,
2394
+ total_comparison_points,
2395
+ regressions_list,
2396
+ improvements_list,
2397
+ unstable_list,
2398
+ baseline_only_list,
2399
+ comparison_only_list,
2400
+ no_datapoints_list,
2401
+ False, # group_change (not applicable for env comparison)
2402
+ False, # command_change (not applicable for env comparison)
2403
+ boxplot_data,
2404
+ )
2405
+
2406
+
2407
+ def from_rts_to_regression_table(
2408
+ baseline_deployment_name,
2409
+ comparison_deployment_name,
2410
+ baseline_str,
2411
+ comparison_str,
2412
+ by_str_baseline,
2413
+ by_str_comparison,
2414
+ from_ts_ms,
2415
+ to_ts_ms,
2416
+ last_n_baseline,
2417
+ last_n_comparison,
2418
+ metric_mode,
2419
+ metric_name,
2420
+ print_improvements_only,
2421
+ print_regressions_only,
2422
+ skip_unstable,
2423
+ regressions_percent_lower_limit,
2424
+ rts,
2425
+ simplify_table,
2426
+ test_filter,
2427
+ test_names,
2428
+ tf_triggering_env_baseline,
2429
+ tf_triggering_env_comparison,
2430
+ verbose,
2431
+ running_platform_baseline=None,
2432
+ running_platform_comparison=None,
2433
+ baseline_github_repo="redis",
2434
+ comparison_github_repo="redis",
2435
+ baseline_github_org="redis",
2436
+ comparison_github_org="redis",
2437
+ regression_str="REGRESSION",
2438
+ improvement_str="IMPROVEMENT",
2439
+ tests_with_config={},
2440
+ extra_filters="",
2441
+ ):
2442
+ print_all = print_regressions_only is False and print_improvements_only is False
2443
+ table_full = []
2444
+ table_unstable = []
2445
+ table_stable = []
2446
+ table_regressions = []
2447
+ table_improvements = []
2448
+ detected_regressions = []
2449
+ total_improvements = 0
2450
+ total_stable = 0
2451
+ total_unstable = 0
2452
+ total_regressions = 0
2453
+ total_comparison_points = 0
2454
+ noise_waterline = 3
2455
+ progress = tqdm(unit="benchmark time-series", total=len(test_names))
2456
+ regressions_list = []
2457
+ improvements_list = []
2458
+ unstable_list = []
2459
+ baseline_only_list = []
2460
+ comparison_only_list = []
2461
+ no_datapoints_list = []
2462
+ no_datapoints_baseline_list = []
2463
+ no_datapoints_comparison_list = []
2464
+ group_change = {}
2465
+ command_change = {}
2466
+ original_metric_mode = metric_mode
2467
+
2468
+ # Data collection for box plot
2469
+ boxplot_data = []
2470
+
2471
+ # First loop: Collect all test results using parallel processing
2472
+ test_results = []
2473
+
2474
+ def process_test_wrapper(test_name):
2475
+ """Wrapper function to process a single test and return test_name with result"""
2476
+ result = process_single_test_comparison(
2477
+ test_name,
2478
+ tests_with_config,
2479
+ original_metric_mode,
2480
+ baseline_str,
2481
+ comparison_str,
2482
+ by_str_baseline,
2483
+ by_str_comparison,
2484
+ metric_name,
2485
+ test_filter,
2486
+ baseline_github_repo,
2487
+ comparison_github_repo,
2488
+ tf_triggering_env_baseline,
2489
+ tf_triggering_env_comparison,
2490
+ extra_filters,
2491
+ baseline_deployment_name,
2492
+ comparison_deployment_name,
2493
+ baseline_github_org,
2494
+ comparison_github_org,
2495
+ running_platform_baseline,
2496
+ running_platform_comparison,
2497
+ rts,
2498
+ from_ts_ms,
2499
+ to_ts_ms,
2500
+ last_n_baseline,
2501
+ last_n_comparison,
2502
+ verbose,
2503
+ regressions_percent_lower_limit,
2504
+ simplify_table,
2505
+ regression_str,
2506
+ improvement_str,
2507
+ progress,
2508
+ )
2509
+ return (test_name, result)
2510
+
2511
+ # Use ThreadPoolExecutor to process tests in parallel
2512
+ with ThreadPoolExecutor() as executor:
2513
+ test_results = list(executor.map(process_test_wrapper, test_names))
2514
+
2515
+ # Second loop: Process all collected results
2516
+ for test_name, result in test_results:
2517
+ # Handle the results from the extracted function
2518
+ if result["skip_test"]:
2519
+ continue
2520
+
2521
+ if result["no_datapoints_baseline"]:
2522
+ no_datapoints_baseline_list.append(test_name)
2523
+ if test_name not in no_datapoints_list:
2524
+ no_datapoints_list.append(test_name)
2525
+
2526
+ if result["no_datapoints_comparison"]:
2527
+ no_datapoints_comparison_list.append(test_name)
2528
+ if test_name not in no_datapoints_list:
2529
+ no_datapoints_list.append(test_name)
2530
+
2531
+ if result["baseline_only"]:
2532
+ baseline_only_list.append(test_name)
2533
+
2534
+ if result["comparison_only"]:
2535
+ comparison_only_list.append(test_name)
2536
+
2537
+ if result["unstable"]:
2538
+ unstable_list.append([test_name, "n/a"])
2539
+
2540
+ if result["boxplot_data"]:
2541
+ boxplot_data.append(result["boxplot_data"])
2542
+
2543
+ # Handle group and command changes
2544
+ for test_group in result["tested_groups"]:
2545
+ if test_group not in group_change:
2546
+ group_change[test_group] = []
2547
+ group_change[test_group].append(result["percentage_change"])
2548
+
2549
+ for test_command in result["tested_commands"]:
2550
+ if test_command not in command_change:
2551
+ command_change[test_command] = []
2552
+ command_change[test_command].append(result["percentage_change"])
2553
+
2554
+ # Handle regression/improvement detection and table updates
2555
+ if result["line"] is not None:
2556
+ detected_regression = result["detected_regression"]
2557
+ detected_improvement = result["detected_improvement"]
2558
+ unstable = result["unstable"]
2559
+ line = result["line"]
2560
+ percentage_change = result["percentage_change"]
2561
+
2562
+ if detected_regression:
2563
+ total_regressions = total_regressions + 1
2564
+ detected_regressions.append(test_name)
2565
+ regressions_list.append([test_name, percentage_change])
2566
+ table_regressions.append(line)
2567
+
2568
+ if detected_improvement:
2569
+ total_improvements = total_improvements + 1
2570
+ improvements_list.append([test_name, percentage_change])
2571
+ table_improvements.append(line)
2572
+
2573
+ if unstable:
2574
+ total_unstable += 1
2575
+ table_unstable.append(line)
2576
+ else:
2577
+ if not detected_regression and not detected_improvement:
2578
+ total_stable = total_stable + 1
2579
+ table_stable.append(line)
2580
+
2581
+ should_add_line = False
2582
+ if print_regressions_only and detected_regression:
2583
+ should_add_line = True
2584
+ if print_improvements_only and detected_improvement:
2585
+ should_add_line = True
2586
+ if print_all:
2587
+ should_add_line = True
2588
+ if unstable and skip_unstable:
2589
+ should_add_line = False
2590
+
2591
+ if should_add_line:
2592
+ total_comparison_points = total_comparison_points + 1
2593
+ table_full.append(line)
2594
+ elif result["no_datapoints_both"]:
2595
+ if test_name not in no_datapoints_list:
2596
+ no_datapoints_list.append(test_name)
2597
+ logging.warning(
2598
+ f"There is a total of {len(no_datapoints_list)} tests without datapoints for baseline AND comparison"
2599
+ )
2600
+ logging.info(
2601
+ f"There is a total of {len(comparison_only_list)} tests without datapoints for baseline"
2602
+ )
2603
+ print(
2604
+ "No datapoint baseline regex={test_names_str}".format(
2605
+ test_names_str="|".join(no_datapoints_baseline_list)
2606
+ )
2607
+ )
2608
+ logging.info(
2609
+ f"There is a total of {len(baseline_only_list)} tests without datapoints for comparison"
2610
+ )
2611
+ print(
2612
+ "No datapoint comparison regex={test_names_str}".format(
2613
+ test_names_str="|".join(no_datapoints_comparison_list)
2614
+ )
2615
+ )
2616
+ logging.info(f"There is a total of {len(unstable_list)} UNSTABLE tests")
2617
+ return (
2618
+ detected_regressions,
2619
+ table_full,
2620
+ table_stable,
2621
+ table_unstable,
2622
+ table_improvements,
2623
+ table_regressions,
2624
+ total_improvements,
2625
+ total_regressions,
2626
+ total_stable,
2627
+ total_unstable,
2628
+ total_comparison_points,
2629
+ regressions_list,
2630
+ improvements_list,
2631
+ unstable_list,
2632
+ baseline_only_list,
2633
+ comparison_only_list,
2634
+ no_datapoints_list,
2635
+ group_change,
2636
+ command_change,
2637
+ boxplot_data,
2638
+ )
2639
+
2640
+
2641
+ def get_only_Totals(baseline_timeseries):
2642
+ logging.warning("\t\tTime-series: {}".format(", ".join(baseline_timeseries)))
2643
+ logging.info(
2644
+ f"Checking if Totals will reduce timeseries. initial len={len(baseline_timeseries)}"
2645
+ )
2646
+ new_base = []
2647
+ for ts_name in baseline_timeseries:
2648
+ if "8.5.0" in ts_name:
2649
+ continue
2650
+ if "io-threads" in ts_name:
2651
+ continue
2652
+ if "oss-cluster" in ts_name:
2653
+ continue
2654
+ if "Totals" in ts_name:
2655
+ new_base.append(ts_name)
2656
+ baseline_timeseries = new_base
2657
+ logging.info(
2658
+ f" final len={len(baseline_timeseries)}"
2659
+ )
2660
+
2661
+ return baseline_timeseries
2662
+
2663
+
2664
+ def check_multi_value_filter(baseline_str):
2665
+ multi_value_baseline = False
2666
+ if "(" in baseline_str and "," in baseline_str and ")" in baseline_str:
2667
+ multi_value_baseline = True
2668
+ return multi_value_baseline
2669
+
2670
+
2671
+ def is_latency_metric(metric_name):
2672
+ """Check if a metric represents latency and should use 3-digit precision"""
2673
+ latency_indicators = [
2674
+ "latency",
2675
+ "percentile",
2676
+ "usec",
2677
+ "msec",
2678
+ "overallQuantiles",
2679
+ "latencystats",
2680
+ "p50",
2681
+ "p95",
2682
+ "p99",
2683
+ "p999",
2684
+ ]
2685
+ metric_name_lower = metric_name.lower()
2686
+ return any(indicator in metric_name_lower for indicator in latency_indicators)
2687
+
2688
+
2689
+ def prepare_value_str(
2690
+ baseline_pct_change, baseline_v, baseline_values, simplify_table, metric_name=""
2691
+ ):
2692
+ """Prepare value string with appropriate precision based on metric type"""
2693
+ # Use 3-digit precision for latency metrics
2694
+ if is_latency_metric(metric_name):
2695
+ if baseline_v < 1.0:
2696
+ baseline_v_str = " {:.3f}".format(baseline_v)
2697
+ elif baseline_v < 10.0:
2698
+ baseline_v_str = " {:.3f}".format(baseline_v)
2699
+ elif baseline_v < 100.0:
2700
+ baseline_v_str = " {:.3f}".format(baseline_v)
2701
+ else:
2702
+ baseline_v_str = " {:.3f}".format(baseline_v)
2703
+ else:
2704
+ # Original formatting for non-latency metrics
2705
+ if baseline_v < 1.0:
2706
+ baseline_v_str = " {:.2f}".format(baseline_v)
2707
+ elif baseline_v < 10.0:
2708
+ baseline_v_str = " {:.1f}".format(baseline_v)
2709
+ else:
2710
+ baseline_v_str = " {:.0f}".format(baseline_v)
2711
+
2712
+ stamp_b = ""
2713
+ if baseline_pct_change > 10.0:
2714
+ stamp_b = "UNSTABLE "
2715
+ if len(baseline_values) > 1:
2716
+ baseline_v_str += " +- {:.1f}% {}".format(
2717
+ baseline_pct_change,
2718
+ stamp_b,
2719
+ )
2720
+ if simplify_table is False and len(baseline_values) > 1:
2721
+ baseline_v_str += "({} datapoints)".format(len(baseline_values))
2722
+ return baseline_v_str
2723
+
2724
+
2725
+ def filter_test_names_by_regex(test_names, tags_regex_string):
2726
+ """
2727
+ Filter test names based on regex pattern.
2728
+
2729
+ Args:
2730
+ test_names: List of test names to filter
2731
+ tags_regex_string: Regex pattern to match against test names
2732
+
2733
+ Returns:
2734
+ List of filtered test names that match the regex pattern
2735
+ """
2736
+ final_test_names = []
2737
+ for test_name in test_names:
2738
+ if not isinstance(test_name, str):
2739
+ test_name = test_name.decode()
2740
+ match_obj = re.search(tags_regex_string, test_name)
2741
+ if match_obj is not None:
2742
+ final_test_names.append(test_name)
2743
+ return final_test_names
2744
+
2745
+
2746
+ def get_test_names_from_db(rts, tags_regex_string, test_names, used_key):
2747
+ try:
2748
+ test_names = rts.smembers(used_key)
2749
+ test_names = list(test_names)
2750
+ test_names.sort()
2751
+ test_names = filter_test_names_by_regex(test_names, tags_regex_string)
2752
+
2753
+ except redis.exceptions.ResponseError as e:
2754
+ logging.warning(
2755
+ "Error while trying to fetch test cases set (key={}) {}. ".format(
2756
+ used_key, e.__str__()
2757
+ )
2758
+ )
2759
+ pass
2760
+ logging.warning(
2761
+ "Based on test-cases set (key={}) we have {} comparison points. ".format(
2762
+ used_key, len(test_names)
2763
+ )
2764
+ )
2765
+ return test_names
2766
+
2767
+
2768
+ def filter_tests_by_command_group_regex(tests_with_config, command_group_regex=".*"):
2769
+ """Filter tests based on command regex matching tested-commands"""
2770
+ if command_group_regex == ".*":
2771
+ return tests_with_config
2772
+
2773
+ logging.info(f"Filtering tests by command group regex: {command_group_regex}")
2774
+ command_regex_compiled = re.compile(command_group_regex, re.IGNORECASE)
2775
+ filtered_tests = {}
2776
+
2777
+ for test_name, test_config in tests_with_config.items():
2778
+ tested_groups = test_config.get("tested-groups", [])
2779
+
2780
+ # Check if any tested command matches the regex
2781
+ command_match = False
2782
+ for command in tested_groups:
2783
+ if re.search(command_regex_compiled, command):
2784
+ command_match = True
2785
+ logging.info(
2786
+ f"Including test {test_name} (matches command group: {command})"
2787
+ )
2788
+ break
2789
+
2790
+ if command_match:
2791
+ filtered_tests[test_name] = test_config
2792
+ else:
2793
+ logging.info(
2794
+ f"Excluding test {test_name} (command groups: {tested_groups})"
2795
+ )
2796
+
2797
+ logging.info(
2798
+ f"Command regex group filtering: {len(filtered_tests)} tests remaining out of {len(tests_with_config)}"
2799
+ )
2800
+ return filtered_tests
2801
+
2802
+
2803
+ def filter_tests_by_command_regex(tests_with_config, command_regex=".*"):
2804
+ """Filter tests based on command regex matching tested-commands"""
2805
+ if command_regex == ".*":
2806
+ return tests_with_config
2807
+
2808
+ logging.info(f"Filtering tests by command regex: {command_regex}")
2809
+ command_regex_compiled = re.compile(command_regex, re.IGNORECASE)
2810
+ filtered_tests = {}
2811
+
2812
+ for test_name, test_config in tests_with_config.items():
2813
+ tested_commands = test_config.get("tested-commands", [])
2814
+
2815
+ # Check if any tested command matches the regex
2816
+ command_match = False
2817
+ for command in tested_commands:
2818
+ if re.search(command_regex_compiled, command):
2819
+ command_match = True
2820
+ logging.info(f"Including test {test_name} (matches command: {command})")
2821
+ break
2822
+
2823
+ if command_match:
2824
+ filtered_tests[test_name] = test_config
2825
+ else:
2826
+ logging.info(f"Excluding test {test_name} (commands: {tested_commands})")
2827
+
2828
+ logging.info(
2829
+ f"Command regex filtering: {len(filtered_tests)} tests remaining out of {len(tests_with_config)}"
2830
+ )
2831
+ return filtered_tests
2832
+
2833
+
2834
+ def get_test_names_from_yaml_files(test_suites_folder, tags_regex_string):
2835
+ """Get test names from YAML files in test-suites folder"""
2836
+ from redis_benchmarks_specification.__common__.runner import get_benchmark_specs
2837
+
2838
+ # Get all YAML files
2839
+ yaml_files = get_benchmark_specs(test_suites_folder, test="", test_regex=".*")
2840
+
2841
+ # Extract test names (remove path and .yml extension)
2842
+ test_names = []
2843
+ for yaml_file in yaml_files:
2844
+ test_name = os.path.basename(yaml_file).replace(".yml", "")
2845
+ # Apply regex filtering like database version
2846
+ match_obj = re.search(tags_regex_string, test_name)
2847
+ if match_obj is not None:
2848
+ test_names.append(test_name)
2849
+
2850
+ test_names.sort()
2851
+ logging.info(
2852
+ "Based on test-suites folder ({}) we have {} comparison points: {}".format(
2853
+ test_suites_folder, len(test_names), test_names
2854
+ )
2855
+ )
2856
+ return test_names
2857
+
2858
+
2859
+ def extract_command_from_test_name(test_name):
2860
+ """Extract Redis command from test name"""
2861
+ # Common patterns in test names
2862
+ test_name_lower = test_name.lower()
2863
+
2864
+ # Handle specific patterns
2865
+ if "memtier_benchmark" in test_name_lower:
2866
+ # Look for command patterns in memtier test names
2867
+ for cmd in [
2868
+ "get",
2869
+ "set",
2870
+ "hget",
2871
+ "hset",
2872
+ "hgetall",
2873
+ "hmset",
2874
+ "hmget",
2875
+ "hdel",
2876
+ "hexists",
2877
+ "hkeys",
2878
+ "hvals",
2879
+ "hincrby",
2880
+ "hincrbyfloat",
2881
+ "hsetnx",
2882
+ "hscan",
2883
+ "multi",
2884
+ "exec",
2885
+ ]:
2886
+ if cmd in test_name_lower:
2887
+ return cmd.upper()
2888
+
2889
+ # Try to extract command from test name directly
2890
+ parts = test_name.split("-")
2891
+ for part in parts:
2892
+ part_upper = part.upper()
2893
+ # Check if it looks like a Redis command
2894
+ if len(part_upper) >= 3 and part_upper.isalpha():
2895
+ return part_upper
2896
+
2897
+ return "UNKNOWN"
2898
+
2899
+
2900
+ def generate_command_performance_boxplot_from_command_data(
2901
+ command_change,
2902
+ output_filename,
2903
+ regression_str="Regression",
2904
+ improvement_str="Improvement",
2905
+ command_group_regex=".*",
2906
+ ):
2907
+ """Generate vertical box plot showing performance change distribution per command using command_change data"""
2908
+ if not MATPLOTLIB_AVAILABLE:
2909
+ logging.error("matplotlib not available, cannot generate box plot")
2910
+ return
2911
+
2912
+ try:
2913
+ if not command_change:
2914
+ logging.warning("No command data found for box plot generation")
2915
+ return
2916
+
2917
+ # Filter commands by command group regex
2918
+ if command_group_regex != ".*":
2919
+ logging.info(
2920
+ f"Filtering commands by command group regex: {command_group_regex}"
2921
+ )
2922
+ group_regex = re.compile(command_group_regex)
2923
+ filtered_command_change = {}
2924
+
2925
+ for cmd, changes in command_change.items():
2926
+ command_group = categorize_command(cmd.lower())
2927
+ if re.search(group_regex, command_group):
2928
+ filtered_command_change[cmd] = changes
2929
+ logging.info(f"Including command {cmd} (group: {command_group})")
2930
+ else:
2931
+ logging.info(f"Excluding command {cmd} (group: {command_group})")
2932
+
2933
+ command_change = filtered_command_change
2934
+
2935
+ if not command_change:
2936
+ logging.warning(
2937
+ f"No commands found matching command group regex: {command_group_regex}"
2938
+ )
2939
+ return
2940
+
2941
+ logging.info(f"After filtering: {len(command_change)} commands remaining")
2942
+
2943
+ # Sort commands by median performance change for better visualization
2944
+ commands_with_median = [
2945
+ (cmd, np.median(changes)) for cmd, changes in command_change.items()
2946
+ ]
2947
+ commands_with_median.sort(key=lambda x: x[1])
2948
+ commands = [cmd for cmd, _ in commands_with_median]
2949
+
2950
+ # Prepare data for plotting (vertical orientation)
2951
+ data_for_plot = [command_change[cmd] for cmd in commands]
2952
+
2953
+ # Create labels with test count
2954
+ labels_with_count = [
2955
+ f"{cmd}\n({len(command_change[cmd])} tests)" for cmd in commands
2956
+ ]
2957
+
2958
+ # Create the plot (vertical orientation)
2959
+ plt.figure(figsize=(10, 16))
2960
+
2961
+ # Create horizontal box plot (which makes it vertical when we rotate)
2962
+ positions = range(1, len(commands) + 1)
2963
+ box_plot = plt.boxplot(
2964
+ data_for_plot,
2965
+ positions=positions,
2966
+ patch_artist=True,
2967
+ showfliers=True,
2968
+ flierprops={"marker": "o", "markersize": 4},
2969
+ vert=False,
2970
+ ) # vert=False makes it horizontal (commands on Y-axis)
2971
+
2972
+ # Color the boxes and add value annotations
2973
+ for i, (patch, cmd) in enumerate(zip(box_plot["boxes"], commands)):
2974
+ changes = command_change[cmd]
2975
+ median_change = np.median(changes)
2976
+ min_change = min(changes)
2977
+ max_change = max(changes)
2978
+
2979
+ # Color based on median performance
2980
+ if median_change > 0:
2981
+ patch.set_facecolor("lightcoral") # Red for improvements
2982
+ patch.set_alpha(0.7)
2983
+ else:
2984
+ patch.set_facecolor("lightblue") # Blue for degradations
2985
+ patch.set_alpha(0.7)
2986
+
2987
+ # Store values for later annotation (after xlim is set)
2988
+ y_pos = i + 1 # Position corresponds to the box position
2989
+
2990
+ # Store annotation data for after xlim is set
2991
+ if not hasattr(plt, "_annotation_data"):
2992
+ plt._annotation_data = []
2993
+ plt._annotation_data.append(
2994
+ {
2995
+ "y_pos": y_pos,
2996
+ "min_change": min_change,
2997
+ "median_change": median_change,
2998
+ "max_change": max_change,
2999
+ }
3000
+ )
3001
+
3002
+ # Calculate optimal x-axis limits for maximum visibility
3003
+ all_values = []
3004
+ for changes in command_change.values():
3005
+ all_values.extend(changes)
3006
+
3007
+ if all_values:
3008
+ data_min = min(all_values)
3009
+ data_max = max(all_values)
3010
+
3011
+ logging.info(f"Box plot data range: {data_min:.3f}% to {data_max:.3f}%")
3012
+
3013
+ # Add minimal padding - tight to the data
3014
+ data_range = data_max - data_min
3015
+ if data_range == 0:
3016
+ # If all values are the same, add minimal symmetric padding
3017
+ padding = max(abs(data_min) * 0.05, 0.5) # At least 5% or 0.5
3018
+ x_min = data_min - padding
3019
+ x_max = data_max + padding
3020
+ else:
3021
+ # Add minimal padding: 2% on each side
3022
+ padding = data_range * 0.02
3023
+ x_min = data_min - padding
3024
+ x_max = data_max + padding
3025
+
3026
+ # Only include 0 if it's actually within or very close to the data range
3027
+ if data_min <= 0 <= data_max:
3028
+ # 0 is within the data range, keep current limits
3029
+ pass
3030
+ elif data_min > 0 and data_min < data_range * 0.1:
3031
+ # All positive values, but 0 is very close - include it
3032
+ x_min = 0
3033
+ elif data_max < 0 and abs(data_max) < data_range * 0.1:
3034
+ # All negative values, but 0 is very close - include it
3035
+ x_max = 0
3036
+
3037
+ plt.xlim(x_min, x_max)
3038
+ logging.info(f"Box plot x-axis limits set to: {x_min:.3f}% to {x_max:.3f}%")
3039
+
3040
+ # Add vertical line at 0% (only if 0 is visible)
3041
+ current_xlim = plt.xlim()
3042
+ if current_xlim[0] <= 0 <= current_xlim[1]:
3043
+ plt.axvline(x=0, color="black", linestyle="-", linewidth=1, alpha=0.8)
3044
+
3045
+ # Add background shading with current limits
3046
+ x_min, x_max = plt.xlim()
3047
+ if x_max > 0:
3048
+ plt.axvspan(max(0, x_min), x_max, alpha=0.1, color="red")
3049
+ if x_min < 0:
3050
+ plt.axvspan(x_min, min(0, x_max), alpha=0.1, color="blue")
3051
+
3052
+ # Add value annotations within the plot area
3053
+ if hasattr(plt, "_annotation_data"):
3054
+ x_range = x_max - x_min
3055
+ for data in plt._annotation_data:
3056
+ y_pos = data["y_pos"]
3057
+ min_change = data["min_change"]
3058
+ median_change = data["median_change"]
3059
+ max_change = data["max_change"]
3060
+
3061
+ # Position annotations inside the plot area
3062
+ # Use the actual values' positions with small offsets
3063
+ offset = x_range * 0.01 # Small offset for readability
3064
+
3065
+ # Position each annotation near its corresponding value
3066
+ plt.text(
3067
+ max_change + offset,
3068
+ y_pos + 0.15,
3069
+ f"{max_change:.1f}%",
3070
+ fontsize=7,
3071
+ va="center",
3072
+ ha="left",
3073
+ color="darkred",
3074
+ weight="bold",
3075
+ bbox=dict(
3076
+ boxstyle="round,pad=0.2",
3077
+ facecolor="white",
3078
+ alpha=0.8,
3079
+ edgecolor="none",
3080
+ ),
3081
+ )
3082
+ plt.text(
3083
+ median_change + offset,
3084
+ y_pos,
3085
+ f"{median_change:.1f}%",
3086
+ fontsize=7,
3087
+ va="center",
3088
+ ha="left",
3089
+ color="black",
3090
+ weight="bold",
3091
+ bbox=dict(
3092
+ boxstyle="round,pad=0.2",
3093
+ facecolor="yellow",
3094
+ alpha=0.8,
3095
+ edgecolor="none",
3096
+ ),
3097
+ )
3098
+ plt.text(
3099
+ min_change + offset,
3100
+ y_pos - 0.15,
3101
+ f"{min_change:.1f}%",
3102
+ fontsize=7,
3103
+ va="center",
3104
+ ha="left",
3105
+ color="darkblue",
3106
+ weight="bold",
3107
+ bbox=dict(
3108
+ boxstyle="round,pad=0.2",
3109
+ facecolor="white",
3110
+ alpha=0.8,
3111
+ edgecolor="none",
3112
+ ),
3113
+ )
3114
+
3115
+ # Clean up the temporary data
3116
+ delattr(plt, "_annotation_data")
3117
+
3118
+ # Set Y-axis labels (commands)
3119
+ plt.yticks(positions, labels_with_count, fontsize=10)
3120
+
3121
+ # Customize the plot
3122
+ title = f"Performance Change Distribution by Redis Command\nRedis is better ← | → Valkey is better"
3123
+ plt.title(title, fontsize=14, fontweight="bold", pad=20)
3124
+ plt.xlabel("Performance Change (%)", fontsize=12)
3125
+ plt.ylabel("Redis Commands", fontsize=12)
3126
+ plt.grid(True, alpha=0.3, axis="x")
3127
+
3128
+ # Add legend for box colors (at the bottom)
3129
+ from matplotlib.patches import Patch
3130
+
3131
+ legend_elements = [
3132
+ Patch(
3133
+ facecolor="lightcoral", alpha=0.7, label="Positive % = Valkey is better"
3134
+ ),
3135
+ Patch(
3136
+ facecolor="lightblue", alpha=0.7, label="Negative % = Redis is better"
3137
+ ),
3138
+ ]
3139
+ plt.legend(
3140
+ handles=legend_elements,
3141
+ bbox_to_anchor=(0.5, -0.05),
3142
+ loc="upper center",
3143
+ fontsize=10,
3144
+ ncol=2,
3145
+ )
3146
+
3147
+ # Add statistics text
3148
+ total_commands = len(command_change)
3149
+ total_measurements = sum(len(changes) for changes in command_change.values())
3150
+ plt.figtext(
3151
+ 0.02,
3152
+ 0.02,
3153
+ f"Commands: {total_commands} | Total measurements: {total_measurements}",
3154
+ fontsize=10,
3155
+ style="italic",
3156
+ )
3157
+
3158
+ # Adjust layout and save
3159
+ plt.tight_layout()
3160
+ plt.savefig(output_filename, dpi=300, bbox_inches="tight")
3161
+ plt.close()
3162
+
3163
+ logging.info(f"Box plot saved to {output_filename}")
3164
+
3165
+ # Print summary statistics
3166
+ logging.info("Command performance summary:")
3167
+ for cmd in commands:
3168
+ changes = command_change[cmd]
3169
+ min_change = min(changes)
3170
+ max_change = max(changes)
3171
+ median_change = np.median(changes)
3172
+ q1_change = np.percentile(changes, 25)
3173
+ q3_change = np.percentile(changes, 75)
3174
+ logging.info(
3175
+ f" {cmd}: min={min_change:.3f}%, max={max_change:.3f}%, median={median_change:.3f}% ({len(changes)} measurements)"
3176
+ )
3177
+
3178
+ # Print quartile summary for boxplot readiness
3179
+ logging.info("Command performance quartile summary (boxplot ready):")
3180
+ for cmd in commands:
3181
+ changes = command_change[cmd]
3182
+ min_change = min(changes)
3183
+ q1_change = np.percentile(changes, 25)
3184
+ median_change = np.median(changes)
3185
+ q3_change = np.percentile(changes, 75)
3186
+ max_change = max(changes)
3187
+ logging.info(
3188
+ f" {cmd}: min={min_change:.3f}%, Q1={q1_change:.3f}%, median={median_change:.3f}%, Q3={q3_change:.3f}%, max={max_change:.3f}%"
3189
+ )
3190
+
3191
+ except Exception as e:
3192
+ logging.error(f"Error generating box plot: {e}")
3193
+ import traceback
3194
+
3195
+ traceback.print_exc()
3196
+
3197
+
3198
+ def generate_command_performance_boxplot(comparison_data, output_filename):
3199
+ """Generate box plot showing performance change distribution per command"""
3200
+ if not MATPLOTLIB_AVAILABLE:
3201
+ logging.error("matplotlib not available, cannot generate box plot")
3202
+ return
3203
+
3204
+ try:
3205
+ # Group data by command
3206
+ command_data = {}
3207
+
3208
+ for test_name, pct_change in comparison_data:
3209
+ command = extract_command_from_test_name(test_name)
3210
+ if command not in command_data:
3211
+ command_data[command] = []
3212
+ command_data[command].append(pct_change)
3213
+
3214
+ if not command_data:
3215
+ logging.warning("No command data found for box plot generation")
3216
+ return
3217
+
3218
+ # Filter out commands with insufficient data
3219
+ filtered_command_data = {
3220
+ cmd: changes
3221
+ for cmd, changes in command_data.items()
3222
+ if len(changes) >= 1 and cmd != "UNKNOWN"
3223
+ }
3224
+
3225
+ if not filtered_command_data:
3226
+ logging.warning("No valid command data found for box plot generation")
3227
+ return
3228
+
3229
+ # Use the new function with the filtered data
3230
+ generate_command_performance_boxplot_from_command_data(
3231
+ filtered_command_data, output_filename, command_group_regex=".*"
3232
+ )
3233
+
3234
+ except Exception as e:
3235
+ logging.error(f"Error generating box plot: {e}")
3236
+ import traceback
3237
+
3238
+ traceback.print_exc()
3239
+
3240
+
3241
+ def get_line(
3242
+ baseline_v_str,
3243
+ comparison_v_str,
3244
+ note,
3245
+ percentage_change,
3246
+ test_name,
3247
+ ):
3248
+ percentage_change_str = "{:.1f}% ".format(percentage_change)
3249
+ return [
3250
+ test_name,
3251
+ baseline_v_str,
3252
+ comparison_v_str,
3253
+ percentage_change_str,
3254
+ note.strip(),
3255
+ ]
3256
+
3257
+
3258
+ def add_line(
3259
+ baseline_v_str,
3260
+ comparison_v_str,
3261
+ note,
3262
+ percentage_change,
3263
+ table,
3264
+ test_name,
3265
+ ):
3266
+ table.append(
3267
+ get_line(
3268
+ baseline_v_str,
3269
+ comparison_v_str,
3270
+ note,
3271
+ percentage_change,
3272
+ test_name,
3273
+ )
3274
+ )
3275
+
3276
+
3277
+ def get_v_pct_change_and_largest_var(
3278
+ comparison_datapoints,
3279
+ comparison_pct_change,
3280
+ comparison_v,
3281
+ comparison_values,
3282
+ largest_variance,
3283
+ last_n=-1,
3284
+ verbose=False,
3285
+ ):
3286
+ comparison_nsamples = len(comparison_datapoints)
3287
+ if comparison_nsamples > 0:
3288
+ _, comparison_v = comparison_datapoints[0]
3289
+ for tuple in comparison_datapoints:
3290
+ if last_n < 0 or (last_n > 0 and len(comparison_values) < last_n):
3291
+ if tuple[1] > 0.0:
3292
+ comparison_values.append(tuple[1])
3293
+ if len(comparison_values) > 0:
3294
+ comparison_df = pd.DataFrame(comparison_values)
3295
+ comparison_median = float(comparison_df.median().iloc[0])
3296
+ comparison_v = comparison_median
3297
+ comparison_std = float(comparison_df.std().iloc[0])
3298
+ if verbose:
3299
+ logging.info(
3300
+ "comparison_datapoints: {} value: {}; std-dev: {}; median: {}".format(
3301
+ comparison_datapoints,
3302
+ comparison_v,
3303
+ comparison_std,
3304
+ comparison_median,
3305
+ )
3306
+ )
3307
+ comparison_pct_change = (comparison_std / comparison_median) * 100.0
3308
+ if comparison_pct_change > largest_variance:
3309
+ largest_variance = comparison_pct_change
3310
+ return comparison_pct_change, comparison_v, largest_variance
3311
+
3312
+
3313
+ def main():
3314
+ _, _, project_version = populate_with_poetry_data()
3315
+ project_name = "redis-benchmarks-spec-cli"
3316
+ parser = argparse.ArgumentParser(
3317
+ description=get_version_string(project_name, project_version),
3318
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
3319
+ )
3320
+ parser = create_compare_arguments(parser)
3321
+ args = parser.parse_args()
3322
+ compare_command_logic(args, project_name, project_version)