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.
- redis_benchmarks_specification/__api__/Readme.md +7 -0
- redis_benchmarks_specification/__api__/__init__.py +5 -0
- redis_benchmarks_specification/__api__/api.py +87 -0
- redis_benchmarks_specification/__api__/app.py +191 -0
- redis_benchmarks_specification/__builder__/Readme.md +7 -0
- redis_benchmarks_specification/__builder__/__init__.py +5 -0
- redis_benchmarks_specification/__builder__/builder.py +1010 -0
- redis_benchmarks_specification/__builder__/schema.py +23 -0
- redis_benchmarks_specification/__cli__/__init__.py +5 -0
- redis_benchmarks_specification/__cli__/args.py +226 -0
- redis_benchmarks_specification/__cli__/cli.py +624 -0
- redis_benchmarks_specification/__cli__/stats.py +1304 -0
- redis_benchmarks_specification/__common__/__init__.py +0 -0
- redis_benchmarks_specification/__common__/builder_schema.py +256 -0
- redis_benchmarks_specification/__common__/env.py +96 -0
- redis_benchmarks_specification/__common__/github.py +280 -0
- redis_benchmarks_specification/__common__/package.py +28 -0
- redis_benchmarks_specification/__common__/runner.py +485 -0
- redis_benchmarks_specification/__common__/spec.py +143 -0
- redis_benchmarks_specification/__common__/suppress_warnings.py +20 -0
- redis_benchmarks_specification/__common__/timeseries.py +1621 -0
- redis_benchmarks_specification/__compare__/__init__.py +5 -0
- redis_benchmarks_specification/__compare__/args.py +240 -0
- redis_benchmarks_specification/__compare__/compare.py +3322 -0
- redis_benchmarks_specification/__init__.py +15 -0
- redis_benchmarks_specification/__runner__/__init__.py +5 -0
- redis_benchmarks_specification/__runner__/args.py +334 -0
- redis_benchmarks_specification/__runner__/remote_profiling.py +535 -0
- redis_benchmarks_specification/__runner__/runner.py +3837 -0
- redis_benchmarks_specification/__self_contained_coordinator__/__init__.py +5 -0
- redis_benchmarks_specification/__self_contained_coordinator__/args.py +210 -0
- redis_benchmarks_specification/__self_contained_coordinator__/artifacts.py +27 -0
- redis_benchmarks_specification/__self_contained_coordinator__/build_info.py +61 -0
- redis_benchmarks_specification/__self_contained_coordinator__/clients.py +58 -0
- redis_benchmarks_specification/__self_contained_coordinator__/cpuset.py +17 -0
- redis_benchmarks_specification/__self_contained_coordinator__/docker.py +108 -0
- redis_benchmarks_specification/__self_contained_coordinator__/post_processing.py +19 -0
- redis_benchmarks_specification/__self_contained_coordinator__/prepopulation.py +96 -0
- redis_benchmarks_specification/__self_contained_coordinator__/runners.py +740 -0
- redis_benchmarks_specification/__self_contained_coordinator__/self_contained_coordinator.py +2554 -0
- redis_benchmarks_specification/__setups__/__init__.py +0 -0
- redis_benchmarks_specification/__setups__/topologies.py +17 -0
- redis_benchmarks_specification/__spec__/__init__.py +5 -0
- redis_benchmarks_specification/__spec__/args.py +78 -0
- redis_benchmarks_specification/__spec__/cli.py +259 -0
- redis_benchmarks_specification/__watchdog__/__init__.py +5 -0
- redis_benchmarks_specification/__watchdog__/args.py +54 -0
- redis_benchmarks_specification/__watchdog__/watchdog.py +175 -0
- redis_benchmarks_specification/commands/__init__.py +0 -0
- redis_benchmarks_specification/commands/commands.py +15 -0
- redis_benchmarks_specification/setups/builders/gcc:15.2.0-amd64-debian-bookworm-default.yml +20 -0
- redis_benchmarks_specification/setups/builders/gcc:15.2.0-arm64-debian-bookworm-default.yml +20 -0
- redis_benchmarks_specification/setups/platforms/aws-ec2-1node-c5.4xlarge.yml +27 -0
- redis_benchmarks_specification/setups/topologies/topologies.yml +153 -0
- redis_benchmarks_specification/test-suites/defaults.yml +32 -0
- redis_benchmarks_specification/test-suites/generate.py +114 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpire-5-fields-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpire-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpireat-5-fields-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hexpireat-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetall-50-fields-100B-values.yml +52 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-5-fields-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hgetex-persist-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpire-5-fields-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpire-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpireat-5-fields-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-hpexpireat-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-hash-htll-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-1000B-values-expiration.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-expiration.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-long-expiration.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-1-fields-with-10B-values-short-expiration.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-20-fields-with-1B-values-pipeline-30.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-1000B-values-expiration.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-expiration.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-long-expiration.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-5-fields-with-10B-values-short-expiration.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-1000B-values-expiration.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-1000B-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-100B-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-expiration.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-long-expiration.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values-short-expiration.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-100Kkeys-load-hash-50-fields-with-10B-values.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-hash-50-fields-with-10000B-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-list-rpush-bulkload-pipeline-50.yml +39 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Kkeys-load-list-with-10B-values-pipeline-50.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values-pipeline-10.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values-pipeline-10.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Kkeys-hash-listpack-500-fields-update-20-fields-with-1B-to-64B-values.yml +75 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-100B-expire-use-case.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-expire-use-case.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-psetex-expire-use-case.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-setex-expire-use-case.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-1KiB-expire-use-case.yml +49 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-4KiB-expire-use-case.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-bitmap-getbit-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-exists-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-expire-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-expireat-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-pexpire-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-count-500-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-count-500-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-count-5000-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-cursor-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-scan-type-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-touch-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-generic-ttl-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hexists.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hget-hgetall-hkeys-hvals-with-100B-values.yml +48 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hgetall-50-fields-10B-values.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hincrby.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hincrbyfloat.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-10-fields-with-10B-values-with-expiration-pipeline-10.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-5-fields-with-100B-values-with-expiration-pipeline-10.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-5-fields-with-10B-values-with-expiration-pipeline-10.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hkeys-50-fields-with-10B-values-with-expiration-pipeline-10.yml +54 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-hmget-5-fields-with-100B-values-pipeline-10.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-hash-transactions-multi-exec-pipeline-20.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-100B-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-10B-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-lpop-rpop-with-1KiB-values.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-list-rpoplpush-with-10B-values.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-50-fields-with-10B-values-long-expiration-pipeline-10.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-hash-hmset-5-fields-with-1000B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-rpush-with-10B-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-100B-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-10B-values-pipeline-10.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-10B-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-list-with-1KiB-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-19-digits-pipeline-10.yml +58 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-19-digits.yml +58 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-set-intset-with-100-elements.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values-pipeline-10.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values-pipeline-10.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-100B-values-pipeline-10.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-100B-values.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-10.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-100-nokeyprefix.yml +29 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-100.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-50.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-500.yml +33 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-10B-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-1KiB-values-pipeline-10.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-1KiB-values.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-string-with-20KiB-values.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-listpack-with-100-elements-double-score.yml +91 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-with-10-elements-double-score.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-load-zset-with-10-elements-int-score.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-append-1-100B-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-append-1-100B.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-decr.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-100B-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-100B.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-100.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-50.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-500.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-1KiB-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-1KiB.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-32B-pipeline-10.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-32B.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incr-pipeline-10.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrby-pipeline-10.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrby.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrbyfloat-pipeline-10.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-incrbyfloat.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-int-encoding-strlen-pipeline-10.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mget-1KiB.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-expire-pipeline-10.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-expire.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-100B.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-1KB-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-1KB.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-32B-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-32B.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-512B-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-512B.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-with-expiration-240B-400_conns.yml +47 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-set-with-ex-100B-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setex-100B-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-1.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-1KiB-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-4KiB-pipeline-1.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-4KiB-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-512B-pipeline-1.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setget200c-512B-pipeline-10.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setrange-100B-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-setrange-100B.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-100M-bits-bitmap-bitcount.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-1Billion-bits-bitmap-bitcount.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-2-elements-geopos.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-2-elements-geosearch-fromlonlat-withcoord.yml +39 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geodist-pipeline-10.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geodist.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geohash-pipeline-10.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geohash.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geopos-pipeline-10.yml +35 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geopos.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-bybox.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-pipeline-10.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-1K-fields-hgetall-pipeline-10.yml +285 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-1K-fields-hgetall.yml +284 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-100B-values-cursor-count-1000.yml +291 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-10B-values-cursor-count-100.yml +291 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-1K-fields-10B-values.yml +290 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-hash-hscan-50-fields-10B-values.yml +54 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10-elements-lrange-all-elements-pipeline-10.yml +37 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10-elements-lrange-all-elements.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-int-7bit-uint-lrange-all-elements-pipeline-10.yml +44 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-int-lrange-all-elements-pipeline-10.yml +52 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-llen-pipeline-10.yml +52 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-lrange-all-elements-pipeline-10.yml +52 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-100-elements-lrange-all-elements.yml +51 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-integer.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-string-pipeline-10.yml +42 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lindex-string.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-linsert-lrem-integer.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-linsert-lrem-string.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lpos-integer.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-10K-elements-lpos-string.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements-pipeline-10.yml +202 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements.yml +201 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-2K-elements-quicklist-lrange-all-elements-longs.yml +258 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-hash-1K-fields-with-5B-values.yml +282 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-zset-with-5-elements-parsing-float-score.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-load-zset-with-5-elements-parsing-hexa-score.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-pfadd-4KB-values-pipeline-10.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smembers-pipeline-10.yml +37 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smembers.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10-elements-smismember.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sismember-is-a-member.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sismember-not-a-member.yml +53 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-smembers.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-smismember.yml +54 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-100-elements-sscan.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10M-elements-sismember-50pct-chance.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-10M-elements-srem-50pct-chance.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-smembers.yml +200 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan-cursor-count-100.yml +201 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan.yml +200 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1M-elements-sismember-50pct-chance.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-200K-elements-sadd-constant.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-2M-elements-sadd-increasing.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zincrby-1M-elements-pipeline-1.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-100K-elements-pipeline-1.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-10M-elements-pipeline-1.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrank-1M-elements-pipeline-1.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrem-5M-elements-pipeline-1.yml +47 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-1.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zrevrank-1M-elements-pipeline-1.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-10-elements-zrange-all-elements-long-scores.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-10-elements-zrange-all-elements.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrange-all-elements.yml +66 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements-long-scores.yml +66 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements.yml +66 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-100-elements-zscan.yml +65 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1K-elements-zrange-all-elements.yml +322 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1K-elements-zscan.yml +321 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zcard-pipeline-10.yml +39 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zremrangebyscore-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zrevrange-5-elements.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zrevrange-withscores-5-elements-pipeline-10.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1M-elements-zscore-pipeline-10.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-600K-elements-zrangestore-1K-elements.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-600K-elements-zrangestore-300K-elements.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-listpack-zrank-100-elements-pipeline-1.yml +50 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-lua-eval-hset-expire.yml +37 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-lua-evalsha-hset-expire.yml +41 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sdiff.yml +57 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sinter.yml +57 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-set-10-100-elements-sunion.yml +57 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-stream-5-entries-xread-all-entries-pipeline-10.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-stream-5-entries-xread-all-entries.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-zset-300-elements-skiplist-encoded-zunion.yml +434 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-2keys-zset-300-elements-skiplist-encoded-zunionstore.yml +434 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-load-string-with-512B-values-pipeline-10.yml +37 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-load-string-with-512B-values.yml +37 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-400_conns.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-40_conns.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-2000_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-400_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-get-with-1KiB-values-pipeline-10-40_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-400_conns.yml +45 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-2000_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-400_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-20-80-with-512B-values-pipeline-10-5200_conns.yml +46 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-50-50-with-512B-values-with-expiration-pipeline-10-400_conns.yml +43 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-connection-hello-pipeline-10.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-connection-hello.yml +32 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-multiple-hll-pfcount-100B-values.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-multiple-hll-pfmerge-100B-values.yml +34 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-connection-ping-pipeline-10.yml +29 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-100-subscribers.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-1000-subscribers.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-5000-subscribers.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-mixed-100-channels-128B-100-publishers-50K-subscribers-5k-conns.yml +40 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-pubsub-publish-1K-channels-10B-no-subscribers.yml +30 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-nokeys-server-time-pipeline-10.yml +29 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-10.yml +68 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-100.yml +69 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-leaderboard-top-1000.yml +68 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-rate-limiting-lua-100k-sessions.yml +64 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-realtime-analytics-membership-pipeline-10.yml +56 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-realtime-analytics-membership.yml +56 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-hash-100k-sessions.yml +108 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-json-100k-sessions.yml +109 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-caching-string-100k-sessions.yml +98 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-storage-100k-sessions.yml +205 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-playbook-session-storage-1k-sessions.yml +205 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xread-count-100.yml +36 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xreadgroup-count-100-noack.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-stream-10M-entries-xreadgroup-count-100.yml +38 -0
- redis_benchmarks_specification/test-suites/memtier_benchmark-stream-concurrent-xadd-xreadgroup-70-30.yml +50 -0
- redis_benchmarks_specification/test-suites/template.txt +18 -0
- redis_benchmarks_specification/vector-search-test-suites/vector_db_benchmark_test.yml +41 -0
- redis_benchmarks_specification-0.2.42.dist-info/LICENSE +201 -0
- redis_benchmarks_specification-0.2.42.dist-info/METADATA +434 -0
- redis_benchmarks_specification-0.2.42.dist-info/RECORD +336 -0
- redis_benchmarks_specification-0.2.42.dist-info/WHEEL +4 -0
- 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)
|